diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index d538568d..18728393 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,5 +1,17 @@ # change log +## v1.16.0 +This version is corresponding to conflux-rust v1.1.3, check it's [changelog](https://github.com/Conflux-Chain/conflux-rust/blob/master/changelogs/CHANGELOG-1.1.x.md#113) for detail info. + +* `format.address` will respect `networkId`, `verbose` flag even if the first parameter is an CIP37 address. +* Add support for standard token contract through `Conflux.CRC20` +* `cfx_getLogs` filter option add one more field `offset` +* Add one RPC method `cfx_getAccountPendingInfo` to get account's transaction pending info +* `epochs` pubsub now accept one parameter `subscription_epoch` the supported values are `latest_mined` (default) and `latest_state` +* Include `blockHash`, `epochHash`, `epochNumber`, `transactionHash`, and `transactionPosition` for trace RPCs +* When abi encoding `bytes-N` type, if the data's length is not enough, will auto pad (right) to `N` + + ## v1.5.13 * `getStatus` method rethurn three new fields `latestState`, `latestConfirmed`, `latestCheckpoint` diff --git a/example/2_send_transaction.js b/example/2_send_transaction.js index 0c26006c..5fb94db8 100644 --- a/example/2_send_transaction.js +++ b/example/2_send_transaction.js @@ -7,7 +7,7 @@ const conflux = new Conflux({ // logger: console, // use console to print log }); -const accountAlice = conflux.wallet.addPrivateKey('0xa816a06117e572ca7ae2f786a046d2bc478051d0717bf5cc4f5397923258d393'); +const accountAlice = conflux.wallet.addPrivateKey('0xa816a06117e572ca7ae2f786a046d2bc478051d0717bf5cc4f5397923258d393', 1); const addressBob = 'cfxtest:aatm5bvugvjwdyp86ruecmhf5vmng5ysy2pehzpz9h'; /* diff --git a/example/wrapProvider/work-with-portal.html b/example/wrapProvider/work-with-portal.html new file mode 100644 index 00000000..3c04a3ba --- /dev/null +++ b/example/wrapProvider/work-with-portal.html @@ -0,0 +1,58 @@ + + + Test sign + + + + + +

Account:

+ + +

