diff --git a/.gitignore b/.gitignore index b66ba2e4..912dce0c 100644 --- a/.gitignore +++ b/.gitignore @@ -228,4 +228,5 @@ typings/ .vscode aelf.umd.js.LICENSE.txt -dist \ No newline at end of file +dist +test-results diff --git a/build/utils.js b/build/utils.js deleted file mode 100644 index 2331b53a..00000000 --- a/build/utils.js +++ /dev/null @@ -1,5 +0,0 @@ -import path from 'path'; - -export const ROOT = path.resolve(process.cwd(), '.'); - -export const OUTPUT_PATH = path.resolve(process.cwd(), '.', 'dist/'); diff --git a/build/webpack.analyze.cjs b/build/webpack.analyze.cjs deleted file mode 100644 index 36dc263a..00000000 --- a/build/webpack.analyze.cjs +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @file webpack bundle analyze 分析包大小 - * @author yangmutong - */ - -/* eslint-env node */ - -const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); -const DeadCodePlugin = require('webpack-deadcode-plugin'); -const { merge } = require('webpack-merge'); -const nodeConfig = require('./webpack.node.js'); -const browserConfig = require('./webpack.browser'); - -const unusedAnalyzeConfig = { - patterns: ['src/**/*.*'], - globOptions: { - ignore: ['**/*.md', 'node_modules/**/*'] - } -}; - -module.exports = merge(process.env.RUNTIME_ENV === 'node' ? nodeConfig : browserConfig, { - plugins: [ - new BundleAnalyzerPlugin({ analyzerMode: 'static', generateStatsFile: true }), - new DeadCodePlugin(unusedAnalyzeConfig) - ] -}); diff --git a/build/webpack.browser.js b/build/webpack.browser.js deleted file mode 100644 index 9a1e892b..00000000 --- a/build/webpack.browser.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @file browser config - * @author atom-yang - */ - -import { merge } from 'webpack-merge'; -import webpack from 'webpack'; -import baseConfig from './webpack.common.js'; -import { OUTPUT_PATH } from './utils.js'; -import { createRequire } from 'module'; - -const require = createRequire(import.meta.url); - -const browserConfig = { - mode: 'production', - output: { - path: OUTPUT_PATH, - filename: 'aelf.umd.js', - library: 'AElf', - libraryTarget: 'umd', - libraryExport: 'default', - globalObject: 'globalThis', - umdNamedDefine: true - }, - resolve: { - alias: {}, - fallback: { - process: false, - assert: require.resolve('minimalistic-assert'), - buffer: require.resolve('buffer'), - crypto: require.resolve('crypto-browserify'), - stream: require.resolve('stream-browserify'), - fs: false, - http: false, - https: false, - child_process: false - } - }, - externals: { - xmlhttprequest: { - commonjs2: 'xmlhttprequest', - commonjs: 'xmlhttprequest', - umd: 'xmlhttprequest', - root: 'xmlhttprequest' - }, - 'xhr2-cookies': { - commonjs2: 'xmlhttprequest', - commonjs: 'xmlhttprequest', - umd: 'xmlhttprequest', - root: 'xmlhttprequest' - } - }, - target: 'web', - node: { - global: true - }, - optimization: { - removeEmptyChunks: true, - chunkIds: 'total-size', - moduleIds: 'size', - sideEffects: true, - minimize: false - }, - plugins: [ - new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'] - }), - new webpack.ProvidePlugin({ - process: 'process/browser' - }) - ] -}; - -export default merge(baseConfig, browserConfig); diff --git a/build/webpack.common.js b/build/webpack.common.js deleted file mode 100644 index b98f82a0..00000000 --- a/build/webpack.common.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-env node */ -import path from 'path'; -import webpack from 'webpack'; -import { createRequire } from 'module'; -import { ROOT } from './utils.js'; - -const require = createRequire(import.meta.url); -const pkg = require(path.resolve(ROOT, './package.json')); -const { version, name } = pkg; - -const banner = `${name}.js v${version} \n(c) 2019-${new Date().getFullYear()} AElf \nReleased under MIT License`; - -const baseConfig = { - entry: path.resolve(ROOT, 'src/index.js'), - devtool: 'source-map', - resolve: { - modules: [path.resolve(ROOT, 'src'), 'node_modules'], - extensions: ['.ts', '.js'] - }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: ['babel-loader'] - } - ] - }, - plugins: [ - new webpack.IgnorePlugin({ - resourceRegExp: /^\.\/wordlists\/(?!english)/, - contextRegExp: /bip39\/src$/ - }), - new webpack.DefinePlugin({ - 'process.env.RUNTIME_ENV': JSON.stringify(process.env.RUNTIME_ENV || 'browser'), - 'process.env.SDK_VERSION': JSON.stringify(version) - }), - new webpack.BannerPlugin(banner) - ], - stats: { - chunkRelations: true - } -}; - -export default baseConfig; diff --git a/build/webpack.esModule.js b/build/webpack.esModule.js deleted file mode 100644 index 4e8d1366..00000000 --- a/build/webpack.esModule.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file node config - * @author atom-yang - */ - -/* eslint-env node */ -import { merge } from 'webpack-merge'; -import webpack from 'webpack'; -import baseConfig from './webpack.common.js'; -import { OUTPUT_PATH } from './utils.js'; - -const nodeConfig = { - mode: 'production', - output: { - path: OUTPUT_PATH, - filename: 'aelf.esm.js', - libraryTarget: 'module' - }, - experiments: { - outputModule: true - }, - resolve: { - alias: {}, - fallback: { - buffer: 'buffer', - crypto: 'crypto-browserify', - stream: 'stream-browserify', - https: false, - http: false, - child_process: false, - fs: false, - url: false - } - }, - plugins: [ - new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'] - }) - ] -}; - -export default merge(baseConfig, nodeConfig); diff --git a/build/webpack.node.js b/build/webpack.node.js deleted file mode 100644 index aea7fc2d..00000000 --- a/build/webpack.node.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file node config - * @author atom-yang - */ - -/* eslint-env node */ -import { merge } from 'webpack-merge'; -import baseConfig from './webpack.common.js'; -import { OUTPUT_PATH } from './utils.js'; - -const nodeConfig = { - mode: 'production', - output: { - path: OUTPUT_PATH, - filename: 'aelf.cjs', - library: { - type: 'commonjs2' - }, - libraryExport: 'default' - }, - target: 'node', - optimization: { - removeEmptyChunks: true, - chunkIds: 'total-size', - moduleIds: 'size', - sideEffects: true, - minimize: false - } -}; - -export default merge(baseConfig, nodeConfig); diff --git a/config/build/vite.browser.config.js b/config/build/vite.browser.config.js new file mode 100644 index 00000000..46bc1f75 --- /dev/null +++ b/config/build/vite.browser.config.js @@ -0,0 +1,59 @@ +import { defineConfig } from 'vite'; +import { resolve, dirname } from 'path'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const require = createRequire(import.meta.url); +const pkg = require('../../package.json'); +const { version, name } = pkg; + +const banner = `/*! ${name}.js v${version} \n(c) 2019-${new Date().getFullYear()} AElf \nReleased under MIT License */ +import { Buffer } from 'buffer'; +if (typeof globalThis.Buffer === 'undefined') { + globalThis.Buffer = Buffer; +}`; + +export default defineConfig({ + build: { + outDir: resolve(__dirname, '../../dist'), + lib: { + entry: resolve(__dirname, '../../src/index.js'), + name: 'AElf', + formats: ['umd'], + fileName: (format) => 'aelf.umd.js' + }, + rollupOptions: { + external: ['xmlhttprequest', 'xhr2-cookies'], + output: { + banner, + globals: { + xmlhttprequest: 'xmlhttprequest', + 'xhr2-cookies': 'xmlhttprequest' + } + } + }, + sourcemap: true, + minify: false, + target: 'es2015' + }, + define: { + 'process.env.RUNTIME_ENV': JSON.stringify('browser'), + 'process.env.SDK_VERSION': JSON.stringify(version), + global: 'globalThis' + }, + resolve: { + alias: { + 'process': 'process/browser', + 'buffer': 'buffer', + 'assert': 'minimalistic-assert', + 'stream': 'stream-browserify' + } + }, + optimizeDeps: { + exclude: ['xmlhttprequest', 'xhr2-cookies'], + include: ['buffer', 'process/browser', 'minimalistic-assert', 'stream-browserify'] + } +}); diff --git a/config/build/vite.esm.config.js b/config/build/vite.esm.config.js new file mode 100644 index 00000000..358aef9d --- /dev/null +++ b/config/build/vite.esm.config.js @@ -0,0 +1,55 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'vite'; +import { resolve, dirname } from 'path'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const require = createRequire(import.meta.url); +const pkg = require('../../package.json'); + +const { version, name } = pkg; + +const banner = `/*! ${name}.js v${version} \n(c) 2019-${new Date().getFullYear()} AElf \nReleased under MIT License */ +import { Buffer } from 'buffer'; +if (typeof globalThis.Buffer === 'undefined') { + globalThis.Buffer = Buffer; +}`; + +export default defineConfig({ + build: { + outDir: resolve(__dirname, '../../dist'), + lib: { + entry: resolve(__dirname, '../../src/index.js'), + name: 'AElf', + formats: ['es'], + fileName: () => 'aelf.esm.js' + }, + rollupOptions: { + output: { + banner + } + }, + sourcemap: true, + minify: false, + target: 'es2015' + }, + define: { + 'process.env.RUNTIME_ENV': JSON.stringify('browser'), + 'process.env.SDK_VERSION': JSON.stringify(version), + global: 'globalThis' + }, + resolve: { + alias: { + 'process': 'process/browser', + 'buffer': 'buffer', + 'assert': 'minimalistic-assert', + 'stream': 'stream-browserify' + } + }, + optimizeDeps: { + include: ['buffer', 'process/browser', 'minimalistic-assert', 'stream-browserify'] + } +}); diff --git a/config/build/vite.node.config.js b/config/build/vite.node.config.js new file mode 100644 index 00000000..2d6c1ebd --- /dev/null +++ b/config/build/vite.node.config.js @@ -0,0 +1,45 @@ +import { defineConfig } from 'vite'; +import { resolve, dirname } from 'path'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const require = createRequire(import.meta.url); +const pkg = require('../../package.json'); +const { version, name } = pkg; + +const banner = `/*! ${name}.js v${version} \n(c) 2019-${new Date().getFullYear()} AElf \nReleased under MIT License */`; + +export default defineConfig({ + build: { + outDir: resolve(__dirname, '../../dist'), + lib: { + entry: resolve(__dirname, '../../src/index.js'), + name: 'AElf', + formats: ['cjs'], + fileName: (format) => 'aelf.cjs' + }, + rollupOptions: { + external: (id) => { + // Externalize Node.js built-in modules + return ['fs', 'path', 'crypto', 'stream', 'util', 'buffer', 'events', 'http', 'https', 'url', 'zlib', 'querystring', 'os', 'child_process'].includes(id); + }, + output: { + banner, + exports: 'default' + } + }, + sourcemap: true, + minify: false, + target: 'node14' + }, + define: { + 'process.env.RUNTIME_ENV': JSON.stringify('node'), + 'process.env.SDK_VERSION': JSON.stringify(version) + }, + resolve: { + alias: {} + } +}); diff --git a/config/test/vitest.browser.config.js b/config/test/vitest.browser.config.js new file mode 100644 index 00000000..d3016778 --- /dev/null +++ b/config/test/vitest.browser.config.js @@ -0,0 +1,65 @@ +// eslint-disable-next-line import/no-unresolved +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + // 浏览器环境配置 + globals: true, + environment: 'jsdom', + testTimeout: 5000, + + // 设置环境变量 + env: { + RUNTIME_ENV: 'browser' + }, + + // 设置全局变量 + setupFiles: [], + + // 测试文件匹配 + include: [ + 'test/unit/**/?(*.)+(test).[jt]s?(x)', + 'test/unit/util/httpProvider.browser-test.js' + ], + + // 覆盖率配置 + coverage: { + provider: 'v8', + enabled: true, + reporter: ['text', 'json-summary', 'html'], + reportsDirectory: 'coverage', + include: [ + 'src/chain/*.js', + 'src/contract/*.js', + 'src/util/*.js', + 'src/wallet/*.js', + 'src/index.js' + ], + exclude: [ + 'src/types/*.js', + 'node_modules/**', + 'examples/**', + 'dist/**', + 'scripts/**', + 'build/**' + ], + thresholds: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80 + } + }, + reportOnFailure: true, + }, + + // 模块解析 + resolve: { + alias: { + '^randombytes$': path.resolve('node_modules/randombytes/index.js') + } + } + } +}); diff --git a/config/test/vitest.compatibility.config.js b/config/test/vitest.compatibility.config.js new file mode 100644 index 00000000..64a74bfa --- /dev/null +++ b/config/test/vitest.compatibility.config.js @@ -0,0 +1,27 @@ +// eslint-disable-next-line import/no-unresolved +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + // 基础配置 + globals: true, + environment: 'node', + testTimeout: 10000, + + // 测试文件匹配 - 只运行compatibility测试 + include: [ + 'test/compatibility/**/?(*.)+(test).[jt]s?(x)' + ], + + // 覆盖率配置 - 不收集compatibility测试的覆盖率 + coverage: { + enabled: false + }, + + // 模块解析 - compatibility测试不需要alias,直接使用原始模块 + resolve: { + alias: {} + } + } +}); diff --git a/config/test/vitest.config.js b/config/test/vitest.config.js new file mode 100644 index 00000000..0deca181 --- /dev/null +++ b/config/test/vitest.config.js @@ -0,0 +1,57 @@ +// eslint-disable-next-line import/no-unresolved +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + // 基础配置 + globals: true, + environment: 'node', + testTimeout: 5000, + + // 测试文件匹配 + include: [ + 'test/unit/**/?(*.)+(test).[jt]s?(x)', + 'test/unit/util/httpProvider.node-test.js', + 'test/unit/util/httpProvider.fetch.node-test.js' + ], + + // 覆盖率配置 + coverage: { + provider: 'v8', + enabled: true, + reporter: ['text', 'json-summary', 'html'], + reportsDirectory: 'coverage', + include: [ + 'src/chain/*.js', + 'src/contract/*.js', + 'src/util/*.js', + 'src/wallet/*.js', + 'src/index.js' + ], + exclude: [ + 'src/types/*.js', + 'node_modules/**', + 'examples/**', + 'dist/**', + 'scripts/**', + 'build/**', + 'src/scrypt-polyfill.js' + ], + thresholds: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80 + } + }, + reportOnFailure: true + }, + + // 模块解析 + resolve: { + alias: {} + } + } +}); diff --git a/config/test/vitest.e2e.config.js b/config/test/vitest.e2e.config.js new file mode 100644 index 00000000..c5b53c01 --- /dev/null +++ b/config/test/vitest.e2e.config.js @@ -0,0 +1,35 @@ +// eslint-disable-next-line import/no-unresolved +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + // E2E 测试配置 + globals: true, + environment: 'node', + testTimeout: 30000, // E2E 测试可能需要更长时间 + + // 测试文件匹配 - 只运行 E2E 测试 + include: [ + 'test/e2e/**/?(*.)+(test).[jt]s?(x)' + ], + + // 覆盖率配置 - E2E 测试不计算覆盖率 + coverage: { + enabled: false + }, + + // 模块解析 + resolve: { + alias: {} + }, + + // 报告配置 + reporter: ['verbose', 'json', 'html'], + outputFile: { + json: './test-results/e2e-results.json', + html: './test-results/e2e-report.html' + } + } +}); + diff --git a/examples/reactDemo/README.md b/examples/reactDemo/README.md new file mode 100644 index 00000000..1747c82d --- /dev/null +++ b/examples/reactDemo/README.md @@ -0,0 +1,274 @@ +# AElf Web3.js React Demo + +A comprehensive React demo application showcasing the complete functionality of AElf Web3.js SDK. This demo provides a visual and interactive interface to test all major AElf features including wallet management, chain operations, contract interactions, transaction handling, and utility functions. + +## 🚀 Features + +### 🔗 Chain Operations +- **Connection Management**: Connect to different AElf network endpoints +- **Chain Status**: Get real-time chain information (Chain ID, block height, etc.) +- **Block Operations**: Query blocks by height or hash +- **Transaction Pool**: Monitor transaction pool status +- **Network Info**: Get peer information and network statistics + +### 💼 Wallet Management +- **Wallet Creation**: Create new wallets with mnemonic phrases +- **Wallet Import**: Import wallets from private keys or mnemonic phrases +- **Signing & Verification**: Sign data and verify signatures +- **Encryption/Decryption**: AES encrypt and decrypt sensitive data +- **Address Operations**: Get addresses from public keys + +### 🔐 KeyStore Management +- **KeyStore Generation**: Create encrypted keyStore files from wallet data +- **KeyStore Import/Export**: Import and export keyStore JSON files +- **Password Protection**: Secure wallet data with password-based encryption +- **Multiple Cipher Support**: Support for various AES encryption modes +- **KeyStore Unlocking**: Decrypt and unlock keyStore files with passwords +- **Password Validation**: Check if passwords are correct for keyStore files + +### 📋 Contract Operations +- **Contract Initialization**: Initialize and interact with smart contracts +- **Token Operations**: Get token information, balances, and perform transfers +- **Method Discovery**: Automatically discover available contract methods +- **Read Operations**: Call read-only contract methods +- **Write Operations**: Send transactions to modify contract state + +### 💸 Transaction Management +- **Transaction Creation**: Create raw transactions with custom parameters +- **Transaction Signing**: Sign transactions with wallet private keys +- **Transaction Broadcasting**: Send transactions to the network +- **Transaction Tracking**: Query transaction results and status +- **Merkle Path**: Get merkle paths for transaction verification + +### 🛠️ Utils Operations +- **Hash Functions**: SHA256 hashing +- **Encoding/Decoding**: Address encoding, Base58 operations +- **String Operations**: Padding, array to hex conversion +- **BigNumber**: Large number handling +- **Wei Conversions**: Convert between wei and other units + +## 🏗️ Architecture + +The application is built with a modular architecture: + +``` +src/ +├── config/ +│ └── index.js # Configuration constants and helpers +├── components/ +│ ├── Chain.jsx # Chain operations component +│ ├── Wallet.jsx # Wallet management component +│ ├── KeyStore.jsx # KeyStore management component +│ ├── contract.jsx # Contract operations component +│ ├── Transaction.jsx # Transaction management component +│ └── Utils.jsx # Utils operations component +├── App.jsx # Main application component +├── App.css # Application styles +└── main.jsx # Application entry point +``` + +## 🚀 Getting Started + +### Prerequisites +- Node.js 16+ +- npm or yarn + +### Installation + +1. **Install dependencies:** +```bash +cd examples/reactDemo +npm install +``` + +2. **Start the development server:** +```bash +npm run dev +``` + +3. **Open your browser:** +Navigate to `http://localhost:5173` + +## 📖 Usage Guide + +### 1. Chain Operations Tab +- Select different network endpoints (Test Net, TDVW Test, Main Net) +- View real-time connection status +- Get chain information and block details +- Monitor transaction pool and network statistics + +### 2. Wallet Management Tab +- Create new wallets or import existing ones +- Test signing and verification functionality +- Encrypt/decrypt sensitive data +- View wallet information and addresses + +### 3. KeyStore Management Tab +- Generate encrypted keyStore files from wallet data +- Import and export keyStore JSON files +- Test password protection and validation +- Unlock keyStore files with passwords +- Support for multiple AES encryption modes + +### 4. Contract Operations Tab +- Initialize token contracts +- Get token information and balances +- Perform transfers and approvals +- Query contract methods and parameters + +### 5. Transaction Management Tab +- Create custom transactions +- Sign and broadcast transactions +- Track transaction status and results +- Query blocks and merkle paths + +### 6. Utils Operations Tab +- Test hash functions and encoding +- Perform string operations and conversions +- Handle BigNumber operations +- Convert between different units + +## ⚙️ Configuration + +The demo uses a centralized configuration system in `src/config/index.js`: + +```javascript +export const CONFIG = { + ENDPOINTS: { + TEST_NET: 'https://aelf-test-node.aelf.io', + TDVW_TEST: 'https://tdvw-test-node.aelf.io', + MAIN_NET: 'https://aelf-node.aelf.io' + }, + DEFAULT_PRIVATE_KEY: '03bd0cea9730bcfc8045248fd7f4841ea19315995c44801a3dfede0ca872f808', + TEST_MNEMONIC: 'orange learn result add snack curtain double state expose bless also clarify', + // ... more configuration +}; +``` + +### Network Endpoints +- **Test Net**: `https://aelf-test-node.aelf.io` (Default) +- **TDVW Test**: `https://tdvw-test-node.aelf.io` +- **Main Net**: `https://aelf-node.aelf.io` + +### Default Test Values +- **Private Key**: Pre-configured test private key +- **Mnemonic**: Test mnemonic phrase +- **Token Symbol**: ELF (default) +- **Test Amounts**: 10000000 (10 ELF) + +## 🧪 Testing + +This demo is based on the comprehensive test suite from the AElf Web3.js project: + +- **E2E Tests**: Based on `test/e2e/aelf-esm.test.js` and `test/e2e/aelf-esm-basic.test.js` +- **Unit Tests**: Incorporates functionality from `test/unit/` directory +- **Contract Tests**: Includes token contract operations from unit tests + +## 🎨 UI Features + +- **Responsive Design**: Works on desktop and mobile devices +- **Tab Navigation**: Easy switching between different functionality areas +- **Real-time Status**: Live connection and network status indicators +- **Error Handling**: Comprehensive error display and handling +- **Loading States**: Visual feedback for async operations +- **Modern UI**: Clean, professional interface with smooth animations + +## 🔧 Development + +### Project Structure +- **Vite**: Fast build tool and development server +- **React 18**: Modern React with hooks +- **CSS Modules**: Scoped styling +- **ES6+**: Modern JavaScript features + +### Key Dependencies +```json +{ + "react": "^18.2.0", + "react-dom": "^18.2.0", + "aelf-sdk": "latest", + "vite": "^4.0.0" +} +``` + +## 📝 Examples + +### Creating a New Wallet +```javascript +const newWallet = AElf.wallet.createNewWallet(); +console.log('Address:', newWallet.address); +console.log('Private Key:', newWallet.privateKey); +console.log('Mnemonic:', newWallet.mnemonic); +``` + +### Getting Chain Status +```javascript +const aelf = new AElf(new AElf.providers.HttpProvider('https://aelf-test-node.aelf.io')); +const chainStatus = await aelf.chain.getChainStatus(); +console.log('Chain ID:', chainStatus.ChainId); +console.log('Block Height:', chainStatus.BestChainHeight); +``` + +### Token Transfer +```javascript +const tokenContract = await aelf.chain.contractAt(tokenAddress, wallet); +const result = await tokenContract.Transfer.sendTransaction({ + to: recipientAddress, + symbol: 'ELF', + amount: '10000000', + memo: 'Test transfer' +}); +console.log('Transaction ID:', result.TransactionId); +``` + +### KeyStore Generation and Unlocking +```javascript +// Generate keyStore +const walletData = { + mnemonic: wallet.mnemonic, + privateKey: wallet.privateKey, + nickName: 'My Wallet', + address: wallet.address +}; +const keyStore = AElf.utils.keyStore.getKeystore(walletData, 'password123', { + cipher: 'aes-256-cbc' +}); + +// Unlock keyStore +const unlocked = AElf.utils.keyStore.unlockKeystore(keyStore, 'password123'); +console.log('Unlocked private key:', unlocked.privateKey); + +// Check password +const isValid = AElf.utils.keyStore.checkPassword(keyStore, 'password123'); +console.log('Password valid:', isValid); +``` + +## 🚨 Important Notes + +- **Test Environment**: This demo uses test networks and test private keys +- **Security**: Never use the default private keys in production +- **Network**: Ensure you have internet connectivity to access AElf nodes +- **Browser**: Modern browsers with ES6+ support required + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## 📄 License + +MIT License - see LICENSE file for details + +## 🆘 Support + +For issues and questions: +- Check the [AElf Documentation](https://docs.aelf.io/) +- Review the test cases in the main repository +- Open an issue in the GitHub repository + +--- + +**Happy coding with AElf Web3.js! 🎉** \ No newline at end of file diff --git a/examples/reactDemo/index.html b/examples/reactDemo/index.html new file mode 100644 index 00000000..9b601cc3 --- /dev/null +++ b/examples/reactDemo/index.html @@ -0,0 +1,13 @@ + + + + + + + AElf E2E Demo + + +
+ + + \ No newline at end of file diff --git a/examples/reactDemo/package.json b/examples/reactDemo/package.json new file mode 100644 index 00000000..b401dd5a --- /dev/null +++ b/examples/reactDemo/package.json @@ -0,0 +1,25 @@ +{ + "name": "aelf-e2e-demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "aelf-sdk": "^3.5.0-beta.8", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "buffer": "^5.2.1", + "minimalistic-assert": "^1.0.1", + "stream-browserify": "^3.0.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "vite": "^5.2.0" + } +} diff --git a/examples/reactDemo/src/App.css b/examples/reactDemo/src/App.css new file mode 100644 index 00000000..80d68ce0 --- /dev/null +++ b/examples/reactDemo/src/App.css @@ -0,0 +1,470 @@ +.app { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; +} + +.app-header { + text-align: center; + margin-bottom: 30px; + padding: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.app-header h1 { + margin-bottom: 10px; + font-size: 2.5rem; + font-weight: 700; +} + +.app-header p { + font-size: 16px; + opacity: 0.9; + margin-bottom: 15px; +} + +.connection-status { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + flex-wrap: wrap; +} + +.status-indicator { + padding: 6px 12px; + border-radius: 20px; + font-size: 14px; + font-weight: 600; +} + +.status-indicator.connected { + background-color: rgba(40, 167, 69, 0.2); + color: #28a745; + border: 1px solid rgba(40, 167, 69, 0.3); +} + +.status-indicator.disconnected { + background-color: rgba(220, 53, 69, 0.2); + color: #dc3545; + border: 1px solid rgba(220, 53, 69, 0.3); +} + +.endpoint-info { + font-size: 12px; + opacity: 0.8; +} + +.app-nav { + display: flex; + gap: 10px; + margin-bottom: 30px; + flex-wrap: wrap; + justify-content: center; +} + +.nav-button { + background-color: #f8f9fa; + color: #495057; + border: 2px solid #e9ecef; + padding: 12px 24px; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + min-width: 150px; +} + +.nav-button:hover { + background-color: #e9ecef; + border-color: #dee2e6; +} + +.nav-button.active { + background-color: #007bff; + color: white; + border-color: #007bff; +} + +.app-main { + display: flex; + flex-direction: column; + gap: 20px; +} + +.status-overview { + display: flex; + gap: 20px; + flex-wrap: wrap; + justify-content: center; + margin-bottom: 20px; + padding: 15px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.status-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + min-width: 120px; +} + +.status-item .label { + font-size: 12px; + color: #6c757d; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.status-item .value { + font-size: 14px; + font-weight: 600; + color: #495057; +} + +.status-item .value.connected { + color: #28a745; +} + +.status-item .value.disconnected { + color: #dc3545; +} + +.status-card, +.info-card { + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 12px; + padding: 25px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; +} + +.status-card h2, +.info-card h2 { + margin-top: 0; + color: #333; + font-size: 20px; + font-weight: 600; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid #e9ecef; +} + +.section { + margin-bottom: 30px; + padding: 20px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.section h3 { + margin-top: 0; + color: #495057; + font-size: 16px; + font-weight: 600; + margin-bottom: 15px; +} + +.input-group { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 15px; +} + +.input-group label { + font-weight: 500; + color: #495057; + font-size: 14px; +} + +.input-group input, +.input-group textarea, +.input-group select { + padding: 10px 12px; + border: 1px solid #ced4da; + border-radius: 6px; + font-size: 14px; + transition: border-color 0.2s; +} + +.input-group input:focus, +.input-group textarea:focus, +.input-group select:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); +} + +.input-group textarea { + resize: vertical; + min-height: 80px; +} + +.button-group { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-top: 15px; +} + +.app button { + background-color: #007bff; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + min-width: 120px; +} + +.app button:hover:not(:disabled) { + background-color: #0056b3; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.app button:disabled { + background-color: #6c757d; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.result { + background: #e7f3ff; + border: 1px solid #b3d9ff; + border-radius: 6px; + padding: 15px; + margin-top: 15px; +} + +.result h4 { + margin-top: 0; + color: #0056b3; + font-size: 14px; + font-weight: 600; +} + +.result p { + margin: 5px 0; + color: #495057; + word-break: break-all; + font-size: 14px; +} + +.error { + background: #f8d7da; + border: 1px solid #f5c6cb; + border-radius: 6px; + padding: 15px; + margin: 15px 0; +} + +.error p { + margin: 0; + color: #721c24; + font-size: 14px; +} + +.success { + color: #28a745; + font-weight: 600; +} + +.wallet-info { + background: #f8f9fa; + border-radius: 6px; + padding: 15px; + margin: 15px 0; +} + +.wallet-info p { + margin: 8px 0; + color: #495057; + word-break: break-all; + font-size: 14px; +} + +.methods-list { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 10px; +} + +.method-tag { + background: #e9ecef; + color: #495057; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.keystore-info { + background: #f8f9fa; + border-radius: 6px; + padding: 15px; + margin: 15px 0; +} + +.keystore-info p { + margin: 8px 0; + color: #495057; + word-break: break-all; + font-size: 14px; +} + +.info-content { + background: #f8f9fa; + border-radius: 6px; + padding: 15px; + margin: 15px 0; +} + +.info-content h4 { + margin-top: 0; + color: #495057; + font-size: 14px; + font-weight: 600; + margin-bottom: 10px; +} + +.info-content ul { + margin: 10px 0; + padding-left: 20px; +} + +.info-content li { + margin: 5px 0; + color: #495057; + font-size: 14px; + line-height: 1.4; +} + +.chain-info, +.pool-info, +.peers-info, +.network-info { + background: #f8f9fa; + border-radius: 6px; + padding: 15px; + margin: 15px 0; +} + +.chain-info p, +.pool-info p, +.peers-info p, +.network-info p { + margin: 8px 0; + color: #495057; + word-break: break-all; + font-size: 14px; +} + +.peers-list { + margin-top: 10px; +} + +.peers-list p { + margin: 5px 0; + font-size: 13px; +} + +.endpoint-selector { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 15px; +} + +.endpoint-selector label { + font-weight: 500; + color: #495057; +} + +.endpoint-selector select { + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 6px; + font-size: 14px; + min-width: 300px; +} + +.app-footer { + margin-top: 40px; + padding: 20px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; + text-align: center; +} + +.footer-info p { + margin: 5px 0; + color: #6c757d; + font-size: 14px; +} + +@media (max-width: 768px) { + .app { + padding: 15px; + } + + .app-header h1 { + font-size: 2rem; + } + + .connection-status { + flex-direction: column; + gap: 10px; + } + + .app-nav { + flex-direction: column; + } + + .nav-button { + min-width: auto; + } + + .status-overview { + flex-direction: column; + align-items: center; + } + + .button-group { + flex-direction: column; + } + + .app button { + width: 100%; + } + + .endpoint-selector { + flex-direction: column; + align-items: flex-start; + } + + .endpoint-selector select { + min-width: auto; + width: 100%; + } +} diff --git a/examples/reactDemo/src/App.jsx b/examples/reactDemo/src/App.jsx new file mode 100644 index 00000000..a39899e7 --- /dev/null +++ b/examples/reactDemo/src/App.jsx @@ -0,0 +1,134 @@ +import React, { useState, useEffect } from 'react'; +import AElf from 'aelf-sdk'; +import './App.css'; +import { CONFIG, createAElfInstance, getDefaultWallet } from './config'; +import WalletComponent from './components/Wallet'; +import ChainComponent from './components/Chain'; +import Contract from './components/contract'; +import TransactionComponent from './components/Transaction'; +import UtilsComponent from './components/Utils'; +import KeyStoreComponent from './components/KeyStore'; + +window.AElf = AElf; + +function App() { + const [activeTab, setActiveTab] = useState('chain'); + const [connectionStatus, setConnectionStatus] = useState('Disconnected'); + const [aelf, setAelf] = useState(null); + const [wallet, setWallet] = useState(null); + const [chainInfo, setChainInfo] = useState(null); + + useEffect(() => { + initializeAElf(); + }, []); + + const initializeAElf = async () => { + try { + // Create AElf instance + const aelfInstance = createAElfInstance(AElf); + setAelf(aelfInstance); + + // Check connection status + const isConnected = aelfInstance.isConnected(); + setConnectionStatus(isConnected ? 'Connected' : 'Connection Failed'); + + if (isConnected) { + // Create wallet + const walletInstance = getDefaultWallet(AElf); + setWallet(walletInstance); + + // Get chain information + try { + const chainInfo = await aelfInstance.chain.getChainStatus(); + setChainInfo(chainInfo); + } catch (error) { + console.error('Failed to get chain information:', error); + } + } + } catch (error) { + console.error('Failed to initialize AElf:', error); + setConnectionStatus('Initialization Failed'); + } + }; + + const tabs = [ + { id: 'chain', label: 'Chain Operations', component: ChainComponent }, + { id: 'wallet', label: 'Wallet Management', component: WalletComponent }, + { id: 'keystore', label: 'KeyStore Management', component: KeyStoreComponent }, + { id: 'contract', label: 'Contract Operations', component: Contract }, + { id: 'transaction', label: 'Transaction Management', component: TransactionComponent }, + { id: 'utils', label: 'Utils Operations', component: UtilsComponent } + ]; + + const ActiveComponent = tabs.find(tab => tab.id === activeTab)?.component; + + return ( +
+
+

AElf Web3.js Demo

+

Complete AElf functionality testing application built with Vite + React

+
+ + {connectionStatus} + + Endpoint: {CONFIG.ENDPOINTS.TEST_NET} +
+
+ + + +
+ {/* Quick Status Overview */} +
+
+ Connection: + + {connectionStatus} + +
+ {chainInfo && ( + <> +
+ Chain ID: + {chainInfo.ChainId} +
+
+ Block Height: + {chainInfo.BestChainHeight} +
+ + )} + {wallet && ( +
+ Wallet: + {wallet.address.substring(0, 10)}... +
+ )} +
+ + {/* Active Component */} + {ActiveComponent && } + + {/* Footer with additional info */} + +
+
+ ); +} + +export default App; diff --git a/examples/reactDemo/src/components/Chain.jsx b/examples/reactDemo/src/components/Chain.jsx new file mode 100644 index 00000000..6074da9b --- /dev/null +++ b/examples/reactDemo/src/components/Chain.jsx @@ -0,0 +1,332 @@ +import React, { useState, useEffect } from 'react'; +import AElf from 'aelf-sdk'; +import { CONFIG, createAElfInstance } from '../config'; + +export default function ChainComponent() { + const [aelf, setAelf] = useState(null); + const [connectionStatus, setConnectionStatus] = useState('Disconnected'); + const [chainStatus, setChainStatus] = useState(null); + const [blockHeight, setBlockHeight] = useState(null); + const [currentBlock, setCurrentBlock] = useState(null); + const [transactionPoolStatus, setTransactionPoolStatus] = useState(null); + const [peers, setPeers] = useState(null); + const [networkInfo, setNetworkInfo] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedEndpoint, setSelectedEndpoint] = useState(CONFIG.ENDPOINTS.TEST_NET); + + // Initialize AElf instance + useEffect(() => { + initializeAElf(); + }, [selectedEndpoint]); + + const initializeAElf = async () => { + try { + setLoading(true); + setError(null); + + const aelfInstance = createAElfInstance(AElf, selectedEndpoint); + setAelf(aelfInstance); + + const isConnected = aelfInstance.isConnected(); + setConnectionStatus(isConnected ? 'Connected' : 'Connection Failed'); + + if (isConnected) { + await getChainStatus(); + } + } catch (error) { + console.error('Failed to initialize AElf:', error); + setError('Failed to initialize AElf: ' + error.message); + setConnectionStatus('Initialization Failed'); + } finally { + setLoading(false); + } + }; + + const getChainStatus = async () => { + if (!aelf) return; + + try { + setLoading(true); + const status = await aelf.chain.getChainStatus(); + setChainStatus(status); + } catch (error) { + console.error('Failed to get chain status:', error); + setError('Failed to get chain status: ' + error.message); + } finally { + setLoading(false); + } + }; + + const getBlockHeight = async () => { + if (!aelf) return; + + try { + setLoading(true); + const height = await aelf.chain.getBlockHeight(); + setBlockHeight(height); + } catch (error) { + console.error('Failed to get block height:', error); + setError('Failed to get block height: ' + error.message); + } finally { + setLoading(false); + } + }; + + const getBlockByHeight = async (height = null) => { + if (!aelf) return; + + try { + setLoading(true); + const blockHeightToUse = height || blockHeight || await aelf.chain.getBlockHeight(); + const block = await aelf.chain.getBlockByHeight(blockHeightToUse, true); + setCurrentBlock(block); + } catch (error) { + console.error('Failed to get block by height:', error); + setError('Failed to get block by height: ' + error.message); + } finally { + setLoading(false); + } + }; + + const getBlockByHash = async (blockHash) => { + if (!aelf || !blockHash) return; + + try { + setLoading(true); + const block = await aelf.chain.getBlock(blockHash, true); + setCurrentBlock(block); + } catch (error) { + console.error('Failed to get block by hash:', error); + setError('Failed to get block by hash: ' + error.message); + } finally { + setLoading(false); + } + }; + + const getTransactionPoolStatus = async () => { + if (!aelf) return; + + try { + setLoading(true); + const poolStatus = await aelf.chain.getTransactionPoolStatus(); + setTransactionPoolStatus(poolStatus); + } catch (error) { + console.error('Failed to get transaction pool status:', error); + setError('Failed to get transaction pool status: ' + error.message); + } finally { + setLoading(false); + } + }; + + const getPeers = async () => { + if (!aelf) return; + + try { + setLoading(true); + const peersList = await aelf.chain.getPeers(false); + setPeers(peersList); + } catch (error) { + console.error('Failed to get peers:', error); + setError('Failed to get peers: ' + error.message); + } finally { + setLoading(false); + } + }; + + const getNetworkInfo = async () => { + if (!aelf) return; + + try { + setLoading(true); + const network = await aelf.chain.networkInfo(); + setNetworkInfo(network); + } catch (error) { + console.error('Failed to get network info:', error); + setError('Failed to get network info: ' + error.message); + } finally { + setLoading(false); + } + }; + + const handleEndpointChange = (endpoint) => { + setSelectedEndpoint(endpoint); + }; + + const refreshAll = async () => { + if (!aelf) return; + + try { + setLoading(true); + await Promise.all([ + getChainStatus(), + getBlockHeight(), + getTransactionPoolStatus(), + getPeers(), + getNetworkInfo() + ]); + } catch (error) { + console.error('Failed to refresh all data:', error); + setError('Failed to refresh all data: ' + error.message); + } finally { + setLoading(false); + } + }; + + return ( +
+

Chain Operations

+ + {/* Connection Status */} +
+

Connection Status

+
+ + +
+

+ {connectionStatus} +

+
+ + +
+
+ + {/* Error Display */} + {error && ( +
+

Error: {error}

+ +
+ )} + + {/* Chain Status */} + {chainStatus && ( +
+

Chain Status

+
+

Chain ID: {chainStatus.ChainId}

+

Genesis Contract Address: {chainStatus.GenesisContractAddress}

+

Best Chain Height: {chainStatus.BestChainHeight}

+

Best Chain Hash: {chainStatus.BestChainHash}

+

Longest Chain Height: {chainStatus.LongestChainHeight}

+

Longest Chain Hash: {chainStatus.LongestChainHash}

+
+ +
+ )} + + {/* Block Operations */} +
+

Block Operations

+
+ + +
+ + {blockHeight && ( +
+

Current Block Height: {blockHeight}

+
+ )} + + {currentBlock && ( +
+

Block Details:

+

Block Hash: {currentBlock.BlockHash}

+

Height: {currentBlock.Header?.Height}

+

Previous Block Hash: {currentBlock.Header?.PreviousBlockHash}

+

Merkle Tree Root: {currentBlock.Header?.MerkleTreeRootOfTransactions}

+

Time: {new Date(currentBlock.Header?.Time).toLocaleString()}

+

Transaction Count: {currentBlock.Body?.Transactions?.length || 0}

+
+ )} +
+ + {/* Transaction Pool Status */} + {transactionPoolStatus && ( +
+

Transaction Pool Status

+
+

Queued Transactions: {transactionPoolStatus.Queued}

+

Validated Transactions: {transactionPoolStatus.Validated}

+
+ +
+ )} + + {/* Peers */} + {peers && ( +
+

Peers

+
+

Total Peers: {peers.length}

+ {peers.length > 0 && ( +
+

Peer List:

+ {peers.slice(0, 5).map((peer, index) => ( +

Peer {index + 1}: {JSON.stringify(peer)}

+ ))} + {peers.length > 5 &&

... and {peers.length - 5} more

} +
+ )} +
+ +
+ )} + + {/* Network Info */} + {networkInfo && ( +
+

Network Info

+
+

Version: {networkInfo.Version}

+

Protocol Version: {networkInfo.ProtocolVersion}

+

Connections: {networkInfo.Connections}

+
+ +
+ )} + + {/* Quick Actions */} +
+

Quick Actions

+
+ + + +
+
+
+ ); +} diff --git a/examples/reactDemo/src/components/KeyStore.jsx b/examples/reactDemo/src/components/KeyStore.jsx new file mode 100644 index 00000000..02d6fe49 --- /dev/null +++ b/examples/reactDemo/src/components/KeyStore.jsx @@ -0,0 +1,449 @@ +import React, { useState, useEffect } from 'react'; +// import AElf from 'aelf-sdk'; +// import AElf from '../../../../dist/aelf.esm.js'; +import { getKeystore } from 'aelf-sdk/keyStore'; +import { CONFIG, getDefaultWallet } from '../config'; + +// const { getKeystore } = AElf.utils.keyStore; + +export default function KeyStoreComponent() { + const [wallet, setWallet] = useState(null); + const [keyStore, setKeyStore] = useState(null); + const [unlockedWallet, setUnlockedWallet] = useState(null); + const [passwordCheck, setPasswordCheck] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [inputData, setInputData] = useState({ + password: '123123', + confirmPassword: '123123', + nickName: 'My Wallet', + cipher: 'aes-256-cbc', + keyStoreJson: '' + }); + + // Available cipher options + const cipherOptions = [ + 'aes-128-ctr', + 'aes-128-cbc', + 'aes-192-cbc', + 'aes-256-cbc', + 'aes-128-ecb', + 'aes-192-ecb', + 'aes-256-ecb' + ]; + + // Initialize with default wallet + useEffect(() => { + initializeWallet(); + }, []); + + const initializeWallet = () => { + try { + const walletInstance = getDefaultWallet(AElf); + setWallet(walletInstance); + } catch (error) { + console.error('Failed to initialize wallet:', error); + setError('Failed to initialize wallet: ' + error.message); + } + }; + + const createNewWallet = () => { + try { + const newWallet = AElf.wallet.createNewWallet(); + setWallet(newWallet); + setError(null); + } catch (error) { + console.error('Failed to create new wallet:', error); + setError('Failed to create new wallet: ' + error.message); + } + }; + + const importWalletFromMnemonic = () => { + try { + if (!inputData.keyStoreJson) { + alert('Please enter a mnemonic phrase'); + return; + } + const walletInstance = AElf.wallet.getWalletByMnemonic(inputData.keyStoreJson); + if (!walletInstance) { + alert('Invalid mnemonic phrase'); + return; + } + setWallet(walletInstance); + setError(null); + } catch (error) { + console.error('Failed to import wallet from mnemonic:', error); + setError('Failed to import wallet from mnemonic: ' + error.message); + } + }; + + const generateKeyStore = () => { + if (!wallet) { + alert('No wallet available'); + return; + } + + if (!inputData.password) { + alert('Please enter a password'); + return; + } + + try { + setLoading(true); + setError(null); + + const keyStoreData = { + mnemonic: wallet.mnemonic, + privateKey: wallet.privateKey, + nickName: inputData.nickName, + address: wallet.address + }; + + const options = { + cipher: inputData.cipher + }; + + // const keyStoreResult = AElf.utils.keyStore.getKeystore(keyStoreData, inputData.password, options); + const keyStoreResult = getKeystore(keyStoreData, inputData.password, options); + setKeyStore(keyStoreResult); + } catch (error) { + console.error('Failed to generate keyStore:', error); + setError('Failed to generate keyStore: ' + error.message); + } finally { + setLoading(false); + } + }; + + const unlockKeyStore = () => { + if (!keyStore) { + alert('No keyStore available'); + return; + } + + if (!inputData.password) { + alert('Please enter a password'); + return; + } + + try { + setLoading(true); + setError(null); + + const unlocked = AElf.utils.keyStore.unlockKeystore(keyStore, inputData.password); + setUnlockedWallet(unlocked); + } catch (error) { + console.error('Failed to unlock keyStore:', error); + setError('Failed to unlock keyStore: ' + error.message); + } finally { + setLoading(false); + } + }; + + const checkKeyStorePassword = () => { + if (!keyStore) { + alert('No keyStore available'); + return; + } + + if (!inputData.password) { + alert('Please enter a password'); + return; + } + + try { + setLoading(true); + setError(null); + + const isValid = AElf.utils.keyStore.checkPassword(keyStore, inputData.password); + setPasswordCheck({ + isValid, + message: isValid ? 'Password is correct' : 'Password is incorrect' + }); + } catch (error) { + console.error('Failed to check password:', error); + setError('Failed to check password: ' + error.message); + } finally { + setLoading(false); + } + }; + + const importKeyStoreFromJson = () => { + if (!inputData.keyStoreJson) { + alert('Please enter keyStore JSON'); + return; + } + + try { + setLoading(true); + setError(null); + + const keyStoreData = JSON.parse(inputData.keyStoreJson); + setKeyStore(keyStoreData); + } catch (error) { + console.error('Failed to import keyStore from JSON:', error); + setError('Failed to import keyStore from JSON: ' + error.message); + } finally { + setLoading(false); + } + }; + + const exportKeyStore = () => { + if (!keyStore) { + alert('No keyStore available to export'); + return; + } + + try { + const keyStoreString = JSON.stringify(keyStore, null, 2); + setInputData(prev => ({ ...prev, keyStoreJson: keyStoreString })); + } catch (error) { + console.error('Failed to export keyStore:', error); + setError('Failed to export keyStore: ' + error.message); + } + }; + + const downloadKeyStore = () => { + if (!keyStore) { + alert('No keyStore available to download'); + return; + } + + try { + const keyStoreString = JSON.stringify(keyStore, null, 2); + const blob = new Blob([keyStoreString], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `keystore-${inputData.nickName || 'wallet'}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } catch (error) { + console.error('Failed to download keyStore:', error); + setError('Failed to download keyStore: ' + error.message); + } + }; + + const handleInputChange = (field, value) => { + setInputData(prev => ({ ...prev, [field]: value })); + }; + + const setDefaultValues = () => { + setInputData(prev => ({ + ...prev, + password: '123123', + confirmPassword: '123123', + nickName: 'My Wallet', + cipher: 'aes-256-cbc' + })); + }; + + return ( +
+

KeyStore Management

+ + {/* Error Display */} + {error && ( +
+

Error: {error}

+ +
+ )} + + {/* Wallet Management */} +
+

Wallet Management

+
+ + +
+ + {wallet && ( +
+

Current Wallet:

+

Address: {wallet.address}

+

Private Key: {wallet.privateKey}

+

Mnemonic: {wallet.mnemonic}

+
+ )} + +
+ +