├── .eslintrc.js ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── README.md ├── SUMMARY.md ├── bin ├── cfxjs.js ├── rpc.js └── sponsor.js ├── change_log.md ├── docs ├── FAQs.md ├── README.md ├── api │ ├── CONST.md │ ├── Conflux.md │ ├── Drip.md │ ├── Message.md │ ├── PersonalMessage.md │ ├── README.md │ ├── Transaction.md │ ├── provider │ │ ├── BaseProvider.md │ │ ├── HttpProvider.md │ │ ├── WebSocketProvider.md │ │ └── index.md │ ├── rpc │ │ ├── pos.md │ │ └── txpool.md │ ├── subscribe │ │ └── PendingTransaction.md │ ├── util │ │ ├── address.md │ │ ├── format.md │ │ └── sign.md │ └── wallet │ │ ├── Account.md │ │ ├── PrivateKeyAccount.md │ │ ├── Wallet.md │ │ └── index.md ├── block_trace.md ├── commands.md ├── guides │ ├── account.md │ ├── batch_rpc.md │ ├── conflux_checksum_address.md │ ├── error_handling.md │ ├── how_to_connect_fluent.md │ ├── how_to_send_tx.md │ ├── interact_with_contract.md │ ├── providers.md │ └── sign_methods.md ├── index.md ├── jssdk-vs-web3.md ├── overview.md ├── pos_rpc.md ├── quick_start.md ├── utilities.md └── v2.0_changes.md ├── example ├── 0_create_conflux.js ├── 1_account_and_balance.js ├── 2_send_transaction.js ├── 3_epoch_block_transaction.js ├── 4_contract_deploy_and_call.js ├── 5_contract_override.js ├── README.md ├── browser │ └── work-with-fluent.html └── contract │ ├── miniERC20.json │ ├── miniERC20.sol │ ├── override.json │ └── override.sol ├── jest.config.js ├── jsdoc.json ├── mock ├── MockProvider.js ├── index.js └── util.js ├── package.json ├── scripts ├── build-frontend.js ├── deal-dts.js ├── document.js ├── jsdoc-to-md.js ├── replaceTSimport.js └── test-create-react-app-default-bundler.sh ├── src ├── CONST.js ├── Conflux.js ├── Drip.js ├── ERROR_CODES.js ├── Message.js ├── PersonalMessage.js ├── Transaction.js ├── contract │ ├── ContractABI.js │ ├── abi │ │ ├── AddressCoder.js │ │ ├── ArrayCoder.js │ │ ├── BaseCoder.js │ │ ├── BoolCoder.js │ │ ├── BytesCoder.js │ │ ├── IntegerCoder.js │ │ ├── NullCoder.js │ │ ├── StringCoder.js │ │ ├── TupleCoder.js │ │ └── index.js │ ├── event │ │ ├── ContractEvent.js │ │ ├── ContractEventOverride.js │ │ ├── EventCoder.js │ │ └── LogFilter.js │ ├── index.js │ ├── internal │ │ └── index.js │ ├── method │ │ ├── ContractConstructor.js │ │ ├── ContractMethod.js │ │ ├── ContractMethodOverride.js │ │ ├── ErrorCoder.js │ │ ├── FunctionCoder.js │ │ └── MethodTransaction.js │ └── standard │ │ └── index.js ├── index.js ├── primitives │ ├── AccessList.js │ └── index.js ├── provider │ ├── BaseProvider.js │ ├── HttpProvider.js │ ├── RPCError.js │ ├── WebSocketProvider.js │ ├── WechatProvider.js │ └── index.js ├── rpc │ ├── Advanced.js │ ├── BatchRequester.js │ ├── cfx.js │ ├── index.js │ ├── pos.js │ ├── rpcPatch.js │ ├── trace.js │ ├── txpool.js │ └── types │ │ ├── Account.js │ │ ├── formatter.js │ │ ├── index.js │ │ └── rawFormatter.js ├── subscribe │ ├── PendingTransaction.js │ └── Subscription.js ├── util │ ├── HexStream.js │ ├── address.js │ ├── callable.js │ ├── format.js │ ├── index.js │ ├── jsbi.js │ ├── namedTuple.js │ ├── parser.js │ ├── providerWrapper.js │ ├── rlp.js │ ├── sign.js │ └── trace.js └── wallet │ ├── Account.js │ ├── PrivateKeyAccount.js │ ├── Wallet.js │ └── index.js ├── test ├── 1559.test.js ├── batch.test.js ├── conflux │ ├── after.test.js │ ├── before.test.js │ └── sendTransaction.test.js ├── contract │ ├── EventCoder.test.js │ ├── FunctionCoder.test.js │ ├── contract.json │ ├── contract.test.js │ ├── internal.test.js │ ├── stringAbi.js │ ├── stringContractAbi.test.js │ └── valueCoder.test.js ├── drip.test.js ├── index.js ├── message.test.js ├── personalMessage.test.js ├── primitives │ └── accessList.test.js ├── provider.test.js ├── rpc │ └── cfxraw.test.js ├── transaction.test.js ├── util │ ├── HexStream.test.js │ ├── PendingTransaction.test.js │ ├── address.test.js │ ├── format.test.js │ ├── index.test.js │ ├── jsbi.test.js │ ├── rlp.test.js │ └── sign.test.js └── wallet.test.js ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | commonjs: true, 4 | es6: true, 5 | node: true, 6 | jest: true, 7 | }, 8 | extends: 'airbnb-base', 9 | globals: { 10 | Atomics: 'readonly', 11 | SharedArrayBuffer: 'readonly', 12 | BigInt: true, 13 | wx: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | }, 18 | rules: { 19 | 'arrow-parens': ['error', 'as-needed'], 20 | 'arrow-body-style': 0, // for readable lambda 21 | 'class-methods-use-this': 0, 22 | 'func-names': 0, // for function without name 23 | 'function-paren-newline': 0, // for readable arguments 24 | 'linebreak-style': 0, // for windows and mac 25 | 'max-classes-per-file': 0, 26 | 'max-len': 0, // for jsdoc 27 | 'no-await-in-loop': 0, // for loop request 28 | 'no-else-return': 0, 29 | 'no-param-reassign': 0, // for merge default Value 30 | 'no-restricted-syntax': 0, // for `for(... of ...)` 31 | 'no-underscore-dangle': 0, // for private attribute 32 | 'no-use-before-define': 0, // for recursive parse 33 | 'object-curly-newline': 0, // for object in one line 34 | 'object-property-newline': 0, // for long object keys 35 | 'operator-linebreak': 0, // for `'string' +\n` 36 | 'prefer-destructuring': 0, 37 | 'quote-props': 0, // for string key 38 | 'yoda': 0, // for `min < number && number < max` 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master, v2 ] 9 | pull_request: 10 | branches: [ master, v2 ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [20.x] # 14.x, 16.x 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | # cache: 'npm' 29 | # cache: 'yarn' 30 | - run: yarn 31 | # - run: npm install 32 | - run: npm run eslint 33 | - run: npm run test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | /coverage/ 4 | /esm/ 5 | /lib/ 6 | /docs/tmp.md 7 | /InternalContractAbi 8 | docs/apiv2 9 | .vscode 10 | apidoc 11 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Home](README.md) 4 | * [Introduction](docs/index.md) 5 | * [QuickStart](docs/quick_start.md) 6 | * [Overview](docs/overview.md) 7 | * [Contrast with web3.js](docs/jssdk-vs-web3.md) 8 | * [Guides](docs/README.md) 9 | * [Providers](docs/guides/providers.md) 10 | * [How to Connect Fluent](docs/guides/how_to_connect_fluent.md) 11 | * [CIP37 Address](docs/guides/conflux_checksum_address.md) 12 | * [Account](docs/guides/account.md) 13 | * [Sending Transaction](docs/guides/how_to_send_tx.md) 14 | * [Interact with Contract](docs/guides/interact_with_contract.md) 15 | * [Sign methods](docs/guides/sign_methods.md) 16 | * [Error Handling](docs/guides/error_handling.md) 17 | * [Batch RPC](docs/guides/batch_rpc.md) 18 | * [API](docs/api/README.md) 19 | * [Conflux](docs/api/Conflux.md) 20 | * [PoS](docs/api/rpc/pos.md) 21 | * [TxPool](docs/api/rpc/txpool.md) 22 | * [Provider](docs/api/provider/BaseProvider.md) 23 | * [Drip](docs/api/Drip.md) 24 | * [Transaction](docs/api/Transaction.md) 25 | * [Wallet](docs/api/wallet/Wallet.md) 26 | * [Release notes](change_log.md) 27 | * [v2.0 Changes](docs/v2.0_changes.md) 28 | * [FAQs](docs/FAQs.md) 29 | -------------------------------------------------------------------------------- /bin/cfxjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | const { program } = require('commander'); 4 | const sign = require('../src/util/sign'); 5 | const format = require('../src/util/format'); 6 | 7 | program.version('0.0.1') 8 | .name('cfxjs'); 9 | 10 | program.command('genEthCMPTaccount') 11 | .description('Generate a ethereum compatible account') 12 | .action(genCompatibleAccount); 13 | 14 | program.parse(process.argv); 15 | 16 | function genCompatibleAccount() { 17 | while (true) { 18 | const privateKey = sign.randomPrivateKey(); 19 | const publicKey = sign.privateKeyToPublicKey(privateKey); 20 | let address = sign.keccak256(publicKey).slice(-20); 21 | address = format.hex(address); 22 | if (address.startsWith('0x1')) { 23 | console.log('PrivateKey: ', format.hex(privateKey)); 24 | console.log('Address: ', address); 25 | break; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bin/rpc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | const { program } = require('commander'); 4 | const { Conflux, Drip } = require('../src'); 5 | 6 | const mainnetMeta = { 7 | url: 'https://main.confluxrpc.com', 8 | networkId: 1029, 9 | }; 10 | 11 | const testnetMeta = { 12 | url: 'https://test.confluxrpc.com', 13 | networkId: 1, 14 | }; 15 | 16 | program 17 | .version('0.0.1') 18 | .name('sponsor') 19 | .option('-t, --testnet', 'Use Conflux testnet network'); 20 | 21 | program 22 | .command('cfx') 23 | .description('call methods in cfx namespace') 24 | .argument('', 'RPC method name to call') 25 | .argument('[args...]', 'args') 26 | .action(cfx); 27 | 28 | program 29 | .command('call') 30 | .description('call methods in cfx namespace') 31 | .argument('', 'RPC namespace: cfx, pos, txpool, trace') 32 | .argument('', 'RPC method name to call') 33 | .argument('[args...]', 'args') 34 | .action(call); 35 | 36 | function _getClient() { 37 | const options = program.opts(); 38 | const conflux = options.testnet ? new Conflux(testnetMeta) : new Conflux(mainnetMeta); 39 | return conflux; 40 | } 41 | 42 | /* function _getAccount(conflux) { 43 | if (!process.env.PRIVATE_KEY) { 44 | console.log('Please set PRIVATE_KEY environment variable to update sponsor'); 45 | process.exit(); 46 | } 47 | return conflux.wallet.addPrivateKey(process.env.PRIVATE_KEY); 48 | } */ 49 | 50 | async function cfx(method, args) { 51 | const conflux = _getClient(); 52 | if (!conflux.cfx[method]) { 53 | console.log(`${method} is not a valid cfx method`); 54 | return; 55 | } 56 | const result = await conflux.cfx[method](...args); 57 | console.log(`cfx_${method}: `, result); 58 | } 59 | 60 | async function call(namespace, method, args) { 61 | const conflux = _getClient(); 62 | 63 | if (!conflux[namespace]) { 64 | console.log(`${namespace} is not a valid namespace`); 65 | return; 66 | } 67 | const methods = conflux[namespace]; 68 | 69 | if (!methods[method]) { 70 | console.log(`${method} is not a valid ${namespace} method`); 71 | return; 72 | } 73 | const result = await methods[method](...args); 74 | console.log(`${namespace}_${method}: `, result); 75 | } 76 | 77 | program.parse(process.argv); 78 | -------------------------------------------------------------------------------- /docs/FAQs.md: -------------------------------------------------------------------------------- 1 | # FAQs 2 | 3 | ## Is js-conflux-sdk support mnemonic or HDwallet ? 4 | 5 | Mnemonic can be support by a third package `@conflux-dev/hdwallet`, check [SDK account doc](./account.md) HD wallet part for detail intro. 6 | 7 | ## Common Errors 8 | 9 | Check [Error Handling](./error_handling.md) for common errors and how to handle them. 10 | 11 | ## Why is the transaction not mined or sending transaction timeout? 12 | 13 | If you are sending a transaction and it is not mined or it is timeout, you can check the following: 14 | 15 | * The sending account have enough balance. 16 | * If the transaction should be sponsored, the sponsor account have enough balance. 17 | * The transaction nonce is correct(not bigger than sender account's nonce). 18 | * If the network is busy, you can try to increase the gas price. 19 | 20 | For more detail, check [why tx is pending?](https://doc.confluxnetwork.org/docs/core/core-space-basics/transactions/why-transaction-is-pending) and [Transaction lifecycle for more info](https://doc.confluxnetwork.org/docs/core/core-space-basics/transactions/lifecycle) 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # docs 2 | 3 | -------------------------------------------------------------------------------- /docs/api/Drip.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Drip 4 | Positive decimal integer string in `Drip` 5 | 6 | **Kind**: global class 7 | 8 | * [Drip](#Drip) 9 | * [new Drip(value)](#new_Drip_new) 10 | * _instance_ 11 | * [.toCFX()](#Drip+toCFX) ⇒ string 12 | * [.toGDrip()](#Drip+toGDrip) ⇒ string 13 | * _static_ 14 | * [.fromCFX(value)](#Drip.fromCFX) ⇒ [Drip](#Drip) 15 | * [.fromGDrip(value)](#Drip.fromGDrip) ⇒ [Drip](#Drip) 16 | 17 | 18 | 19 | ### new Drip(value) 20 | 21 | | Param | Type | 22 | | --- | --- | 23 | | value | number \| string \| BigInt | 24 | 25 | **Example** 26 | ```js 27 | > new Drip(1.00) 28 | [String (Drip): '1'] 29 | > new Drip('0xab') 30 | [String (Drip): '171'] 31 | ``` 32 | 33 | 34 | ### drip.toCFX() ⇒ string 35 | Get `CFX` number string 36 | 37 | **Kind**: instance method of [Drip](#Drip) 38 | **Example** 39 | ```js 40 | > Drip(1e9).toCFX() 41 | "0.000000001" 42 | ``` 43 | 44 | 45 | ### drip.toGDrip() ⇒ string 46 | Get `GDrip` number string 47 | 48 | **Kind**: instance method of [Drip](#Drip) 49 | **Example** 50 | ```js 51 | > Drip(1e9).toGDrip() 52 | "1" 53 | ``` 54 | 55 | 56 | ### Drip.fromCFX(value) ⇒ [Drip](#Drip) 57 | Get `Drip` string from `CFX` 58 | 59 | **Kind**: static method of [Drip](#Drip) 60 | 61 | | Param | Type | 62 | | --- | --- | 63 | | value | string \| number \| BigInt | 64 | 65 | **Example** 66 | ```js 67 | > Drip.fromCFX(3.14) 68 | [String (Drip): '3140000000000000000'] 69 | > Drip.fromCFX('0xab') 70 | [String (Drip): '171000000000000000000'] 71 | ``` 72 | 73 | 74 | ### Drip.fromGDrip(value) ⇒ [Drip](#Drip) 75 | Get `Drip` string from `GDrip` 76 | 77 | **Kind**: static method of [Drip](#Drip) 78 | 79 | | Param | Type | 80 | | --- | --- | 81 | | value | string \| number \| BigInt | 82 | 83 | **Example** 84 | ```js 85 | > Drip.fromGDrip(3.14) 86 | [String (Drip): '3140000000'] 87 | > Drip.fromGDrip('0xab') 88 | [String (Drip): '171000000000'] 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | * [Conflux](./Conflux.md) The `Conflux` class provide methods to interact with RPC methods and send transaction. 4 | * [Wallet](./wallet/Wallet.md) The `Wallet` class provide methods to manage accounts and sign transactions. 5 | * [PrivateKeyAccount](./wallet/PrivateKeyAccount.md) The `PrivateKeyAccount` class can be used to sign transactions or messages. It can be created from a private key or be random generated. 6 | * [Transaction](./Transaction.md) The `Transaction` class provide methods to construct and encode transactions. 7 | * [Drip](./Drip.md) Drip - CFX converter 8 | * [format](./util/format.md) Type formaters 9 | * [sign](./util/sign.md) Crypto utilities 10 | * [address utilities](./util/address.md) Address utilities -------------------------------------------------------------------------------- /docs/api/provider/BaseProvider.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## BaseProvider 4 | **Kind**: global class 5 | 6 | * [BaseProvider](#BaseProvider) 7 | * [new BaseProvider([options])](#new_BaseProvider_new) 8 | * [.requestId()](#BaseProvider+requestId) ⇒ string 9 | * [.request(data)](#BaseProvider+request) ⇒ Promise.<\*> 10 | * [.call(method, ...params)](#BaseProvider+call) ⇒ Promise.<\*> 11 | * [.send(method, [params])](#BaseProvider+send) ⇒ Promise.<\*> 12 | * [.batch(array)](#BaseProvider+batch) ⇒ Promise.<Array> 13 | 14 | 15 | 16 | ### new BaseProvider([options]) 17 | 18 | | Param | Type | Default | Description | 19 | | --- | --- | --- | --- | 20 | | [options] | object | | | 21 | | options.url | string | | Full json rpc http url | 22 | | [options.timeout] | number | 30*1000 | Request time out in ms | 23 | | [options.retry] | number | 1 | Retry number | 24 | | [options.keepAlive] | boolean | false | Whether open the http keep-alive option | 25 | | [options.logger] | object | | Logger with `info` and `error` | 26 | 27 | 28 | 29 | ### baseProvider.requestId() ⇒ string 30 | Gen a random json rpc id. 31 | It is used in `call` method, overwrite it to gen your own id. 32 | 33 | **Kind**: instance method of [BaseProvider](#BaseProvider) 34 | 35 | 36 | ### baseProvider.request(data) ⇒ Promise.<\*> 37 | Call a json rpc method with params 38 | 39 | **Kind**: instance method of [BaseProvider](#BaseProvider) 40 | **Returns**: Promise.<\*> - Json rpc method return value. 41 | 42 | | Param | Type | Description | 43 | | --- | --- | --- | 44 | | data | object | | 45 | | data.method | string | Json rpc method name. | 46 | | [data.params] | array | Json rpc method params. | 47 | 48 | **Example** 49 | ```js 50 | > await provider.request({method: 'cfx_epochNumber'}); 51 | > await provider.request({method: 'cfx_getBlockByHash', params: [blockHash]}); 52 | ``` 53 | 54 | 55 | ### baseProvider.call(method, ...params) ⇒ Promise.<\*> 56 | Call a json rpc method with params 57 | 58 | **Kind**: instance method of [BaseProvider](#BaseProvider) 59 | **Returns**: Promise.<\*> - Json rpc method return value. 60 | 61 | | Param | Type | Description | 62 | | --- | --- | --- | 63 | | method | string | Json rpc method name. | 64 | | ...params | Array.<any> | Json rpc method params. | 65 | 66 | **Example** 67 | ```js 68 | > await provider.call('cfx_epochNumber'); 69 | > await provider.call('cfx_getBlockByHash', blockHash); 70 | ``` 71 | 72 | 73 | ### baseProvider.send(method, [params]) ⇒ Promise.<\*> 74 | Send a json rpc method request 75 | 76 | **Kind**: instance method of [BaseProvider](#BaseProvider) 77 | **Returns**: Promise.<\*> - Json rpc method return value. 78 | 79 | | Param | Type | Description | 80 | | --- | --- | --- | 81 | | method | string | Json rpc method name. | 82 | | [params] | array | Json rpc method params. | 83 | 84 | **Example** 85 | ```js 86 | > await provider.send('cfx_epochNumber'); 87 | > await provider.send('cfx_getBlockByHash', [blockHash]); 88 | ``` 89 | 90 | 91 | ### baseProvider.batch(array) ⇒ Promise.<Array> 92 | Batch call json rpc methods with params 93 | 94 | **Kind**: instance method of [BaseProvider](#BaseProvider) 95 | 96 | | Param | Type | Description | 97 | | --- | --- | --- | 98 | | array | Array.<object> | Array of object with "method" and "params" | 99 | 100 | **Example** 101 | ```js 102 | > await provider.batch([ 103 | { method: 'cfx_epochNumber' }, 104 | { method: 'cfx_getBalance', params: ['cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp'] }, 105 | { method: 'InValidInput' }, 106 | ]) 107 | [ '0x3b734d', '0x22374d959c622f74728', RPCError: Method not found ] 108 | ``` 109 | -------------------------------------------------------------------------------- /docs/api/provider/HttpProvider.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## HttpProvider 4 | Http protocol json rpc provider. 5 | 6 | **Kind**: global class 7 | -------------------------------------------------------------------------------- /docs/api/provider/WebSocketProvider.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## WebSocketProvider 4 | Websocket protocol json rpc provider. 5 | 6 | **Kind**: global class 7 | 8 | 9 | ### new WebSocketProvider([options]) 10 | 11 | | Param | Type | Default | Description | 12 | | --- | --- | --- | --- | 13 | | [options] | object | | See [W3CWebSocket](https://github.com/theturtle32/WebSocket-Node/blob/c91a6cb8f0cf896edf0d2d49faa0c9e0a9985172/docs/W3CWebSocket.md) | 14 | | options.url | string | | Full json rpc http url | 15 | | [options.timeout] | number | 30*1000 | Request time out in ms | 16 | | [options.logger] | object | | Logger with `info` and `error` | 17 | | [options.protocols] | Array.<string> | | See [w3](https://www.w3.org/TR/websockets/) | 18 | | [options.origin] | string | | | 19 | | [options.headers] | object | | | 20 | | [options.requestOptions] | object | | | 21 | | [options.clientConfig] | object | | See [websocket/lib/WebSocketClient](https://github.com/theturtle32/WebSocket-Node/blob/c91a6cb8f0cf896edf0d2d49faa0c9e0a9985172/docs/WebSocketClient.md) | 22 | | [options.clientConfig.maxReceivedFrameSize] | number | 0x100000 | 1MiB max frame size. | 23 | | [options.clientConfig.maxReceivedMessageSize] | number | 0x800000 | 8MiB max message size, only applicable if assembleFragments is true | 24 | | [options.clientConfig.closeTimeout] | number | 5000 | The number of milliseconds to wait after sending a close frame for an acknowledgement to come back before giving up and just closing the socket. | 25 | 26 | -------------------------------------------------------------------------------- /docs/api/provider/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## providerFactory(options) ⇒ WebsocketProvider \| HttpProvider \| BaseProvider \| WechatProvider 4 | **Kind**: global function 5 | 6 | | Param | Type | Description | 7 | | --- | --- | --- | 8 | | options | object | | 9 | | options.url | string | | 10 | | [options.useWechatProvider] | boolean | Whether use wechat provider. | 11 | 12 | **Example** 13 | ```js 14 | > providerFactory() 15 | BaseProvider { 16 | url: undefined, 17 | timeout: 300000, 18 | logger: { info: [Function: info], error: [Function: error] } 19 | } 20 | ``` 21 | **Example** 22 | ```js 23 | > providerFactory({ url: 'http://localhost:12537' }) 24 | HttpProvider { 25 | url: 'http://localhost:12537', 26 | timeout: 300000, 27 | logger: { info: [Function: info], error: [Function: error] } 28 | } 29 | > providerFactory({ 30 | url: 'http://main.confluxrpc.org', 31 | timeout: 60 * 60 * 1000, 32 | logger: console, 33 | } 34 | HttpProvider { 35 | url: 'http://main.confluxrpc.org', 36 | timeout: 3600000, 37 | logger: {...} 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/api/rpc/txpool.md: -------------------------------------------------------------------------------- 1 | ## Classes 2 | 3 |
4 |
TxPool
5 |

Class contains txpool RPC methods

6 |
7 |
8 | 9 | 10 | 11 | ## TxPool 12 | Class contains txpool RPC methods 13 | 14 | **Kind**: global class 15 | 16 | 17 | ### new TxPool(conflux) 18 | TxPool constructor. 19 | 20 | **Returns**: [TxPool](#TxPool) - The TxPool instance 21 | 22 | | Param | Type | Description | 23 | | --- | --- | --- | 24 | | conflux | Conflux | A Conflux instance | 25 | 26 | 27 | 28 | ## .nextNonce ⇒ Promise.<number> 29 | Get user next nonce in transaction pool 30 | 31 | **Kind**: instance member 32 | **Returns**: Promise.<number> - The next usable nonce 33 | 34 | | Param | Type | Description | 35 | | --- | --- | --- | 36 | | address | string | The address of the account | 37 | 38 | **Example** *(Example usage of txpool.nextNonce)* 39 | ```js 40 | await conflux.txpool.nextNonce('cfxtest:aak2rra2njvd77ezwjvx04kkds9fzagfe6d5r8e957'); 41 | // returns 100 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/api/subscribe/PendingTransaction.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## PendingTransaction 4 | **Kind**: global class 5 | 6 | * [PendingTransaction](#PendingTransaction) 7 | * [new PendingTransaction(conflux, func, args)](#new_PendingTransaction_new) 8 | * [.get([options])](#PendingTransaction+get) ⇒ Promise.<(Transaction\|null)> 9 | * [.mined([options])](#PendingTransaction+mined) ⇒ Promise.<Transaction> 10 | * [.executed([options])](#PendingTransaction+executed) ⇒ Promise.<TransactionReceipt> 11 | * [.confirmed([options])](#PendingTransaction+confirmed) ⇒ Promise.<TransactionReceipt> 12 | 13 | 14 | 15 | ### new PendingTransaction(conflux, func, args) 16 | PendingTransaction constructor. 17 | 18 | 19 | | Param | Type | 20 | | --- | --- | 21 | | conflux | Conflux | 22 | | func | function | 23 | | args | array | 24 | 25 | 26 | 27 | ### pendingTransaction.get([options]) ⇒ Promise.<(Transaction\|null)> 28 | Get transaction by hash. 29 | 30 | **Kind**: instance method of [PendingTransaction](#PendingTransaction) 31 | **Returns**: Promise.<(Transaction\|null)> - See [Conflux.getTransactionByHash](#Conflux.js/getTransactionByHash) 32 | 33 | | Param | Type | Default | Description | 34 | | --- | --- | --- | --- | 35 | | [options] | object | | | 36 | | [options.delay] | number | 0 | Defer execute after `delay` ms. | 37 | 38 | 39 | 40 | ### pendingTransaction.mined([options]) ⇒ Promise.<Transaction> 41 | Async wait till transaction been mined. 42 | 43 | - blockHash !== null 44 | 45 | **Kind**: instance method of [PendingTransaction](#PendingTransaction) 46 | **Returns**: Promise.<Transaction> - See [Conflux.getTransactionByHash](#Conflux.js/getTransactionByHash) 47 | 48 | | Param | Type | Default | Description | 49 | | --- | --- | --- | --- | 50 | | [options] | object | | | 51 | | [options.delta] | number | 1000 | Loop transaction interval in ms. | 52 | | [options.timeout] | number | 60*1000 | Loop timeout in ms. | 53 | 54 | 55 | 56 | ### pendingTransaction.executed([options]) ⇒ Promise.<TransactionReceipt> 57 | Async wait till transaction been executed. 58 | 59 | - mined 60 | - receipt !== null 61 | - receipt.outcomeStatus === 0 62 | 63 | **Kind**: instance method of [PendingTransaction](#PendingTransaction) 64 | **Returns**: Promise.<TransactionReceipt> - See [Conflux.getTransactionReceipt](#Conflux.js/getTransactionReceipt) 65 | 66 | | Param | Type | Default | Description | 67 | | --- | --- | --- | --- | 68 | | [options] | object | | | 69 | | [options.delta] | number | 1000 | Loop transaction interval in ms. | 70 | | [options.timeout] | number | 5*60*1000 | Loop timeout in ms. | 71 | 72 | 73 | 74 | ### pendingTransaction.confirmed([options]) ⇒ Promise.<TransactionReceipt> 75 | Async wait till transaction been confirmed. 76 | 77 | - executed 78 | - transaction block risk coefficient < threshold 79 | 80 | **Kind**: instance method of [PendingTransaction](#PendingTransaction) 81 | **Returns**: Promise.<TransactionReceipt> - See [Conflux.getTransactionReceipt](#Conflux.js/getTransactionReceipt) 82 | 83 | | Param | Type | Default | Description | 84 | | --- | --- | --- | --- | 85 | | [options] | object | | | 86 | | [options.delta] | number | 1000 | Loop transaction interval in ms. | 87 | | [options.timeout] | number | 30*60*1000 | Loop timeout in ms. | 88 | | [options.threshold] | number | 1e-8 | Number in range (0,1) | 89 | 90 | -------------------------------------------------------------------------------- /docs/api/wallet/Account.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Account 4 | Account abstract class 5 | 6 | **Kind**: global class 7 | 8 | * [Account](#Account) 9 | * [new Account(address)](#new_Account_new) 10 | * [.signTransaction(options)](#Account+signTransaction) ⇒ Promise.<Transaction> 11 | * [.signMessage(message)](#Account+signMessage) ⇒ Promise.<Message> 12 | * [.toString()](#Account+toString) ⇒ string 13 | * [.toJSON()](#Account+toJSON) ⇒ string 14 | 15 | 16 | 17 | ### new Account(address) 18 | 19 | | Param | Type | 20 | | --- | --- | 21 | | address | string | 22 | 23 | 24 | 25 | ### account.signTransaction(options) ⇒ Promise.<Transaction> 26 | **Kind**: instance method of [Account](#Account) 27 | 28 | | Param | Type | 29 | | --- | --- | 30 | | options | object | 31 | 32 | 33 | 34 | ### account.signMessage(message) ⇒ Promise.<Message> 35 | **Kind**: instance method of [Account](#Account) 36 | 37 | | Param | Type | 38 | | --- | --- | 39 | | message | string | 40 | 41 | 42 | 43 | ### account.toString() ⇒ string 44 | **Kind**: instance method of [Account](#Account) 45 | **Returns**: string - Address as string. 46 | 47 | 48 | ### account.toJSON() ⇒ string 49 | **Kind**: instance method of [Account](#Account) 50 | **Returns**: string - Address as JSON string. 51 | -------------------------------------------------------------------------------- /docs/api/wallet/Wallet.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Wallet 4 | Wallet to manager accounts. 5 | 6 | **Kind**: global class 7 | 8 | * [Wallet](#Wallet) 9 | * [new Wallet(networkId)](#new_Wallet_new) 10 | * [.setNetworkId(networkId)](#Wallet+setNetworkId) 11 | * [.has(address)](#Wallet+has) ⇒ boolean 12 | * [.delete(address)](#Wallet+delete) ⇒ boolean 13 | * [.clear()](#Wallet+clear) 14 | * [.set(address, account)](#Wallet+set) ⇒ any 15 | * [.get(address)](#Wallet+get) ⇒ Account 16 | * [.addPrivateKey(privateKey)](#Wallet+addPrivateKey) ⇒ PrivateKeyAccount 17 | * [.addRandom([entropy])](#Wallet+addRandom) ⇒ PrivateKeyAccount 18 | * [.addKeystore(keystore, password)](#Wallet+addKeystore) ⇒ PrivateKeyAccount 19 | 20 | 21 | 22 | ### new Wallet(networkId) 23 | 24 | | Param | Type | 25 | | --- | --- | 26 | | networkId | number | 27 | 28 | 29 | 30 | ### wallet.setNetworkId(networkId) 31 | Set network id 32 | 33 | **Kind**: instance method of [Wallet](#Wallet) 34 | 35 | | Param | Type | 36 | | --- | --- | 37 | | networkId | number | 38 | 39 | 40 | 41 | ### wallet.has(address) ⇒ boolean 42 | Check if address exist 43 | 44 | **Kind**: instance method of [Wallet](#Wallet) 45 | 46 | | Param | Type | 47 | | --- | --- | 48 | | address | string | 49 | 50 | 51 | 52 | ### wallet.delete(address) ⇒ boolean 53 | Drop one account by address 54 | 55 | **Kind**: instance method of [Wallet](#Wallet) 56 | 57 | | Param | Type | 58 | | --- | --- | 59 | | address | string | 60 | 61 | 62 | 63 | ### wallet.clear() 64 | Drop all account in wallet 65 | 66 | **Kind**: instance method of [Wallet](#Wallet) 67 | 68 | 69 | ### wallet.set(address, account) ⇒ any 70 | **Kind**: instance method of [Wallet](#Wallet) 71 | 72 | | Param | Type | Description | 73 | | --- | --- | --- | 74 | | address | any | Key of account, usually is `address` | 75 | | account | any | Account instance | 76 | 77 | 78 | 79 | ### wallet.get(address) ⇒ Account 80 | **Kind**: instance method of [Wallet](#Wallet) 81 | 82 | | Param | Type | 83 | | --- | --- | 84 | | address | string | 85 | 86 | 87 | 88 | ### wallet.addPrivateKey(privateKey) ⇒ PrivateKeyAccount 89 | **Kind**: instance method of [Wallet](#Wallet) 90 | 91 | | Param | Type | Description | 92 | | --- | --- | --- | 93 | | privateKey | string \| Buffer | Private key of account | 94 | 95 | 96 | 97 | ### wallet.addRandom([entropy]) ⇒ PrivateKeyAccount 98 | **Kind**: instance method of [Wallet](#Wallet) 99 | 100 | | Param | Type | Description | 101 | | --- | --- | --- | 102 | | [entropy] | string \| Buffer | Entropy of random account | 103 | 104 | 105 | 106 | ### wallet.addKeystore(keystore, password) ⇒ PrivateKeyAccount 107 | **Kind**: instance method of [Wallet](#Wallet) 108 | 109 | | Param | Type | Description | 110 | | --- | --- | --- | 111 | | keystore | object | Keystore version 3 object. | 112 | | password | string \| Buffer | Password for keystore to decrypt with. | 113 | 114 | -------------------------------------------------------------------------------- /docs/api/wallet/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Conflux-Chain/js-conflux-sdk/c5174dfebb6d8687c182e6d5bbcc6f91304bedb6/docs/api/wallet/index.md -------------------------------------------------------------------------------- /docs/block_trace.md: -------------------------------------------------------------------------------- 1 | # Traces 2 | 3 | ## block_trace 4 | 5 | Block response data structure 6 | 7 | ```javascript 8 | { 9 | "transactionTraces": [ 10 | { 11 | "traces": [ 12 | { 13 | "action": {} 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | ``` 20 | 21 | ## Action enum: 22 | 23 | 1. Call 24 | 2. Create 25 | 3. CallResult 26 | 4. CreateResult 27 | 5. InternalTransferAction 28 | 29 | ### Call 30 | 31 | * pub from: RpcAddress, 32 | * pub to: RpcAddress, 33 | * pub value: U256, 34 | * pub gas: U256, 35 | * pub input: Bytes, 36 | * pub call_type: CallType, 37 | 38 | ### Create 39 | 40 | * pub from: RpcAddress, 41 | * pub value: U256, 42 | * pub gas: U256, 43 | * pub init: Bytes, 44 | 45 | ### CallResult 46 | 47 | * pub outcome: Outcome, 48 | * pub gas_left: U256, 49 | * pub return_data: Bytes, 50 | 51 | ### CreateResult 52 | 53 | * pub outcome: Outcome, 54 | * pub addr: RpcAddress, 55 | * pub gas_left: U256, 56 | * pub return_ata: Bytes, 57 | 58 | ### InternalTransferAction 59 | 60 | * pub from: RpcAddress, 61 | * pub to: RpcAddress, 62 | * pub value: U256, 63 | 64 | ### CallType 65 | 66 | * None, 0 67 | * Call, 1 68 | * CallCode, 2 69 | * DelegateCall, 3 70 | * StaticCall, 4 71 | 72 | ### OutCome 73 | 74 | * Success, 0 75 | * Revert, 1 76 | * Fail, 2 77 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | The js-conflux-sdk also provide three command line tool to help ease Conflux developer's life: 4 | 5 | 1. `sponsor` - Can be used to query and set contract sponsor 6 | 2. `rpc` - Can be used to invoke rpc methods 7 | 3. `cfxjs` - Can be used generate a compatiable account 8 | 9 | ## sponsor 10 | 11 | ```sh 12 | $ npx sponsor -h 13 | Usage: sponsor [options] [command] 14 | 15 | Options: 16 | -V, --version output the version number 17 | -t, --testnet Use Conflux testnet network 18 | -h, --help display help for command 19 | 20 | Commands: 21 | info Get contract sponsor info 22 | setGasSponsor Set gas sponsor 23 | setCollateralSponsor Set collateral sponsor 24 | addToWhiteList Add address to whitelist 25 | removeFromWhiteList Remove address from whitelist 26 | help [command] display help for command 27 | ``` 28 | 29 | ### Examples 30 | 31 | ```sh 32 | $ npx sponsor info cfx:achc8nxj7r451c223m18w2dwjnmhkd6rxawrvkvsy2 33 | Gas Sponsor: cfx:acgzz08m8z2ywkeda0jzu52fgaz9u95y1y50rnwmt3 34 | Gas Sponsor Balance: 19.999971068173696366 CFX 35 | Gas Sponsor upperBound: 10 GDrip 36 | Collateral Sponsor: cfx:acbkxbtruayaf2he1899e1533x4wg2a07eyjjrzu31 37 | Collateral Sponsor Balance: 926.1875 CFX 38 | IsAllWhitelisted: true 39 | Contact Admin: cfx:aatecdbb8fgnmadea7ng2xpkz2rt2wdgway9ghk03y 40 | ``` 41 | 42 | ## rpc 43 | 44 | ```sh 45 | $ npx rpc -h 46 | Usage: sponsor [options] [command] 47 | 48 | Options: 49 | -V, --version output the version number 50 | -t, --testnet Use Conflux testnet network 51 | -h, --help display help for command 52 | 53 | Commands: 54 | cfx [args...] call methods in cfx namespace 55 | call [args...] call methods in cfx namespace 56 | help [command] display help for command 57 | ``` 58 | 59 | ### Examples 60 | 61 | ```sh 62 | $ npx rpc cfx epochNumber 63 | cfx_epochNumber: 46833206 64 | 65 | $ npx rpc cfx getBalance cfx:achc8nxj7r451c223m18w2dwjnmhkd6rxawrvkvsy2 66 | cfx_getBalance: 0n 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/guides/account.md: -------------------------------------------------------------------------------- 1 | # Account 2 | 3 | Account can be used to sign `Transaction` or `Message`. Wallet like Fluent can help you manage your accounts (privateKeys) and provide signing functions to you. 4 | SDK also provide account manage and signing functions. 5 | 6 | ## Send transaction 7 | 8 | ```js 9 | // If you want send transaction signed by your own private key, it's need add to wallet before you send transaction 10 | const privateKey = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; // use your own private key 11 | const account = conflux.wallet.addPrivateKey(privateKey); 12 | await conflux.cfx.sendTransaction({ 13 | from: account.address, 14 | to: 'cfxtest:xxxx', 15 | value: 100 16 | }); 17 | ``` 18 | 19 | ## Random create 20 | 21 | ```js 22 | // create through wallet 23 | const account = conflux.wallet.addRandom(); 24 | // create though PrivateKeyAccount 25 | const { PrivateKeyAccount } = require('js-conflux-sdk'); 26 | const networkId = 1; 27 | const randomSeed = '0xfffff'; // any random buffer 28 | const account = PrivateKeyAccount.random(randomSeed, networkId); 29 | ``` 30 | 31 | ## Import keystore file 32 | 33 | ```js 34 | const keystoreJson = {}; // read from keystore file 35 | const account = conflux.wallet.addKeystore(keystoreJson, 'password'); 36 | ``` 37 | 38 | ## Export to keystore file 39 | 40 | ```js 41 | const keystoreJson = account.encrypt('password'); 42 | ``` 43 | 44 | ## Sign message 45 | 46 | The `Message` class can be used to sign an arbitrary message. 47 | 48 | ```js 49 | const { Message } = require('js-conflux-sdk'); 50 | const privateKey = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; // privateKey 51 | const messageHash = '0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba'; 52 | 53 | const signatureHexStr = Message.sign(privateKey, messageHash); 54 | const publicKey = Message.recover(signatureHexStr, messageHash); 55 | // Or you can build a Message instance 56 | 57 | const msg = new Message('hello world'); 58 | console.log(msg.hash); 59 | msg.sign(privateKey, networkId); 60 | console.log(msg.r, msg.s, msg.v); 61 | console.log(msg.from); 62 | ``` 63 | 64 | ## HD Wallet 65 | 66 | If you want to use account from mnemonic, there is a independent package `@conflux-dev/hdwallet` can fulfill your requirements. 67 | 68 | First step is install it by npm 69 | 70 | ```sh 71 | $ npm install @conflux-dev/hdwallet 72 | ``` 73 | 74 | Then you can use it to get the private key and add it to conflux wallet. 75 | 76 | ```js 77 | const { HDWallet } = require('@conflux-dev/hdwallet'); 78 | 79 | const mnemonic = 'faint also eye industry survey unhappy boil public lemon myself cube sense'; 80 | const rootNode = new HDWallet(mnemonic); 81 | 82 | const privateKey0 = wallet.getPrivateKeyByIndex(0); 83 | const account0 = conflux.wallet.addPrivateKey(privateKey0); 84 | console.log(privateKey0.toString('hex')); 85 | // 40d0f137665463584cc57fce2b761572a85d1cbf1601fc93d001c129b2a11c92 86 | console.log(account0.address); 87 | // cfxtest:aargrnff46pmuy2g1mmrntctkhr5mzamh6nmg361n0 88 | 89 | const privateKey1 = wallet.getPrivateKey("m/44'/503'/0'/0/1"); 90 | const account1 = conflux.wallet.addPrivateKey(privateKey1); 91 | ``` -------------------------------------------------------------------------------- /docs/guides/how_to_connect_fluent.md: -------------------------------------------------------------------------------- 1 | # How to Connect Fluent Wallet 2 | 3 | ## Fluent Wallet 4 | 5 | [Fluent Wallet](https://fluentwallet.com/) is a Conflux Core Space wallet that allows you to manage your assets and interact with the Conflux network. It is a secure, non-custodial wallet that allows you to store, send, and receive CFX and other Conflux-based assets. 6 | 7 | You can install it from Chrome Web Store, it's use experience is similar to MetaMask. 8 | 9 | Fluent Wallet injects a global `conflux` object into the browser (as detailed in the [provider API documentation](https://docs.fluentwallet.com/conflux/reference/provider-api/)). This object enables interaction between DApps and Fluent Wallet, allowing actions such as retrieving account information and initiating transactions. 10 | 11 | The js-conflux-sdk also supports Fluent Wallet by simply configuring the SDK instance's provider to the `conflux` object. This setup enables the use of js-conflux-sdk within a DApp to initiate transactions through Fluent Wallet. 12 | 13 | ```js 14 | import { Conflux, Drip } from 'js-conflux-sdk'; 15 | 16 | const cfxClient = new Conflux(); 17 | // Actually,it is the window.conflux injected by Fluent Wallet. You can also access via window.conflux 18 | cfxClient.provider = conflux; 19 | conflux.on('chainChanged', cfxClient.updateNetworkId); 20 | 21 | // Request accounts from fluent wallet, it will pop up a window to let user select account to connect with DApp when the DApp is first connected. 22 | let accounts = await cfxClient.provider.request({ method: 'cfx_requestAccounts', params: [] }); 23 | 24 | // the you can use cfxClient to perform any operations that js-conflux-sdk supports. 25 | let status = await cfxClient.getStatus(); 26 | console.log('chainId: ', status.chainId); 27 | 28 | // the send transaction experience is same as js-conflux-sdk 29 | let txHash = await cfxClient.cfx.sendTransaction({ 30 | from: accounts[0], 31 | to: 'cfx:xxx', 32 | value: Drip.fromCFX(1), 33 | }); 34 | // this will pop up a window to let user confirm the transaction. 35 | ``` 36 | 37 | To learn more about how to use js-conflux-sdk with Fluent Wallet, please refer to the [Fluent Wallet 'how to' guide](https://docs.fluentwallet.com/conflux/how-to/use-sdk/) -------------------------------------------------------------------------------- /docs/guides/providers.md: -------------------------------------------------------------------------------- 1 | # Providers 2 | 3 | The provider is how blockchain SDK talks to the blockchain. Providers take JSON-RPC requests and return the response. This is normally done by submitting the request to an HTTP or Websocket based server. 4 | 5 | ## Choosing How to Connect to Your Node 6 | 7 | Most nodes have a variety of ways to connect to them. The most common ways to connect to your node are: 8 | 9 | - Websockets (works remotely, faster than HTTP) 10 | - HTTP (more nodes support it) 11 | 12 | Currently, js-conflux-sdk has support for both `HTTP` and `Websockets`. Normally, websockets is recommended than HTTP, because it's faster and can use [Pub/Sub methods](https://doc.confluxnetwork.org/docs/core/build/json-rpc/pubsub). If you want to use the PubSub API, websocket provider is the only choice. Conflux-rust's HTTP default port is `12537`, websocket default port is `12535`. They can be changed through [config file](https://doc.confluxnetwork.org/docs/general/run-a-node/advanced-topics/node-configuration). 13 | 14 | When initialize a Conflux object, the underline provider can be configured through the `url` option. If you want to use HTTP provider, then provide an HTTP URL: 15 | 16 | ```js 17 | const cfxClient = new Conflux({ 18 | url: 'https://test.confluxrpc.com', 19 | networkId: 1, 20 | }); 21 | ``` 22 | 23 | If want use websockts provider then provide a WSS URL. 24 | 25 | ```js 26 | const cfxClient = new Conflux({ 27 | url: 'wss://test.confluxrpc.com', 28 | networkId: 1, 29 | }); 30 | ``` 31 | 32 | ## Connect through Fluent Wallet 33 | 34 | A Dapp can connect Conflux Blockchain through [Fluent](https://fluentwallet.com/), which is a browser extension that provides: 35 | 36 | - A connection to the Conflux network (a Provider) 37 | - Holds your private key and helps you sign things (a Signer) 38 | 39 | js-conflux-sdk (v2) can work with Fluent to talk with Conflux blockchain, simply by set `conflux` to the `Conflux` instance's provider. 40 | 41 | Note: Fluent wallet won't export `window.confluxJS` anymore, Dapp developers need to install `js-conflux-sdk` by themselves and set `Conflux` instance's provider to Fluent provider. 42 | 43 | ```js 44 | // Firstly initialize the Conflux object without url 45 | // Here Conflux indicate the SDK, TreeGraph.Conflux is the class used to talk with blockchain 46 | const cfxClient = new TreeGraph.Conflux({ 47 | networkId: 1, 48 | }); 49 | // "conflux" indicate the fluent's browser object 50 | cfxClient.provider = conflux; 51 | // update sdk network id when chain changed 52 | conflux.on('chainChanged', cfxClient.updateNetworkId); 53 | ``` 54 | 55 | If you can't determine the `networkId` of the selected network of Fluent, you can get it asynchronously like below. 56 | 57 | ```js 58 | const cfxClient = new TreeGraph.Conflux(); 59 | cfxClient.provider = conflux; 60 | conflux.on('chainChanged', cfxClient.updateNetworkId); 61 | await cfxClient.updateNetworkId(); 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/guides/sign_methods.md: -------------------------------------------------------------------------------- 1 | # sign methods 2 | 3 | ## Recover ConfluxPortal's personal_sign message 4 | 5 | To verify wallet's personal_sign method signed signature, developer need to recover publicKey or address from it and message. `PersonalMessage` class provide a static method can do this. 6 | 7 | ```js 8 | const { PersonalMessage } = require('js-conflux-sdk'); 9 | const message = 'Hello World'; 10 | const signature = '0xxxxx'; 11 | 12 | // message can be a normal string, or a hex encoded string 13 | const publicKey = PersonalMessage.recoverPortalPersonalSign(signature, message); 14 | // 0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559 15 | ``` 16 | 17 | ## CIP-23 personal_sign 18 | 19 | The SDK has provide a `PersonalMessage` class with can be used to personal_sign a message. 20 | 21 | ```js 22 | const { PersonalMessage } = require('js-conflux-sdk'); 23 | const privateKey = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; // use your own private key here 24 | const message = 'Hello World'; 25 | const signature = PersonalMessage.sign(privateKey, message); 26 | // 0xd72ea2020802d6dfce0d49fc1d92a16b43baa58fc152d6f437d852a014e0c5740b3563375b0b844a835be4f1521b4ae2a691048622f70026e0470acc5351043a01 27 | const publicKey = PersonalMessage.recover(signature, message); 28 | // 0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559 29 | ``` 30 | 31 | ## CIP-23 typedDataSign 32 | 33 | Conflux also have support for typed data sign through [CIP23](https://github.com/Conflux-Chain/CIPs/blob/2d9fdbdb08f66f705348669a6cd85e2d53509e97/CIPs/cip-23.md), which is similar to [EIP712](https://eips.ethereum.org/EIPS/eip-712). 34 | 35 | There is a Node.js package [`cip-23`](https://www.npmjs.com/package/cip-23) which provide some utility functions that can help with signing and verifying CIP-23 based messages 36 | 37 | ```json 38 | { 39 | "types": { 40 | "CIP23Domain": [ 41 | { "name": "name", "type": "string" }, 42 | { "name": "version", "type": "string" }, 43 | { "name": "chainId", "type": "uint256" }, 44 | { "name": "verifyingContract", "type": "address" } 45 | ], 46 | "Person": [ 47 | { "name": "name", "type": "string" }, 48 | { "name": "wallet", "type": "address" } 49 | ], 50 | "Mail": [ 51 | { "name": "from", "type": "Person" }, 52 | { "name": "to", "type": "Person" }, 53 | { "name": "contents", "type": "string" } 54 | ] 55 | }, 56 | "primaryType": "Mail", 57 | "domain": { 58 | "name": "Ether Mail", 59 | "version": "1", 60 | "chainId": 1, 61 | "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" 62 | }, 63 | "message": { 64 | "from": { 65 | "name": "Cow", 66 | "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" 67 | }, 68 | "to": { 69 | "name": "Bob", 70 | "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" 71 | }, 72 | "contents": "Hello, Bob!" 73 | } 74 | } 75 | ``` 76 | 77 | ```js 78 | import { getMessage } from 'cip-23'; 79 | import { sign } from 'js-conflux-sdk'; 80 | 81 | const typedData = { /*...*/ }; 82 | const message = getMessage(typedData).toString('hex'); // Build message 83 | // 1901f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e 84 | const messageHash = getMessage(typedData, true).toString('hex'); // Build message hash 85 | // be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2 86 | const privateKey = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; // use your own private key here 87 | // Sign message hash with private key 88 | const sig = sign.ecdsaSign(messageHash, privateKey); 89 | ``` 90 | 91 | ## Useful links 92 | 93 | 1. [MetaMask sign methods](https://docs.metamask.io/guide/signing-data.html) 94 | 2. [CIP23](https://github.com/Conflux-Chain/CIPs/blob/2d9fdbdb08f66f705348669a6cd85e2d53509e97/CIPs/cip-23.md) 95 | 3. [EIP712](https://eips.ethereum.org/EIPS/eip-712) 96 | 4. [EIP712 is here: What to expect and how to use it](https://medium.com/metamask/eip712-is-coming-what-to-expect-and-how-to-use-it-bb92fd1a7a26) -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | `js-conflux-sdk` is a Javascript library for interacting with Conflux. 4 | 5 | It’s commonly found in decentralized apps (dapps) to help with sending transactions, interacting with smart contracts, reading block data, and a variety of other use cases. 6 | 7 | ## Getting Started 8 | 9 | * Unfamiliar with Conflux? → [confluxnetwork](http://confluxnetwork.org) 10 | * Unfamiliar with Solidity? → [Solidity documentation](https://soliditylang.org/) 11 | * Ready to code? → [QuickStart](quick_start.md) 12 | * Interested in a quick tour? → [Overview](overview.md) 13 | * Like to give back? → [Contribute](https://github.com/conflux-chain/js-conflux-sdk) 14 | * Want to get testnet CFX? → [Conflux testnet faucet](http://faucet.confluxnetwork.org/) 15 | * Conflux sponsorship mechanism? → [Sponsorship](https://doc.confluxnetwork.org/docs/core/core-space-basics/sponsor-mechanism) 16 | 17 | ## Table of Contents 18 | 19 | * [QuickStart](quick_start.md) 20 | * [Overview](overview.md) 21 | * [Changelog](../change_log.md) 22 | * [js-sdk examples](https://github.com/conflux-fans/js-sdk-example) 23 | 24 | ## Guides 25 | 26 | * [Providers](providers.md) How to connect to Conflux network in different ways. 27 | * [CIP37 Address](conflux_checksum_address.md) How to convert between different address formats. 28 | * [Account](account.md) How to create and manage accounts. 29 | * [Sending Transaction](how_to_send_tx.md) How to send transactions and wait tx confirmed. 30 | * [Interact with Contract](interact_with_contract.md) How to interact with smart contracts. 31 | * [Sign methods](sign_methods.md) How to sign messages and transactions. 32 | * [Error Handling](error_handling.md) How to handle errors. 33 | * [Batch RPC](batch_rpc.md) How to batch RPC requests. 34 | 35 | ## Examples 36 | 37 | * [`Conflux` class initialization](https://github.com/Conflux-Chain/js-conflux-sdk/blob/v2/example/0_create_conflux.js) 38 | * [Account and balance](https://github.com/Conflux-Chain/js-conflux-sdk/blob/v2/example/1_account_and_balance.js) 39 | * [Send transaction](https://github.com/Conflux-Chain/js-conflux-sdk/blob/v2/example/2_send_transaction.js) 40 | * [Epoch, Block, Transaction](https://github.com/Conflux-Chain/js-conflux-sdk/blob/v2/example/3_epoch_block_transaction.js) 41 | * [Contract deploy and interact](https://github.com/Conflux-Chain/js-conflux-sdk/blob/v2/example/4_contract_deploy_and_call.js) 42 | * [Contract override](https://github.com/Conflux-Chain/js-conflux-sdk/blob/v2/example/5_contract_override.js) 43 | 44 | Check more community examples in [js-sdk-example](https://github.com/conflux-fans/js-sdk-example) 45 | 46 | ## API 47 | 48 | * [Conflux](./api/Conflux.md) The `Conflux` class provide methods to interact with RPC methods and send transaction. 49 | * [Wallet](./api/wallet/Wallet.md) The `Wallet` class provide methods to manage accounts and sign transactions. 50 | * [PrivateKeyAccount](./api/wallet/PrivateKeyAccount.md) The `PrivateKeyAccount` class can be used to sign transactions or messages. It can be created from a private key or be random generated. 51 | * [Transaction](./api/Transaction.md) The `Transaction` class provide methods to construct and encode transactions. 52 | * [Drip](./api/Drip.md) Drip - CFX converter 53 | * [format](./api/util/format.md) Type formaters 54 | * [sign](./api/util/sign.md) Crypto utilities 55 | * [address utilities](./api/util/address.md) Address utilities 56 | 57 | ## Other Docs 58 | 59 | 1. [Official developer documentation](https://doc.confluxnetwork.org/) 60 | 2. [RPC API](https://doc.confluxnetwork.org/docs/core/build/json-rpc/) 61 | 3. [Subscription](https://doc.confluxnetwork.org/docs/core/build/json-rpc/pubsub) 62 | 4. [FluentWallet Official Site](https://fluentwallet.com/) 63 | 5. [Fluent documentation](https://fluent-wallet.zendesk.com/hc/en-001) 64 | -------------------------------------------------------------------------------- /docs/pos_rpc.md: -------------------------------------------------------------------------------- 1 | # PoS RPC 2 | 3 | ## Available methods 4 | 5 | Check the PoS [OPEN-RPC documentation](https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/conflux-fans/conflux-rpc-doc/pos/pos-openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=false) 6 | 7 | ## How to use 8 | 9 | All PoS RPC methods are available at `Conflux` object's `pos` attribute. 10 | 11 | ```js 12 | const { Conflux } = require('js-conflux-sdk'); 13 | const conflux = new Conflux({ 14 | url: 'http://localhost:12537', 15 | networkId: 1 16 | }); 17 | 18 | async function main() { 19 | const status = await conflux.pos.getStatus(); 20 | console.log(status); 21 | // { blockNumber: 69, epoch: 2, pivotDecision: 15650 } 22 | } 23 | 24 | main().catch(console.log); 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/quick_start.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | > All code starting with a $ is meant to run on your terminal. All code starting with a > is meant to run in a node.js interpreter. 4 | 5 | ## Install 6 | 7 | Install the SDK with npm. 8 | 9 | ```text 10 | $ npm install js-conflux-sdk 11 | ``` 12 | 13 | ## Using Conflux 14 | 15 | This library depends on a connection to an Conflux node. These connections normally called Providers and there are several ways to configure them. This guide will use Conflux testnet provider `https://test.confluxrpc.com`. 16 | 17 | ### Povider: Official testnet 18 | 19 | The quickest way to interact with the Conflux blockchain is using a remote node provider, like official testnet. You can connect to a remote node by specifying the endpoint. 20 | 21 | ```javascript 22 | // import Conflux Class 23 | const { Conflux } = require('js-conflux-sdk'); 24 | // initialize a Conflux object 25 | const conflux = new Conflux({ 26 | url: 'https://test.confluxrpc.com', // testnet provider 27 | logger: console, // for debug: this will log all the RPC request and response to console 28 | networkId: 1, // note networkId is required to initiat 29 | // timeout: 300 * 1000, // request timeout in ms, default 300*1000 ms === 5 minute 30 | }); 31 | ``` 32 | 33 | ## Getting balance 34 | 35 | Then we can use the Conflux instance get blockchain data. 36 | 37 | ```javascript 38 | async function main() { 39 | // use conflux to get balance (in Drip) of a conflux address 40 | const address = 'cfxtest:aak2rra2njvd77ezwjvx04kkds9fzagfe6d5r8e957'; 41 | const balance = await conflux.cfx.getBalance(address); 42 | console.log(balance); 43 | } 44 | 45 | main(); 46 | ``` 47 | 48 | The conflux instance have a lot methods that correspond to Conflux RPC methods, such as `getBalance` map to RPC `cfx_getBalance`. Call these methods will return a promise or thenable, which means you can use it with ES6 `async/await` syntax. 49 | 50 | ## Transfer `CFX` 51 | 52 | CFX is the native token of Conflux network, can be transfered from one address to another address through transaction. 53 | To send one account's CFX, you must know address's private key. 54 | 55 | ```js 56 | // ... conflux init code 57 | // NOTE: before send transaction, `from`'s privateKey must add to wallet first. 58 | const account = conflux.wallet.addPrivateKey('0xxxxxxxxxx'); 59 | const targetAddress = 'cfxtest:xxxxxxx'; 60 | let pendingTx = conflux.cfx.sendTransaction({ 61 | from: account.address, 62 | to: targetAddress, 63 | value: 1 // the unit is drip 64 | }); 65 | let txHash = await pendingTx; 66 | // 0xedcfece4cc7a128992c18147cdc2b9ee58861249c97889654932d3162f78b556 67 | let tx = await pendingTx.mined(); 68 | // tx 69 | ``` 70 | 71 | ## Work with Wallet Plugin 72 | 73 | `js-conflux-sdk` can be used in browser, and co-work with [FluentWallet](https://fluentwallet.com/), by simply set the Fluent exported `window.conflux` as SDK instance's provider. 74 | 75 | ```js 76 | // Step1 - initialize the Conflux object without url 77 | // If you load the js-conflux-sdk through script tag: 78 | let cfxClient = new TreeGraph.Conflux({ 79 | networkId: 1, 80 | }); 81 | // If you use bundler to develop your frontend project, 82 | import { Conflux } from 'js-conflux-sdk'; 83 | let cfxClient = new Conflux({ 84 | networkId: 1, 85 | }); 86 | 87 | // Step2 - set window.conflux (provided by Fluent wallet) as client's provider 88 | cfxClient.provider = window.conflux; 89 | 90 | // Then you can retrive blockchain data and send transaction with the cfxClient 91 | 92 | cfxClient.cfx.getStatus().then(console.log); 93 | 94 | cfxClient.cfx.sendTransaction({ 95 | from: 'Your fluent wallet choosen account address', 96 | to: 'The target address', 97 | value: 1 98 | }) 99 | ``` 100 | -------------------------------------------------------------------------------- /docs/utilities.md: -------------------------------------------------------------------------------- 1 | # Common utilities 2 | 3 | ## Types Conversion 4 | 5 | The `format` utility can be used for format types: 6 | 7 | * uInt 8 | * bigInt 9 | * bigUInt 10 | * hex 11 | * address 12 | * hexAddress 13 | * hexBuffer 14 | * bytes 15 | * boolean 16 | * keccak256 17 | 18 | ```js 19 | const { format } = require('js-conflux-sdk'); 20 | // ======================== to hex string 21 | format.hex(null) 22 | // '0x' 23 | format.hex(1) 24 | // "0x01" 25 | format.hex(256) 26 | // "0x0100" 27 | format.hex(true) 28 | // "0x01" 29 | format.hex(Buffer.from([1,10,255])) 30 | // "0x010aff" 31 | format.hex("0x0a") 32 | // "0x0a" 33 | 34 | // ======================== to Conflux address 35 | format.address('0x0123456789012345678901234567890123456789', 1) 36 | // "cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp" 37 | 38 | // ======================== to hex40 address 39 | format.hexAddress('cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp') 40 | // 0x0123456789012345678901234567890123456789 41 | 42 | // ======================== to buffer 43 | format.hexBuffer(Buffer.from([0, 1])) 44 | // 45 | format.hexBuffer(null) 46 | // 47 | format.hexBuffer(1024) 48 | // 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/v2.0_changes.md: -------------------------------------------------------------------------------- 1 | # V2.0 Changes 2 | 3 | js-conflux-sdk `v2.0` has imported several big features. 4 | 5 | ## Group RPC methods by namespace 6 | 7 | From `Conflux-rust v2.0.0`, the [`pos` RPC]() group has been added, which can be used to get PoS chain info. `js-conflux-sdk` will add support to PoS related RPC in v2.0. These methods are located at Conflux's sub object pos `conflux.pos`, for example: 8 | 9 | ```js 10 | async function main() { 11 | const conflux = new Conflux({ 12 | url: 'https://test.confluxrpc.com', 13 | networkId: 1 14 | }); 15 | const status = await conflux.pos.getStatus(); 16 | console.log('Current PoS status: ', status); 17 | /* 18 | { 19 | epoch: 29, 20 | latestCommitted: 1725, 21 | latestTxNumber: '0x1e36', 22 | latestVoted: 1728, 23 | pivotDecision: { 24 | blockHash: '0xc8c2c58ef952f48adf00d6204d6e930f23aee26c6b9a903820bea1c012f72f3e', 25 | height: 120480 26 | } 27 | } 28 | */ 29 | } 30 | ``` 31 | 32 | Check [PoS RPC documentation]() for the complete method list. 33 | 34 | And previous `cfx` RPC methods are also been moved to their own namespace, for example: 35 | 36 | ```js 37 | const balance = await conflux.cfx.getBalance('cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp'); 38 | ``` 39 | 40 | Besides `txpool` and `trace` namespace is also added, so js-sdk v2.0 have four RPC namespace: 41 | 42 | * `cfx` 43 | * `pos` 44 | * `txpool` 45 | * `trace` 46 | 47 | There also is an `advanced` namespace, which will provide some useful combined RPC utilities, for example: 48 | 49 | * `getNextUsableNonce` 50 | * `getPoSInterestRate` 51 | 52 | ## Add new Internal contracts 53 | 54 | Two more internal contracts are added: 55 | 56 | * `ConfluxContext` 57 | * `PoSRegister` 58 | 59 | ## Add support for batch request RPC 60 | 61 | v2.0 has add support for RPC batch invoke, for example: 62 | 63 | ```js 64 | const batcher = conflux.BatchRequest(); 65 | batcher.add(conflux.cfx.getStatus.request()); 66 | batcher.add(conflux.cfx.getBalance.request('cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp')); 67 | 68 | const [status, balance] = await batcher.execute(); 69 | ``` 70 | 71 | Check [here](./batch_rpc.md) for detail introduction. 72 | 73 | ## Browser exported SDK name changed to `TreeGraph` 74 | 75 | **Note this is a breaking change** 76 | 77 | From v2.0 the browser exported SDK class name has change from `Conflux` to `TreeGraph`. 78 | 79 | Previous 80 | 81 | ```html 82 | 89 | ``` 90 | 91 | Currently: 92 | 93 | ```html 94 | 101 | ``` 102 | 103 | ## Enable fast send transaction 104 | 105 | If your RPC endpoint support `txpool` RPC methods, now you can fast send transaction sequencely. For example: 106 | 107 | ```js 108 | for(let i = 0; i < 100; i++) { 109 | let hash = await conflux.cfx.sendTransaction({ 110 | from: account, 111 | to: 'cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp', 112 | value: 1 113 | }); 114 | console.log(`TX hash: ${hash}`); 115 | } 116 | ``` 117 | 118 | Note the max sequence transaction send count can not surpass `2000` 119 | 120 | ## Readable ABI support 121 | 122 | ```js 123 | const tokenAbi = [ 124 | // Some details about the token 125 | "function name() view returns (string)", 126 | "function symbol() view returns (string)", 127 | 128 | // Get the account balance 129 | "function balanceOf(address) view returns (uint)", 130 | 131 | // Send some of your tokens to someone else 132 | "function transfer(address to, uint amount)", 133 | 134 | // An event triggered whenever anyone transfers to someone else 135 | "event Transfer(address indexed from, address indexed to, uint amount)" 136 | ]; 137 | 138 | const contract = conflux.Contract({ 139 | abi: tokenAbi, 140 | address: 'cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp' // use a valid token address here 141 | }); 142 | 143 | const balance = await contract.balanceOf('cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp'); 144 | ``` 145 | -------------------------------------------------------------------------------- /example/0_create_conflux.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { Conflux } = require('../src'); // require('js-conflux-sdk'); 3 | 4 | // create conflux sdk instance and connect to remote node 5 | const conflux = new Conflux({ 6 | // url: 'http://localhost:12537', 7 | url: 'https://test.confluxrpc.com', 8 | networkId: 1, 9 | // logger: console, // use console to print log 10 | }); 11 | 12 | function listConfluxPrototypes() { 13 | console.log(Object.getOwnPropertyNames(conflux.constructor.prototype)); 14 | /* 15 | [ 16 | 'constructor', 17 | 'Contract', 18 | 'InternalContract', 19 | 'close', 20 | 'getClientVersion', 21 | 'getStatus', 22 | 'getGasPrice', 23 | 'getInterestRate', 24 | 'getAccumulateInterestRate', 25 | 'getAccount', 26 | 'getBalance', 27 | 'getStakingBalance', 28 | 'getNextNonce', 29 | 'getAdmin', 30 | 'getEpochNumber', 31 | 'getBlockByEpochNumber', 32 | 'getBlocksByEpochNumber', 33 | 'getBlockRewardInfo', 34 | 'getBestBlockHash', 35 | 'getBlockByHash', 36 | 'getBlockByHashWithPivotAssumption', 37 | 'getConfirmationRiskByHash', 38 | 'getTransactionByHash', 39 | 'getTransactionReceipt', 40 | 'sendRawTransaction', 41 | 'sendTransaction', 42 | 'getCode', 43 | 'getStorageAt', 44 | 'getStorageRoot', 45 | 'getSponsorInfo', 46 | 'getCollateralForStorage', 47 | 'call', 48 | 'estimateGasAndCollateral', 49 | 'getLogs', 50 | 'subscribe', 51 | 'subscribeEpochs', 52 | 'subscribeNewHeads', 53 | 'subscribeLogs', 54 | 'unsubscribe' 55 | ] 56 | */ 57 | } 58 | 59 | async function getStatus() { 60 | // call RPC and get connected node status 61 | console.log(await conflux.getStatus()); 62 | /* 63 | { 64 | chainId: 1, 65 | epochNumber: 779841, 66 | blockNumber: 1455488, 67 | pendingTxNumber: 2, 68 | bestHash: '0x3e5816431723620a40876454f6cccbd8d62188dc07ce9ce2cb38563a22c26cdb' 69 | } 70 | */ 71 | } 72 | 73 | async function getStatusByProvider() { 74 | // call RPC and get status by provider directly 75 | console.log(await conflux.provider.call('cfx_getStatus')); 76 | /* 77 | { 78 | bestHash: '0xbec9b9318a5473416b5bdf95d7f378c966ea0356aa98e2d96c8cad48aff32ebe', 79 | blockNumber: '0x163aaa', 80 | chainId: '0x1', 81 | epochNumber: '0xbe939', 82 | pendingTxNumber: '0x2' 83 | } 84 | */ 85 | } 86 | 87 | // ---------------------------------------------------------------------------- 88 | async function main() { 89 | listConfluxPrototypes(); 90 | 91 | await getStatus(); 92 | 93 | await getStatusByProvider(); 94 | } 95 | 96 | main().finally(() => conflux.close()); 97 | -------------------------------------------------------------------------------- /example/1_account_and_balance.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { Conflux, Drip } = require('../src'); // require('js-conflux-sdk'); 3 | 4 | const conflux = new Conflux({ 5 | url: 'https://test.confluxrpc.com', 6 | networkId: 1, 7 | logger: console, // use console to print log 8 | }); 9 | 10 | // ---------------------------------------------------------------------------- 11 | async function getAccountBalance() { 12 | const balance = await conflux.getBalance('cfxtest:aar8jzybzv0fhzreav49syxnzut8s0jt1a1pdeeuwb'); 13 | 14 | console.log(balance); // "4999998839889983249999999950307784" 15 | console.log(Drip(balance).toGDrip()); // "4999998839889983249999999.950307784" 16 | console.log(Drip(balance).toCFX()); // "4999998839889983.249999999950307784" 17 | } 18 | 19 | function encryptAndDecryptPrivateKeyAccount() { 20 | // create Account by privateKey 21 | const account = conflux.wallet.addPrivateKey('0x46b9e861b63d3509c88b7817275a30d22d62c8cd8fa6486ddee35ef0d8e0495f'); 22 | console.log(conflux.wallet.has(account.address)); // true 23 | 24 | conflux.wallet.delete(account.address); 25 | console.log(conflux.wallet.has(account.address)); // false 26 | 27 | const keystore = account.encrypt('password'); 28 | console.log(keystore); 29 | /* 30 | { 31 | version: 3, 32 | id: '635b0de3-76f2-7ca7-9f70-78708b2aba89', 33 | address: '1be45681ac6c53d5a40475f7526bac1fe7590fb8', 34 | crypto: { 35 | ciphertext: '62a107a733d30a17b0c63c6c270c4943e0b39f5d64256b99d8d8755c51e96b34', 36 | cipherparams: { iv: 'd5febf02a0129ab2430192065a5f6a0b' }, 37 | cipher: 'aes-128-ctr', 38 | kdf: 'scrypt', 39 | kdfparams: { 40 | dklen: 32, 41 | salt: 'e566feee04948dc918b9e0e851969bb080a6daa9f5d4cd18bab59652fa737ad3', 42 | n: 8192, 43 | r: 8, 44 | p: 1 45 | }, 46 | mac: '30e638cffc0ec64343aa487e733e869c8cb6ee03e9aef540e2c16de5f5cee010' 47 | } 48 | } 49 | */ 50 | 51 | const decryptedAccount = conflux.wallet.addKeystore(keystore, 'password'); 52 | console.log(decryptedAccount); 53 | /* 54 | PrivateKeyAccount { 55 | address: 'cfxtest:aar8jzybzv0fhzreav49syxnzut8s0jt1a1pdeeuwb', 56 | publicKey: '0x2500e7f3fbddf2842903f544ddc87494ce95029ace4e257d54ba77f2bc1f3a8837a9461c4f1c57fecc499753381e772a128a5820a924a2fa05162eb662987a9f', 57 | privateKey: '0x46b9e861b63d3509c88b7817275a30d22d62c8cd8fa6486ddee35ef0d8e0495f' 58 | } 59 | */ 60 | 61 | console.log(decryptedAccount.privateKey === account.privateKey); // true 62 | console.log(decryptedAccount.publicKey === account.publicKey); // true 63 | console.log(decryptedAccount.address === account.address); // true 64 | } 65 | 66 | function genRandomAccount() { 67 | const randomAccount = conflux.wallet.addRandom(); 68 | console.log(randomAccount); 69 | /* 70 | PrivateKeyAccount { 71 | address: 'cfxtest:aapwgme763vhz9jw38nnbdjg1bzx1g8n3e6mtfmm6r', 72 | publicKey: '0x06402a2970f35e195ee9eaa4a9cb1464a9e5a8a79df1e48808e88f4a6c1744326affe84b6f37dcc3a48d8d5b5f6e2b4130e11b812c04f92edb48c40363764cae', 73 | privateKey: '0xa3bcf3d3d083b12120dbebb7fbb4bcbfd7ad0d2528f366546431668685765814' 74 | } 75 | */ 76 | 77 | console.log(conflux.wallet.addRandom()); // different account 78 | /* 79 | PrivateKeyAccount { 80 | address: 'cfxtest:aas95jhnaf9m2z019wp8rfz1fgje63tc268aaeyaup', 81 | publicKey: '0x27589308adf0f0b6362a834da664a200453152b0103b3952d4b92e30c9f37587107dbc37953038b8e5ea697a7f745a59c36a50318cfa5d75e42637df7c6e5b8b', 82 | privateKey: '0xad5a63169fe8af526bd9834e3763e169bf28f8b9f72241ce647ebb2eb5d02dcb' 83 | } 84 | */ 85 | } 86 | 87 | async function main() { 88 | await getAccountBalance(); 89 | encryptAndDecryptPrivateKeyAccount(); 90 | genRandomAccount(); 91 | } 92 | 93 | main().finally(() => conflux.close()); 94 | -------------------------------------------------------------------------------- /example/3_epoch_block_transaction.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* 3 | each `epoch` has many `block` 4 | each `block` has many `transaction` 5 | 6 | tx.blockHash => block 7 | block.epochNumber => epoch 8 | */ 9 | const JSBI = require('jsbi'); 10 | const { Conflux, sign, format } = require('../src'); // require('js-conflux-sdk'); 11 | 12 | const conflux = new Conflux({ 13 | url: 'https://test.confluxrpc.com', 14 | networkId: 1, 15 | }); 16 | 17 | async function getGenesisBlock() { 18 | const genesisBlock = await conflux.getBlockByEpochNumber(0); 19 | 20 | console.log('genesisBlock', JSON.stringify(genesisBlock, null, 2)); 21 | // as you see, genesisBlock.timestamp === 0 22 | /* 23 | genesisBlock { 24 | "epochNumber": 0, 25 | "blame": 0, 26 | "height": 0, 27 | "size": 108, 28 | "timestamp": 0, 29 | "gasLimit": "30000000", 30 | "gasUsed": null, 31 | "difficulty": "0", 32 | "transactions": [ 33 | "0x952535c89db77abac5006cc3a5020e9c1da9a23f616e0293e94a3fa380a5d9be" 34 | ], 35 | "adaptive": false, 36 | "deferredLogsBloomHash": "0xd397b3b043d87fcd6fad1291ff0bfd16401c274896d8c63a923727f077b8e0b5", 37 | "deferredReceiptsRoot": "0x09f8709ea9f344a810811a373b30861568f5686e649d6177fd92ea2db7477508", 38 | "deferredStateRoot": "0x867d0d102ed7eb638e25ab718c4ac4ba3d7fa0d87748382d9580b25f608dc80a", 39 | "hash": "0x2cfd947cd88b0876a0c7e696698188f8ea3b82dcdc239e32255e6f046045f595", 40 | "miner": "CFXTEST:TYPE.USER:AAJAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAV2WGH9U7", 41 | "nonce": "0x0", 42 | "parentHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", 43 | "powQuality": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 44 | "refereeHashes": [], 45 | "transactionsRoot": "0x835cd391da58faedec5486f31c3392ed21386b3926d3ac5301c4c8af5cf8e27f" 46 | } 47 | */ 48 | 49 | // genesisBlock.parentHash is not a block, but keccak of empty 50 | console.log(genesisBlock.parentHash === format.hex(sign.keccak256(''))); // true 51 | 52 | const parent = await conflux.getBlockByHash(genesisBlock.parentHash); 53 | console.log(parent); // null 54 | } 55 | 56 | /* 57 | user send a transaction and returned txHash. 58 | this example show how to find some useful info by txHash. 59 | */ 60 | async function findInformationAboutTransaction() { 61 | const txHash = '0x288243d348cf4ab60ed87b20a0442eea8672feb452a1522f6ccaeea3df1a545d'; 62 | 63 | const transaction = await conflux.getTransactionByHash(txHash); 64 | console.log(transaction); 65 | 66 | const receipt = await conflux.getTransactionReceipt(txHash); 67 | console.log(receipt); 68 | 69 | const block = await conflux.getBlockByHash(transaction.blockHash); 70 | console.log(block); 71 | 72 | console.log(block.epochNumber === receipt.epochNumber); // true 73 | console.log(transaction.status === receipt.outcomeStatus); // true 74 | } 75 | 76 | async function main() { 77 | await getGenesisBlock(); 78 | await findInformationAboutTransaction(); 79 | } 80 | 81 | main().finally(() => conflux.close()); 82 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # EXAMPLES -------------------------------------------------------------------------------- /example/browser/work-with-fluent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test sign 4 | 5 | 6 | 7 | 8 | 9 |

Account:

10 | 31 | 32 |

33 | 34 | 61 | 62 | -------------------------------------------------------------------------------- /example/contract/miniERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.10; 3 | 4 | contract MiniERC20 { 5 | uint256 constant private MAX_UINT256 = 2 ** 256 - 1; 6 | 7 | string public name; 8 | uint8 public decimals; 9 | string public symbol; 10 | uint256 public totalSupply; 11 | mapping(address => uint256) public balances; 12 | mapping(address => mapping(address => uint256)) public allowed; 13 | 14 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 15 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 16 | 17 | constructor(string memory _name, uint8 _decimals, string memory _symbol, uint256 _totalSupply) public { 18 | name = _name; 19 | decimals = _decimals; 20 | symbol = _symbol; 21 | totalSupply = _totalSupply; 22 | balances[msg.sender] = _totalSupply; 23 | } 24 | 25 | function transfer(address _to, uint256 _value) public returns (bool success) { 26 | require(balances[msg.sender] >= _value, 'balances not enough'); 27 | balances[msg.sender] -= _value; 28 | balances[_to] += _value; 29 | emit Transfer(msg.sender, _to, _value); 30 | return true; 31 | } 32 | 33 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { 34 | uint256 allowance = allowed[_from][msg.sender]; 35 | require(balances[_from] >= _value, 'balances not enough'); 36 | require(allowance >= _value, 'allowance not enough'); 37 | 38 | balances[_to] += _value; 39 | balances[_from] -= _value; 40 | if (allowance < MAX_UINT256) { 41 | allowed[_from][msg.sender] -= _value; 42 | } 43 | emit Transfer(_from, _to, _value); 44 | return true; 45 | } 46 | 47 | function balanceOf(address _owner) public view returns (uint256 balance) { 48 | return balances[_owner]; 49 | } 50 | 51 | function approve(address _spender, uint256 _value) public returns (bool success) { 52 | allowed[msg.sender][_spender] = _value; 53 | emit Approval(msg.sender, _spender, _value); 54 | return true; 55 | } 56 | 57 | function allowance(address _owner, address _spender) public view returns (uint256 remaining) { 58 | return allowed[_owner][_spender]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/contract/override.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.10; 3 | pragma experimental ABIEncoderV2; 4 | 5 | contract Override { 6 | struct Pair { 7 | uint num; 8 | string str; 9 | } 10 | 11 | event Event(bool indexed boolean); 12 | event Event(address indexed addr); 13 | event Event(int indexed num); 14 | event Event(uint indexed num); 15 | event Event(bytes indexed buffer); 16 | event Event(string indexed str); 17 | event Event(uint indexed num, string str, string indexed strIndex); 18 | event Event(Pair indexed pairIndex, Pair pair); 19 | 20 | constructor() public {} // constructor can not be override 21 | 22 | function func(bool boolean) public 23 | returns (bool) 24 | { 25 | emit Event(boolean); 26 | return boolean; 27 | } 28 | 29 | function func(int num) public 30 | returns (int) 31 | { 32 | emit Event(num); 33 | return num; 34 | } 35 | 36 | function func(uint num) public 37 | returns (uint) 38 | { 39 | emit Event(num); 40 | return num; 41 | } 42 | 43 | function func(address addr) public 44 | returns (address) 45 | { 46 | emit Event(addr); 47 | return addr; 48 | } 49 | 50 | function func(bytes memory buffer) public 51 | returns (bytes memory) 52 | { 53 | emit Event(buffer); 54 | return buffer; 55 | } 56 | 57 | function func(string memory str) public 58 | returns (string memory) 59 | { 60 | emit Event(str); 61 | return str; 62 | } 63 | 64 | function func(uint num, string memory str) public 65 | returns (bytes memory) 66 | { 67 | emit Event(num, str, str); 68 | return abi.encodePacked(num, str); 69 | } 70 | 71 | function func(Pair memory pair) public 72 | returns (Pair memory) 73 | { 74 | emit Event(pair, pair); 75 | return pair; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // collectCoverage: true, 3 | coverageDirectory: './coverage', 4 | coverageReporters: ['html'], 5 | 6 | testEnvironment: 'node', 7 | testTimeout: 100000, 8 | testRegex: 'test/.*test.js', 9 | }; 10 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": [ 5 | "jsdoc", 6 | "closure" 7 | ] 8 | }, 9 | "source": { 10 | "include": [ 11 | "src" 12 | ], 13 | "includePattern": ".+\\.js(doc)?$", 14 | "excludePattern": "(^|\\/|\\\\)_" 15 | }, 16 | "opts": { 17 | "encoding": "utf8", 18 | "destination": "./apidoc", 19 | "recurse": true 20 | }, 21 | "plugins": [ 22 | "node_modules/jsdoc-tsimport-plugin/index.js", 23 | "plugins/markdown", 24 | "plugins/summarize" 25 | ], 26 | "templates": { 27 | "cleverLinks": false, 28 | "monospaceLinks": false 29 | }, 30 | "metadata": { 31 | "title": "My JavaScript Library" 32 | } 33 | } -------------------------------------------------------------------------------- /mock/index.js: -------------------------------------------------------------------------------- 1 | module.exports.MockProvider = require('./MockProvider'); 2 | -------------------------------------------------------------------------------- /mock/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | 0xff 255 3 | 0xffff 65,535 4 | 0xffffff 16,777,215 5 | 0xffffffff ‭4,294,967,295‬ 6 | */ 7 | 8 | const lodash = require('lodash'); 9 | 10 | const HEX_CHARS = '0123456789abcdef'; 11 | 12 | // ---------------------------------------------------------------------------- 13 | function toHex(value) { 14 | if (Number.isFinite(value)) { 15 | return `0x${value.toString(16)}`; 16 | } 17 | 18 | if (/^0x[0-9a-f]*$/.test(value)) { 19 | return value; 20 | } 21 | 22 | throw new Error(`can not get hex from ${value}`); 23 | } 24 | 25 | function padHex(value = 0, length = undefined, alignLeft = false) { 26 | let nakedHex = toHex(value).replace('0x', ''); 27 | 28 | nakedHex = alignLeft 29 | ? lodash.padEnd(nakedHex, length, '0') 30 | : lodash.padStart(nakedHex, length, '0'); 31 | 32 | if (length !== undefined && length !== nakedHex.length) { 33 | throw new Error(`"${nakedHex}" length !=== ${length}`); 34 | } 35 | 36 | return `0x${nakedHex}`; 37 | } 38 | 39 | function concatHex(...hexArray) { 40 | return `0x${hexArray.map(hex => hex.replace('0x', '')).join('')}`; 41 | } 42 | 43 | class HexStruct { 44 | constructor(prefix, template, length) { 45 | this.prefix = prefix; 46 | this.template = template; 47 | this.length = length; 48 | } 49 | 50 | encode(options) { 51 | const list = lodash.map(this.template, (length, key) => padHex(options[key], length)); 52 | return padHex(concatHex(this.prefix, ...list), this.length, true); 53 | } 54 | 55 | decode(hex) { 56 | let index = this.prefix.length; 57 | 58 | return lodash.mapValues(this.template, length => { 59 | const body = hex.substr(index, length); 60 | index += length; 61 | return Number(`0x${body}`); 62 | }); 63 | } 64 | } 65 | 66 | // ---------------------------------------------------------------------------- 67 | function randomPick(...args) { 68 | return args[lodash.random(0, args.length - 1)]; 69 | } 70 | 71 | function randomHex(size) { 72 | const body = lodash.range(size) 73 | .map(() => HEX_CHARS.charAt(lodash.random(0, HEX_CHARS.length - 1))) 74 | .join(''); 75 | return `0x${body}`; 76 | } 77 | 78 | module.exports = { 79 | toHex, 80 | padHex, 81 | HexStruct, 82 | randomPick, 83 | randomHex, 84 | }; 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-conflux-sdk", 3 | "description": "JavaScript Conflux Software Development Kit", 4 | "version": "2.5.0", 5 | "license": "LGPL-3.0", 6 | "author": "pan.wang@confluxnetwork.org", 7 | "repository": "https://github.com/Conflux-Chain/js-conflux-sdk.git", 8 | "keywords": [ 9 | "conflux", 10 | "sdk" 11 | ], 12 | "main": "src/index.js", 13 | "bin": { 14 | "cfxjs": "bin/cfxjs.js", 15 | "sponsor": "bin/sponsor.js", 16 | "rpc": "bin/rpc.js" 17 | }, 18 | "types": "./dist/types/index.d.ts", 19 | "browser": "dist/js-conflux-sdk.umd.min.js", 20 | "browserify-browser": { 21 | "secp256k1": "secp256k1/elliptic", 22 | "./src/util/jsbi": "jsbi" 23 | }, 24 | "files": [ 25 | "dist", 26 | "mock", 27 | "src" 28 | ], 29 | "scripts": { 30 | "eslint": "npx eslint ./src ./test ./mock", 31 | "build": "node scripts/build-frontend.js && npm run gendts", 32 | "gendts": "npx tsc && node scripts/deal-dts.js", 33 | "document": "node scripts/document.js", 34 | "jsdocToMd": "node scripts/jsdoc-to-md.js", 35 | "jsdoc": "jsdoc -c jsdoc.json", 36 | "prepublishOnly": "npm run build", 37 | "test": "jest --coverage", 38 | "react": "npm run build & cd react & yarn build & yarn start" 39 | }, 40 | "browserslit": "cover 99.5%", 41 | "dependencies": { 42 | "@conflux-dev/conflux-address-js": "1.3.16", 43 | "abi-util-lite": "^0.1.0", 44 | "big.js": "^5.2.2", 45 | "commander": "^8.0.0", 46 | "keccak": "^2.0.0", 47 | "lodash": "^4.17.21", 48 | "rlp": "^2.2.7", 49 | "scrypt-js": "^3.0.1", 50 | "secp256k1": "^3.8.1", 51 | "superagent": "^6.1.0", 52 | "websocket": "^1.0.35" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.8.4", 56 | "@babel/plugin-transform-runtime": "^7.8.3", 57 | "@babel/preset-env": "^7.8.4", 58 | "@babel/runtime": "^7.8.4", 59 | "@conflux-dev/jsdoc-tsimport-plugin": "^1.0.5", 60 | "@conflux-dev/jsonrpc-spec": "^0.2.1", 61 | "@geekberry/jsdoc-to-md": "0.0.8", 62 | "@types/node": "^14.0.23", 63 | "babel-plugin-lodash": "^3.3.4", 64 | "babelify": "^10.0.0", 65 | "browserify": "^16.5.1", 66 | "concurrently": "^5.1.0", 67 | "eslint": "^7.12.0", 68 | "eslint-config-airbnb-base": "^14.0.0", 69 | "eslint-plugin-import": "^2.18.2", 70 | "exorcist": "^1.0.1", 71 | "fs-extra": "^8.1.0", 72 | "jest": "26.6.0", 73 | "jsbi": "^3.1.4", 74 | "jsdoc-to-markdown": "^7.1.0", 75 | "jsdoc-tsimport-plugin": "^1.0.5", 76 | "minify-stream": "^2.0.1", 77 | "mold-source-map": "^0.4.0", 78 | "solc": "^0.6.10", 79 | "tidy-jsdoc": "^1.4.1", 80 | "typescript": "^4.6.4" 81 | }, 82 | "resolutions": { 83 | "tinyify/acorn-node/acorn": "7.1.1", 84 | "eslint/espree/acorn": "7.1.1", 85 | "tinyify/unassertify/unassert/acorn": "7.1.1", 86 | "**/minimist": "^1.2.3", 87 | "**/kind-of": "^6.0.3", 88 | "**/elliptic": "^6.5.3", 89 | "**/lodash": "^4.17.20", 90 | "**/babel-jest": "^26.6.0", 91 | "jest/jest-cli/jest-config/jest-environment-jsdom/jsdom/acorn-globals/acorn": "6.4.1" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /scripts/build-frontend.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const path = require('path'); 3 | const minify = require('minify-stream'); 4 | const { mkdirpSync } = require('fs-extra'); 5 | const browserify = require('browserify'); 6 | const babelify = require('babelify'); 7 | const fs = require('fs'); 8 | const exorcist = require('exorcist'); 9 | const mold = require('mold-source-map'); 10 | 11 | const browserifyOptions = { 12 | browserField: 'browserify-browser', 13 | entries: ['./src/index.js'], 14 | debug: true, // gen inline sourcemap to extract with exorcist 15 | standalone: 'TreeGraph', // generate a umd file to load directly into browser 16 | }; 17 | 18 | const OUTPUT_FILE_NAME = 'js-conflux-sdk'; 19 | 20 | mkdirpSync('dist'); 21 | 22 | // use babel to remove unused lodash code 23 | // https://www.blazemeter.com/blog/the-correct-way-to-import-lodash-libraries-a-benchmark/ 24 | const babelTransform = babelify.configure({ 25 | presets: ['@babel/preset-env'], 26 | plugins: [ 27 | 'lodash', 28 | [ 29 | '@babel/plugin-transform-runtime', 30 | { 31 | regenerator: true, 32 | }, 33 | ], 34 | ], 35 | }); 36 | 37 | browserify(browserifyOptions) 38 | .transform(babelTransform) 39 | .bundle() 40 | .pipe(minify()) 41 | .pipe(mold.transformSourcesRelativeTo(path.resolve(__dirname, '../'))) 42 | .pipe(exorcist(`./dist/${OUTPUT_FILE_NAME}.umd.min.js.map`)) 43 | .pipe(fs.createWriteStream(`./dist/${OUTPUT_FILE_NAME}.umd.min.js`)); 44 | -------------------------------------------------------------------------------- /scripts/deal-dts.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const toReplace = 'constructor: ContractConstructor;'; 5 | const fileName = path.join(__dirname, '../dist/types/contract/index.d.ts'); 6 | const content = fs.readFileSync(fileName, 'utf-8'); 7 | const replaced = content.replace(toReplace, `// ${toReplace}`); 8 | fs.writeFileSync(fileName, replaced); 9 | -------------------------------------------------------------------------------- /scripts/document.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const lodash = require('lodash'); 3 | const jsdocToMd = require('@geekberry/jsdoc-to-md'); // eslint-disable-line import/no-extraneous-dependencies 4 | const { sep } = require('path'); 5 | 6 | function generateMarkdown(filters, apiName) { 7 | const markdown = jsdocToMd(`${__dirname}/../src`, { 8 | filter: filename => { 9 | if (lodash.some(filters, suffix => filename.endsWith(suffix))) { 10 | console.log(`File "${filename}" parsing...`); // eslint-disable-line no-console 11 | return true; 12 | } 13 | return false; 14 | }, 15 | }); 16 | 17 | fs.writeFileSync(`${__dirname}/../docs/api/${apiName}.md`, `${markdown}`); 18 | } 19 | 20 | const APIs = [ 21 | { 22 | name: 'Conflux', 23 | files: [ 24 | `${sep}Conflux.js`, 25 | ], 26 | }, { 27 | name: 'Wallet', 28 | files: [ 29 | `${sep}wallet${sep}Wallet.js`, 30 | `${sep}wallet${sep}PrivateKeyAccount.js`, 31 | ], 32 | }, { 33 | name: 'Provider', 34 | files: [ 35 | `${sep}provider${sep}index.js`, 36 | `${sep}provider${sep}BaseProvider.js`, 37 | `${sep}provider${sep}HttpProvider.js`, 38 | `${sep}provider${sep}WebSocketProvider.js`, 39 | ], 40 | }, { 41 | name: 'Contract', 42 | files: [ 43 | `${sep}contract${sep}Contract.js`, 44 | ], 45 | }, { 46 | name: 'Transaction', 47 | files: [ 48 | `${sep}Transaction.js`, 49 | ], 50 | }, { 51 | name: 'Drip', 52 | files: [ 53 | `${sep}Drip.js`, 54 | ], 55 | }, { 56 | name: 'utils', 57 | files: [ 58 | `${sep}util${sep}format.js`, 59 | `${sep}util${sep}sign.js`, 60 | ], 61 | }, { 62 | name: 'Subscribe', 63 | files: [ 64 | `${sep}subscribe${sep}PendingTransaction.js`, 65 | `${sep}subscribe${sep}Subscription.js`, 66 | ], 67 | }, { 68 | name: 'Misc', 69 | files: [ 70 | `${sep}CONST.js`, 71 | `${sep}Message.js`, 72 | ], 73 | }, 74 | ]; 75 | 76 | for (const API of APIs) { 77 | generateMarkdown(API.files, API.name); 78 | } 79 | -------------------------------------------------------------------------------- /scripts/jsdoc-to-md.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const jsdoc2md = require('jsdoc-to-markdown'); 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | const { replaceTSImport } = require('./replaceTSimport'); 6 | 7 | const DOC_FOLDER = './docs/api/'; 8 | 9 | const files = [ 10 | './src/Conflux.js', 11 | './src/CONST.js', 12 | './src/Drip.js', 13 | './src/Message.js', 14 | './src/PersonalMessage.js', 15 | './src/Transaction.js', 16 | './src/rpc/txpool.js', 17 | './src/rpc/pos.js', 18 | './src/subscribe/PendingTransaction.js', 19 | './src/provider/BaseProvider.js', 20 | './src/provider/index.js', 21 | './src/provider/HttpProvider.js', 22 | './src/provider/WebSocketProvider.js', 23 | './src/wallet/Wallet.js', 24 | './src/wallet/Account.js', 25 | './src/wallet/index.js', 26 | './src/wallet/PrivateKeyAccount.js', 27 | './src/util/address.js', 28 | './src/util/sign.js', 29 | './src/util/format.js', 30 | ]; 31 | 32 | async function renderFile(fileMeta) { 33 | let source = fs.readFileSync(fileMeta, 'utf8'); 34 | source = replaceTSImport({ source }); 35 | const result = await jsdoc2md.render({ source }); 36 | // calculate the final markdown file name 37 | let fileName = path.join(DOC_FOLDER, fileMeta.replace('./src/', '')); 38 | fileName = fileName.replace('.js', '.md'); 39 | await fs.ensureFile(fileName); 40 | fs.writeFileSync(fileName, result); 41 | } 42 | 43 | async function main() { 44 | for (const file of files) { 45 | await renderFile(file); 46 | } 47 | } 48 | 49 | main().catch(console.error); 50 | -------------------------------------------------------------------------------- /scripts/replaceTSimport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A regex to capture all doc comments. 3 | */ 4 | // eslint-disable-next-line no-useless-escape 5 | const docCommentsRegex = /\/\*\*\s*(?:[^\*]|(?:\*(?!\/)))*\*\//g; 6 | 7 | /** 8 | * Finds a ts import. 9 | */ 10 | // eslint-disable-next-line no-useless-escape 11 | const importRegex = /import\(['"](\@?[\.\/_a-zA-Z0-9-\$]*)(?:\.js)?['"]\)\.?([_a-zA-Z0-9-\$]*)?/g; 12 | 13 | function replaceTSImport(e) { 14 | return e.source.replace(docCommentsRegex, 15 | substring => { 16 | return substring.replace(importRegex, 17 | (_substring2, relImportPath, symbolName) => { 18 | // const moduleId = getModuleId(e.filename, relImportPath); 19 | const moduleId = null; 20 | return (moduleId) ? `module:${moduleId}${symbolName ? `~${symbolName}` : ''}` : symbolName; 21 | }); 22 | }); 23 | } 24 | 25 | module.exports = { 26 | replaceTSImport, 27 | }; 28 | -------------------------------------------------------------------------------- /scripts/test-create-react-app-default-bundler.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | 4 | # install cra 5 | yarn global add create-react-app 6 | 7 | # create a exmaple project 8 | create-react-app test-sdk-frontend-import-bundler 9 | 10 | cd test-sdk-frontend-import-bundler 11 | 12 | # prepend import { Conflux } from '../../'; to cra's default App.js 13 | sed -i.old -e '1 i\ 14 | import { Conflux } from "../../"\;' src/App.js 15 | sed -i.old -e '1 i\ 16 | // eslint-disable-next-line no-unused-vars' src/App.js 17 | yarn build 18 | -------------------------------------------------------------------------------- /src/Drip.js: -------------------------------------------------------------------------------- 1 | const format = require('./util/format'); 2 | 3 | /** 4 | * Positive decimal integer string in `Drip` 5 | */ 6 | class Drip extends String { 7 | /** 8 | * Get `Drip` string from `CFX` 9 | * 10 | * @param {string|number|BigInt} value 11 | * @return {Drip} 12 | * 13 | * @example 14 | * > Drip.fromCFX(3.14) 15 | [String (Drip): '3140000000000000000'] 16 | * > Drip.fromCFX('0xab') 17 | [String (Drip): '171000000000000000000'] 18 | */ 19 | static fromCFX(value) { 20 | return new this(format.big(value).times(1e18).toFixed()); 21 | } 22 | 23 | /** 24 | * Get `Drip` string from `GDrip` 25 | * 26 | * @param {string|number|BigInt} value 27 | * @return {Drip} 28 | * 29 | * @example 30 | * > Drip.fromGDrip(3.14) 31 | [String (Drip): '3140000000'] 32 | * > Drip.fromGDrip('0xab') 33 | [String (Drip): '171000000000'] 34 | */ 35 | static fromGDrip(value) { 36 | return new this(format.big(value).times(1e9).toFixed()); 37 | } 38 | 39 | /** 40 | * @param {number|string|BigInt} value 41 | * @return {Drip} 42 | * 43 | * @example 44 | * > new Drip(1.00) 45 | [String (Drip): '1'] 46 | * > new Drip('0xab') 47 | [String (Drip): '171'] 48 | */ 49 | constructor(value) { 50 | super(format.bigUInt(value).toString(10)); 51 | } 52 | 53 | /** 54 | * Get `CFX` number string 55 | * @return {string} 56 | * 57 | * @example 58 | * > Drip(1e9).toCFX() 59 | "0.000000001" 60 | */ 61 | toCFX() { 62 | return format.big(this).div(1e18).toFixed(); 63 | } 64 | 65 | /** 66 | * Get `GDrip` number string 67 | * @return {string} 68 | * 69 | * @example 70 | * > Drip(1e9).toGDrip() 71 | "1" 72 | */ 73 | toGDrip() { 74 | return format.big(this).div(1e9).toFixed(); 75 | } 76 | } 77 | 78 | module.exports = new Proxy(Drip, { 79 | apply(target, thisArg, argArray) { 80 | return new Drip(...argArray); 81 | }, 82 | }); 83 | -------------------------------------------------------------------------------- /src/ERROR_CODES.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Parser parse data failed 3 | PARSER_ERROR: 5200, 4 | }; 5 | -------------------------------------------------------------------------------- /src/PersonalMessage.js: -------------------------------------------------------------------------------- 1 | const PREFIX = '\x19Conflux Signed Message:\n'; 2 | const format = require('./util/format'); 3 | const Message = require('./Message'); 4 | const { keccak256 } = require('./util/sign'); 5 | const { isHexString } = require('./util'); 6 | 7 | class PersonalMessage extends Message { 8 | /** 9 | * Assemble the personal message 10 | * @param {string|Buffer} message - The origin message 11 | * @return {string} 12 | */ 13 | static personalMessage(message) { 14 | const msgBuf = isHexString(message) ? format.hexBuffer(message) : Buffer.from(message); 15 | return PREFIX + msgBuf.length + msgBuf.toString(); 16 | } 17 | 18 | /** 19 | * Assemble the personal message hash 20 | * @param {string|Buffer} message - The origin message 21 | * @return {string} The personal message hash 22 | */ 23 | static personalHash(message) { 24 | const personalMsg = this.personalMessage(message); 25 | return format.hex(keccak256(Buffer.from(personalMsg))); 26 | } 27 | 28 | /** 29 | * Signs the hash with the privateKey. 30 | * 31 | * @param {string|Buffer} privateKey 32 | * @param {string|Buffer} message 33 | * @return {string} The signature as hex string. 34 | * 35 | * @example 36 | * > PersonalMessage.sign( 37 | '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', // privateKey 38 | 'Hello world!', 39 | ) 40 | "0xa2d98c5d47b35ba4ebdf03e2d9496312355dccc609bf38c93f19cc9f970e131d0e95504eb3c786714ab703f6924876704bc44bb71680802a87b4c4d2599ac96a00" 41 | */ 42 | static sign(privateKey, message) { 43 | return super.sign(privateKey, this.personalHash(message)); 44 | } 45 | 46 | /** 47 | * Recovers the signers publicKey from the signature. 48 | * 49 | * @param {string|Buffer} signature 50 | * @param {string|Buffer} message 51 | * @return {string} The publicKey as hex string. 52 | * 53 | * @example 54 | * > PersonalMessage.recover( 55 | '0xa2d98c5d47b35ba4ebdf03e2d9496312355dccc609bf38c93f19cc9f970e131d0e95504eb3c786714ab703f6924876704bc44bb71680802a87b4c4d2599ac96a00', 56 | 'Hello world!', 57 | ) 58 | "0x5e3eb3a2fbe124c62b382f078a1766c5b0b1306c38a496aa49e3702024a06cffe9da86ab15e4d017b6ef12794e9fe1751ce261a7b7c03be0c5b81ab9b040668a" 59 | */ 60 | static recover(signature, message) { 61 | return super.recover(signature, this.personalHash(message)); 62 | } 63 | 64 | /** 65 | * Recovers the wallet signers publicKey from the signature. 66 | * 67 | * @param {string} signature 68 | * @param {string} message 69 | * @return {string} The publicKey as hex string. 70 | * 71 | * @example 72 | > PersonalMessage.recoverPortalPersonalSign( 73 | '0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f01', 74 | '0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba', 75 | ) 76 | "0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559" 77 | * 78 | > PersonalMessage.recoverPortalPersonalSign( 79 | '0x5f8499879ce281ff083f5716de68ab6d05b176edbb27b6c5882ab482dc00478e33679f15a30bc60510faab49c2bd0bf883ad0a45ad3160e424b35cddcc1ee85d1c', 80 | 'Hello World', 81 | ) 82 | "0x41f3b66efde8121599072d1c215c88682f491c4f9e3b2345667a3f9f4adb8449b3de23832f435f4d923872ed043449ee7843a0bfc3594c46c982ab5297009f78" 83 | */ 84 | static recoverPortalPersonalSign(signature, message) { 85 | const v = parseInt(signature.slice(130), 16) - 27; 86 | signature = signature.slice(0, 130) + format.hex(v).slice(2); 87 | const messageHex = isHexString(message) ? message : format.hex(Buffer.from(message)); 88 | const msg = new Message(PREFIX + messageHex.length + messageHex); 89 | return Message.recover(signature, msg.hash); 90 | } 91 | 92 | /** 93 | * Assemble the personal message hash 94 | * @param {string|Buffer} message - The origin message 95 | * @return {PersonalMessage} 96 | */ 97 | constructor(message) { 98 | const personalMessage = PersonalMessage.personalMessage(message); 99 | super(personalMessage); 100 | this._originMsg = message; 101 | this._personalMsg = personalMessage; 102 | this._prefix = PREFIX; 103 | } 104 | } 105 | 106 | module.exports = PersonalMessage; 107 | -------------------------------------------------------------------------------- /src/contract/ContractABI.js: -------------------------------------------------------------------------------- 1 | class ContractABI { 2 | constructor(contract) { 3 | this.contract = contract; 4 | } 5 | 6 | decodeData(data) { 7 | const method = this.contract[data.slice(0, 10)] || this.contract.constructor; 8 | 9 | const tuple = method.decodeData(data); 10 | if (!tuple) { 11 | return undefined; 12 | } 13 | 14 | return { 15 | name: method.name, 16 | fullName: method.fullName, 17 | type: method.type, 18 | signature: method.signature, 19 | array: [...tuple], 20 | object: tuple.toObject(), 21 | }; 22 | } 23 | 24 | decodeLog(log) { 25 | const event = this.contract[log.topics[0]]; 26 | if (!event) { 27 | return undefined; 28 | } 29 | 30 | const tuple = event.decodeLog(log); 31 | return { 32 | name: event.name, 33 | fullName: event.fullName, 34 | type: event.type, 35 | signature: event.signature, 36 | array: [...tuple], 37 | object: tuple.toObject(), 38 | }; 39 | } 40 | } 41 | 42 | module.exports = ContractABI; 43 | -------------------------------------------------------------------------------- /src/contract/abi/AddressCoder.js: -------------------------------------------------------------------------------- 1 | const { alignBuffer } = require('../../util'); 2 | const format = require('../../util/format'); 3 | const BaseCoder = require('./BaseCoder'); 4 | 5 | class AddressCoder extends BaseCoder { 6 | static from({ type, ...options }) { 7 | if (type !== 'address') { 8 | return undefined; 9 | } 10 | return new this({ ...options, type }); 11 | } 12 | 13 | constructor({ type, ...options }) { 14 | super({ ...options, type }); 15 | this.networkId = options.networkId; 16 | } 17 | 18 | /** 19 | * @param {string} address 20 | * @return {Buffer} 21 | */ 22 | encode(address) { 23 | return alignBuffer(format.hexBuffer(format.hexAddress(address))); 24 | } 25 | 26 | /** 27 | * @param {import('../../util/HexStream')} stream 28 | * @return {string} 29 | */ 30 | decode(stream) { 31 | const hexAddress = stream.read(40); 32 | const isConfluxAddress = hexAddress.startsWith('1') || hexAddress.startsWith('0') || hexAddress.startsWith('8'); 33 | return (isConfluxAddress && this.networkId) ? format.address(`0x${hexAddress}`, this.networkId) : format.hexAddress(`0x${hexAddress}`); 34 | } 35 | } 36 | 37 | module.exports = AddressCoder; 38 | -------------------------------------------------------------------------------- /src/contract/abi/ArrayCoder.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const { assert } = require('../../util'); 3 | const format = require('../../util/format'); 4 | const BaseCoder = require('./BaseCoder'); 5 | const { uIntCoder } = require('./IntegerCoder'); 6 | const { pack, unpack } = require('./TupleCoder'); 7 | 8 | class ArrayCoder extends BaseCoder { 9 | static from({ type, components, ...options }, valueCoder) { 10 | const match = type.match(/^(.*)\[([0-9]*)]$/); 11 | if (!match) { 12 | return undefined; 13 | } 14 | 15 | const [, subType, size] = match; 16 | return new this({ 17 | ...options, 18 | coder: valueCoder({ type: subType, components, ...options }), 19 | size: size ? parseInt(size, 10) : undefined, 20 | }); 21 | } 22 | 23 | constructor({ name, coder, size }) { 24 | if (size !== undefined) { 25 | assert(Number.isInteger(size) && 0 < size, { 26 | message: 'invalid size', 27 | expect: 'integer && >0', 28 | got: size, 29 | coder: { name }, 30 | }); 31 | } 32 | 33 | super({ name }); 34 | this.type = `${coder.type}[${size > 0 ? size : ''}]`; 35 | this.size = size; 36 | this.coder = coder; 37 | this.dynamic = Boolean(size === undefined) || coder.dynamic; 38 | } 39 | 40 | /** 41 | * @param {array} array 42 | * @return {Buffer} 43 | */ 44 | encode(array) { 45 | assert(Array.isArray(array), { 46 | message: 'unexpected type', 47 | expect: 'array', 48 | got: typeof array, 49 | coder: this, 50 | }); 51 | 52 | if (this.size !== undefined) { 53 | assert(array.length === this.size, { 54 | message: 'length not match', 55 | expect: this.size, 56 | got: array.length, 57 | coder: this, 58 | }); 59 | } 60 | 61 | const coders = lodash.range(array.length).map(() => this.coder); 62 | let buffer = pack(coders, array); 63 | if (this.size === undefined) { 64 | buffer = Buffer.concat([uIntCoder.encode(array.length), buffer]); 65 | } 66 | return buffer; 67 | } 68 | 69 | /** 70 | * @param {import('../../util/HexStream')} stream 71 | * @return {array} 72 | */ 73 | decode(stream) { 74 | let length = this.size; 75 | 76 | if (length === undefined) { 77 | length = format.uInt(uIntCoder.decode(stream)); // XXX: BigInt => Number, for length is enough. 78 | } 79 | 80 | const coders = lodash.range(length).map(() => this.coder); 81 | return unpack(coders, stream); 82 | } 83 | 84 | encodeTopic(value) { 85 | try { 86 | return format.hex64(value); 87 | } catch (e) { 88 | // TODO https://solidity.readthedocs.io/en/v0.7.4/abi-spec.html#encoding-of-indexed-event-parameters 89 | throw new Error('not supported encode array to index'); 90 | } 91 | } 92 | 93 | decodeTopic(hex) { 94 | return hex; 95 | } 96 | } 97 | 98 | module.exports = ArrayCoder; 99 | -------------------------------------------------------------------------------- /src/contract/abi/BaseCoder.js: -------------------------------------------------------------------------------- 1 | const HexStream = require('../../util/HexStream'); 2 | 3 | class BaseCoder { 4 | static from(component) {} // eslint-disable-line no-unused-vars 5 | 6 | constructor({ type, name }) { 7 | this.type = type; 8 | this.name = name; 9 | this.dynamic = false; 10 | } 11 | 12 | /** 13 | * @param {boolean} [value] 14 | * @return {Buffer} 15 | */ 16 | encode(value) { // eslint-disable-line no-unused-vars 17 | throw new Error(`${this.constructor.name}.encode not implemented`); 18 | } 19 | 20 | /** 21 | * @param {import('../../util/HexStream')} stream 22 | * @return {boolean} 23 | */ 24 | decode(stream) { // eslint-disable-line no-unused-vars 25 | throw new Error(`${this.constructor.name}.decode not implemented`); 26 | } 27 | 28 | encodeTopic(value) { 29 | return this.encode(value); 30 | } 31 | 32 | decodeTopic(hex) { 33 | const stream = new HexStream(hex); 34 | return this.decode(stream); 35 | } 36 | } 37 | 38 | module.exports = BaseCoder; 39 | -------------------------------------------------------------------------------- /src/contract/abi/BoolCoder.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const { assert } = require('../../util'); 3 | const JSBI = require('../../util/jsbi'); 4 | const IntegerCoder = require('./IntegerCoder'); 5 | 6 | class BoolCoder extends IntegerCoder { 7 | static from({ type, ...options }) { 8 | if (type !== 'bool') { 9 | return undefined; 10 | } 11 | return new this({ ...options, type }); 12 | } 13 | 14 | constructor({ type, name }) { 15 | super({ name }); 16 | this.type = type; 17 | } 18 | 19 | /** 20 | * @param {boolean} value 21 | * @return {Buffer} 22 | */ 23 | encode(value) { 24 | assert(lodash.isBoolean(value), { 25 | message: 'unexpected type', 26 | expect: 'boolean', 27 | got: value, 28 | coder: this, 29 | }); 30 | 31 | return super.encode(value ? 1 : 0); 32 | } 33 | 34 | /** 35 | * @param {import('../../util/HexStream')} stream 36 | * @return {boolean} 37 | */ 38 | decode(stream) { 39 | return JSBI.notEqual(super.decode(stream), JSBI.BigInt(0)); 40 | } 41 | } 42 | 43 | module.exports = BoolCoder; 44 | -------------------------------------------------------------------------------- /src/contract/abi/BytesCoder.js: -------------------------------------------------------------------------------- 1 | const { WORD_BYTES } = require('../../CONST'); 2 | const { assert, alignBuffer } = require('../../util'); 3 | const format = require('../../util/format'); 4 | const sign = require('../../util/sign'); 5 | const BaseCoder = require('./BaseCoder'); 6 | const { uIntCoder } = require('./IntegerCoder'); 7 | 8 | class BytesCoder extends BaseCoder { 9 | static from({ type, ...options }) { 10 | const match = type.match(/^bytes([0-9]*)$/); 11 | if (!match) { 12 | return undefined; 13 | } 14 | 15 | const [, size] = match; 16 | return new this({ 17 | ...options, 18 | size: size ? parseInt(size, 10) : undefined, 19 | }); 20 | } 21 | 22 | constructor({ name, size, _decodeToHex }) { 23 | if (size !== undefined) { 24 | assert(Number.isInteger(size) && size <= WORD_BYTES, { 25 | message: 'invalid size', 26 | expect: `integer && <=${WORD_BYTES}`, 27 | got: size, 28 | coder: { name }, 29 | }); 30 | } 31 | 32 | super({ name }); 33 | this.type = `bytes${size > 0 ? size : ''}`; 34 | this.size = size; 35 | this.dynamic = Boolean(size === undefined); 36 | this._decodeToHex = _decodeToHex; 37 | } 38 | 39 | /** 40 | * @param {any[]|string} value 41 | * @return {Buffer} 42 | */ 43 | encode(value) { 44 | value = format.bytes(value); 45 | 46 | if (this.size !== undefined && this.size !== value.length) { 47 | if (value.length < this.size) { 48 | // if short than the expect size, auto complete it 49 | value = Buffer.concat([value, Buffer.alloc(this.size - value.length)]); 50 | } else { 51 | assert(false, { 52 | message: 'length not match', 53 | expect: this.size, 54 | got: value.length, 55 | coder: this, 56 | }); 57 | } 58 | } 59 | 60 | let buffer = alignBuffer(value, true); 61 | if (this.size === undefined) { 62 | buffer = Buffer.concat([uIntCoder.encode(value.length), buffer]); 63 | } 64 | return buffer; 65 | } 66 | 67 | /** 68 | * @param {import('../../util/HexStream')} stream 69 | * @return {Buffer} 70 | */ 71 | decode(stream) { 72 | let length = this.size; 73 | if (length === undefined) { 74 | length = format.uInt(uIntCoder.decode(stream)); // XXX: BigInt => Number, for length is enough. 75 | } 76 | 77 | if (this._decodeToHex) { 78 | return `0x${stream.read(length * 2, true)}`; 79 | } 80 | 81 | return Buffer.from(stream.read(length * 2, true), 'hex'); 82 | } 83 | 84 | encodeTopic(value) { 85 | assert(Buffer.isBuffer(value), { 86 | message: 'value type error', 87 | expect: Buffer.name, 88 | got: value.constructor.name, 89 | coder: this, 90 | }); 91 | 92 | return sign.keccak256(value); 93 | } 94 | 95 | decodeTopic(hex) { 96 | return hex; 97 | } 98 | } 99 | 100 | module.exports = BytesCoder; 101 | -------------------------------------------------------------------------------- /src/contract/abi/IntegerCoder.js: -------------------------------------------------------------------------------- 1 | const { UINT_BOUND } = require('../../CONST'); 2 | const { assert, alignBuffer } = require('../../util'); 3 | const format = require('../../util/format'); 4 | const JSBI = require('../../util/jsbi'); 5 | const BaseCoder = require('./BaseCoder'); 6 | 7 | class IntegerCoder extends BaseCoder { 8 | static from({ type, ...options }) { 9 | const match = type.match(/^(int|uint)([0-9]*)$/); 10 | if (!match) { 11 | return undefined; 12 | } 13 | 14 | const [, label, bits] = match; 15 | return new this({ 16 | ...options, 17 | type: label, 18 | signed: !label.startsWith('u'), 19 | bits: bits ? parseInt(bits, 10) : undefined, 20 | }); 21 | } 22 | 23 | constructor({ name, type, signed = false, bits = 256 }) { 24 | assert(Number.isInteger(bits) && 0 < bits && bits <= 256 && (bits % 8 === 0), { 25 | message: 'invalid bits', 26 | expect: 'integer && 0 { 23 | const buffer = coder.encode(value); 24 | 25 | if (coder.dynamic) { 26 | offset += WORD_BYTES; 27 | staticList.push(new Pointer(dynamicList.length)); // push index of dynamic to static 28 | dynamicList.push(buffer); 29 | } else { 30 | offset += buffer.length; 31 | staticList.push(buffer); 32 | } 33 | }); 34 | 35 | // write back the dynamic address 36 | staticList.forEach((pointer, index) => { 37 | if (pointer instanceof Pointer) { 38 | staticList[index] = uIntCoder.encode(offset); 39 | offset += dynamicList[pointer].length; 40 | } 41 | }); 42 | 43 | return Buffer.concat([...staticList, ...dynamicList]); 44 | } 45 | 46 | /** 47 | * 48 | * @param {BaseCoder[]} coders 49 | * @param {import('../../util/HexStream')} stream 50 | * @return {array} 51 | */ 52 | function unpack(coders, stream) { 53 | const startIndex = stream.index; 54 | 55 | const array = coders.map(coder => { 56 | if (coder.dynamic) { 57 | const offset = format.uInt(uIntCoder.decode(stream)); // XXX: BigInt => Number, for length is enough. 58 | return new Pointer(startIndex + offset * 2); 59 | } else { 60 | return coder.decode(stream); 61 | } 62 | }); 63 | 64 | lodash.zip(coders, array) 65 | .forEach(([coder, value], index) => { 66 | if (value instanceof Pointer) { 67 | assert(Number(value) === stream.index, { 68 | message: 'stream.index error', 69 | expect: value, 70 | got: stream.index, 71 | coder, 72 | stream, 73 | }); 74 | 75 | array[index] = coder.decode(stream); 76 | } 77 | }); 78 | 79 | return array; 80 | } 81 | 82 | class TupleCoder extends BaseCoder { 83 | static from({ type, components, ...options }, valueCoder) { 84 | if (type !== 'tuple') { 85 | return undefined; 86 | } 87 | return new this({ ...options, coders: components.map(valueCoder) }); 88 | } 89 | 90 | constructor({ name, coders }) { 91 | super({ name }); 92 | this.type = `(${coders.map(coder => coder.type).join(',')})`; 93 | this.size = coders.length; 94 | this.coders = coders; 95 | this.dynamic = lodash.some(coders, coder => coder.dynamic); 96 | this.names = coders.map((coder, index) => coder.name || `${index}`); 97 | /** @type {object} */ 98 | this.NamedTuple = namedTuple(...this.names); 99 | } 100 | 101 | /** 102 | * @param {array} array 103 | * @return {Buffer} 104 | */ 105 | encode(array) { 106 | if (lodash.isPlainObject(array)) { 107 | array = this.NamedTuple.fromObject(array); 108 | } 109 | 110 | assert(Array.isArray(array), { 111 | message: 'unexpected type', 112 | expect: 'array', 113 | got: typeof array, 114 | coder: this, 115 | }); 116 | 117 | assert(array.length === this.size, { 118 | message: 'length not match', 119 | expect: this.size, 120 | got: array.length, 121 | coder: this, 122 | }); 123 | 124 | return pack(this.coders, array); 125 | } 126 | 127 | /** 128 | * @param {import('../../util/HexStream')} stream 129 | * @return {NamedTuple} 130 | */ 131 | decode(stream) { 132 | const array = unpack(this.coders, stream); 133 | return new this.NamedTuple(...array); 134 | } 135 | 136 | encodeTopic(value) { 137 | try { 138 | return format.hex64(value); 139 | } catch (e) { 140 | // TODO https://solidity.readthedocs.io/en/v0.7.4/abi-spec.html#encoding-of-indexed-event-parameters 141 | throw new Error('not supported encode tuple to index'); 142 | } 143 | } 144 | 145 | decodeTopic(hex) { 146 | return hex; 147 | } 148 | } 149 | 150 | module.exports = TupleCoder; 151 | module.exports.pack = pack; 152 | module.exports.unpack = unpack; 153 | -------------------------------------------------------------------------------- /src/contract/abi/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | @see https://solidity.readthedocs.io/en/v0.5.13/abi-spec.html 3 | @see https://solidity.readthedocs.io/en/v0.5.13/abi-spec.html#encoding-of-indexed-event-parameters 4 | */ 5 | 6 | const { assert } = require('../../util'); 7 | const BaseCoder = require('./BaseCoder'); 8 | const NullCoder = require('./NullCoder'); 9 | const AddressCoder = require('./AddressCoder'); 10 | const IntegerCoder = require('./IntegerCoder'); 11 | const BoolCoder = require('./BoolCoder'); 12 | const BytesCoder = require('./BytesCoder'); 13 | const StringCoder = require('./StringCoder'); 14 | const TupleCoder = require('./TupleCoder'); 15 | const ArrayCoder = require('./ArrayCoder'); 16 | // TODO FixedCoder 17 | 18 | /** 19 | * Get coder by abi component. 20 | * 21 | * @param {object} component 22 | * @param {string} component.type 23 | * @param {string} [component.name] 24 | * @param {array} [component.components] - For TupleCoder 25 | * @return {BaseCoder} 26 | */ 27 | function valueCoder(component) { 28 | // must parse ArrayCoder first, others sorted by probability 29 | const coder = ArrayCoder.from(component, valueCoder) // recursion 30 | || TupleCoder.from(component, valueCoder) // recursion 31 | || AddressCoder.from(component) 32 | || IntegerCoder.from(component) 33 | || StringCoder.from(component) 34 | || BytesCoder.from(component) 35 | || BoolCoder.from(component) 36 | || NullCoder.from(component); 37 | 38 | assert(coder instanceof BaseCoder, { 39 | message: 'can not found matched coder', 40 | component, 41 | }); 42 | 43 | return coder; 44 | } 45 | 46 | function formatType({ name, inputs }) { 47 | return `${name}(${inputs.map(param => valueCoder(param).type).join(',')})`; 48 | } 49 | 50 | function formatFullName({ name, inputs }) { 51 | return `${name}(${inputs.map(param => `${valueCoder(param).type} ${param.indexed ? 'indexed ' : ''}${param.name}`).join(', ')})`; 52 | } 53 | 54 | module.exports = { 55 | valueCoder, 56 | formatType, 57 | formatFullName, 58 | }; 59 | -------------------------------------------------------------------------------- /src/contract/event/ContractEvent.js: -------------------------------------------------------------------------------- 1 | const callable = require('../../util/callable'); 2 | const EventCoder = require('./EventCoder'); 3 | const LogFilter = require('./LogFilter'); 4 | 5 | class ContractEvent extends EventCoder { 6 | constructor(fragment, contract, conflux) { 7 | super(fragment); 8 | this.contract = contract; 9 | this.conflux = conflux; 10 | 11 | return callable(this, this.call.bind(this)); 12 | } 13 | 14 | call(...args) { 15 | const address = this.contract.address; // dynamic get `contract.address` 16 | const topics = [this.signature, ...this.encodeTopics(args)]; 17 | return new LogFilter({ address: [address], topics }, this); 18 | } 19 | } 20 | 21 | module.exports = ContractEvent; 22 | -------------------------------------------------------------------------------- /src/contract/event/ContractEventOverride.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const callable = require('../../util/callable'); 3 | 4 | class ContractEventOverride { 5 | constructor(events, contract, conflux) { 6 | this.signatureToEvent = lodash.keyBy(events, 'signature'); 7 | this.contract = contract; 8 | this.conflux = conflux; 9 | 10 | return callable(this, this.call.bind(this)); 11 | } 12 | 13 | call(...args) { 14 | const acceptArray = []; 15 | const rejectArray = []; 16 | 17 | let filter; 18 | for (const event of Object.values(this.signatureToEvent)) { 19 | try { 20 | filter = event(...args); 21 | acceptArray.push(event.type); 22 | } catch (e) { 23 | rejectArray.push(event.type); 24 | } 25 | } 26 | 27 | if (!acceptArray.length) { 28 | throw new Error(`can not match override "${rejectArray.join(',')}" with args (${args.join(',')})`); 29 | } 30 | if (acceptArray.length > 1) { 31 | throw new Error(`can not determine override "${acceptArray.join('|')}" with args (${args.join(',')})`); 32 | } 33 | 34 | return filter; 35 | } 36 | 37 | decodeLog(log) { 38 | const topic = log.topics[0]; 39 | const event = this.signatureToEvent[topic]; 40 | return event.decodeLog(log); 41 | } 42 | } 43 | 44 | module.exports = ContractEventOverride; 45 | -------------------------------------------------------------------------------- /src/contract/event/LogFilter.js: -------------------------------------------------------------------------------- 1 | class LogFilter { 2 | constructor({ address, topics }, event) { 3 | this.address = address; 4 | this.topics = topics; 5 | Reflect.defineProperty(this, 'event', { value: event }); // XXX: use defineProperty to avoid from JSON.stringify 6 | } 7 | 8 | async getLogs(options = {}) { 9 | const logs = await this.event.conflux.cfx.getLogs({ ...this, ...options }); 10 | 11 | logs.forEach(log => { 12 | log.arguments = this.event.decodeLog(log); 13 | }); 14 | 15 | return logs; 16 | } 17 | 18 | async subscribeLogs(options = {}) { 19 | const subscription = await this.event.conflux.subscribeLogs({ ...this, ...options }); 20 | 21 | subscription.on('data', log => { 22 | log.arguments = this.event.decodeLog(log); 23 | }); 24 | 25 | return subscription; 26 | } 27 | } 28 | 29 | module.exports = LogFilter; 30 | -------------------------------------------------------------------------------- /src/contract/method/ContractConstructor.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const ContractMethod = require('./ContractMethod'); 3 | const { WORD_CHARS } = require('../../CONST'); 4 | 5 | class ContractConstructor extends ContractMethod { 6 | constructor(fragment, bytecode, contract, conflux) { 7 | super(lodash.defaults(fragment, { name: 'constructor', inputs: [] }), contract, conflux); 8 | 9 | this.signature = ''; // MUST be '' for `super.encodeData` 10 | this.bytecode = bytecode; 11 | this.decodeOutputs = hex => hex; 12 | } 13 | 14 | call(...args) { 15 | if (!this.bytecode) { 16 | throw new Error('bytecode is empty'); 17 | } 18 | 19 | const transaction = super.call(...args); 20 | transaction.to = null; 21 | return transaction; 22 | } 23 | 24 | /** 25 | * Encode contract deploy data 26 | * 27 | * @param {array} args 28 | * @return {string} 29 | */ 30 | encodeData(args) { 31 | return `${this.bytecode}${super.encodeData(args)}`; 32 | } 33 | 34 | /** 35 | * Reverse try to decode word by word 36 | * 37 | * @param {string} hex - Hex string 38 | * @return {array} NamedTuple 39 | */ 40 | decodeData(hex) { 41 | for (let index = WORD_CHARS; index <= hex.length; index += WORD_CHARS) { 42 | try { 43 | return super.decodeData(hex.slice(-index)); 44 | } catch (e) { 45 | // pass 46 | } 47 | } 48 | return undefined; 49 | } 50 | } 51 | 52 | module.exports = ContractConstructor; 53 | -------------------------------------------------------------------------------- /src/contract/method/ContractMethod.js: -------------------------------------------------------------------------------- 1 | const callable = require('../../util/callable'); 2 | const MethodTransaction = require('./MethodTransaction'); 3 | const FunctionCoder = require('./FunctionCoder'); 4 | 5 | class ContractMethod extends FunctionCoder { 6 | constructor(fragment, contract, conflux) { 7 | super(fragment); 8 | this.contract = contract; 9 | this.conflux = conflux; 10 | 11 | return callable(this, this.call.bind(this)); 12 | } 13 | 14 | call(...args) { 15 | const to = this.contract.address; // dynamic get `contract.address` 16 | const data = this.encodeData(args); 17 | return new MethodTransaction({ to, data }, this); 18 | } 19 | } 20 | 21 | module.exports = ContractMethod; 22 | -------------------------------------------------------------------------------- /src/contract/method/ContractMethodOverride.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const callable = require('../../util/callable'); 3 | 4 | class ContractMethodOverride { 5 | constructor(methods, contract, conflux) { 6 | this.signatureToMethod = lodash.keyBy(methods, 'signature'); 7 | this.contract = contract; 8 | this.conflux = conflux; 9 | 10 | return callable(this, this.call.bind(this)); 11 | } 12 | 13 | call(...args) { 14 | const acceptArray = []; 15 | const rejectArray = []; 16 | 17 | let transaction; 18 | for (const method of Object.values(this.signatureToMethod)) { 19 | try { 20 | transaction = method(...args); 21 | acceptArray.push(method.type); 22 | } catch (e) { 23 | rejectArray.push(method.type); 24 | } 25 | } 26 | 27 | if (!acceptArray.length) { 28 | throw new Error(`can not match override "${rejectArray.join('|')}" with args (${args.join(',')})`); 29 | } 30 | if (acceptArray.length > 1) { 31 | throw new Error(`can not determine override "${acceptArray.join('|')}" with args (${args.join(',')})`); 32 | } 33 | 34 | return transaction; 35 | } 36 | 37 | decodeData(hex) { 38 | const signature = hex.slice(0, 10); // '0x' + 8 hex 39 | const method = this.signatureToMethod[signature]; 40 | return method.decodeData(hex); 41 | } 42 | } 43 | 44 | module.exports = ContractMethodOverride; 45 | -------------------------------------------------------------------------------- /src/contract/method/ErrorCoder.js: -------------------------------------------------------------------------------- 1 | const FunctionCoder = require('./FunctionCoder'); 2 | 3 | class ErrorCoder extends FunctionCoder { 4 | constructor() { 5 | super({ name: 'Error', inputs: [{ type: 'string', name: 'message' }] }); 6 | } 7 | 8 | decodeError(error) { 9 | try { 10 | const { message } = this.decodeData(error.data); 11 | return new Error(message); 12 | } catch (e) { 13 | return error; 14 | } 15 | } 16 | } 17 | 18 | module.exports = ErrorCoder; 19 | -------------------------------------------------------------------------------- /src/contract/method/MethodTransaction.js: -------------------------------------------------------------------------------- 1 | const Transaction = require('../../Transaction'); 2 | 3 | /** 4 | * @typedef { import('../../Transaction').TransactionMeta } TransactionMeta 5 | */ 6 | 7 | class MethodTransaction extends Transaction { 8 | constructor(options, method) { 9 | super(options); 10 | Reflect.defineProperty(this, 'method', { value: method }); // XXX: use defineProperty to avoid from JSON.stringify 11 | } 12 | 13 | /** 14 | * Will send a transaction to the smart contract and execute its method. 15 | * set contract.address as `to`, 16 | * set contract method encode as `data`. 17 | * 18 | * > Note: This can alter the smart contract state. 19 | * 20 | * @param {TransactionMeta} options - See [Transaction](Transaction.md#Transaction.js/Transaction/**constructor**) 21 | * @param {string} [password] - See [conflux.sendTransaction](#Conflux.js/Conflux/sendTransaction) 22 | * @return {import('../../subscribe/PendingTransaction')} The PendingTransaction object. 23 | */ 24 | sendTransaction(options, ...extra) { 25 | return this.method.conflux.cfx.sendTransaction({ ...this, ...options }, ...extra); 26 | } 27 | 28 | populateTransaction(options) { 29 | return this.method.conflux.cfx.populateTransaction({ ...this, ...options }); 30 | } 31 | 32 | /** 33 | * Executes a message call or transaction and returns the amount of the gas used. 34 | * set contract.address as `to`, 35 | * set contract method encode as `data`. 36 | * 37 | * @param {TransactionMeta} options - See [Transaction](Transaction.md#Transaction.js/Transaction/**constructor**) 38 | * @param {string|number} epochNumber - See [Conflux.estimateGasAndCollateral](#Conflux.js/estimateGasAndCollateral) 39 | * @return {Promise} The gas used and storage occupied for the simulated call/transaction. 40 | */ 41 | async estimateGasAndCollateral(options, epochNumber) { 42 | return this.method.conflux.cfx.estimateGasAndCollateral({ ...this, ...options }, epochNumber); 43 | } 44 | 45 | /** 46 | * Executes a message call transaction, 47 | * set contract.address as `to`, 48 | * set contract method encode as `data`. 49 | * 50 | * > Note: Can not alter the smart contract state. 51 | * 52 | * @param {TransactionMeta} options - See [Transaction](Transaction.md#Transaction.js/Transaction/**constructor**) 53 | * @param {string|number} epochNumber - See [Conflux.call](#Conflux.js/call) 54 | * @return {Promise<*>} Decoded contact call return. 55 | */ 56 | async call(options, epochNumber) { 57 | const hex = await this.method.conflux.cfx.call({ ...this, ...options }, epochNumber); 58 | return this.method.decodeOutputs(hex); 59 | } 60 | 61 | request(options, epochNumber) { 62 | const methodMeta = this.method.conflux.cfx.call.request({ ...this, ...options }, epochNumber); 63 | methodMeta.decoder = this.method.decodeOutputs.bind(this.method); 64 | return methodMeta; 65 | } 66 | 67 | async then(resolve, reject) { 68 | try { 69 | return resolve(await this.call()); 70 | } catch (e) { 71 | return reject(e); 72 | } 73 | } 74 | 75 | async catch(callback) { 76 | return this.then(v => v, callback); 77 | } 78 | 79 | async finally(callback) { 80 | try { 81 | return await this; 82 | } finally { 83 | await callback(); 84 | } 85 | } 86 | } 87 | 88 | module.exports = MethodTransaction; 89 | -------------------------------------------------------------------------------- /src/contract/standard/index.js: -------------------------------------------------------------------------------- 1 | const CRC20_ABI = [ 2 | 'function name() view returns (string)', 3 | 'function symbol() view returns (string)', 4 | 'function decimals() view returns (uint8)', 5 | 'function totalSupply() view returns (uint256)', 6 | 'function balanceOf(address) view returns (uint256)', 7 | 'function transfer(address to, uint256 amount)', 8 | 'function allowance(address owner, address spender) view returns (uint256)', 9 | 'function approve(address spender, uint256 amount) returns (bool)', 10 | 'function transferFrom(address sender, address recipient, uint256 amount) returns (bool)', 11 | 'function increaseAllowance(address spender, uint256 amount) returns (bool)', 12 | 'function decreaseAllowance(address spender, uint256 amount) returns (bool)', 13 | 'event Transfer(address indexed from, address indexed to, uint256 amount)', 14 | 'event Approval(address indexed owner, address indexed spender, uint256 amount)', 15 | ]; 16 | 17 | const ERROR_ABI = [ 18 | 'function Error(string)', 19 | ]; 20 | 21 | module.exports = { 22 | CRC20_ABI, 23 | ERROR_ABI, 24 | }; 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const CONST = require('./CONST'); 2 | const ERROR_CODES = require('./ERROR_CODES'); 3 | const Conflux = require('./Conflux'); 4 | const Contract = require('./contract'); 5 | const Wallet = require('./wallet'); 6 | const Transaction = require('./Transaction'); 7 | const Message = require('./Message'); 8 | const PersonalMessage = require('./PersonalMessage'); 9 | const Drip = require('./Drip'); 10 | const providerFactory = require('./provider'); 11 | const sign = require('./util/sign'); 12 | const format = require('./util/format'); 13 | const PrivateKeyAccount = require('./wallet/PrivateKeyAccount'); 14 | const address = require('./util/address'); 15 | 16 | module.exports = { 17 | CONST, 18 | ERROR_CODES, 19 | Conflux, 20 | Contract, 21 | Wallet, 22 | Transaction, 23 | Message, 24 | PersonalMessage, 25 | Drip, 26 | providerFactory, 27 | sign, 28 | format, 29 | PrivateKeyAccount, 30 | address, 31 | }; 32 | -------------------------------------------------------------------------------- /src/primitives/AccessList.js: -------------------------------------------------------------------------------- 1 | const format = require('../util/format'); 2 | 3 | class AccessListEntry { 4 | constructor(address, storageKeys = []) { 5 | this.address = format.hexAddress(address); 6 | this.storageKeys = storageKeys.map(format.hex); 7 | } 8 | 9 | // encode to buffer for RLP encoding 10 | encode() { 11 | return [ 12 | format.hexBuffer(this.address), 13 | this.storageKeys.map(val => format.hexBuffer(val)), 14 | ]; 15 | } 16 | } 17 | 18 | class AccessList { 19 | /** 20 | * 21 | * @param {object[]|array[]} entries 22 | */ 23 | constructor(entries = []) { 24 | // initiate AccessListEntry 25 | for (const i in entries) { 26 | if (Object.hasOwn(entries, i)) { 27 | const entry = entries[i]; 28 | if (Array.isArray(entry)) { 29 | entries[i] = new AccessListEntry(entry[0], entry[1]); 30 | } else if (typeof entry === 'object') { 31 | entries[i] = new AccessListEntry(entry.address, entry.storageKeys); 32 | } else { 33 | throw new Error('Invalid AccessListEntry'); 34 | } 35 | } 36 | } 37 | this.entries = entries; 38 | } 39 | 40 | encode() { 41 | return this.entries.map(entry => entry.encode()); 42 | } 43 | } 44 | 45 | module.exports = { 46 | AccessListEntry, 47 | AccessList, 48 | }; 49 | -------------------------------------------------------------------------------- /src/primitives/index.js: -------------------------------------------------------------------------------- 1 | const { AccessList, AccessListEntry } = require('./AccessList'); 2 | 3 | module.exports = { 4 | AccessList, 5 | AccessListEntry, 6 | }; 7 | -------------------------------------------------------------------------------- /src/provider/HttpProvider.js: -------------------------------------------------------------------------------- 1 | const superagent = require('superagent'); 2 | const BaseProvider = require('./BaseProvider'); 3 | 4 | /** 5 | * Http protocol json rpc provider. 6 | */ 7 | class HttpProvider extends BaseProvider { 8 | async _doRequest(data) { 9 | const { body } = await superagent 10 | .post(this.url) 11 | .retry(this.retry) 12 | .set(this.headers) 13 | .send(data) 14 | .timeout(this.timeout); 15 | return body; 16 | } 17 | 18 | async _request(data) { 19 | const body = await this._doRequest(data); 20 | return body || {}; 21 | } 22 | 23 | async _requestBatch(dataArray) { 24 | const body = await this._doRequest(dataArray); 25 | return body || []; 26 | } 27 | } 28 | 29 | module.exports = HttpProvider; 30 | -------------------------------------------------------------------------------- /src/provider/RPCError.js: -------------------------------------------------------------------------------- 1 | const { isHexString } = require('../util'); 2 | const format = require('../util/format'); 3 | 4 | class RPCError extends Error { 5 | constructor(object, payload = {}) { 6 | supplementErrorInfo(object, payload); 7 | super(object); 8 | Object.assign(this, object); 9 | Object.assign(this, payload); 10 | } 11 | } 12 | 13 | module.exports = RPCError; 14 | 15 | function supplementErrorInfo(object, payload) { 16 | // If use base32 address with full node before v1.1.1, will encounter this error 17 | if (object.message.match('0x prefix is missing')) { 18 | object.data = 'You should connect a node with version 1.1.1 or pass a valid hex value'; 19 | return; 20 | } 21 | if (object.message === 'Method not found' && payload.method === 'cfx_sendTransaction') { 22 | object.message = `${object.message} Can't find 'from' in cfx.wallet, check 'error.data' for detail`; 23 | object.data = 'Please use cfx.wallet.addPrivateKey() to add a account before call cfx.sendTransaction()'; 24 | } 25 | // decode hex encoded error message 26 | if (isHexString(object.data)) { 27 | object.data = format.hexBuffer(object.data).toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/provider/WechatProvider.js: -------------------------------------------------------------------------------- 1 | const BaseProvider = require('./BaseProvider'); 2 | 3 | /** 4 | * Wechat provider 5 | */ 6 | class WechatProvider extends BaseProvider { 7 | async _doRequest(data) { 8 | return new Promise((resolve, reject) => { 9 | let retryCount = this.retry; 10 | const sendRequest = () => { 11 | wx.request({ 12 | method: 'POST', 13 | url: this.url, 14 | header: this.headers, 15 | data, 16 | timeout: this.timeout, 17 | success: res => { 18 | resolve(res.data); 19 | }, 20 | fail: () => { 21 | if (retryCount > 0) { 22 | retryCount -= 1; 23 | sendRequest(); 24 | } else { 25 | reject(new Error('SendWechatRequestError')); 26 | } 27 | }, 28 | }); 29 | }; 30 | // 31 | sendRequest(); 32 | }); 33 | } 34 | 35 | async _request(data) { 36 | const body = await this._doRequest(data); 37 | return body || {}; 38 | } 39 | 40 | async _requestBatch(dataArray) { 41 | const body = await this._doRequest(dataArray); 42 | return body || []; 43 | } 44 | } 45 | 46 | module.exports = WechatProvider; 47 | -------------------------------------------------------------------------------- /src/provider/index.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const BaseProvider = require('./BaseProvider'); 3 | const HttpProvider = require('./HttpProvider'); 4 | const WechatProvider = require('./WechatProvider'); 5 | const WebsocketProvider = require('./WebSocketProvider'); 6 | 7 | /** 8 | * @param {object} options 9 | * @param {string} options.url 10 | * @param {boolean} [options.useWechatProvider] - Whether use wechat provider. 11 | * @return {WebsocketProvider|HttpProvider|BaseProvider|WechatProvider} 12 | * 13 | * @example 14 | * > providerFactory() 15 | BaseProvider { 16 | url: undefined, 17 | timeout: 300000, 18 | logger: { info: [Function: info], error: [Function: error] } 19 | } 20 | * @example 21 | * > providerFactory({ url: 'http://localhost:12537' }) 22 | HttpProvider { 23 | url: 'http://localhost:12537', 24 | timeout: 300000, 25 | logger: { info: [Function: info], error: [Function: error] } 26 | } 27 | 28 | * > providerFactory({ 29 | url: 'http://main.confluxrpc.org', 30 | timeout: 60 * 60 * 1000, 31 | logger: console, 32 | } 33 | HttpProvider { 34 | url: 'http://main.confluxrpc.org', 35 | timeout: 3600000, 36 | logger: {...} 37 | } 38 | */ 39 | function providerFactory({ url, useWechatProvider, ...rest }) { 40 | if (!url) { 41 | return new BaseProvider(rest); // empty provider 42 | } 43 | 44 | if (lodash.startsWith(url, 'http')) { 45 | return useWechatProvider ? new WechatProvider({ url, ...rest }) : new HttpProvider({ url, ...rest }); 46 | } 47 | 48 | if (lodash.startsWith(url, 'ws')) { 49 | return new WebsocketProvider({ url, ...rest }); // FIXME: support ws in browser 50 | } 51 | 52 | throw new Error('Invalid provider options'); 53 | } 54 | 55 | module.exports = providerFactory; 56 | -------------------------------------------------------------------------------- /src/rpc/Advanced.js: -------------------------------------------------------------------------------- 1 | const Big = require('big.js'); 2 | const CONST = require('../CONST'); 3 | const format = require('../util/format'); 4 | 5 | class AdvancedRPCUtilities { 6 | constructor(conflux) { 7 | this.conflux = conflux; 8 | } 9 | 10 | /** 11 | * First try to use txpool_nextNonce method, if failed use cfx_getNextNonce 12 | * 13 | * @param {string} address - The address to get nonce 14 | * @returns {Promise} 15 | */ 16 | async getNextUsableNonce(address) { 17 | address = this.conflux._formatAddress(address); 18 | let nonce; 19 | try { 20 | nonce = await this.conflux.txpool.nextNonce(address); 21 | } catch (e) { 22 | nonce = await this.conflux.cfx.getNextNonce(address); 23 | } 24 | return nonce; 25 | } 26 | 27 | /** 28 | * Get PoS interest rate 29 | * 30 | * @returns {Promise} PoS interest rate 31 | */ 32 | async getPoSInterestRate() { 33 | const RATIO = new Big(0.04); 34 | const batchRequest = this.conflux.BatchRequest(); 35 | batchRequest.add(this.conflux.cfx.getSupplyInfo.request()); 36 | batchRequest.add(this.conflux.cfx.getPoSEconomics.request()); 37 | batchRequest.add(this.conflux.cfx.getBalance.request(CONST.ZERO_ADDRESS_HEX)); 38 | const [ 39 | { totalCirculating }, 40 | { totalPosStakingTokens }, 41 | balanceOfZeroAddress, 42 | ] = await batchRequest.execute(); 43 | const bigTotalStaking = new Big(totalCirculating - balanceOfZeroAddress); 44 | const bigTotalPosStakingTokens = new Big(totalPosStakingTokens); 45 | const bigRatio = RATIO.div(bigTotalPosStakingTokens.div(bigTotalStaking).sqrt()); 46 | return bigRatio.toString(); 47 | } 48 | 49 | /** 50 | * A advance method to check whether user's balance is enough to pay one transaction 51 | * 52 | * @param {Object} options Transaction info 53 | * @param {string|number} [epochNumber] Optional epoch number 54 | * @returns {Promise} A object indicate whether user's balance is capable to pay the transaction. 55 | * - `BigInt` gasUsed: The gas used. 56 | * - `BigInt` gasLimit: The gas limit. 57 | * - `BigInt` storageCollateralized: The storage collateralized in Byte. 58 | * - `Boolean` isBalanceEnough: indicate balance is enough for gas and storage fee 59 | * - `Boolean` isBalanceEnoughForValueAndFee: indicate balance is enough for gas and storage fee plus value 60 | * - `Boolean` willPayCollateral: false if the transaction is eligible for storage collateral sponsorship, true otherwise 61 | * - `Boolean` willPayTxFee: false if the transaction is eligible for gas sponsorship, true otherwise 62 | */ 63 | async estimateGasAndCollateralAdvance(options, epochNumber) { 64 | const estimateResult = await this.conflux.cfx.estimateGasAndCollateral(options, epochNumber); 65 | if (!options.from) { 66 | throw new Error('Can not check balance without `from`'); 67 | } 68 | options = this.conflux._formatCallTx(options); 69 | const gasPrice = format.bigInt(options.gasPrice || BigInt(1)); 70 | const txValue = format.bigInt(options.value || BigInt(0)); 71 | const gasFee = gasPrice * estimateResult.gasLimit; 72 | const storageFee = estimateResult.storageCollateralized * (BigInt(1e18) / BigInt(1024)); 73 | const balance = await this.conflux.cfx.getBalance(options.from); 74 | estimateResult.balance = balance; 75 | if (!options.to) { 76 | estimateResult.willPayCollateral = true; 77 | estimateResult.willPayTxFee = true; 78 | estimateResult.isBalanceEnough = balance > (gasFee + storageFee); 79 | estimateResult.isBalanceEnoughForValueAndFee = balance > (gasFee + storageFee + txValue); 80 | } else { 81 | const checkResult = await this.conflux.cfx.checkBalanceAgainstTransaction( 82 | options.from, 83 | options.to, 84 | estimateResult.gasLimit, 85 | gasPrice, 86 | estimateResult.storageCollateralized, 87 | epochNumber, 88 | ); 89 | Object.assign(estimateResult, checkResult); 90 | let totalValue = txValue; 91 | totalValue += checkResult.willPayTxFee ? gasFee : BigInt(0); 92 | totalValue += checkResult.willPayCollateral ? storageFee : BigInt(0); 93 | estimateResult.isBalanceEnoughForValueAndFee = balance > totalValue; 94 | } 95 | return estimateResult; 96 | } 97 | } 98 | 99 | module.exports = AdvancedRPCUtilities; 100 | -------------------------------------------------------------------------------- /src/rpc/index.js: -------------------------------------------------------------------------------- 1 | const format = require('../util/format'); 2 | const { rpcPatch } = require('./rpcPatch'); 3 | 4 | class RPCMethodFactory { 5 | constructor(conflux, methods = []) { 6 | this.conflux = conflux; 7 | this.addMethods(methods); 8 | } 9 | 10 | addMethods(methods) { 11 | for (const methodMeta of methods) { 12 | rpcPatch(methodMeta); 13 | const method = methodMeta.method.split('_')[1]; 14 | this[method] = this.createRPCMethod(methodMeta); 15 | // create method alias 16 | if (methodMeta.alias) { 17 | this[methodMeta.alias] = this[method]; 18 | } 19 | } 20 | } 21 | 22 | createRPCMethod({ method, requestFormatters = [], responseFormatter = format.any, beforeHook }) { 23 | async function rpcMethod(...args) { 24 | let result; 25 | let paramsVerified = false; 26 | try { 27 | if (beforeHook) { 28 | beforeHook(...args); 29 | } 30 | const params = Array.from(args).map((arg, i) => (requestFormatters[i] ? requestFormatters[i](arg) : arg)); 31 | paramsVerified = true; 32 | result = await this.conflux.request({ method, params }); 33 | return responseFormatter(result); 34 | } catch (error) { 35 | error.rpcMethod = method; 36 | error.paramsVerified = paramsVerified; 37 | error.rpcParams = args; 38 | // if rpc result is not null, means params normalization and rpc call is successful 39 | error.rpcResult = result; 40 | throw error; 41 | } 42 | } 43 | 44 | rpcMethod.request = function (...args) { 45 | let paramsVerified = false; 46 | try { 47 | const params = Array.from(args).map((arg, i) => (requestFormatters[i] ? requestFormatters[i](arg) : arg)); 48 | paramsVerified = true; 49 | return { 50 | request: { 51 | method, 52 | params, 53 | }, 54 | decoder: responseFormatter, 55 | }; 56 | } catch (error) { 57 | error.rpcMethod = method; 58 | error.rpcParams = args; 59 | error.paramsVerified = paramsVerified; 60 | throw error; 61 | } 62 | }; 63 | 64 | return rpcMethod; 65 | } 66 | } 67 | 68 | module.exports = RPCMethodFactory; 69 | -------------------------------------------------------------------------------- /src/rpc/rpcPatch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Give a chance to change the RPC behavior. 3 | * For example, you may want to use a different responseFormatter for traceBlock. 4 | * @param rpcDef 5 | */ 6 | 7 | // eslint-disable-next-line no-unused-vars 8 | function emptyPatchRPCMethod(rpcDef) { 9 | // const { method, requestFormatters, responseFormatter } = rpcDef; 10 | } 11 | 12 | let rpcPatch = emptyPatchRPCMethod; 13 | 14 | // set it before initializing a new Conflux instance. 15 | function setPRCMethodPatch(fn) { 16 | rpcPatch = fn; 17 | } 18 | 19 | module.exports = { rpcPatch, setPRCMethodPatch }; 20 | -------------------------------------------------------------------------------- /src/rpc/txpool.js: -------------------------------------------------------------------------------- 1 | const RPCMethodFactory = require('./index'); 2 | const format = require('../util/format'); 3 | 4 | /** 5 | * Class contains txpool RPC methods 6 | * @class 7 | */ 8 | class TxPool extends RPCMethodFactory { 9 | /** 10 | * TxPool constructor. 11 | * @param {import('../Conflux').Conflux} conflux A Conflux instance 12 | * @return {TxPool} The TxPool instance 13 | */ 14 | constructor(conflux) { 15 | super(conflux); 16 | this.conflux = conflux; 17 | super.addMethods(this.methods()); 18 | } 19 | 20 | methods() { 21 | return [ 22 | /** 23 | * Get user next nonce in transaction pool 24 | * @instance 25 | * @async 26 | * @name nextNonce 27 | * @param {string} address The address of the account 28 | * @return {Promise} The next usable nonce 29 | * @example Example usage of txpool.nextNonce 30 | * await conflux.txpool.nextNonce('cfxtest:aak2rra2njvd77ezwjvx04kkds9fzagfe6d5r8e957'); 31 | * // returns 100 32 | */ 33 | { 34 | method: 'txpool_nextNonce', 35 | requestFormatters: [ 36 | this.conflux._formatAddress.bind(this.conflux), 37 | ], 38 | responseFormatter: format.bigUInt, 39 | }, 40 | ]; 41 | } 42 | } 43 | 44 | module.exports = TxPool; 45 | -------------------------------------------------------------------------------- /src/rpc/types/Account.js: -------------------------------------------------------------------------------- 1 | const format = require('../../util/format'); 2 | const CONST = require('../../CONST'); 3 | 4 | const formatAccount = format({ 5 | accumulatedInterestReturn: format.bigUInt, 6 | balance: format.bigUInt, 7 | collateralForStorage: format.bigUInt, 8 | nonce: format.bigUInt, 9 | stakingBalance: format.bigUInt, 10 | }, { 11 | name: 'format.account', 12 | }); 13 | 14 | class Account { 15 | static format(data) { 16 | return formatAccount(data); 17 | } 18 | 19 | constructor(accountMeta) { 20 | const { 21 | address, 22 | balance, 23 | nonce, 24 | codeHash, 25 | stakingBalance, 26 | collateralForStorage, 27 | accumulatedInterestReturn, 28 | admin, 29 | } = Account.format(accountMeta); 30 | /** @type {string} */ 31 | this.address = address; 32 | /** @type {BigInt} */ 33 | this.balance = balance; 34 | /** @type {BigInt} */ 35 | this.nonce = nonce; 36 | /** @type {string} */ 37 | this.codeHash = codeHash; 38 | /** @type {BigInt} */ 39 | this.stakingBalance = stakingBalance; 40 | /** @type {BigInt} */ 41 | this.collateralForStorage = collateralForStorage; 42 | /** @type {BigInt} */ 43 | this.accumulatedInterestReturn = accumulatedInterestReturn; 44 | /** @type {string} */ 45 | this.admin = admin; 46 | return this; 47 | } 48 | 49 | isExternalAccount() { 50 | return this.codeHash === CONST.KECCAK_EMPTY; 51 | } 52 | } 53 | 54 | module.exports = Account; 55 | -------------------------------------------------------------------------------- /src/rpc/types/index.js: -------------------------------------------------------------------------------- 1 | exports.Account = require('./Account'); 2 | exports.formatters = require('./formatter'); 3 | -------------------------------------------------------------------------------- /src/rpc/types/rawFormatter.js: -------------------------------------------------------------------------------- 1 | const format = require('../../util/format'); 2 | 3 | // Reverse formatters 4 | 5 | format.rawStatus = format({ 6 | networkId: format.bigUIntHex, 7 | chainId: format.bigUIntHex, 8 | epochNumber: format.bigUIntHex, 9 | blockNumber: format.bigUIntHex, 10 | pendingTxNumber: format.bigUIntHex, 11 | latestCheckpoint: format.bigUIntHex, 12 | latestConfirmed: format.bigUIntHex, 13 | latestFinalized: format.bigUIntHex, 14 | latestState: format.bigUIntHex, 15 | ethereumSpaceChainId: format.bigUIntHex, 16 | }, { 17 | name: 'format.rawStatus', 18 | }); 19 | 20 | format.rawTransaction = format({ 21 | nonce: format.bigUIntHex, 22 | gasPrice: format.bigUIntHex, 23 | gas: format.bigUIntHex, 24 | value: format.bigUIntHex, 25 | storageLimit: format.bigUIntHex, 26 | epochHeight: format.bigUIntHex, 27 | chainId: format.bigUIntHex, 28 | v: format.bigUIntHex, 29 | status: format.bigUIntHex.$or(null), 30 | transactionIndex: format.bigUIntHex.$or(null), 31 | maxFeePerGas: format.bigUIntHex.$or(null), 32 | maxPriorityFeePerGas: format.bigUIntHex.$or(null), 33 | type: format.bigUIntHex.$or(null), 34 | yParity: format.bigUIntHex.$or(null), 35 | }, { 36 | name: 'format.rawTransaction', 37 | }).$or(null); 38 | 39 | format.rawReceipt = format({ 40 | index: format.bigUIntHex, 41 | epochNumber: format.bigUIntHex, 42 | outcomeStatus: format.bigUIntHex.$or(null), 43 | gasUsed: format.bigUIntHex, 44 | gasFee: format.bigUIntHex, 45 | storageCollateralized: format.bigUIntHex, 46 | type: format.bigUIntHex.$or(null), 47 | effectiveGasPrice: format.bigUIntHex.$or(null), 48 | burntGasFee: format.bigUIntHex.$or(null), 49 | storageReleased: [{ 50 | collaterals: format.bigUIntHex, 51 | }], 52 | }, { 53 | name: 'format.rawReceipt', 54 | }).$or(null); 55 | 56 | format.rawLog = format({ 57 | epochNumber: format.bigUIntHex, 58 | logIndex: format.bigUIntHex, 59 | transactionIndex: format.bigUIntHex, 60 | transactionLogIndex: format.bigUIntHex, 61 | }, { 62 | name: 'format.rawLog', 63 | }); 64 | 65 | format.rawLogs = format([format.rawLog]); 66 | 67 | format.rawBlock = format({ 68 | epochNumber: format.bigUIntHex.$or(null), 69 | blockNumber: format.bigUIntHex.$or(null), 70 | baseFeePerGas: format.bigUIntHex.$or(null), 71 | blame: format.bigUIntHex, 72 | height: format.bigUIntHex, 73 | size: format.bigUIntHex, 74 | timestamp: format.bigUIntHex, 75 | gasLimit: format.bigUIntHex, 76 | gasUsed: format.bigUIntHex.$or(null).$or(undefined), 77 | difficulty: format.bigUIntHex, 78 | transactions: [(format.rawTransaction).$or(format.transactionHash)], 79 | }, { 80 | name: 'format.rawBlock', 81 | }).$or(null); 82 | 83 | format.rawSupplyInfo = format({ 84 | totalCirculating: format.bigUIntHex, 85 | totalIssued: format.bigUIntHex, 86 | totalStaking: format.bigUIntHex, 87 | totalCollateral: format.bigUIntHex, 88 | totalEspaceTokens: format.bigUIntHex.$or(null), 89 | }, { 90 | name: 'format.rawSupplyInfo', 91 | }); 92 | 93 | format.rawSponsorInfo = format({ 94 | sponsorBalanceForCollateral: format.bigUIntHex, 95 | sponsorBalanceForGas: format.bigUIntHex, 96 | sponsorGasBound: format.bigUIntHex, 97 | }, { 98 | name: 'format.rawSponsorInfo', 99 | }); 100 | 101 | format.rawRewardInfo = format([ 102 | { 103 | baseReward: format.bigUIntHex, 104 | totalReward: format.bigUIntHex, 105 | txFee: format.bigUIntHex, 106 | }, 107 | ]); 108 | 109 | format.rawVoteList = format([ 110 | { 111 | amount: format.bigUIntHex, 112 | }, 113 | ]); 114 | 115 | format.rawDepositList = format([ 116 | { 117 | amount: format.bigUIntHex, 118 | accumulatedInterestRate: format.bigUIntHex, 119 | }, 120 | ]); 121 | 122 | format.rawAccount = format({ 123 | accumulatedInterestReturn: format.bigUIntHex, 124 | balance: format.bigUIntHex, 125 | collateralForStorage: format.bigUIntHex, 126 | nonce: format.bigUIntHex, 127 | stakingBalance: format.bigUIntHex, 128 | }, { 129 | name: 'format.rawAccount', 130 | }); 131 | 132 | format.rawPosEconomics = format({ 133 | distributablePosInterest: format.bigUIntHex, 134 | lastDistributeBlock: format.bigUIntHex, 135 | totalPosStakingTokens: format.bigUIntHex, 136 | }, { 137 | name: 'format.rawPosEconomics', 138 | }); 139 | 140 | module.exports = format; 141 | -------------------------------------------------------------------------------- /src/subscribe/Subscription.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | 3 | /** 4 | * Subscription event emitter 5 | */ 6 | class Subscription extends EventEmitter { 7 | constructor(id) { 8 | super(); 9 | this.id = id; 10 | } 11 | 12 | toString() { 13 | return this.id; 14 | } 15 | } 16 | 17 | module.exports = Subscription; 18 | -------------------------------------------------------------------------------- /src/util/HexStream.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('./index'); 2 | const { WORD_CHARS } = require('../CONST'); 3 | 4 | class HexStream { 5 | constructor(hex) { 6 | this.string = hex; 7 | this.index = hex.startsWith('0x') ? 2 : 0; 8 | } 9 | 10 | eof() { 11 | return this.index >= this.string.length; 12 | } 13 | 14 | read(length, alignLeft = false) { 15 | assert(Number.isInteger(length) && 0 <= length, { 16 | message: 'invalid length', 17 | expect: 'integer && >= 0', 18 | got: length, 19 | stream: this, 20 | }); 21 | 22 | const size = Math.ceil(length / WORD_CHARS) * WORD_CHARS; 23 | const string = alignLeft 24 | ? this.string.substr(this.index, length) 25 | : this.string.substr(this.index + (size - length), length); 26 | 27 | assert(string.length === length, { 28 | message: 'length not match', 29 | expect: length, 30 | got: string.length, 31 | stream: this, 32 | }); 33 | 34 | this.index += size; 35 | return string; 36 | } 37 | } 38 | 39 | module.exports = HexStream; 40 | -------------------------------------------------------------------------------- /src/util/address.js: -------------------------------------------------------------------------------- 1 | const { 2 | encode, 3 | decode, 4 | isValidCfxAddress, 5 | verifyCfxAddress, 6 | hasNetworkPrefix, 7 | simplifyCfxAddress, 8 | shortenCfxAddress, 9 | isZeroAddress, 10 | isInternalContractAddress, 11 | isValidHexAddress, 12 | isValidCfxHexAddress, 13 | } = require('@conflux-dev/conflux-address-js'); 14 | const { checksumAddress, keccak256 } = require('./sign'); 15 | const { ADDRESS_TYPES } = require('../CONST'); 16 | 17 | /** 18 | * encode hex40 address to base32 address 19 | * @function encodeCfxAddress 20 | * @param {string|Buffer} address - hex40 address 21 | * @param {number} numberId - networkId 22 | * @param {boolean} [verbose] - if true, return verbose address 23 | * @return {string} base32 string address 24 | */ 25 | 26 | /** 27 | * decode base32 address to hex40 address 28 | * @function decodeCfxAddress 29 | * @param {string} address - base32 string 30 | * @return {object} 31 | */ 32 | 33 | /** 34 | * check if the address is valid 35 | * @function isValidCfxAddress 36 | * @param {string} address - base32 string 37 | * @return {boolean} 38 | */ 39 | 40 | /** 41 | * verify base32 address if pass return true if not throw error 42 | * @function verifyCfxAddress 43 | * @param {string} address - base32 string 44 | * @return {boolean} 45 | */ 46 | 47 | /** 48 | * check if the address has network prefix 49 | * @function hasNetworkPrefix 50 | * @param {string} address - base32 string 51 | * @return {boolean} 52 | */ 53 | 54 | /** 55 | * simplify base32 address to non verbose address 56 | * @function simplifyCfxAddress 57 | * @param {string} address - base32 string 58 | * @return {string} return a non verbose address 59 | */ 60 | 61 | /** 62 | * @function shortenCfxAddress 63 | * @param {string} address - base32 string 64 | * @return {string} Return a short address 65 | */ 66 | 67 | /** 68 | * @function isZeroAddress 69 | * @param {string} address - base32 string 70 | * @return {boolean} 71 | */ 72 | 73 | /** 74 | * @function isInternalContractAddress 75 | * @param {string} address - base32 string 76 | * @return {boolean} 77 | */ 78 | 79 | /** 80 | * @function isValidHexAddress 81 | * @param {string} address - hex string 82 | * @return {boolean} 83 | */ 84 | 85 | /** 86 | * check if the address is valid conflux hex address 87 | * @function isValidCfxHexAddress 88 | * @param {string} address - hex string 89 | * @return {boolean} 90 | */ 91 | 92 | /** 93 | * Makes a ethereum checksum address 94 | * 95 | * > Note: support [EIP-55](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md) 96 | * > Note: not support [RSKIP60](https://github.com/rsksmart/RSKIPs/blob/master/IPs/RSKIP60.md) yet 97 | * 98 | * @param {string} address - Hex string 99 | * @return {string} 100 | * 101 | * @example 102 | * > ethChecksumAddress('0x1b716c51381e76900ebaa7999a488511a4e1fd0a') 103 | "0x1B716c51381e76900EBAA7999A488511A4E1fD0a" 104 | */ 105 | function ethChecksumAddress(address) { 106 | return checksumAddress(address); 107 | } 108 | 109 | /** 110 | * Convert an ethereum address to conflux hex address by replace it's first letter to 1 111 | * @param {string} address 112 | * @return {string} 113 | */ 114 | function ethAddressToCfxAddress(address) { 115 | return `0x1${address.toLowerCase().slice(3)}`; 116 | } 117 | 118 | /** 119 | * Calculate CFX space address's mapped EVM address 120 | * @param {string} address - base32 string 121 | * @returns {string} 122 | * 123 | * @example 124 | * > cfxMappedEVMSpaceAddress(cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91) 125 | * "0x12Bf6283CcF8Ad6ffA63f7Da63EDc217228d839A" 126 | */ 127 | function cfxMappedEVMSpaceAddress(address) { 128 | const { hexAddress } = decode(address); 129 | const mappedBuf = keccak256(hexAddress).slice(-20); 130 | return checksumAddress(`0x${mappedBuf.toString('hex')}`); 131 | } 132 | 133 | module.exports = { 134 | encodeCfxAddress: encode, 135 | decodeCfxAddress: decode, 136 | ethChecksumAddress, 137 | ethAddressToCfxAddress, 138 | cfxMappedEVMSpaceAddress, 139 | ADDRESS_TYPES, 140 | isValidCfxAddress, 141 | verifyCfxAddress, 142 | hasNetworkPrefix, 143 | simplifyCfxAddress, 144 | shortenCfxAddress, 145 | isZeroAddress, 146 | isInternalContractAddress, 147 | isValidHexAddress, 148 | isValidCfxHexAddress, 149 | }; 150 | -------------------------------------------------------------------------------- /src/util/callable.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | 3 | function callable(object, func) { 4 | if (!lodash.isFunction(func)) { 5 | throw new Error('except to be function'); 6 | } 7 | 8 | return new Proxy(func, { 9 | getPrototypeOf: () => Object.getPrototypeOf(object), 10 | // setPrototypeOf 11 | // isExtensible 12 | // preventExtensions 13 | getOwnPropertyDescriptor: (_, key) => Object.getOwnPropertyDescriptor(object, key), 14 | has: (_, key) => (Reflect.has(object, key) || Reflect.has(func, key)), 15 | get: (_, key) => (Reflect.has(object, key) ? Reflect.get(object, key) : Reflect.get(func, key)), 16 | set: (_, key, value) => Reflect.set(object, key, value), 17 | deleteProperty: (_, key) => Reflect.deleteProperty(object, key), 18 | defineProperty: (_, key, attributes) => Reflect.defineProperty(object, key, attributes), 19 | ownKeys: () => Reflect.ownKeys(object), 20 | // apply 21 | // construct 22 | }); 23 | } 24 | 25 | module.exports = callable; 26 | -------------------------------------------------------------------------------- /src/util/index.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const { WORD_BYTES } = require('../CONST'); 3 | 4 | function assert(bool, value) { 5 | if (!bool) { 6 | if (lodash.isPlainObject(value)) { 7 | value = JSON.stringify(value); 8 | } 9 | throw new Error(value); 10 | } 11 | } 12 | 13 | /** 14 | * @param {Buffer} buffer 15 | * @param {boolean} alignLeft 16 | * @return {Buffer} 17 | */ 18 | function alignBuffer(buffer, alignLeft = false) { 19 | const count = WORD_BYTES - (buffer.length % WORD_BYTES); 20 | if (0 < count && count < WORD_BYTES) { 21 | buffer = alignLeft 22 | ? Buffer.concat([buffer, Buffer.alloc(count)]) 23 | : Buffer.concat([Buffer.alloc(count), buffer]); 24 | } 25 | 26 | return buffer; 27 | } 28 | 29 | function awaitTimeout(promise, timeout) { 30 | return new Promise((resolve, reject) => { 31 | const error = new Error(`Timeout after ${timeout} ms`); 32 | const timer = setTimeout(() => reject(error), timeout); 33 | promise.then(resolve).catch(reject).finally(() => clearTimeout(timer)); 34 | }); 35 | } 36 | 37 | function sleep(ms) { 38 | return new Promise(resolve => setTimeout(resolve, ms)); 39 | } 40 | 41 | function decodeHexEncodedStr(hexEncodedStr) { 42 | return Buffer.from(hexEncodedStr.slice(2), 'hex').toString(); 43 | } 44 | 45 | function isHexString(v) { 46 | return lodash.isString(v) && v.match(/^0x[0-9A-Fa-f]*$/); 47 | } 48 | 49 | function isBytes(value) { 50 | if (value == null) { return false; } 51 | if (value.constructor === Uint8Array) { return true; } 52 | if (typeof value === 'string') { return false; } 53 | if (value.length == null) { return false; } 54 | 55 | // eslint-disable-next-line no-plusplus 56 | for (let i = 0; i < value.length; i++) { 57 | const v = value[i]; 58 | if (typeof v !== 'number' || v < 0 || v >= 256 || (v % 1)) { 59 | return false; 60 | } 61 | } 62 | return true; 63 | } 64 | 65 | function validAddressPrefix(addressBuf) { 66 | // eslint-disable-next-line no-bitwise 67 | const prefix = addressBuf[0] & 0xf0; 68 | return prefix === 0x10 || prefix === 0x80 || prefix === 0x00; 69 | } 70 | 71 | module.exports = { 72 | assert, 73 | alignBuffer, 74 | awaitTimeout, 75 | decodeHexEncodedStr, 76 | isHexString, 77 | isBytes, 78 | validAddressPrefix, 79 | sleep, 80 | }; 81 | -------------------------------------------------------------------------------- /src/util/jsbi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * support interface of [jsbi](https://github.com/GoogleChromeLabs/jsbi#readme) 3 | * - for node.js using native BigInt as JSBI.BigInt 4 | * - for browser using browserify to replace with jsbi 5 | */ 6 | 7 | /* eslint-disable no-bitwise */ 8 | module.exports = BigInt; 9 | module.exports.BigInt = BigInt; 10 | 11 | module.exports.toNumber = x => Number(x); 12 | 13 | module.exports.unaryMinus = x => -x; 14 | module.exports.bitwiseNot = x => ~x; 15 | 16 | module.exports.exponentiate = (x, y) => x ** y; 17 | module.exports.multiply = (x, y) => x * y; 18 | module.exports.divide = (x, y) => x / y; 19 | module.exports.remainder = (x, y) => x % y; 20 | module.exports.add = (x, y) => x + y; 21 | module.exports.subtract = (x, y) => x - y; 22 | module.exports.leftShift = (x, y) => x << y; 23 | module.exports.signedRightShift = (x, y) => x >> y; 24 | 25 | module.exports.lessThan = (x, y) => x < y; 26 | module.exports.lessThanOrEqual = (x, y) => x <= y; 27 | module.exports.greaterThan = (x, y) => x > y; 28 | module.exports.greaterThanOrEqual = (x, y) => x >= y; 29 | module.exports.equal = (x, y) => x === y; 30 | module.exports.notEqual = (x, y) => x !== y; 31 | 32 | module.exports.bitwiseAnd = (x, y) => x & y; 33 | module.exports.bitwiseXor = (x, y) => x ^ y; 34 | module.exports.bitwiseOr = (x, y) => x | y; 35 | 36 | module.exports.ADD = (x, y) => x + y; 37 | module.exports.LT = (x, y) => x < y; 38 | module.exports.LE = (x, y) => x <= y; 39 | module.exports.GT = (x, y) => x > y; 40 | module.exports.GE = (x, y) => x >= y; 41 | module.exports.EQ = (x, y) => x === y; 42 | module.exports.NE = (x, y) => x !== y; 43 | -------------------------------------------------------------------------------- /src/util/namedTuple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Make a NamedTuple Class 3 | * @typedef {Object} NamedTuple 4 | * @template NamedTuple 5 | * @param {string[]} names 6 | * @returns {NamedTuple} 7 | * 8 | * @example 9 | * > Student = namedTuple('name', 'age') 10 | * > student = new Student('Tom', 18) 11 | * > Array.isArray(student) 12 | true 13 | * > student 14 | NamedTuple(name,age) [ 'Tom', 18 ] 15 | * > student.toObject() 16 | { name: 'Tom', age: 18 } 17 | * > student.name 18 | "Tom" 19 | * > student.age 20 | 18 21 | * > student.age = 19 22 | Error: can not change element to a NamedTuple 23 | */ 24 | function namedTuple(...names) { 25 | const _nameToIndex = {}; 26 | names.forEach((name, index) => { 27 | _nameToIndex[name] = index; 28 | }); 29 | 30 | class NamedTuple extends Array { 31 | constructor(...args) { 32 | super(args.length); 33 | args.forEach((v, i) => Reflect.set(this, i, v)); // XXX: new Array(0) === [] 34 | 35 | return new Proxy(this, { 36 | has: (_, key) => { 37 | const index = _nameToIndex[key]; 38 | return index !== undefined ? true : (key in this); 39 | }, 40 | get: (_, key) => { 41 | const index = _nameToIndex[key]; 42 | return index === undefined ? this[key] : this[index]; 43 | }, 44 | set: () => { 45 | throw new Error('can not change element to a NamedTuple'); 46 | }, 47 | deleteProperty: () => { 48 | throw new Error('can not delete element to a NamedTuple'); 49 | }, 50 | }); 51 | } 52 | 53 | static get name() { 54 | return `NamedTuple(${names.join(',')})`; 55 | } 56 | 57 | static fromObject(object) { 58 | return new this(...names.map(name => object[name])); 59 | } 60 | 61 | toObject() { 62 | const obj = {}; 63 | names.forEach(name => { 64 | obj[name] = this[name]; 65 | }); 66 | return obj; 67 | } 68 | } 69 | 70 | return NamedTuple; 71 | } 72 | 73 | module.exports = namedTuple; 74 | -------------------------------------------------------------------------------- /src/util/providerWrapper.js: -------------------------------------------------------------------------------- 1 | const Transaction = require('../Transaction'); 2 | const { MIN_GAS_PRICE } = require('../CONST'); 3 | const sign = require('./sign'); 4 | const format = require('./format'); 5 | 6 | function parseSignature(sig) { 7 | return { 8 | r: sig.slice(0, 66), 9 | s: `0x${sig.slice(66, 66 + 64)}`, 10 | v: Number(`0x${sig.slice(66 + 64, 66 + 66)}`) - 27, 11 | }; 12 | } 13 | 14 | async function signWithMetaMask(txInfo) { 15 | const tx = new Transaction(txInfo); 16 | const unsignedHash = format.hex(sign.keccak256(tx.encode(false))); 17 | /* eslint-disable */ 18 | const signature = await ethereum.request({ 19 | method: 'eth_sign', 20 | params: [ethereum.selectedAddress, unsignedHash], 21 | }); 22 | const sigInfo = parseSignature(signature); 23 | tx.r = sigInfo.r; 24 | tx.s = sigInfo.s; 25 | tx.v = sigInfo.v; 26 | return tx.serialize(); 27 | } 28 | 29 | async function useEthereumPrepareTx(txInfo, callRPC) { 30 | if (!txInfo.chainId) { 31 | txInfo.chainId = await callRPC({ method: 'eth_chainId' }); 32 | } 33 | if (!txInfo.gasPrice) { 34 | txInfo.gasPrice = MIN_GAS_PRICE; 35 | } 36 | if (!txInfo.nonce) { 37 | txInfo.nonce = await callRPC({ method: 'eth_getTransactionCount', params: [txInfo.from] }); 38 | } 39 | if (!txInfo.epochHeight) { 40 | txInfo.epochHeight = await callRPC({ method: 'eth_blockNumber', params: [] }); 41 | } 42 | } 43 | 44 | function wrapEthereum(provider) { 45 | if (typeof ethereum === 'undefined') { 46 | throw new Error('MetaMask is not installed!'); 47 | } 48 | const originRequest = provider.request; 49 | 50 | async function request(payload) { 51 | const { method, params } = payload; 52 | if (method !== 'eth_sendTransaction') { 53 | return originRequest(payload); 54 | } 55 | const txInfo = params[0]; 56 | if (!txInfo.gas || !txInfo.storageLimit) { 57 | throw new Error("'gas' and 'storageLimit' field is needed"); 58 | } 59 | await useEthereumPrepareTx(txInfo, originRequest); 60 | const rawTx = await signWithMetaMask(txInfo); 61 | return originRequest({ 62 | method: 'eth_sendRawTransaction', 63 | params: [rawTx], 64 | }); 65 | } 66 | 67 | provider.request = request.bind(provider); 68 | } 69 | 70 | async function useConfluxPrepareTx(txInfo, callRPC) { 71 | if (!txInfo.chainId) { 72 | const { chainId } = await callRPC('cfx_getStatus'); 73 | txInfo.chainId = chainId; 74 | } 75 | if (!txInfo.gas || !txInfo.storageLimit) { 76 | let chainId = Number(txInfo.chainId); 77 | if(isNaN(chainId)) { 78 | throw new Error('Invalid chainId'); 79 | } 80 | txInfo = format.callTxAdvance()(txInfo); 81 | const { gasLimit, storageCollateralized } = await callRPC('cfx_estimateGasAndCollateral', txInfo); 82 | if (!txInfo.gas) { 83 | txInfo.gas = gasLimit; 84 | } 85 | if (!txInfo.storageLimit) { 86 | txInfo.storageLimit = storageCollateralized; 87 | } 88 | } 89 | if (!txInfo.gasPrice) { 90 | txInfo.gasPrice = MIN_GAS_PRICE; 91 | } 92 | if (!txInfo.nonce) { 93 | txInfo.nonce = await callRPC('cfx_getNextNonce', txInfo.from); 94 | } 95 | if (!txInfo.epochHeight) { 96 | txInfo.epochHeight = await callRPC('cfx_epochNumber'); 97 | } 98 | return txInfo; 99 | } 100 | 101 | function wrapConflux(provider) { 102 | if (typeof ethereum === 'undefined') { 103 | throw new Error('MetaMask is not installed!'); 104 | } 105 | const originRequest = provider.call.bind(provider); 106 | 107 | async function request(method, ...params) { 108 | if (method !== 'cfx_sendTransaction') { 109 | return originRequest(method, ...params); 110 | } 111 | let txInfo = params[0]; 112 | txInfo = await useConfluxPrepareTx(txInfo, originRequest); 113 | const rawTx = await signWithMetaMask(txInfo); 114 | return originRequest('cfx_sendRawTransaction', rawTx); 115 | } 116 | 117 | provider.call = request.bind(provider); 118 | } 119 | 120 | module.exports = { 121 | wrapEthereum, 122 | wrapConflux, 123 | }; 124 | -------------------------------------------------------------------------------- /src/util/rlp.js: -------------------------------------------------------------------------------- 1 | const { decode } = require('rlp'); 2 | /* 3 | prefix | delta | note | code 4 | ----------|-------|---------------|-------------------------------------------------------------- 5 | 0x00~0x7f | 127 | single buffer | 6 | 0x80~0xb7 | 55 | short buffer | <0x80+length(buffer)>, ... 7 | 0xb8~0xbf | 7 | long buffer | <0xb8+length(length(buffer))>, ..., ... 8 | 0xc0~0xf7 | 55 | short array | <0xc0+length(array.bytes)>, ... 9 | 0xf8~0xff | 7 | long array | <0xf8+length(length(array.bytes))>, ..., ... 10 | */ 11 | 12 | const SHORT_RANGE = 55; 13 | const BUFFER_OFFSET = 0x80; 14 | const ARRAY_OFFSET = 0xc0; 15 | 16 | function concat(...args) { 17 | return Buffer.concat(args.map(value => { 18 | if (Buffer.isBuffer(value)) { 19 | return value; 20 | } 21 | 22 | if (Number.isSafeInteger(value) && value >= 0) { 23 | const hex = value.toString(16); 24 | return Buffer.from(hex.length % 2 ? `0${hex}` : hex, 'hex'); 25 | } 26 | 27 | throw new Error(`invalid value, expect unsigned integer or buffer, got ${value}`); 28 | })); 29 | } 30 | 31 | // ---------------------------------------------------------------------------- 32 | /** 33 | * @param {Array|Buffer} value 34 | * @return {Buffer} 35 | */ 36 | function encode(value) { 37 | if (Buffer.isBuffer(value)) { 38 | return encodeBuffer(value); 39 | } 40 | 41 | if (Array.isArray(value)) { 42 | return encodeArray(value); 43 | } 44 | 45 | throw new Error(`invalid value, expect buffer or array, got ${value}`); 46 | } 47 | 48 | /** 49 | * @param {number} length 50 | * @param {number} offset - Enum of [BUFFER_OFFSET=0x80, ARRAY_OFFSET=0xc0] 51 | * @return {Buffer} 52 | */ 53 | function encodeLength(length, offset) { 54 | if (length <= SHORT_RANGE) { 55 | return concat(length + offset); 56 | } else { 57 | const lengthBuffer = concat(length); 58 | return concat(offset + SHORT_RANGE + lengthBuffer.length, lengthBuffer); 59 | } 60 | } 61 | 62 | /** 63 | * @param {Buffer} buffer 64 | * @return {Buffer} 65 | */ 66 | function encodeBuffer(buffer) { 67 | if (buffer.length === 1 && buffer[0] === 0) { 68 | buffer = Buffer.from(''); 69 | } 70 | 71 | return buffer.length === 1 && buffer[0] < BUFFER_OFFSET 72 | ? buffer 73 | : concat(encodeLength(buffer.length, BUFFER_OFFSET), buffer); 74 | } 75 | 76 | /** 77 | * @param {Array} array 78 | * @return {Buffer} 79 | */ 80 | function encodeArray(array) { 81 | const buffer = concat(...array.map(encode)); 82 | return concat(encodeLength(buffer.length, ARRAY_OFFSET), buffer); 83 | } 84 | 85 | // TODO decode 86 | 87 | module.exports = { encode, decode }; 88 | -------------------------------------------------------------------------------- /src/util/trace.js: -------------------------------------------------------------------------------- 1 | const { ACTION_TYPES, CALL_STATUS } = require('../CONST'); 2 | const Contract = require('../contract'); 3 | const { ERROR_ABI: abi } = require('../contract/standard'); 4 | const { decodeHexEncodedStr } = require('./index'); 5 | 6 | // Reorg an traces array in tree structure 7 | function tracesInTree(txTrace) { 8 | const result = []; 9 | const stack = []; 10 | const levelCalls = {}; 11 | let maxLevel = 0; 12 | if (!txTrace || txTrace.length === 0) return []; 13 | // eslint-disable-next-line no-plusplus 14 | for (let i = 0; i < txTrace.length; i++) { 15 | const t = txTrace[i]; 16 | // set basic info 17 | t.index = i; 18 | t.level = 0; 19 | t.calls = []; 20 | 21 | if (t.type === ACTION_TYPES.CALL_RESULT || t.type === ACTION_TYPES.CREATE_RESULT) { 22 | // if the result is fail or reverted then decode the returnData 23 | t.action.decodedMessage = _decodeErrorMessage(t.action); 24 | // set result 25 | const tp = stack.pop(); 26 | txTrace[tp.index].result = t.action; 27 | if (stack.length === 0) { 28 | result.push(txTrace[tp.index]); 29 | } 30 | } else { 31 | // set parent relation and invoke level 32 | if (stack.length > 0) { 33 | const ta = txTrace[stack[stack.length - 1].index]; 34 | t.parent = ta.index; 35 | t.level = ta.level + 1; 36 | if (t.level > maxLevel) { 37 | maxLevel = t.level; 38 | } 39 | } 40 | 41 | if (!levelCalls[t.level]) levelCalls[t.level] = []; 42 | 43 | levelCalls[t.level].push(t.index); 44 | // if is a call or create push to stack top 45 | if (t.type === ACTION_TYPES.CALL || t.type === ACTION_TYPES.CREATE) { 46 | stack.push(t); 47 | } else if (t.type === ACTION_TYPES.INTERNAL_TRANSFER_ACTION && stack.length === 0) { 48 | result.push(t); 49 | } 50 | } 51 | } 52 | 53 | // eslint-disable-next-line no-plusplus 54 | for (let i = maxLevel; i > 0; i--) { 55 | for (const index of levelCalls[i]) { 56 | const item = txTrace[index]; 57 | txTrace[item.parent].calls.push(item); 58 | _cleanTrace(item); 59 | } 60 | } 61 | // eslint-disable-next-line no-plusplus 62 | for (let i = 0; i < result.length; i++) { 63 | _cleanTrace(result[i]); 64 | } 65 | return result; 66 | } 67 | 68 | function _cleanTrace(trace) { 69 | delete trace.index; 70 | delete trace.level; 71 | delete trace.parent; 72 | } 73 | 74 | const errorContract = new Contract({ abi }); 75 | 76 | function _decodeErrorMessage(action) { 77 | let errorMessage; 78 | if (action.outcome === CALL_STATUS.REVERTED && action.returnData !== '0x') { 79 | const decoded = errorContract.abi.decodeData(action.returnData); 80 | errorMessage = decoded ? decoded.object.message : ''; 81 | } 82 | if (action.outcome === CALL_STATUS.FAIL) { 83 | errorMessage = decodeHexEncodedStr(action.returnData); 84 | errorMessage = decodeHexEncodedStr(errorMessage); // decode second time 85 | } 86 | return errorMessage; 87 | } 88 | 89 | module.exports = { 90 | tracesInTree, 91 | }; 92 | -------------------------------------------------------------------------------- /src/wallet/Account.js: -------------------------------------------------------------------------------- 1 | const Transaction = require('../Transaction'); 2 | const Message = require('../Message'); 3 | 4 | /** 5 | * Account abstract class 6 | */ 7 | class Account { 8 | /** 9 | * @param {string} address 10 | */ 11 | constructor(address) { 12 | this.address = address; 13 | } 14 | 15 | /** 16 | * @param {object} options 17 | * @return {Promise} 18 | */ 19 | async signTransaction(options) { 20 | return new Transaction(options); 21 | } 22 | 23 | /** 24 | * @param {string} message 25 | * @return {Promise} 26 | */ 27 | async signMessage(message) { 28 | return new Message(message); 29 | } 30 | 31 | /** 32 | * @return {string} Address as string. 33 | */ 34 | toString() { 35 | return this.address; 36 | } 37 | 38 | /** 39 | * @return {string} Address as JSON string. 40 | */ 41 | toJSON() { 42 | return this.address; 43 | } 44 | } 45 | 46 | module.exports = Account; 47 | -------------------------------------------------------------------------------- /src/wallet/Wallet.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('../util'); 2 | const format = require('../util/format'); 3 | const Account = require('./Account'); 4 | const PrivateKeyAccount = require('./PrivateKeyAccount'); 5 | 6 | /** 7 | * Wallet to manager accounts. 8 | */ 9 | class Wallet extends Map { 10 | /** 11 | * @param {number} networkId 12 | * @return {Wallet} 13 | */ 14 | constructor(networkId) { 15 | super(); 16 | this.networkId = networkId; 17 | } 18 | 19 | /** 20 | * Set network id 21 | * @param {number} networkId 22 | */ 23 | setNetworkId(networkId) { 24 | this.networkId = networkId; 25 | } 26 | 27 | /** 28 | * Check if address exist 29 | * 30 | * @param {string} address 31 | * @return {boolean} 32 | */ 33 | has(address) { 34 | try { 35 | address = format.address(address, this.networkId); 36 | return super.has(address); 37 | } catch (e) { 38 | return false; 39 | } 40 | } 41 | 42 | /** 43 | * Drop one account by address 44 | * 45 | * @param {string} address 46 | * @return {boolean} 47 | */ 48 | delete(address) { 49 | try { 50 | address = format.address(address, this.networkId); 51 | return super.delete(address); 52 | } catch (e) { 53 | return false; 54 | } 55 | } 56 | 57 | /** 58 | * Drop all account in wallet 59 | */ 60 | clear() { 61 | return super.clear(); 62 | } 63 | 64 | /** 65 | * @param {any} address - Key of account, usually is `address` 66 | * @param {any} account - Account instance 67 | * @return {any} 68 | */ 69 | set(address, account) { 70 | address = format.address(address, this.networkId); 71 | 72 | assert(!this.has(address), `Wallet already has account "${address}"`); 73 | assert(account instanceof Account, `value not instance of Account, got ${account}`); 74 | return super.set(address, account); 75 | } 76 | 77 | /** 78 | * @param {string} address 79 | * @return {Account} 80 | */ 81 | get(address) { 82 | address = format.address(address, this.networkId); 83 | 84 | const account = super.get(address); 85 | assert(account instanceof Account, `can not found Account by "${address}"`); 86 | return account; 87 | } 88 | 89 | /** 90 | * @param {string|Buffer} privateKey - Private key of account 91 | * @return {PrivateKeyAccount} 92 | */ 93 | addPrivateKey(privateKey) { 94 | if (!this.networkId) { 95 | console.warn('wallet.addPrivateKey: networkId is not set properly, please set it'); 96 | } 97 | const account = new PrivateKeyAccount(privateKey, this.networkId); 98 | this.set(account.address, account); 99 | return account; 100 | } 101 | 102 | /** 103 | * @param {string|Buffer} [entropy] - Entropy of random account 104 | * @return {PrivateKeyAccount} 105 | */ 106 | addRandom(entropy) { 107 | const account = PrivateKeyAccount.random(entropy, this.networkId); 108 | this.set(account.address, account); 109 | return account; 110 | } 111 | 112 | /** 113 | * @param {object} keystore - Keystore version 3 object. 114 | * @param {string|Buffer} password - Password for keystore to decrypt with. 115 | * @return {PrivateKeyAccount} 116 | */ 117 | addKeystore(keystore, password) { 118 | const account = PrivateKeyAccount.decrypt(keystore, password, this.networkId); 119 | this.set(account.address, account); 120 | return account; 121 | } 122 | } 123 | 124 | module.exports = Wallet; 125 | -------------------------------------------------------------------------------- /src/wallet/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./Wallet'); 2 | -------------------------------------------------------------------------------- /test/1559.test.js: -------------------------------------------------------------------------------- 1 | const { Conflux } = require('../src'); 2 | const { TEST_KEY } = require('./index'); 3 | 4 | const NET8888_URL = 'https://net8888cfx.confluxrpc.com'; 5 | const NET8888_ID = 8888; 6 | const STORAGE_KEY1 = '0x0000000000000000000000000000000000000000000000000000000000000001'; 7 | 8 | const conflux = new Conflux({ 9 | url: NET8888_URL, 10 | networkId: NET8888_ID, 11 | // logger: { 12 | // info: val => console.log(JSON.stringify(val, null, '\t')), 13 | // error: err => console.error(err) 14 | // }, 15 | }); 16 | 17 | // net8888:aasm4c231py7j34fghntcfkdt2nm9xv1tup330k3e4 18 | // Note: This account need to be funded in the respective network, otherwise the test will fail 19 | const account = conflux.wallet.addPrivateKey(TEST_KEY); 20 | 21 | test('Test 1559 Base', async () => { 22 | const block = await conflux.cfx.getBlockByEpochNumber('latest_state', false); 23 | expect(block).toHaveProperty('baseFeePerGas'); 24 | 25 | const receipt = await conflux.cfx.sendTransaction({ 26 | type: 2, 27 | from: account.address, 28 | to: account.address, 29 | accessList: [{ 30 | address: account.address, 31 | storageKeys: [STORAGE_KEY1], 32 | }], 33 | value: 1, 34 | }).executed(); 35 | 36 | expect(receipt.type).toBe(2); 37 | expect(receipt).toHaveProperty('burntGasFee'); 38 | expect(receipt).toHaveProperty('effectiveGasPrice'); 39 | 40 | const tx = await conflux.cfx.getTransactionByHash(receipt.transactionHash); 41 | expect(tx.type).toBe(2); 42 | expect(tx).toHaveProperty('maxPriorityFeePerGas'); 43 | expect(tx).toHaveProperty('maxFeePerGas'); 44 | expect(tx).toHaveProperty('accessList'); 45 | expect(tx).toHaveProperty('yParity'); 46 | }); 47 | 48 | test('1559 estimate', async () => { 49 | const estimate = await conflux.cfx.estimateGasAndCollateral({ 50 | type: 2, 51 | from: account.address, 52 | to: account.address, 53 | value: 1, 54 | accessList: [{ 55 | address: account.address, 56 | storageKeys: [STORAGE_KEY1], 57 | }], 58 | }); 59 | expect(estimate).toHaveProperty('gasUsed'); 60 | 61 | const estimate2 = await conflux.cfx.estimateGasAndCollateral({ 62 | type: 2, 63 | from: account.address, 64 | to: account.address, 65 | value: 1, 66 | }); 67 | expect(estimate2).toHaveProperty('gasUsed'); 68 | }); 69 | -------------------------------------------------------------------------------- /test/batch.test.js: -------------------------------------------------------------------------------- 1 | const { Conflux, format } = require('../src'); 2 | const cfxFormat = require('../src/rpc/types/formatter'); 3 | 4 | const conflux = new Conflux({ 5 | url: 'http://localhost:12537', 6 | // url: 'https://test.confluxrpc.com', 7 | networkId: 1, 8 | }); 9 | const account = conflux.wallet.addPrivateKey('0x7d2fb0bafa614aa26c1776b7dc2f79e1d0598aeaf57c6e526c35e9e427ac823f'); 10 | const targetAddress = 'cfxtest:aasm4c231py7j34fghntcfkdt2nm9xv1tu6jd3r1s7'; 11 | 12 | /* test('Populate tx', async () => { 13 | async function populate() { 14 | const tx = await conflux.cfx.populateTransaction({ 15 | from: account.address, 16 | to: targetAddress, 17 | value: 1, 18 | }); 19 | return tx; 20 | } 21 | 22 | await expect(populate()).resolves.toEqual({ 23 | from: account.address, 24 | }); 25 | }); */ 26 | 27 | test('RPC methods request builder', () => { 28 | /** 29 | * RPC method request template 30 | expect(conflux.cfx.epochNumber.request()).toEqual({ 31 | request: { 32 | method: '', 33 | params: [], 34 | }, 35 | decoder: format.any, 36 | }); 37 | */ 38 | expect(conflux.cfx.getStatus.request()).toEqual({ 39 | request: { 40 | method: 'cfx_getStatus', 41 | params: [], 42 | }, 43 | decoder: cfxFormat.status, 44 | }); 45 | 46 | expect(conflux.cfx.getBalance.request(format.hexAddress(account.address))).toEqual({ 47 | request: { 48 | method: 'cfx_getBalance', 49 | params: [account.address], 50 | }, 51 | decoder: format.bigUInt, 52 | }); 53 | 54 | expect(conflux.cfx.getBalance.request(account.address)).toEqual({ 55 | request: { 56 | method: 'cfx_getBalance', 57 | params: [account.address], 58 | }, 59 | decoder: format.bigUInt, 60 | }); 61 | 62 | expect(conflux.cfx.epochNumber.request()).toEqual({ 63 | request: { 64 | method: 'cfx_epochNumber', 65 | params: [], 66 | }, 67 | decoder: format.uInt, 68 | }); 69 | 70 | expect(conflux.cfx.epochNumber.request('latest_state')).toEqual({ 71 | request: { 72 | method: 'cfx_epochNumber', 73 | params: ['latest_state'], 74 | }, 75 | decoder: format.uInt, 76 | }); 77 | 78 | expect(conflux.cfx.call.request({ 79 | from: account.address, 80 | to: targetAddress, 81 | value: 1, 82 | })).toEqual({ 83 | request: { 84 | method: 'cfx_call', 85 | params: [{ 86 | from: account.address, 87 | to: targetAddress, 88 | value: '0x1', 89 | }, undefined], 90 | }, 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/contract/EventCoder.test.js: -------------------------------------------------------------------------------- 1 | const JSBI = require('../../src/util/jsbi'); 2 | const EventCoder = require('../../src/contract/event/EventCoder'); 3 | 4 | test('event', () => { 5 | const abi = { 6 | name: 'EventName', 7 | anonymous: false, 8 | inputs: [ 9 | { 10 | indexed: true, 11 | name: 'account', 12 | type: 'address', 13 | networkId: 1, 14 | }, 15 | { 16 | indexed: false, 17 | name: 'number', 18 | type: 'uint', 19 | }, 20 | ], 21 | }; 22 | 23 | const log = { 24 | data: '0x000000000000000000000000000000000000000000000000000000000000000a', 25 | topics: [ 26 | '0xb0333e0e3a6b99318e4e2e0d7e5e5f93646f9cbf62da1587955a4092bf7df6e7', 27 | '0x0000000000000000000000000123456789012345678901234567890123456789', 28 | ], 29 | }; 30 | 31 | const coder = new EventCoder(abi); 32 | expect(coder.signature).toEqual('0xb0333e0e3a6b99318e4e2e0d7e5e5f93646f9cbf62da1587955a4092bf7df6e7'); 33 | 34 | expect(coder.encodeTopics(['cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp', null])) 35 | .toEqual(['0x0000000000000000000000000123456789012345678901234567890123456789']); 36 | 37 | expect(() => coder.encodeTopics(['0x0123456789012345678901234567890123456789'])) 38 | .toThrow('length not match'); 39 | 40 | expect([...coder.decodeLog(log)]) 41 | .toEqual(['cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp', JSBI.BigInt(10)]); 42 | }); 43 | 44 | test('event.anonymous', () => { 45 | const abi = { 46 | anonymous: true, 47 | inputs: [ 48 | { 49 | indexed: true, 50 | name: 'account', 51 | type: 'address', 52 | networkId: 1, 53 | }, 54 | { 55 | indexed: false, 56 | name: 'number', 57 | type: 'uint', 58 | }, 59 | ], 60 | }; 61 | 62 | const log = { 63 | data: '0x000000000000000000000000000000000000000000000000000000000000000a', 64 | topics: [ 65 | '0x0000000000000000000000000123456789012345678901234567890123456789', 66 | ], 67 | }; 68 | 69 | const coder = new EventCoder(abi); 70 | const tuple = coder.decodeLog(log); 71 | expect([...tuple]).toEqual(['cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp', JSBI.BigInt(10)]); 72 | expect(tuple.toObject()).toEqual({ 73 | account: 'cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp', 74 | number: JSBI.BigInt(10), 75 | }); 76 | }); 77 | 78 | test('event without name', () => { 79 | const abi = { 80 | type: 'event', 81 | name: 'Event', 82 | inputs: [ 83 | { 84 | type: 'uint256', 85 | name: undefined, 86 | }, 87 | ], 88 | }; 89 | 90 | const log = { 91 | topics: [ 92 | '0x510e730eb6600b4c67d51768c6996795863364461fee983d92d5e461f209c7cf', 93 | ], 94 | data: '0x0000000000000000000000000000000000000000000000000000000000000003', 95 | }; 96 | 97 | const coder = new EventCoder(abi); 98 | const tuple = coder.decodeLog(log); 99 | expect([...tuple]).toEqual([JSBI.BigInt(3)]); 100 | expect(tuple.toObject()).toEqual({ 0: JSBI.BigInt(3) }); 101 | }); 102 | 103 | test('event without inputs', () => { 104 | const abi = { 105 | type: 'event', 106 | name: 'Event', 107 | }; 108 | 109 | const log = { 110 | topics: [ 111 | '0x57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e', 112 | ], 113 | }; 114 | 115 | const coder = new EventCoder(abi); 116 | const tuple = coder.decodeLog(log); 117 | expect([...tuple]).toEqual([]); 118 | expect(tuple.toObject()).toEqual({}); 119 | }); 120 | -------------------------------------------------------------------------------- /test/contract/FunctionCoder.test.js: -------------------------------------------------------------------------------- 1 | const JSBI = require('../../src/util/jsbi'); 2 | const FunctionCoder = require('../../src/contract/method/FunctionCoder'); 3 | 4 | test('function', () => { 5 | const abi = { 6 | name: 'func', 7 | inputs: [ 8 | { type: 'int' }, 9 | { 10 | type: 'tuple', 11 | components: [ 12 | { type: 'address' }, 13 | { type: 'string[]' }, 14 | ], 15 | }, 16 | { 17 | type: 'tuple', 18 | components: [ 19 | { 20 | type: 'tuple', 21 | components: [ 22 | { type: 'int' }, 23 | { type: 'int' }, 24 | ], 25 | }, 26 | { type: 'bool' }, 27 | ], 28 | }, 29 | ], 30 | outputs: [ 31 | { 32 | type: 'uint256', 33 | }, 34 | ], 35 | }; 36 | 37 | const params = [ 38 | JSBI.BigInt(-1), 39 | ['0x0123456789012345678901234567890123456789', ['Hello', 'World']], 40 | [[JSBI.BigInt(0xab), JSBI.BigInt(0xcd)], true], 41 | ]; 42 | 43 | const hex = '0x664b7e11' + 44 | 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + 45 | '00000000000000000000000000000000000000000000000000000000000000a0' + 46 | '00000000000000000000000000000000000000000000000000000000000000ab' + 47 | '00000000000000000000000000000000000000000000000000000000000000cd' + 48 | '0000000000000000000000000000000000000000000000000000000000000001' + 49 | '0000000000000000000000000123456789012345678901234567890123456789' + 50 | '0000000000000000000000000000000000000000000000000000000000000040' + 51 | '0000000000000000000000000000000000000000000000000000000000000002' + 52 | '0000000000000000000000000000000000000000000000000000000000000040' + 53 | '0000000000000000000000000000000000000000000000000000000000000080' + 54 | '0000000000000000000000000000000000000000000000000000000000000005' + 55 | '48656c6c6f000000000000000000000000000000000000000000000000000000' + 56 | '0000000000000000000000000000000000000000000000000000000000000005' + 57 | '576f726c64000000000000000000000000000000000000000000000000000000'; 58 | 59 | const coder = new FunctionCoder(abi); 60 | expect(coder.signature).toEqual('0x664b7e11'); 61 | expect(coder.type).toEqual('func(int256,(address,string[]),((int256,int256),bool))'); 62 | expect(coder.encodeData(params)).toEqual(hex); 63 | // expect(coder.decodeData(hex)).toEqual(params); 64 | expect(coder.decodeOutputs('0x0000000000000000000000000000000000000000000000000000000000000001')).toEqual(JSBI.BigInt(1)); 65 | }); 66 | 67 | test('function not inputs many outputs', () => { 68 | const abi = { 69 | name: 'func', 70 | outputs: [ 71 | { 72 | type: 'address', 73 | name: 'account', 74 | networkId: 1, 75 | }, 76 | { 77 | type: 'uint256', 78 | name: 'number', 79 | }, 80 | ], 81 | }; 82 | 83 | const coder = new FunctionCoder(abi); 84 | 85 | const tuple = coder.decodeOutputs('0x' + 86 | '0000000000000000000000000123456789012345678901234567890123456789' + 87 | '0000000000000000000000000000000000000000000000000000000000000001', 88 | ); 89 | expect([...tuple]).toEqual(['cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp', JSBI.BigInt(1)]); 90 | expect(tuple.toObject()).toEqual({ 91 | account: 'cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp', 92 | number: JSBI.BigInt(1), 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/contract/internal.test.js: -------------------------------------------------------------------------------- 1 | const { Conflux, format } = require('../../src'); 2 | 3 | const conflux = new Conflux({ 4 | networkId: 1, 5 | }); 6 | 7 | test('AdminControl', () => { 8 | const contract = conflux.InternalContract('AdminControl'); 9 | 10 | expect(format.hexAddress(contract.address)).toEqual('0x0888000000000000000000000000000000000000'); 11 | expect(contract.constructor).toBeDefined(); 12 | }); 13 | 14 | test('SponsorWhitelistControl', () => { 15 | const contract = conflux.InternalContract('SponsorWhitelistControl'); 16 | 17 | expect(format.hexAddress(contract.address)).toEqual('0x0888000000000000000000000000000000000001'); 18 | expect(contract.constructor).toBeDefined(); 19 | }); 20 | 21 | test('Staking', () => { 22 | const contract = conflux.InternalContract('Staking'); 23 | 24 | expect(format.hexAddress(contract.address)).toEqual('0x0888000000000000000000000000000000000002'); 25 | expect(contract.constructor).toBeDefined(); 26 | }); 27 | 28 | test('NOT EXIST', () => { 29 | expect(() => conflux.InternalContract('NOT EXIST')).toThrow('can not find internal contract'); 30 | }); 31 | -------------------------------------------------------------------------------- /test/contract/stringAbi.js: -------------------------------------------------------------------------------- 1 | const abi = [ 2 | 'constructor(uint256 num)', 3 | 'function count() public view returns (uint256)', 4 | 'function self() public', 5 | 'function inc(uint256 num) public returns (uint256)', 6 | 'function override(bytes buffer) public returns (bytes)', 7 | 'function override(string memory str) public returns (string memory)', 8 | 'function override(uint num, string memory str) public returns (bytes)', 9 | 'event SelfEvent(address indexed sender, uint256 current)', 10 | 'event ArrayEvent(string[3] indexed _array)', 11 | 'event StringEvent(string indexed _string)', 12 | // TODO: 'StructEvent' 13 | 'event OverrideEvent(bytes indexed buffer)', 14 | 'event OverrideEvent(string indexed str)', 15 | 'event OverrideEvent(uint256 indexed num, string memory str)', 16 | ]; 17 | 18 | module.exports = { 19 | abi, 20 | }; 21 | -------------------------------------------------------------------------------- /test/drip.test.js: -------------------------------------------------------------------------------- 1 | const { Drip } = require('../src'); 2 | 3 | test('Drip.fromCFX', () => { 4 | expect(() => Drip.fromCFX(null)).toThrow('Invalid number'); 5 | expect(() => Drip.fromCFX(-1)).toThrow('not match "bigUInt"'); 6 | expect(Drip.fromCFX(3.14).toString()).toEqual('3140000000000000000'); 7 | expect(Drip.fromCFX(1e-18).toString()).toEqual('1'); 8 | expect(() => Drip.fromCFX(1e-19)).toThrow('Cannot'); 9 | 10 | expect(() => Drip.fromCFX('')).toThrow('Invalid number'); 11 | expect(Drip.fromCFX('0.0').toString()).toEqual('0'); 12 | expect(Drip.fromCFX('0x0a').toString()).toEqual('10000000000000000000'); 13 | expect(Drip.fromCFX('1e-18').toString()).toEqual('1'); 14 | expect(() => Drip.fromCFX('1e-19')).toThrow('Cannot'); 15 | }); 16 | 17 | test('Drip.fromGDrip', () => { 18 | expect(() => Drip.fromGDrip(null)).toThrow('Invalid number'); 19 | expect(() => Drip.fromGDrip(-1)).toThrow('not match "bigUInt"'); 20 | expect(Drip.fromGDrip(3.14).toString()).toEqual('3140000000'); 21 | expect(Drip.fromGDrip(1e-9).toString()).toEqual('1'); 22 | expect(() => Drip.fromGDrip(1e-10)).toThrow('Cannot'); 23 | 24 | expect(() => Drip.fromGDrip('')).toThrow('Invalid number'); 25 | expect(Drip.fromGDrip('0.0').toString()).toEqual('0'); 26 | expect(Drip.fromGDrip('0x0a').toString()).toEqual('10000000000'); 27 | expect(Drip.fromGDrip('1e-9').toString()).toEqual('1'); 28 | expect(() => Drip.fromGDrip('1e-10')).toThrow('Cannot'); 29 | }); 30 | 31 | test('Drip', () => { 32 | expect(Drip('').toString()).toEqual('0'); 33 | expect(Drip('0.0').toString()).toEqual('0'); 34 | expect(Drip('0x0a').toString()).toEqual('10'); 35 | expect(Drip(1e2).toString()).toEqual('100'); 36 | expect((new Drip(1e2)).toString()).toEqual('100'); 37 | 38 | expect(() => Drip()).toThrow('Cannot'); 39 | expect(() => Drip(null)).toThrow('Cannot'); 40 | expect(() => Drip(-1)).toThrow('not match "bigUInt"'); 41 | expect(() => Drip(3.14)).toThrow('Cannot'); 42 | }); 43 | 44 | test('Drip.toXXX', () => { 45 | const drip = Drip.fromGDrip(3.14); 46 | 47 | expect(drip.toString()).toEqual('3140000000'); 48 | expect(drip.toGDrip()).toEqual('3.14'); 49 | expect(drip.toCFX()).toEqual('0.00000000314'); 50 | 51 | expect(JSON.stringify(drip)).toEqual('"3140000000"'); 52 | }); 53 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const TESTNET_MOCK_SERVER = 'http://39.100.93.109'; 2 | const CONST = require('../src/CONST'); 3 | const util = require('../src/util'); 4 | 5 | function isHash(hash) { 6 | return util.isHexString(hash) && hash.length === 66; 7 | } 8 | 9 | const TEST_KEY = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; 10 | 11 | module.exports = { 12 | TESTNET_MOCK_SERVER, 13 | TESTNET_NETWORK_ID: CONST.TESTNET_ID, 14 | ZERO_HASH: CONST.ZERO_HASH, 15 | isHash, 16 | TEST_KEY, 17 | }; 18 | -------------------------------------------------------------------------------- /test/message.test.js: -------------------------------------------------------------------------------- 1 | const { Message, sign, CONST } = require('../src'); 2 | 3 | const KEY = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; 4 | const PUBLIC = '0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559'; 5 | const ADDRESS = 'cfxtest:aasm4c231py7j34fghntcfkdt2nm9xv1tu6jd3r1s7'; 6 | 7 | test('new Message(string)', () => { 8 | const msg = new Message('Hello World'); 9 | 10 | expect(msg.message).toEqual('Hello World'); 11 | expect(msg.hash).toEqual('0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba'); // virtual attribute 12 | expect(msg.r).toEqual(undefined); // virtual attribute 13 | expect(msg.s).toEqual(undefined); // virtual attribute 14 | expect(msg.v).toEqual(undefined); // virtual attribute 15 | expect(msg.from).toEqual(undefined); // virtual attribute 16 | expect(msg.signature).toEqual(undefined); 17 | 18 | msg.sign(KEY, CONST.TESTNET_ID); 19 | 20 | expect(msg.message).toEqual('Hello World'); 21 | expect(msg.hash).toEqual('0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba'); // virtual attribute 22 | expect(msg.r).toEqual('0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c'); // virtual attribute 23 | expect(msg.s).toEqual('0x29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f'); // virtual attribute 24 | expect(msg.v).toEqual(1); // virtual attribute 25 | expect(msg.from).toEqual(ADDRESS); // virtual attribute 26 | expect(msg.signature).toEqual('0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f01'); 27 | }); 28 | 29 | test('Message.sign/recover', () => { 30 | const signature = Message.sign(KEY, sign.keccak256('Hello World')); 31 | expect(signature).toEqual('0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f01'); 32 | 33 | const publicKey = Message.recover(signature, sign.keccak256('Hello World')); 34 | expect(publicKey).toEqual(PUBLIC); 35 | }); 36 | -------------------------------------------------------------------------------- /test/personalMessage.test.js: -------------------------------------------------------------------------------- 1 | const { PersonalMessage, sign, format } = require('../src'); 2 | 3 | const KEY = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; 4 | const PUBLIC = '0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559'; 5 | const ADDRESS = 'cfxtest:aasm4c231py7j34fghntcfkdt2nm9xv1tu6jd3r1s7'; 6 | const NET_ID = 1; 7 | const message = 'Hello World'; 8 | 9 | test('new PersonalMessage(string)', () => { 10 | const messageBuf = Buffer.from(message); 11 | const msg = new PersonalMessage(message); 12 | const personalMsg = msg._prefix + messageBuf.length + message; 13 | expect(msg.hash).toEqual(format.hex(sign.keccak256(personalMsg))); 14 | expect(msg._originMsg).toEqual(message); 15 | expect(msg._personalMsg).toEqual(personalMsg); 16 | msg.sign(KEY, NET_ID); 17 | expect(msg.signature).toEqual(PersonalMessage.sign(KEY, message)); 18 | expect(msg.from).toEqual(ADDRESS); 19 | }); 20 | 21 | test('PersonalMessage.sign/recover', () => { 22 | const sig = '0xd72ea2020802d6dfce0d49fc1d92a16b43baa58fc152d6f437d852a014e0c5740b3563375b0b844a835be4f1521b4ae2a691048622f70026e0470acc5351043a01'; 23 | expect(PersonalMessage.sign(KEY, message)).toEqual(sig); 24 | expect(PersonalMessage.recover(sig, message)).toEqual(PUBLIC); 25 | }); 26 | -------------------------------------------------------------------------------- /test/primitives/accessList.test.js: -------------------------------------------------------------------------------- 1 | const { AccessList } = require('../../src/primitives/AccessList'); 2 | const format = require('../../src/util/format'); 3 | 4 | const TEST_ADDRESS = '0x1123456789012345678901234567890123456789'; 5 | const TEST_BASE32_ADDRESS = 'cfx:aajwgvnhveawgvnhveawgvnhveawgvnhve8c2ukvxz'; 6 | 7 | const STORAGE_KEY3 = '0x0000000000000000000000000000000000000000000000000000000000000003'; 8 | const STORAGE_KEY7 = '0x0000000000000000000000000000000000000000000000000000000000000007'; 9 | 10 | test('AccessList', () => { 11 | const accessList1 = new AccessList([[TEST_ADDRESS, []]]); 12 | expect(accessList1.entries[0].address).toEqual(TEST_ADDRESS); 13 | expect(accessList1.entries[0].storageKeys).toEqual([]); 14 | 15 | const accessList2 = new AccessList([[TEST_ADDRESS, [STORAGE_KEY3]]]); 16 | expect(accessList2.entries[0].address).toEqual(TEST_ADDRESS); 17 | expect(accessList2.entries[0].storageKeys).toEqual([STORAGE_KEY3]); 18 | 19 | const accessList3 = new AccessList([{ address: TEST_ADDRESS, storageKeys: [STORAGE_KEY7] }]); 20 | expect(accessList3.entries[0].address).toEqual(TEST_ADDRESS); 21 | expect(accessList3.entries[0].storageKeys).toEqual([STORAGE_KEY7]); 22 | }); 23 | 24 | test('AccessList Base32', () => { 25 | const accessList1 = new AccessList([[TEST_BASE32_ADDRESS, []]]); 26 | expect(accessList1.entries[0].address).toEqual(TEST_ADDRESS); 27 | expect(accessList1.entries[0].storageKeys).toEqual([]); 28 | 29 | expect(accessList1.encode()[0][0]).toEqual(format.hexBuffer(TEST_ADDRESS)); 30 | }); 31 | 32 | test('AccessList encode', () => { 33 | const accessList1 = new AccessList([[TEST_BASE32_ADDRESS, [STORAGE_KEY3]]]); 34 | 35 | expect(accessList1.encode()).toEqual([ 36 | [format.hexBuffer(TEST_ADDRESS), [format.hexBuffer(STORAGE_KEY3)]], 37 | ]); 38 | }); 39 | -------------------------------------------------------------------------------- /test/provider.test.js: -------------------------------------------------------------------------------- 1 | const providerFactory = require('../src/provider'); 2 | 3 | const HTTP_URL = 'http://main.confluxrpc.org'; 4 | const WS_URL = 'ws://main.confluxrpc.org/ws'; 5 | 6 | test('BaseProvider', async () => { 7 | const provider = providerFactory({}); 8 | expect(provider.constructor.name).toEqual('BaseProvider'); 9 | 10 | await expect(provider.call('cfx_epochNumber')).rejects.toThrow('BaseProvider.request not implement'); 11 | 12 | expect(await provider.batch([])).toEqual([]); 13 | 14 | await expect( 15 | provider.batch([{ method: 'cfx_epochNumber' }, { method: 'NOT_EXIST' }]), 16 | ).rejects.toThrow('BaseProvider.requestBatch not implement'); 17 | }, 60 * 1000); 18 | 19 | test('Invalid provider', () => { 20 | expect(() => providerFactory({ url: 'xxx' })).toThrow('Invalid provider options'); 21 | }); 22 | 23 | test.skip('HttpProvider', async () => { 24 | const provider = providerFactory({ url: HTTP_URL }); 25 | expect(provider.constructor.name).toEqual('HttpProvider'); 26 | 27 | await expect(provider.call('NOT_EXIST')).rejects.toThrow('Method not found'); 28 | 29 | const result = await provider.call('cfx_epochNumber'); 30 | expect(result).toMatch(/^0x[\da-f]+$/); 31 | 32 | const array = await provider.batch([{ method: 'cfx_epochNumber' }, { method: 'NOT_EXIST' }]); 33 | expect(array.length).toEqual(2); 34 | expect(array[0]).toMatch(/^0x[\da-f]+$/); 35 | expect(array[1].message).toMatch('Method not found'); 36 | 37 | await provider.close(); 38 | }, 60 * 1000); 39 | 40 | test.skip('WebSocketProvider', async () => { 41 | const provider = providerFactory({ url: WS_URL }); 42 | expect(provider.constructor.name).toEqual('WebSocketProvider'); 43 | 44 | const result = await provider.call('cfx_epochNumber'); 45 | expect(result).toMatch(/^0x[\da-f]+$/); 46 | 47 | const array = await provider.batch([{ method: 'cfx_epochNumber' }]); 48 | expect(array.length).toEqual(1); 49 | expect(array[0]).toMatch(/^0x[\da-f]+$/); 50 | 51 | const id = await provider.call('cfx_subscribe', 'epochs'); 52 | await new Promise(resolve => provider.once(id, resolve)); 53 | 54 | const promise = provider.call('cfx_epochNumber'); 55 | provider.close(); 56 | provider.close(); 57 | const error = await promise.catch(e => e); 58 | expect(error.message).toMatch(/Normal connection closure/); 59 | 60 | await new Promise(resolve => setTimeout(resolve, 1000)); 61 | }, 60 * 1000); 62 | -------------------------------------------------------------------------------- /test/rpc/cfxraw.test.js: -------------------------------------------------------------------------------- 1 | const { CFX } = require('@conflux-dev/jsonrpc-spec'); 2 | const { Conflux } = require('../../src'); 3 | const rawFormatter = require('../../src/rpc/types/rawFormatter'); 4 | 5 | const { 6 | TESTNET_MOCK_SERVER, 7 | TESTNET_NETWORK_ID, 8 | } = require('..'); 9 | 10 | const EXAMPLES = CFX.Examples.examples; 11 | 12 | const cfxClient = new Conflux({ 13 | url: TESTNET_MOCK_SERVER, 14 | networkId: TESTNET_NETWORK_ID, 15 | useVerboseAddress: true, 16 | }); 17 | 18 | const rawFormatters = { 19 | 'cfx_getStatus': rawFormatter.rawStatus, 20 | 'cfx_getBlockByEpochNumber': rawFormatter.rawBlock, 21 | 'cfx_getBlockByBlockNumber': rawFormatter.rawBlock, 22 | 'cfx_getBlockByHash': rawFormatter.rawBlock, 23 | 'cfx_getLogs': rawFormatter.rawLogs, 24 | 'cfx_getTransactionByHash': rawFormatter.rawTransaction, 25 | 'cfx_getTransactionReceipt': rawFormatter.rawReceipt, 26 | 'cfx_gasPrice': rawFormatter.bigUIntHex, 27 | 'cfx_getBalance': rawFormatter.bigUIntHex, 28 | 'cfx_getStakingBalance': rawFormatter.bigUIntHex, 29 | 'cfx_getCollateralForStorage': rawFormatter.bigUIntHex, 30 | 'cfx_getNextNonce': rawFormatter.bigUIntHex, 31 | 'cfx_epochNumber': rawFormatter.bigUIntHex, 32 | 'cfx_clientVersion': rawFormatter.any, 33 | 'cfx_getBestBlockHash': rawFormatter.any, 34 | 'cfx_getAdmin': rawFormatter.any, 35 | 'cfx_getCode': rawFormatter.any, 36 | 'cfx_getStorageRoot': rawFormatter.any, 37 | 'cfx_getBlocksByEpoch': rawFormatter.any, 38 | 'cfx_getInterestRate': rawFormatter.bigUIntHex, 39 | 'cfx_getAccumulateInterestRate': rawFormatter.bigUIntHex, 40 | 'cfx_getSupplyInfo': rawFormatter.rawSupplyInfo, 41 | 'cfx_getDepositList': rawFormatter.rawDepositList, 42 | 'cfx_getVoteList': rawFormatter.rawVoteList, 43 | 'cfx_getBlockRewardInfo': rawFormatter.rawRewardInfo, 44 | 'cfx_getSponsorInfo': rawFormatter.rawSponsorInfo, 45 | 'cfx_getAccount': rawFormatter.rawAccount, 46 | 'cfx_getPoSEconomics': rawFormatter.rawPosEconomics, 47 | }; 48 | 49 | test('GeneralRPCTest', async () => { 50 | // eslint-disable-next-line guard-for-in 51 | for (const key in rawFormatters) { 52 | const method = key.slice(4); 53 | const cases = EXAMPLES[key]; 54 | for (const testCase of cases) { 55 | // eslint-disable-next-line no-continue 56 | if (testCase.error) continue; 57 | const result = await cfxClient.cfx[method](...testCase.params); 58 | expect(rawFormatters[key](result)).toEqual(testCase.result); 59 | } 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /test/util/HexStream.test.js: -------------------------------------------------------------------------------- 1 | const HexStream = require('../../src/util/HexStream'); 2 | 3 | test('HexStream', () => { 4 | const stream = new HexStream( 5 | 'a000000000000000000000000000000000000000000000000000000000000001' + 6 | 'b000000000000000000000000000000000000000000000000000000000000002', 7 | ); 8 | 9 | expect(stream.read(0)).toEqual(''); 10 | expect(stream.read(2)).toEqual('01'); 11 | expect(stream.read(2, true)).toEqual('b0'); 12 | expect(() => stream.read(2)).toThrow('length not match'); 13 | }); 14 | -------------------------------------------------------------------------------- /test/util/PendingTransaction.test.js: -------------------------------------------------------------------------------- 1 | const { Conflux } = require('../../src'); 2 | const MockProvider = require('../../mock/MockProvider'); 3 | 4 | const conflux = new Conflux({ 5 | defaultGasPrice: 1000000, 6 | }); 7 | conflux.provider = new MockProvider(); 8 | 9 | test('PendingTransaction', async () => { 10 | const getTransactionByHash = jest.spyOn(conflux, 'getTransactionByHash'); 11 | getTransactionByHash 12 | .mockResolvedValueOnce(null) 13 | .mockResolvedValue({ blockHash: 'blockHash' }); 14 | 15 | const getTransactionReceipt = jest.spyOn(conflux, 'getTransactionReceipt'); 16 | getTransactionReceipt 17 | .mockResolvedValueOnce(null) 18 | .mockResolvedValue({ outcomeStatus: 0, contractCreated: 'address' }); 19 | 20 | const getConfirmationRiskByHash = jest.spyOn(conflux, 'getConfirmationRiskByHash'); 21 | getConfirmationRiskByHash 22 | .mockResolvedValueOnce(1) 23 | .mockResolvedValue(0); 24 | 25 | const pending = conflux.sendRawTransaction('0x'); 26 | 27 | expect((await pending).startsWith('0x')).toEqual(true); 28 | expect(await pending.mined({ delta: 0 })).toEqual({ blockHash: 'blockHash' }); 29 | expect(await pending.executed({ delta: 0 })).toEqual({ outcomeStatus: 0, contractCreated: 'address' }); 30 | expect(await pending.confirmed({ delta: 0 })).toEqual({ outcomeStatus: 0, contractCreated: 'address' }); 31 | 32 | getTransactionByHash.mockRestore(); 33 | getTransactionReceipt.mockRestore(); 34 | getConfirmationRiskByHash.mockRestore(); 35 | }); 36 | 37 | test('PendingTransaction failed', async () => { 38 | conflux.getTransactionByHash = jest.fn(); 39 | conflux.getTransactionByHash.mockResolvedValue({ blockHash: 'blockHash' }); 40 | 41 | conflux.getTransactionReceipt = jest.fn(); 42 | conflux.getTransactionReceipt.mockResolvedValue({ outcomeStatus: 1 }); 43 | 44 | const pending = conflux.sendRawTransaction('0x'); 45 | 46 | await expect(pending.confirmed()).rejects.toThrow('executed failed, outcomeStatus 1'); 47 | }); 48 | 49 | test('PendingTransaction catch', async () => { 50 | const call = jest.spyOn(conflux.provider, 'call'); 51 | 52 | const hash = await conflux.sendRawTransaction('0x').catch(v => v); 53 | expect(hash).toMatch(/0x.{64}/); 54 | 55 | call.mockRejectedValueOnce(new Error('XXX')); 56 | const e = await conflux.sendRawTransaction('0x').catch(v => v); 57 | expect(e.message).toEqual('XXX'); 58 | 59 | call.mockRestore(); 60 | }); 61 | 62 | test('PendingTransaction finally', async () => { 63 | const call = jest.spyOn(conflux.provider, 'call'); 64 | let called; 65 | 66 | called = false; 67 | await conflux.sendRawTransaction('0x').finally(() => { 68 | called = true; 69 | }); 70 | expect(called).toEqual(true); 71 | 72 | called = false; 73 | call.mockRejectedValueOnce(new Error('XXX')); 74 | await expect( 75 | conflux.sendRawTransaction('0x').finally(() => { 76 | called = true; 77 | }), 78 | ).rejects.toThrow('XXX'); 79 | expect(called).toEqual(true); 80 | 81 | call.mockRestore(); 82 | }); 83 | -------------------------------------------------------------------------------- /test/util/address.test.js: -------------------------------------------------------------------------------- 1 | const { address } = require('../../src'); 2 | 3 | const MAINNET_ADDRESS = 'cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91'; 4 | const TESTNET_ADDRESS = 'cfxtest:aak2rra2njvd77ezwjvx04kkds9fzagfe6d5r8e957'; 5 | const mappedAddress = '0x12Bf6283CcF8Ad6ffA63f7Da63EDc217228d839A'; 6 | 7 | test('shorten', () => { 8 | expect(address.shortenCfxAddress(MAINNET_ADDRESS)).toEqual('cfx:aak...ku8scz91'); 9 | expect(address.shortenCfxAddress(MAINNET_ADDRESS, true)).toEqual('cfx:aak...cz91'); 10 | expect(address.shortenCfxAddress(TESTNET_ADDRESS)).toEqual('cfxtest:aak...e957'); 11 | expect(address.shortenCfxAddress(TESTNET_ADDRESS, true)).toEqual('cfxtest:aak...e957'); 12 | }); 13 | 14 | test('cfxMappedEVMSpaceAddress', () => { 15 | expect(address.cfxMappedEVMSpaceAddress(MAINNET_ADDRESS)).toEqual(mappedAddress); 16 | expect(address.cfxMappedEVMSpaceAddress(TESTNET_ADDRESS)).toEqual(mappedAddress); 17 | }); 18 | -------------------------------------------------------------------------------- /test/util/index.test.js: -------------------------------------------------------------------------------- 1 | const { alignBuffer, awaitTimeout } = require('../../src/util'); 2 | const format = require('../../src/util/format'); 3 | 4 | function sleep(ms) { 5 | return new Promise(resolve => setTimeout(resolve, ms)); 6 | } 7 | 8 | test('alignBuffer', () => { 9 | const buffer0 = alignBuffer(Buffer.alloc(0)); 10 | expect(buffer0.length).toEqual(0); 11 | 12 | const buffer20 = alignBuffer(format.hexBuffer('0x0123456789012345678901234567890123456789')); 13 | expect(buffer20.length).toEqual(32); 14 | expect(format.hex(buffer20)).toEqual('0x0000000000000000000000000123456789012345678901234567890123456789'); 15 | 16 | const buffer32 = alignBuffer(format.hexBuffer('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')); 17 | expect(buffer32.length).toEqual(32); 18 | expect(format.hex(buffer32)).toEqual('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'); 19 | 20 | const buffer33 = alignBuffer(format.hexBuffer('0xff0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')); 21 | expect(buffer33.length).toEqual(32 * 2); 22 | expect(format.hex(buffer33)).toEqual('0x' + 23 | '00000000000000000000000000000000000000000000000000000000000000ff' + 24 | '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 25 | ); 26 | }); 27 | 28 | test('awaitTimeout', async () => { 29 | const result = await awaitTimeout(sleep(100), 1000); 30 | expect(result).toEqual(undefined); 31 | 32 | await expect(awaitTimeout(sleep(100), 10)).rejects.toThrow('Timeout'); 33 | }); 34 | -------------------------------------------------------------------------------- /test/util/rlp.test.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const { format } = require('../../src'); 3 | const { encode } = require('../../src/util/rlp'); 4 | 5 | test('zero', () => { 6 | const value = format.hexBuffer(0); 7 | expect(format.hex(encode(value))).toEqual('0x80'); 8 | }); 9 | 10 | test('number', () => { 11 | const value = format.hexBuffer(1); 12 | expect(format.hex(encode(value))).toEqual('0x01'); 13 | }); 14 | 15 | test('big endian', () => { 16 | const value = format.hexBuffer(1024); 17 | expect(format.hex(encode(value))).toEqual('0x820400'); 18 | }); 19 | 20 | test('empty buffer', () => { 21 | const value = Buffer.from(''); 22 | expect(format.hex(encode(value))).toEqual('0x80'); 23 | }); 24 | 25 | test('short buffer', () => { 26 | const value = Buffer.from('dog'); 27 | expect(format.hex(encode(value))).toEqual('0x83646f67'); 28 | }); 29 | 30 | test('long buffer', () => { 31 | const value = Buffer.from(lodash.range(1024).map(() => Buffer.from(''))); 32 | expect(format.hex(encode(value).slice(0, 3))).toEqual('0xb90400'); 33 | }); 34 | 35 | test('empty array', () => { 36 | const value = []; 37 | expect(format.hex(encode(value))).toEqual('0xc0'); 38 | }); 39 | 40 | test('short array', () => { 41 | const value = [Buffer.from('cat'), Buffer.from([0]), Buffer.from('dog')]; 42 | expect(format.hex(encode(value))).toEqual('0xc9836361748083646f67'); 43 | }); 44 | 45 | test('long array', () => { 46 | const value = lodash.range(1024).map(() => []); 47 | expect(format.hex(encode(value).slice(0, 3))).toEqual('0xf90400'); 48 | }); 49 | 50 | test('nested array', () => { 51 | const value = [[], [[]], [[], [[]]]]; 52 | expect(format.hex(encode(value))).toEqual('0xc7c0c1c0c3c0c1c0'); 53 | }); 54 | -------------------------------------------------------------------------------- /test/util/sign.test.js: -------------------------------------------------------------------------------- 1 | const lodash = require('lodash'); 2 | const { format, sign, CONST } = require('../../src'); 3 | 4 | const { 5 | checksumAddress, 6 | randomBuffer, 7 | randomPrivateKey, 8 | 9 | privateKeyToPublicKey, 10 | publicKeyToAddress, 11 | privateKeyToAddress, 12 | 13 | ecdsaSign, 14 | ecdsaRecover, 15 | encrypt, 16 | decrypt, 17 | } = sign; 18 | 19 | const KEY = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; 20 | const PUBLIC = '0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559'; 21 | const ADDRESS = 'cfxtest:aasm4c231py7j34fghntcfkdt2nm9xv1tu6jd3r1s7'; 22 | const PASSWORD = 'password'; 23 | 24 | test('checksumAddress', () => { 25 | expect(checksumAddress('0XFB6916095CA1DF60BB79CE92CE3EA74C37C5D359')) 26 | .toEqual('0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359'); 27 | 28 | expect(checksumAddress('0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359')) 29 | .toEqual('0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359'); 30 | }); 31 | 32 | test('randomBuffer', () => { 33 | const buffer1 = randomBuffer(32); 34 | const buffer2 = randomBuffer(32); 35 | 36 | expect(buffer1.length).toEqual(32); 37 | expect(format.hex(buffer1).length).toEqual(2 + 64); 38 | expect(buffer1.equals(buffer2)).toEqual(false); // almost impossible 39 | }); 40 | 41 | test('randomPrivateKey', () => { 42 | const key1 = format.privateKey(randomPrivateKey()); 43 | const key2 = format.privateKey(randomPrivateKey()); 44 | expect(key1).not.toEqual(key2); // almost impossible 45 | 46 | const entropy = format.hexBuffer('0x0123456789012345678901234567890123456789012345678901234567890123'); 47 | const key3 = format.privateKey(randomPrivateKey(entropy)); 48 | const key4 = format.privateKey(randomPrivateKey(entropy)); 49 | expect(key3).not.toEqual(key4); // almost impossible 50 | 51 | const entropyInvalid = format.hexBuffer('0x0123456789'); 52 | expect(() => randomPrivateKey(entropyInvalid)).toThrow('entropy must be 32 length Buffer'); 53 | }); 54 | 55 | test('privateKeyToPublicKey', () => { 56 | const publicKey = format.publicKey(privateKeyToPublicKey(format.hexBuffer(KEY))); 57 | expect(publicKey).toEqual(PUBLIC); 58 | }); 59 | 60 | test('publicKeyToAddress', () => { 61 | const address = format.address(publicKeyToAddress(format.hexBuffer(PUBLIC)), CONST.TESTNET_ID); 62 | expect(address).toEqual(ADDRESS); 63 | }); 64 | 65 | test('privateKeyToAddress', () => { 66 | const address = format.address(privateKeyToAddress(format.hexBuffer(KEY)), CONST.TESTNET_ID); 67 | expect(address).toEqual(ADDRESS); 68 | }); 69 | 70 | test('encrypt and decrypt', () => { 71 | const keystore = encrypt(format.hexBuffer(KEY), PASSWORD); 72 | 73 | expect(keystore.version).toEqual(3); 74 | expect(lodash.isString(keystore.id)).toEqual(true); 75 | expect(/^[0-9a-f]{40}$/.test(keystore.address)).toEqual(true); 76 | expect(lodash.isPlainObject(keystore.crypto)).toEqual(true); 77 | expect(/^[0-9a-f]{64}$/.test(keystore.crypto.ciphertext)).toEqual(true); 78 | 79 | expect(lodash.isPlainObject(keystore.crypto.cipherparams)).toEqual(true); 80 | expect(/^[0-9a-f]{32}$/.test(keystore.crypto.cipherparams.iv)).toEqual(true); 81 | expect(keystore.crypto.cipher).toEqual('aes-128-ctr'); 82 | expect(keystore.crypto.kdf).toEqual('scrypt'); 83 | expect(lodash.isPlainObject(keystore.crypto.kdfparams)).toEqual(true); 84 | expect(keystore.crypto.kdfparams.dklen).toEqual(32); 85 | expect(/^[0-9a-f]{64}$/.test(keystore.crypto.kdfparams.salt)).toEqual(true); 86 | expect(keystore.crypto.kdfparams.n).toEqual(8192); 87 | expect(keystore.crypto.kdfparams.r).toEqual(8); 88 | expect(keystore.crypto.kdfparams.p).toEqual(1); 89 | expect(/^[0-9a-f]{64}$/.test(keystore.crypto.mac)).toEqual(true); 90 | 91 | const key = format.hex(decrypt(keystore, PASSWORD)); 92 | expect(key).toEqual(KEY); 93 | 94 | expect(() => decrypt(keystore, 'WRONG_PASSWORD')).toThrow('Key derivation failed, possibly wrong password!'); 95 | }); 96 | 97 | test('ecdsaSign and ecdsaRecover', () => { 98 | const hash = randomBuffer(32); 99 | const { r, s, v } = ecdsaSign(hash, format.hexBuffer(KEY)); 100 | 101 | expect(r.length).toEqual(32); 102 | expect(s.length).toEqual(32); 103 | expect(Number.isInteger(v)).toEqual(true); 104 | 105 | const publicKey = ecdsaRecover(hash, { r, s, v }); 106 | const address = format.address(publicKeyToAddress(publicKey), CONST.TESTNET_ID); 107 | expect(address).toEqual(ADDRESS); 108 | }); 109 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Change this to match your project 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | // Tells TypeScript to read JS files, as 6 | // normally they are ignored as source files 7 | "allowJs": true, 8 | // Generate d.ts files 9 | "declaration": true, 10 | // This compiler run should 11 | // only output d.ts files 12 | "emitDeclarationOnly": true, 13 | // Types should go into this directory. 14 | // Removing this would place the .d.ts files 15 | // next to the .js files 16 | "outDir": "dist/types", 17 | // go to js file when using IDE functions like 18 | // "Go to Definition" in VSCode 19 | "declarationMap": true, 20 | } 21 | } --------------------------------------------------------------------------------