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
+
+
+
+
+ Enable Portal
+ Account:
+
+
+
+ SendTx with js-conflux-sdk
+
+
+
\ 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"');
});