+ + + + \ No newline at end of file diff --git a/package.json b/package.json index d3e49991..09b2719c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "js-conflux-sdk", "description": "JavaScript Conflux Software Development Kit", - "version": "1.5.15", + "version": "1.6.0", "license": "LGPL-3.0", "author": "Haizhou@conflux-chain.org", "repository": "https://github.com/Conflux-Chain/js-conflux-sdk.git", diff --git a/src/Conflux.js b/src/Conflux.js index 591363f2..9697350e 100644 --- a/src/Conflux.js +++ b/src/Conflux.js @@ -5,6 +5,7 @@ const providerFactory = require('./provider'); const Wallet = require('./wallet'); const Contract = require('./contract'); const internalContract = require('./contract/internal'); +const { CRC20_ABI } = require('./contract/standard'); const PendingTransaction = require('./subscribe/PendingTransaction'); const Subscription = require('./subscribe/Subscription'); const pkg = require('../package.json'); @@ -180,6 +181,16 @@ class Conflux { return this.Contract(options); } + /** + * Create an token CRC20 contract with standard CRC20 abi + * + * @param address {string} + * @returns {Contract} - A token contract instance + */ + CRC20(address) { + return this.Contract({ address, abi: CRC20_ABI }); + } + /** * close connection. * @@ -1047,6 +1058,24 @@ class Conflux { return format.sponsorInfo(result); } + /** + * Return pending info of an account + * + * @param address {string} - Address to account + * @returns {Promise} An account pending info object. + * - localNonce `BigInt`: then next nonce can use in the transaction pool + * - nextPendingTx `string`: the hash of next pending transaction + * - pendingCount `BigInt`: the count of pending transactions + * - pendingNonce `BigInt`: the nonce of pending transaction + * + */ + async getAccountPendingInfo(address) { + const result = await this.provider.call('cfx_getAccountPendingInfo', + this._formatAddress(address), + ); + return format.accountPendingInfo(result); + } + /** * Returns the size of the collateral storage of given address, in Byte. * @@ -1292,6 +1321,8 @@ class Conflux { * If you see the same epoch twice, this suggests a pivot chain reorg has happened (this might happen for recent epochs). * For each epoch, the last hash in epochHashesOrdered is the hash of the pivot block. * + * @param [sub_epoch] {string} Available values are latest_mined(default value) and latest_state + * * @return {Promise} EventEmitter instance with the follow events: * - 'data': * - epochNumber `number`: epoch number @@ -1316,8 +1347,8 @@ class Conflux { ] } */ - async subscribeEpochs() { - const id = await this.subscribe('epochs'); + async subscribeEpochs(sub_epoch = CONST.EPOCH_NUMBER.LATEST_MINED) { + const id = await this.subscribe('epochs', sub_epoch); const subscription = new Subscription(id); this.provider.on(id, data => { diff --git a/src/Drip.js b/src/Drip.js index a3afbb3b..67f12833 100644 --- a/src/Drip.js +++ b/src/Drip.js @@ -41,9 +41,9 @@ class Drip extends String { * @return {Drip} * * @example - * > Drip(1.00) + * > new Drip(1.00) [String (Drip): '1'] - * > Drip('0xab') + * > new Drip('0xab') [String (Drip): '171'] */ constructor(value) { diff --git a/src/contract/abi/BytesCoder.js b/src/contract/abi/BytesCoder.js index 771e042c..66be4dae 100644 --- a/src/contract/abi/BytesCoder.js +++ b/src/contract/abi/BytesCoder.js @@ -42,13 +42,18 @@ class BytesCoder extends BaseCoder { encode(value) { value = format.bytes(value); - if (this.size !== undefined) { - assert(value.length === this.size, { - message: 'length not match', - expect: this.size, - got: value.length, - coder: this, - }); + if (this.size !== undefined && this.size !== value.length) { + if (value.length < this.size) { + // if short than the expect size, auto complete it + value = Buffer.concat([value, Buffer.alloc(this.size - value.length)]); + } else { + assert(false, { + message: 'length not match', + expect: this.size, + got: value.length, + coder: this, + }); + } } let buffer = alignBuffer(value, true); diff --git a/src/contract/standard/crc20.json b/src/contract/standard/crc20.json new file mode 100644 index 00000000..a1175e8a --- /dev/null +++ b/src/contract/standard/crc20.json @@ -0,0 +1,290 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/src/contract/standard/index.js b/src/contract/standard/index.js new file mode 100644 index 00000000..192a6c37 --- /dev/null +++ b/src/contract/standard/index.js @@ -0,0 +1,5 @@ +const CRC20_ABI = require('./crc20.json').abi; + +module.exports = { + CRC20_ABI, +}; diff --git a/src/util/address.js b/src/util/address.js index dc6b0fa5..34f28933 100644 --- a/src/util/address.js +++ b/src/util/address.js @@ -76,8 +76,7 @@ function hasNetworkPrefix(address) { if (!lodash.isString(address)) { return false; } - address = address.toLowerCase(); - const parts = address.split(':'); + const parts = address.toLowerCase().split(':'); if (parts.length !== 2 && parts.length !== 3) { return false; } diff --git a/src/util/format.js b/src/util/format.js index 0a56ada1..86198174 100644 --- a/src/util/format.js +++ b/src/util/format.js @@ -234,21 +234,23 @@ format.hex = format(toHex); format.hex40 = format.hex.$validate(v => v.length === 2 + 40, 'hex40'); function toAddress(address, networkId, verbose = false) { - // convert Account instance to string + // if is an (Account) object, convert it to string (address) if (lodash.isObject(address) && addressUtil.hasNetworkPrefix(address.toString())) { address = address.toString(); } - if (lodash.isString(address) && addressUtil.isValidCfxAddress(address)) { - return address; + if (lodash.isString(address) && addressUtil.hasNetworkPrefix(address)) { + const _decodedAddress = addressUtil.decodeCfxAddress(address); + address = _decodedAddress.hexAddress; + networkId = networkId || _decodedAddress.netId; } - const buffer = format.hexBuffer(address); - if ((lodash.isString(address) && address.length !== 2 + 40) || buffer.length !== 20) { + address = format.hexBuffer(address); + if (address.length !== 20) { throw new Error('not match "hex40"'); } if (!networkId) { throw new Error('expected parameter: networkId'); } - return addressUtil.encodeCfxAddress(buffer, networkId, verbose); + return addressUtil.encodeCfxAddress(address, networkId, verbose); } /** @@ -392,6 +394,8 @@ format.publicKey = format.hex.$validate(v => v.length === 2 + 128, 'publicKey'); format.hexBuffer = format.hex.$after(v => Buffer.from(v.substr(2), 'hex')); /** + * If pass an string it will decode with ASCII encoding + * * @param arg {string|Buffer|array} * @return {Buffer} * @@ -439,6 +443,7 @@ format.keccak256 = format.bytes.$after(sign.keccak256).$after(format.hex); // -------------------------- format method arguments ------------------------- format.getLogs = format({ limit: format.bigUIntHex, + offset: format.bigUIntHex, fromEpoch: format.epochNumber, toEpoch: format.epochNumber, blockHashes: format.blockHash.$or([format.blockHash]), @@ -451,6 +456,7 @@ format.getLogsAdvance = function (networkId, toHexAddress = false) { const fromatAddress = toHexAddress ? format.hexAddress : format.netAddress(networkId); return format({ limit: format.bigUIntHex, + offset: format.bigUIntHex, fromEpoch: format.epochNumber, toEpoch: format.epochNumber, blockHashes: format.blockHash.$or([format.blockHash]), @@ -632,20 +638,38 @@ format.epoch = format({ // ---------------------------- trace formater ------------------------- format.action = format({ action: { - gas: format.bigUInt, + from: format.any, + to: format.any, value: format.bigUInt, + gas: format.bigUInt, gasLeft: format.bigUInt, + input: format.hex, + init: format.hex, + returnData: format.hex, + callType: format.any, + outcome: format.uInt, + addr: format.any, }, -}); + epochNumber: format.bigUInt, + epochHash: format.hex, + blockHash: format.hex, + transactionHash: format.hex, + transactionPosition: format.bigUInt, + type: format.any, +}, { pick: true }); +// only used in block traces format.txTraces = format({ traces: [format.action], + transactionPosition: format.bigUInt, }); format.blockTraces = format({ transactionTraces: [format.txTraces], + epochNumber: format.bigUInt, }).$or(null); +// trace array format.traces = format([format.action]).$or(null); format.traceFilter = format({ @@ -657,4 +681,10 @@ format.traceFilter = format({ actionTypes: format([format.any]).$or(null), }); +format.accountPendingInfo = format({ + localNonce: format.bigUInt, + pendingCount: format.bigUInt, + pendingNonce: format.bigUInt, +}); + module.exports = format; diff --git a/test/conflux/before.test.js b/test/conflux/before.test.js index cb4b00e1..deacf857 100644 --- a/test/conflux/before.test.js +++ b/test/conflux/before.test.js @@ -437,7 +437,7 @@ test('subscribeEpochs', async () => { const call = jest.spyOn(conflux.provider, 'call'); await conflux.subscribeEpochs(); - expect(call).toHaveBeenLastCalledWith('cfx_subscribe', 'epochs'); + expect(call).toHaveBeenLastCalledWith('cfx_subscribe', 'epochs', 'latest_mined'); call.mockRestore(); }); diff --git a/test/contract/valueCoder.test.js b/test/contract/valueCoder.test.js index 2a32db0c..1400fcb3 100644 --- a/test/contract/valueCoder.test.js +++ b/test/contract/valueCoder.test.js @@ -160,7 +160,7 @@ describe('bytes', () => { ); testDecode(coder, Buffer.from([0, 1, 2, 3]), '0x00010203'); - expect(() => coder.encode(Buffer.from([0, 1, 2]))).toThrow('length not match'); + // expect(() => coder.encode(Buffer.from([0, 1, 2]))).toThrow('length not match'); }); test('bytes', () => { diff --git a/test/util/format.test.js b/test/util/format.test.js index 92df7b9f..25ff30d2 100644 --- a/test/util/format.test.js +++ b/test/util/format.test.js @@ -239,6 +239,5 @@ test('address', () => { expect(format.address(Buffer.from('0123456789012345678901234567890123456789', 'hex'), 1)).toEqual('cfxtest:aaawgvnhveawgvnhveawgvnhveawgvnhvey1umfzwp'); expect(() => format.address('0x0123456789012345678')).toThrow('not match "hex40"'); - expect(() => format.address('cfx:123')).toThrow('not match "hex"'); expect(() => format.address('cfx123')).toThrow('not match "hex"'); });