From a5a6aac936b56c256fbecbd5254534dd4e6ad7da Mon Sep 17 00:00:00 2001 From: Akash Gianchandani Date: Wed, 20 Dec 2023 18:45:03 +0530 Subject: [PATCH] feat(abstract-eth): add chain id as network identifier WIN-1446 TICKET: WIN-1446 --- modules/abstract-eth/package.json | 5 +- .../src/abstractEthLikeNewCoins.ts | 15 ++- .../abstract-eth/src/lib/transferBuilder.ts | 13 +-- .../baseNFTTransferBuilder.ts | 7 +- .../transferBuilderERC1155.ts | 7 ++ .../transferBuilders/transferBuilderERC721.ts | 7 ++ .../abstract-eth/test/unit/transferBuilder.ts | 80 +++++++++++++ .../src/lib/transactionBuilder.ts | 3 +- .../src/lib/transferBuilder.ts | 24 +--- modules/sdk-coin-arbeth/test/resources.ts | 21 ++++ modules/sdk-coin-arbeth/test/unit/arbeth.ts | 6 +- .../test/unit/transferBuilder.ts | 107 ++++++++++++++++++ modules/sdk-coin-celo/test/resources/celo.ts | 15 +++ .../test/unit/transferBuilder.ts | 68 +++++++++++ .../src/lib/transactionBuilder.ts | 3 +- modules/sdk-coin-eth/test/resources/eth.ts | 12 +- .../test/unit/transactionBuilder/send.ts | 79 ++++++++++++- .../sdk-coin-eth/test/unit/transferBuilder.ts | 104 +---------------- .../src/lib/transactionBuilder.ts | 1 + .../sdk-coin-opeth/src/lib/transferBuilder.ts | 9 +- modules/sdk-coin-opeth/test/resources.ts | 21 ++++ modules/sdk-coin-opeth/test/unit/opeth.ts | 6 +- .../test/unit/transferBuilder.ts | 107 ++++++++++++++++++ .../src/lib/transactionBuilder.ts | 1 + modules/sdk-coin-rbtc/package.json | 3 +- .../test/unit/transactionBuilder/send.ts | 81 +------------ .../sdk-coin-zketh/src/lib/transferBuilder.ts | 24 +--- modules/statics/src/networks.ts | 28 +++-- 28 files changed, 581 insertions(+), 276 deletions(-) create mode 100644 modules/abstract-eth/test/unit/transferBuilder.ts create mode 100644 modules/sdk-coin-arbeth/test/unit/transferBuilder.ts create mode 100644 modules/sdk-coin-celo/test/unit/transferBuilder.ts create mode 100644 modules/sdk-coin-opeth/test/unit/transferBuilder.ts diff --git a/modules/abstract-eth/package.json b/modules/abstract-eth/package.json index 401290fff2..9f9954681d 100644 --- a/modules/abstract-eth/package.json +++ b/modules/abstract-eth/package.json @@ -10,7 +10,10 @@ "check-fmt": "prettier --check .", "clean": "rm -r ./dist", "lint": "eslint --quiet .", - "prepare": "npm run build" + "prepare": "npm run build", + "test": "npm run coverage", + "coverage": "nyc -- npm run unit-test", + "unit-test": "mocha" }, "author": "BitGo SDK Team ", "license": "MIT", diff --git a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts index 9f6e5f0f89..06ff2cc540 100644 --- a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts +++ b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts @@ -1003,7 +1003,10 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { } const txBuilder = this.getTransactionBuilder(); txBuilder.from(params.txPrebuild.txHex); - txBuilder.transfer().key(new KeyPairLib({ prv: params.prv }).getKeys().prv!); + txBuilder + .transfer() + .coin(this.staticsCoin?.name as string) + .key(new KeyPairLib({ prv: params.prv }).getKeys().prv!); const transaction = await txBuilder.build(); const recipients = transaction.outputs.map((output) => ({ address: output.address, amount: output.value })); @@ -1415,6 +1418,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { }); const transferBuilder = txBuilder.transfer() as TransferBuilder; transferBuilder + .coin(this.staticsCoin?.name as string) .amount(recipients[0].amount) .contractSequenceId(sequenceId) .expirationTime(this.getDefaultExpireTime()) @@ -1440,7 +1444,10 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { return response; } - txBuilder.transfer().key(new KeyPairLib({ prv: userKey }).getKeys().prv as string); + txBuilder + .transfer() + .coin(this.staticsCoin?.name as string) + .key(new KeyPairLib({ prv: userKey }).getKeys().prv as string); txBuilder.sign({ key: backupSigningKey }); const signedTx = await txBuilder.build(); @@ -1594,6 +1601,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { const transferBuilder = txBuilder.transfer() as TransferBuilder; transferBuilder + .coin(this.staticsCoin?.name as string) .amount(batchExecutionInfo.totalAmount) .contractSequenceId(sequenceId) .expirationTime(this.getDefaultExpireTime()) @@ -1601,7 +1609,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { .data(batchData); if (params.walletPassphrase) { - txBuilder.transfer().key(userSigningKey); + transferBuilder.key(userSigningKey); } const tx = await txBuilder.build(); @@ -1731,6 +1739,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { const token = getToken(params.tokenContractAddress as string, network as EthLikeNetwork)?.name as string; transferBuilder + .coin(token) .amount(txAmount) .contractSequenceId(sequenceId) .expirationTime(this.getDefaultExpireTime()) diff --git a/modules/abstract-eth/src/lib/transferBuilder.ts b/modules/abstract-eth/src/lib/transferBuilder.ts index a010fb7e5a..5a0e5d2df7 100644 --- a/modules/abstract-eth/src/lib/transferBuilder.ts +++ b/modules/abstract-eth/src/lib/transferBuilder.ts @@ -1,7 +1,7 @@ import * as ethUtil from 'ethereumjs-util'; import EthereumAbi from 'ethereumjs-abi'; import BN from 'bn.js'; -import { coins, BaseCoin, ContractAddressDefinedToken } from '@bitgo/statics'; +import { coins, BaseCoin, ContractAddressDefinedToken, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics'; import { BuildTransactionError, InvalidParameterValueError } from '@bitgo/sdk-core'; import { decodeTransferData, sendMultiSigData, sendMultiSigTokenData, isValidEthAddress, isValidAmount } from './utils'; @@ -30,15 +30,14 @@ export class TransferBuilder { } /** - * A method to set the ERC20 token to be transferred. + * A method to set the native coin or ERC20 token to be transferred. * This ERC20 token may not be compatible with the network. * - * @param {string} coin the ERC20 coin to be set + * @param {string} coin - the native coin or ERC20 token to be set * @returns {TransferBuilder} the transfer builder instance modified */ coin(coin: string): TransferBuilder { this._coin = coins.get(coin); - if (this._coin instanceof ContractAddressDefinedToken) { this._tokenContractAddress = this._coin.contractAddress.toString(); } @@ -171,7 +170,7 @@ export class TransferBuilder { * @returns the string prefix */ protected getTokenOperationHashPrefix(): string { - return 'ERC20'; + return (this._coin.network as EthLikeNetwork).tokenOperationHashPrefix ?? 'ERC20'; } /** @@ -180,7 +179,7 @@ export class TransferBuilder { * @returns the string prefix */ protected getNativeOperationHashPrefix(): string { - return 'ETHER'; + return (this._coin.network as EthLikeNetwork).nativeCoinOperationHashPrefix ?? 'ETHER'; } /** Return an expiration time, in seconds, set to one hour from now @@ -208,7 +207,7 @@ export class TransferBuilder { protected ethSignMsgHash(): string { const data = this.getOperationHash(); const keyBuffer = Buffer.from(ethUtil.padToEven(this._signKey), 'hex'); - if (keyBuffer.length != 32) { + if (keyBuffer.length !== 32) { throw new Error('private key length is invalid'); } const signatureInParts = ethUtil.ecsign( diff --git a/modules/abstract-eth/src/lib/transferBuilders/baseNFTTransferBuilder.ts b/modules/abstract-eth/src/lib/transferBuilders/baseNFTTransferBuilder.ts index 25227aa54f..55bee024de 100644 --- a/modules/abstract-eth/src/lib/transferBuilders/baseNFTTransferBuilder.ts +++ b/modules/abstract-eth/src/lib/transferBuilders/baseNFTTransferBuilder.ts @@ -1,6 +1,7 @@ import { InvalidParameterValueError, InvalidSignatureError } from '@bitgo/sdk-core'; import { isValidEthAddress } from '../utils'; import { joinSignature, solidityKeccak256, SigningKey } from 'ethers/lib/utils'; +import { BaseCoin } from '@bitgo/statics'; export abstract class BaseNFTTransferBuilder { protected readonly _EMPTY_HEX_VALUE = '0x'; @@ -13,6 +14,8 @@ export abstract class BaseNFTTransferBuilder { protected _signature: string; protected _data: string; protected _tokenContractAddress: string; + protected _coin: Readonly; + protected _nativeCoinOperationHashPrefix?: string; public abstract build(): string; @@ -85,7 +88,7 @@ export abstract class BaseNFTTransferBuilder { if (this._signKey) { this._signature = this.ethSignMsgHash(); } - if (this._signature == null) { + if (this._signature === null) { throw new InvalidSignatureError('Null signature value'); } return this._signature; @@ -97,7 +100,7 @@ export abstract class BaseNFTTransferBuilder { * @returns the string prefix */ protected getNativeOperationHashPrefix(): string { - return 'ETHER'; + return this._nativeCoinOperationHashPrefix ?? 'ETHER'; } /** diff --git a/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC1155.ts b/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC1155.ts index 27de84c5ba..c0e639903e 100644 --- a/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC1155.ts +++ b/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC1155.ts @@ -10,6 +10,7 @@ import { ERC1155SafeTransferTypeMethodId, } from '../walletUtil'; import { BaseNFTTransferBuilder } from './baseNFTTransferBuilder'; +import { coins, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics'; export class ERC1155TransferBuilder extends BaseNFTTransferBuilder { private _tokenIds: string[]; @@ -27,6 +28,12 @@ export class ERC1155TransferBuilder extends BaseNFTTransferBuilder { } } + coin(coin: string): ERC1155TransferBuilder { + this._coin = coins.get(coin); + this._nativeCoinOperationHashPrefix = (this._coin.network as EthLikeNetwork).nativeCoinOperationHashPrefix; + return this; + } + tokenContractAddress(address: string): ERC1155TransferBuilder { if (isValidEthAddress(address)) { this._tokenContractAddress = address; diff --git a/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC721.ts b/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC721.ts index 59fa3c714d..e89f1177cb 100644 --- a/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC721.ts +++ b/modules/abstract-eth/src/lib/transferBuilders/transferBuilderERC721.ts @@ -5,6 +5,7 @@ import { ContractCall } from '../contractCall'; import { decodeERC721TransferData, isValidEthAddress, sendMultiSigData } from '../utils'; import { BaseNFTTransferBuilder } from './baseNFTTransferBuilder'; import { ERC721SafeTransferTypeMethodId, ERC721SafeTransferTypes } from '../walletUtil'; +import { coins, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics'; export class ERC721TransferBuilder extends BaseNFTTransferBuilder { private _tokenId: string; @@ -18,6 +19,12 @@ export class ERC721TransferBuilder extends BaseNFTTransferBuilder { } } + coin(coin: string): ERC721TransferBuilder { + this._coin = coins.get(coin); + this._nativeCoinOperationHashPrefix = (this._coin.network as EthLikeNetwork).nativeCoinOperationHashPrefix; + return this; + } + tokenContractAddress(address: string): ERC721TransferBuilder { if (isValidEthAddress(address)) { this._tokenContractAddress = address; diff --git a/modules/abstract-eth/test/unit/transferBuilder.ts b/modules/abstract-eth/test/unit/transferBuilder.ts new file mode 100644 index 0000000000..d8be6eebd0 --- /dev/null +++ b/modules/abstract-eth/test/unit/transferBuilder.ts @@ -0,0 +1,80 @@ +import assert from 'assert'; +import should from 'should'; +import { KeyPair, TransferBuilder } from '../../src'; + +describe('Eth send multi sig builder', function () { + const toAddress = '0x7325A3F7d4f9E86AE62Cf742426078C3755730d5'; + const xprv = + 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2'; + const key = new KeyPair({ prv: xprv }).getKeys().prv as string; + const amount = '100000000000000000'; // equivalent to 0.1 ether + const ethLikeCoins = ['hteth', 'tarbeth', 'topeth', 'zketh']; + + describe('should fail', () => { + it('should fail if a coin does not exists in @bitgo/statics', () => { + should(() => { + new TransferBuilder().coin('inexistentcoin'); + }).throw(); + }); + + ethLikeCoins.forEach((coin) => { + it('should fail with an invalid key', () => { + const builder = new TransferBuilder() + .coin(coin) + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .key('invalidkey'); + should(() => { + builder.signAndBuild(); + }).throw('private key length is invalid'); + }); + }); + + it('should fail with an invalid sequence id', () => { + should(() => { + new TransferBuilder().contractSequenceId(-1); + }).throw('Invalid contract sequence id'); + }); + + it('should fail with an invalid destination address', () => { + should(() => { + new TransferBuilder().to('invalidaddress'); + }).throw('Invalid address'); + }); + + it('should fail with an invalid amount: text value', () => { + should(() => { + new TransferBuilder().amount('invalidamount'); + }).throw('Invalid amount'); + }); + + it('should fail with an invalid amount: negative value', () => { + should(() => { + new TransferBuilder().amount('-10'); + }).throw('Invalid amount'); + }); + + it('should fail with an invalid expiration time', () => { + should(() => { + new TransferBuilder().expirationTime(-1); + }).throw('Invalid expiration time'); + }); + + it('should fail if a sequenceId param is missing', () => { + const builder = new TransferBuilder().amount(amount).to(toAddress).key(key); + assert.throws(() => builder.signAndBuild()); + }); + + it('should fail if a destination param is missing', () => { + const builder = new TransferBuilder().amount(amount).contractSequenceId(2).key(key); + assert.throws(() => builder.signAndBuild()); + }); + + it('should fail if a amount param is missing', () => { + const builder = new TransferBuilder().to(toAddress).contractSequenceId(2).key(key); + assert.throws(() => builder.signAndBuild()); + }); + }); +}); diff --git a/modules/sdk-coin-arbeth/src/lib/transactionBuilder.ts b/modules/sdk-coin-arbeth/src/lib/transactionBuilder.ts index dbebbfa8aa..7a789bef54 100644 --- a/modules/sdk-coin-arbeth/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-arbeth/src/lib/transactionBuilder.ts @@ -1,8 +1,9 @@ import { BaseCoin as CoinConfig } from '@bitgo/statics'; import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core'; import { KeyPair, Transaction, TransactionBuilder as EthLikeTransactionBuilder } from '@bitgo/abstract-eth'; -import { getCommon } from './utils'; + import { TransferBuilder } from './transferBuilder'; +import { getCommon } from './utils'; import { walletSimpleByteCode } from './walletUtil'; export class TransactionBuilder extends EthLikeTransactionBuilder { diff --git a/modules/sdk-coin-arbeth/src/lib/transferBuilder.ts b/modules/sdk-coin-arbeth/src/lib/transferBuilder.ts index 3f216cc477..7447c0cf3d 100644 --- a/modules/sdk-coin-arbeth/src/lib/transferBuilder.ts +++ b/modules/sdk-coin-arbeth/src/lib/transferBuilder.ts @@ -1,23 +1 @@ -import { TransferBuilder as EthTransferBuilder } from '@bitgo/abstract-eth'; - -export class TransferBuilder extends EthTransferBuilder { - /** - * Get the prefix used in generating an operation hash for sending native coins - * See https://github.com/BitGo/eth-multisig-v4/blob/master/contracts/coins/ArbethWalletSimple.sol - * - * @returns the string prefix - */ - protected getNativeOperationHashPrefix(): string { - return 'ARBETH'; - } - - /** - * Get the prefix used in generating an operation hash for sending tokens - * See https://github.com/BitGo/eth-multisig-v4/blob/master/contracts/coins/ArbethWalletSimple.sol - * - * @returns the string prefix - */ - protected getTokenOperationHashPrefix(): string { - return 'ARBETH-ERC20'; - } -} +export { TransferBuilder } from '@bitgo/abstract-eth'; diff --git a/modules/sdk-coin-arbeth/test/resources.ts b/modules/sdk-coin-arbeth/test/resources.ts index f6c0c2314d..d070067a63 100644 --- a/modules/sdk-coin-arbeth/test/resources.ts +++ b/modules/sdk-coin-arbeth/test/resources.ts @@ -35,3 +35,24 @@ export const PRIVATE_KEY_2 = export const PUBLIC_KEY_2 = 'xpub661MyMwAqRbcFXrZT6GpipqTPdFdhYX5DFDGv5ws1iW8ViCWSTK3zVhVntpiKDjPqXrqP1hhQscJZhVpbmZt9qygxgPdmN9RAZVKqovPfLw'; + +export const SEND_FUNDS_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041d8decc5aa3e56cba81cb5405cdd187a01885fd118ca3add06fc10c471d86b94459fe0c0b7a906e2147f64d948616236241288d8bf775873692b9963803ac608c1c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_SEQUENCE_ZERO_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041cb8ce1d2f2e482a7c6e50ad9c7cc6df33f30a0ef6e6792100d20d08181b054d034eae3fc8a18e7d48344d193c14786fbc91483f395e6e89cbeb36e9ced6429291b00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_AMOUNT_ZERO_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041aac0763277264c9be1e79a7b4bf056f3011a545ca2ead9fe76aa6efaa80b35db1b9769bf44693896ca655eb845b8e796f71073096e00f4ab8eddf1c46b8d7e401c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_ARBETH_LINK_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000143e1dae4f018ff86051a01d44a1b49b13704056000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041c6f5ab6c29ea92fec02229e8f87981fdb0863bf465f3d4f08df81d66d12d771a588acaf9d2313704dfb120da5e50a3a6089abbe610fca825d1a1fc9f3287ab8a1c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_TOKEN_SEQUENCE_ZERO_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000143e1dae4f018ff86051a01d44a1b49b13704056000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041c92569648e4bb36f8890254998b71359992f6afa97bba96975297cff3272dc402bfae501804561ca3bed0f38057ca6cc896950c50a35322b6062a5f132c653711b00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_TOKEN_AMOUNT_ZERO_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000143e1dae4f018ff86051a01d44a1b49b13704056000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041f159de6649d213fe6b882444d525ac3f9a7393618d510506f250931a28f6c34b50bab2af7f77e893e281ab3d0f629190d70ab271e491b4b41fa75b4dbf97cd191c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_NO_KEY_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; diff --git a/modules/sdk-coin-arbeth/test/unit/arbeth.ts b/modules/sdk-coin-arbeth/test/unit/arbeth.ts index 0fb3487dcb..b17fe5dec0 100644 --- a/modules/sdk-coin-arbeth/test/unit/arbeth.ts +++ b/modules/sdk-coin-arbeth/test/unit/arbeth.ts @@ -172,7 +172,8 @@ describe('Arbitrum', function () { builder.counter(1); builder.type(TransactionType.Send); builder.contract(account_1.address); - builder.transfer().amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); + const transferBuilder = builder.transfer() as TransferBuilder; + transferBuilder.coin('tarbeth').amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); const unsignedTx = await builder.build(); const unsignedTxForBroadcasting = unsignedTx.toBroadcastFormat(); @@ -207,7 +208,8 @@ describe('Arbitrum', function () { builder.counter(1); builder.type(TransactionType.Send); builder.contract(account_1.address); - builder.transfer().amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); + const transferBuilder = builder.transfer() as TransferBuilder; + transferBuilder.coin('tarbeth').amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); const unsignedTx = await builder.build(); const unsignedTxForBroadcasting = unsignedTx.toBroadcastFormat(); diff --git a/modules/sdk-coin-arbeth/test/unit/transferBuilder.ts b/modules/sdk-coin-arbeth/test/unit/transferBuilder.ts new file mode 100644 index 0000000000..33504a48af --- /dev/null +++ b/modules/sdk-coin-arbeth/test/unit/transferBuilder.ts @@ -0,0 +1,107 @@ +import should from 'should'; +import { KeyPair, TransferBuilder } from '../../src'; + +import * as testData from '../resources'; + +describe('Eth send multi sig builder', function () { + const toAddress = '0x7325A3F7d4f9E86AE62Cf742426078C3755730d5'; + const xprv = + 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2'; + const key = new KeyPair({ prv: xprv }).getKeys().prv as string; + const amount = '100000000000000000'; // equivalent to 0.1 ether + + describe('should build', () => { + it('native coin transfer should succeed', async () => { + const builder = new TransferBuilder() + .coin('tarbeth') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .key(key) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_DATA); + }); + + it('native coin transfer with sequenceId zero should succeed', async () => { + const builder = new TransferBuilder() + .coin('tarbeth') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(0) + .key(key) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_SEQUENCE_ZERO_DATA); + }); + + it('native coin transfer with amount 0 should succeed', async () => { + const builder = new TransferBuilder() + .coin('tarbeth') + .expirationTime(1590078260) + .amount('0') + .to(toAddress) + .contractSequenceId(2) + .key(key) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_AMOUNT_ZERO_DATA); + }); + + it('ERC20 token transfer should succeed', async () => { + const builder = new TransferBuilder() + .coin('tarbeth:link') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_ARBETH_LINK_DATA); + }); + + it('erc20 transfer should succeed', async () => { + const builder = new TransferBuilder() + .coin('tarbeth:link') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(0) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_TOKEN_SEQUENCE_ZERO_DATA); + }); + + it('erc20 transfer with amount 0 should succeed', async () => { + const builder = new TransferBuilder() + .coin('tarbeth:link') + .expirationTime(1590078260) + .amount('0') + .to(toAddress) + .contractSequenceId(2) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_TOKEN_AMOUNT_ZERO_DATA); + }); + + it('should build without a signature set', () => { + const builder = new TransferBuilder() + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_NO_KEY_DATA); + }); + + it('should build from a non signed serialized data', () => { + const builder = new TransferBuilder(testData.SEND_FUNDS_NO_KEY_DATA); + builder.coin('tarbeth').key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_DATA); + }); + }); +}); diff --git a/modules/sdk-coin-celo/test/resources/celo.ts b/modules/sdk-coin-celo/test/resources/celo.ts index 23e0bd41d3..4dfb145df2 100644 --- a/modules/sdk-coin-celo/test/resources/celo.ts +++ b/modules/sdk-coin-celo/test/resources/celo.ts @@ -144,3 +144,18 @@ export const TEST_WALLET_CREATION = export const WALLET_CREATION_TX_CHECK_SIGNATURE_VALIDATION = '0xf916558203e08502540be400831e84808080808080b915fb60606040526000600160006101000a81548160ff021916908315150217905550341561002a57600080fd5b60405161155b38038061155b833981016040528080518201919050506003815114151561005657600080fd5b806000908051906020019061006c929190610073565b5050610140565b8280548282559060005260206000209081019282156100ec579160200282015b828111156100eb5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610093565b5b5090506100f991906100fd565b5090565b61013d91905b8082111561013957600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550600101610103565b5090565b90565b61140c8061014f6000396000f300606060405260043610610099576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dcd7a6c146101335780632079fb9a146101e95780632da034091461024c57806339125215146102a45780637df73e271461037e578063a0b7967b146103cf578063a68a76cc146103f8578063abe3219c1461044d578063fc0f392d1461047a575b6000341115610131577f6e89d517057028190560dd200cf6bf792842861353d1173761dfa362e1c133f03334600036604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200184815260200180602001828103825284848281815260200192508082843782019150509550505050505060405180910390a15b005b341561013e57600080fd5b6101e7600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803590602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061048f565b005b34156101f457600080fd5b61020a6004808035906020019091905050610668565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561025757600080fd5b6102a2600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506106a7565b005b34156102af57600080fd5b61037c600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001909190803590602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610773565b005b341561038957600080fd5b6103b5600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a72565b604051808215151515815260200191505060405180910390f35b34156103da57600080fd5b6103e2610b16565b6040518082815260200191505060405180910390f35b341561040357600080fd5b61040b610b70565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561045857600080fd5b610460610b95565b604051808215151515815260200191505060405180910390f35b341561048557600080fd5b61048d610ba8565b005b60008061049b33610a72565b15156104a657600080fd5b878787878760405180807f43454c4f2d455243323000000000000000000000000000000000000000000000815250600a018673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018581526020018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140183815260200182815260200195505050505050604051809103902091506105898883858888610c3b565b508590508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb89896000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b151561063857600080fd5b6102c65a03f1151561064957600080fd5b50505060405180519050151561065e57600080fd5b5050505050505050565b60008181548110151561067757fe5b90600052602060002090016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006106b233610a72565b15156106bd57600080fd5b8290508073ffffffffffffffffffffffffffffffffffffffff16633ef13367836040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b151561075a57600080fd5b6102c65a03f1151561076b57600080fd5b505050505050565b60008061077f33610a72565b151561078a57600080fd5b878787878760405180807f43454c4f000000000000000000000000000000000000000000000000000000008152506004018673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140185815260200184805190602001908083835b602083101515610833578051825260208201915060208101905060208303925061080e565b6001836020036101000a038019825116818451168082178552505050505050905001838152602001828152602001955050505050506040518091039020915061087f8883858888610c3b565b90508773ffffffffffffffffffffffffffffffffffffffff16878760405180828051906020019080838360005b838110156108c75780820151818401526020810190506108ac565b50505050905090810190601f1680156108f45780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876187965a03f192505050151561091857600080fd5b7f59bed9ab5d78073465dd642a9e3e76dfdb7d53bcae9d09df7d0b8f5234d5a8063382848b8b8b604051808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200185600019166000191681526020018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610a29578082015181840152602081019050610a0e565b50505050905090810190601f168015610a565780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15050505050505050565b600080600090505b600080549050811015610b0b578273ffffffffffffffffffffffffffffffffffffffff16600082815481101515610aad57fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610afe5760019150610b10565b8080600101915050610a7a565b600091505b50919050565b6000806000809150600090505b600a811015610b655781600282600a81101515610b3c57fe5b01541115610b5857600281600a81101515610b5357fe5b015491505b8080600101915050610b23565b600182019250505090565b6000610b7a610e90565b604051809103906000f0801515610b9057600080fd5b905090565b600160009054906101000a900460ff1681565b610bb133610a72565b1515610bbc57600080fd5b60018060006101000a81548160ff0219169083151502179055507f0909e8f76a4fd3e970f2eaef56c0ee6dfaf8b87c5b8d3f56ffce78e825a9115733604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1565b600080610c488686610ce6565b9050600160009054906101000a900460ff168015610c6c5750610c6a87610a72565b155b15610c7657600080fd5b42841015610c8357600080fd5b610c8c83610db9565b610c9581610a72565b1515610ca057600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610cd957600080fd5b8091505095945050505050565b60008060008060418551141515610cfc57600080fd5b602085015192506040850151915060ff6041860151169050601b8160ff161015610d2757601b810190505b600186828585604051600081526020016040526000604051602001526040518085600019166000191681526020018460ff1660ff16815260200183600019166000191681526020018260001916600019168152602001945050505050602060405160208103908084039060008661646e5a03f11515610da557600080fd5b505060206040510351935050505092915050565b600080610dc533610a72565b1515610dd057600080fd5b60009150600090505b600a811015610e385782600282600a81101515610df257fe5b01541415610dff57600080fd5b600282600a81101515610e0e57fe5b0154600282600a81101515610e1f57fe5b01541015610e2b578091505b8080600101915050610dd9565b600282600a81101515610e4757fe5b0154831015610e5557600080fd5b612710600283600a81101515610e6757fe5b015401831115610e7657600080fd5b82600283600a81101515610e8657fe5b0181905550505050565b60405161054080610ea18339019056006060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506104e28061005e6000396000f300606060405260043610610056576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168062821de3146101475780633ef133671461019c5780636b9f96ea146101d5575b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f1935050505015156100b757600080fd5b7f69b31548dea9b3b707b4dff357d326e3e9348b24e7a6080a218a6edeeec48f9b3334600036604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200184815260200180602001828103825284848281815260200192508082843782019150509550505050505060405180910390a1005b341561015257600080fd5b61015a6101ea565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101a757600080fd5b6101d3600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061020f565b005b34156101e057600080fd5b6101e861043c565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561026f57600080fd5b8392503091508273ffffffffffffffffffffffffffffffffffffffff166370a08231836000604051602001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b151561031857600080fd5b6102c65a03f1151561032957600080fd5b505050604051805190509050600081141561034357610436565b8273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16836000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b151561040f57600080fd5b6102c65a03f1151561042057600080fd5b50505060405180519050151561043557600080fd5b5b50505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f1935050505015156104b457600080fd5b5600a165627a7a7230582042193b8fa36bb211143819a525fe07f597a57ac51bda735499ecebe2eda648870029a165627a7a7230582011b3e786820bbf974fbf8802c6bfd0fb44ab2ec30ed9018b91aad7503cd214aa002900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000386fe4e3d2b6acce93cc13d06e92b00aa50f429c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000603e077acd3f01e81b95fb92ce42ff60dff3d4c783015e079f893ed93992be2e90d384f4ba4bd85c422902e894f8f53d8c783fa3ce02e99aa018d3045801b07fcde103cb130312586f9f9e4b8a290fe1dab19d18dd413f5e72'; + +export const SEND_TOKEN_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000874069fa1eb16d44d622f2e0ca25eea172369bc1000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000412313d5cef3e0c3d1272ca26b4d67042bdf024b41ec9b7a8eaa0ff2e8bc8f6bd03524e46cbffdac1c9b4a5ba0320b4fe0d0bae7a75abc73306181c3cd1df3d02c1b00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_TOKEN_SEQUENCE_ZERO_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000874069fa1eb16d44d622f2e0ca25eea172369bc1000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004168909fb57215a413fe2d095b5a444ae1789c9505a8fec840343e82c77b6e1c635efaba58a210f573bd275ca3263692e5733cd63c9a7172d7b3344d6dce4ddc4e1c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_TOKEN_AMOUNT_ZERO_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000874069fa1eb16d44d622f2e0ca25eea172369bc1000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000412611aad1a6dbb5bcb1eb17a516d9efe180237af70b13e0c55e8f75cfce0ec1356dc34cc1bf13d29fa1e25e4e583df56e7d0b0a965f25dc58b7c29aa2c363c26d1c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_NO_KEY_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000f194afdf50b03e69bd7d057c1aa9e10c9954e4c9000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000f194afdf50b03e69bd7d057c1aa9e10c9954e4c9000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004187678b6cc2461428c23eb546d3fcda754f362796aa93b4314e90cc0a62626bed2b12b7a7a277c2d57e06d39a1e4b712289711e7df5cffe7d93bb3fc02772fa6b1c00000000000000000000000000000000000000000000000000000000000000'; diff --git a/modules/sdk-coin-celo/test/unit/transferBuilder.ts b/modules/sdk-coin-celo/test/unit/transferBuilder.ts new file mode 100644 index 0000000000..79f41e0e59 --- /dev/null +++ b/modules/sdk-coin-celo/test/unit/transferBuilder.ts @@ -0,0 +1,68 @@ +import should from 'should'; +import { KeyPair, TransferBuilder } from '../../src'; +import * as testData from '../resources/celo'; + +describe('Celo send multi sig builder', function () { + const toAddress = '0x7325A3F7d4f9E86AE62Cf742426078C3755730d5'; + const xprv = + 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2'; + const key = new KeyPair({ prv: xprv }).getKeys().prv as string; + const amount = '100000000000000000'; // equivalent to 0.1 ether + + describe('should build', () => { + it('celo token transfer should succeed', async () => { + const builder = new TransferBuilder() + .coin('tcusd') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_TOKEN_DATA); + }); + + it('celo erc20 transfer with zero sequence id should succeed', async () => { + const builder = new TransferBuilder() + .coin('tcusd') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(0) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_TOKEN_SEQUENCE_ZERO_DATA); + }); + + it('celo erc20 transfer with amount 0 should succeed', async () => { + const builder = new TransferBuilder() + .coin('tcusd') + .expirationTime(1590078260) + .amount('0') + .to(toAddress) + .contractSequenceId(2) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_TOKEN_AMOUNT_ZERO_DATA); + }); + + it('should build without a signature set', () => { + const builder = new TransferBuilder() + .coin('tcelo') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_NO_KEY_DATA); + }); + + it('should build from a non signed serialized data', () => { + const builder = new TransferBuilder(testData.SEND_FUNDS_NO_KEY_DATA); + builder.key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_DATA); + }); + }); +}); diff --git a/modules/sdk-coin-eth/src/lib/transactionBuilder.ts b/modules/sdk-coin-eth/src/lib/transactionBuilder.ts index 3c79dd3ed4..56e80ce65a 100644 --- a/modules/sdk-coin-eth/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-eth/src/lib/transactionBuilder.ts @@ -9,7 +9,6 @@ import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core'; import { walletSimpleByteCode } from './walletUtil'; import { ERC721TransferBuilder, ERC1155TransferBuilder } from './transferBuilders'; - /** * Ethereum transaction builder. */ @@ -30,7 +29,7 @@ export class TransactionBuilder extends EthLikeTransactionBuilder { * Gets the transfer funds builder if exist, or creates a new one for this transaction and returns it * * @param [data] transfer data to initialize the transfer builder with, empty if none given - * @returns {TransferBuilder} the transfer builder + * @returns {TransferBuilder | ERC721TransferBuilder | ERC1155TransferBuilder} the transfer builder */ transfer(data?: string): TransferBuilder | ERC721TransferBuilder | ERC1155TransferBuilder { if ( diff --git a/modules/sdk-coin-eth/test/resources/eth.ts b/modules/sdk-coin-eth/test/resources/eth.ts index e71c6d0fb8..6f6395539e 100644 --- a/modules/sdk-coin-eth/test/resources/eth.ts +++ b/modules/sdk-coin-eth/test/resources/eth.ts @@ -88,18 +88,9 @@ export const SEND_FUNDS_AMOUNT_ZERO_DATA = export const SEND_FUNDS_NO_KEY_DATA = '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; -export const SEND_TOKEN_DATA = - '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000874069fa1eb16d44d622f2e0ca25eea172369bc1000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041bbcebed166404052b6ce8ef36231930191ee4dc6b0d4ab61b04870adae904cba48ee1d306d7aee3df0bc480c130f1e823fe6a7c5b3f8ea88a91101d0692bfcbf1b00000000000000000000000000000000000000000000000000000000000000'; - export const SEND_TERC_DATA = '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000945ac907cf021a6bcd07852bb3b8c087051706a9000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041c27c1fc860c7e9bd557166b1514e15ab604dc41ca7bfff9205a4e8bad66a815e7508e81372628f2ded2d4f3f40904be9ac705a8be7a898be4bae692efc4f40151b00000000000000000000000000000000000000000000000000000000000000'; -export const SEND_TOKEN_SEQUENCE_ZERO_DATA = - '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000874069fa1eb16d44d622f2e0ca25eea172369bc1000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000410421e183dfcf356e1d1b5432559ac7be4fa223c09cf869f0212c1521d6c0ed50146970bd042d9f5fc699e18ff8e8dc45826d11b74eaade9276016f75aaeed79b1b00000000000000000000000000000000000000000000000000000000000000'; - -export const SEND_TOKEN_AMOUNT_ZERO_DATA = - '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000874069fa1eb16d44d622f2e0ca25eea172369bc1000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041f9b703732ca4eab9f3de59245233fd37e6e09f85d515c7efc9b780544628da69675861979a655d03cb6ca0ea68fd045d31b51ed582c0887cc71147d13eeeba011c00000000000000000000000000000000000000000000000000000000000000'; - export const CONTRACT_TOKEN_CUSD_ADDRESS = '0xa561131a1C8aC25925FB848bCa45A74aF61e5A38'; export const SEND_TX_BROADCAST_LEGACY = @@ -137,3 +128,6 @@ export const RECOVERY_WALLET_DEPLOYMENT_TXHEX = export const SEND_FUNDS_METHOD_CALL = '0x2e7e33df000000000000000000000000326c977e6efc84e512bb9c30f76e30c160ed06fb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_TOKEN_TX_BROADCAST = + '0xf901aa02843b9aca0083b8a1a094df7decb1baa8f529f0c8982cbb4be5035719529980b901440dcd7a6c00000000000000000000000072c2c8e08bf91d755cd7d26b49a2ee3dc99de1b9000000000000000000000000000000000000000000000000000000000000006400000000000000000000000019f64674d8a5b4e652319f5e239efd3bc969a1fe000000000000000000000000000000000000000000000000000000005ec67e28000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004118e288f7cefa56b17e878f34566af7103c6fe860a923e5fb1f94d90f9580dacf43c6ba97727a713ed63ac4730bec7b71d9a99fb99d14866ac16756af44d9451a1c0000000000000000000000000000000000000000000000000000000000000078a036705c58646867be2dad8293e4905471892ff6a3086904a2934b4e8a83cb251fa04f1700ea096b7aa1c0c73218f2cc3f68ec1a4d497fd82a2bb589d9ee777417e2'; diff --git a/modules/sdk-coin-eth/test/unit/transactionBuilder/send.ts b/modules/sdk-coin-eth/test/unit/transactionBuilder/send.ts index 777aa93eec..70eb27f906 100644 --- a/modules/sdk-coin-eth/test/unit/transactionBuilder/send.ts +++ b/modules/sdk-coin-eth/test/unit/transactionBuilder/send.ts @@ -1,8 +1,11 @@ -import { TransactionType } from '@bitgo/sdk-core'; +import { TransactionType, BaseTransaction } from '@bitgo/sdk-core'; import { coins, EthereumNetwork } from '@bitgo/statics'; +import EthereumAbi from 'ethereumjs-abi'; import assert from 'assert'; import should from 'should'; -import { decodeTransferData, getCommon, Transaction, TransactionBuilder, TransferBuilder } from '../../../src'; +import * as ethUtil from 'ethereumjs-util'; + +import { decodeTransferData, getCommon, Transaction, TransactionBuilder, TransferBuilder, KeyPair } from '../../../src'; import * as testData from '../../resources/eth'; import { getBuilder } from '../getBuilder'; @@ -28,6 +31,23 @@ describe('Eth transaction builder send', () => { let key; let contractAddress; + const getOperationHash = function (tx: BaseTransaction): string { + const { data } = tx.toJson(); + const { tokenContractAddress, expireTime, sequenceId, amount, to } = decodeTransferData(data); + const operationParams = [ + ['string', 'address', 'uint', 'address', 'uint', 'uint'], + [ + 'ERC20', + new ethUtil.BN(ethUtil.stripHexPrefix(to), 16), + amount, + new ethUtil.BN(ethUtil.stripHexPrefix(tokenContractAddress || ''), 16), + expireTime, + sequenceId, + ], + ]; + return EthereumAbi.soliditySHA3(...operationParams); + }; + beforeEach(() => { contractAddress = '0x8f977e912ef500548a0c3be6ddde9899f1199b81'; txBuilder = getBuilder('teth') as TransactionBuilder; @@ -134,6 +154,61 @@ describe('Eth transaction builder send', () => { const tx = await txBuilder.build(); should.equal(tx.toJson().chainId, 17000); }); + + it('a send token transaction', async () => { + const recipient = '0x72c2c8e08bf91d755cd7d26b49a2ee3dc99de1b9'; + const contractAddress = '0xdf7decb1baa8f529f0c8982cbb4be50357195299'; + const amount = '100'; + txBuilder.contract(contractAddress); + txBuilder + .transfer() + .coin('trif') + .amount(amount) + .to(recipient) + .expirationTime(1590066728) + .contractSequenceId(5) + .key(key); + txBuilder.sign({ + key: testData.PRIVATE_KEY, + }); + const tx = await txBuilder.build(); + should.equal(tx.toBroadcastFormat(), testData.SEND_TOKEN_TX_BROADCAST); + should.equal(tx.signature.length, 2); + should.equal(tx.inputs.length, 1); + should.equal(tx.inputs[0].address, contractAddress); + should.equal(tx.inputs[0].value, amount); + should.equal(tx.inputs[0].coin, 'trif'); + + should.equal(tx.outputs.length, 1); + should.equal(tx.outputs[0].address, recipient); + should.equal(tx.outputs[0].value, amount); + should.equal(tx.outputs[0].coin, 'trif'); + + const { signature } = decodeTransferData(tx.toJson().data); + const operationHash = getOperationHash(tx); + + const { v, r, s } = ethUtil.fromRpcSig(signature); + const senderPubKey = ethUtil.ecrecover(Buffer.from(operationHash, 'hex'), v, r, s); + const senderAddress = ethUtil.pubToAddress(senderPubKey); + const senderKey = new KeyPair({ prv: testData.PRIVATE_KEY }); + ethUtil.bufferToHex(senderAddress).should.equal(senderKey.getAddress()); + }); + + it('a send token transactions from serialized', async () => { + const txBuilder = new TransactionBuilder(coins.get('teth')); + txBuilder.from(testData.SEND_TOKEN_TX_BROADCAST); + const tx = await txBuilder.build(); + should.equal(tx.toBroadcastFormat(), testData.SEND_TOKEN_TX_BROADCAST); + + const { signature } = decodeTransferData(tx.toJson().data); + const operationHash = getOperationHash(tx); + + const { v, r, s } = ethUtil.fromRpcSig(signature); + const senderPubKey = ethUtil.ecrecover(Buffer.from(operationHash || ''), v, r, s); + const senderAddress = ethUtil.pubToAddress(senderPubKey); + const senderKey = new KeyPair({ prv: testData.PRIVATE_KEY }); + ethUtil.bufferToHex(senderAddress).should.equal(senderKey.getAddress()); + }); }); describe('sign and build from serialized', () => { diff --git a/modules/sdk-coin-eth/test/unit/transferBuilder.ts b/modules/sdk-coin-eth/test/unit/transferBuilder.ts index 9e44013e7f..3fe7789b81 100644 --- a/modules/sdk-coin-eth/test/unit/transferBuilder.ts +++ b/modules/sdk-coin-eth/test/unit/transferBuilder.ts @@ -1,7 +1,6 @@ -import assert from 'assert'; import should from 'should'; -import { KeyPair, TransferBuilder } from '../../src'; +import { KeyPair, TransferBuilder } from '../../src'; import * as testData from '../resources/eth'; describe('Eth send multi sig builder', function () { @@ -61,18 +60,6 @@ describe('Eth send multi sig builder', function () { should.equal(result, testData.SEND_FUNDS_AMOUNT_ZERO_DATA); }); - it('celo token transfer should succeed', async () => { - const builder = new TransferBuilder() - .coin('tcusd') - .expirationTime(1590078260) - .amount(amount) - .to(toAddress) - .contractSequenceId(2) - .key(key); - const result = builder.signAndBuild(); - should.equal(result, testData.SEND_TOKEN_DATA); - }); - it('ERC20 token transfer should succeed', async () => { const builder = new TransferBuilder() .coin('terc') @@ -85,30 +72,6 @@ describe('Eth send multi sig builder', function () { should.equal(result, testData.SEND_TERC_DATA); }); - it('erc20 transfer should succeed', async () => { - const builder = new TransferBuilder() - .coin('tcusd') - .expirationTime(1590078260) - .amount(amount) - .to(toAddress) - .contractSequenceId(0) - .key(key); - const result = builder.signAndBuild(); - should.equal(result, testData.SEND_TOKEN_SEQUENCE_ZERO_DATA); - }); - - it('erc20 transfer with amount 0 should succeed', async () => { - const builder = new TransferBuilder() - .coin('tcusd') - .expirationTime(1590078260) - .amount('0') - .to(toAddress) - .contractSequenceId(2) - .key(key); - const result = builder.signAndBuild(); - should.equal(result, testData.SEND_TOKEN_AMOUNT_ZERO_DATA); - }); - it('should build without a signature set', () => { const builder = new TransferBuilder() .expirationTime(1590078260) @@ -127,69 +90,4 @@ describe('Eth send multi sig builder', function () { should.equal(result, testData.SEND_FUNDS_DATA); }); }); - - describe('should fail', () => { - it('should fail if a coin does not exists in @bitgo/statics', () => { - should(() => { - new TransferBuilder().coin('inexistentcoin'); - }).throw(); - }); - - it('should fail with an invalid key', () => { - const builder = new TransferBuilder() - .expirationTime(1590078260) - .amount(amount) - .to(toAddress) - .contractSequenceId(2) - .key('invalidkey'); - should(() => { - builder.signAndBuild(); - }).throw('private key length is invalid'); - }); - - it('should fail with an invalid sequence id', () => { - should(() => { - new TransferBuilder().contractSequenceId(-1); - }).throw('Invalid contract sequence id'); - }); - - it('should fail with an invalid destination address', () => { - should(() => { - new TransferBuilder().to('invalidaddress'); - }).throw('Invalid address'); - }); - - it('should fail with an invalid amount: text value', () => { - should(() => { - new TransferBuilder().amount('invalidamount'); - }).throw('Invalid amount'); - }); - - it('should fail with an invalid amount: negative value', () => { - should(() => { - new TransferBuilder().amount('-10'); - }).throw('Invalid amount'); - }); - - it('should fail with an invalid expiration time', () => { - should(() => { - new TransferBuilder().expirationTime(-1); - }).throw('Invalid expiration time'); - }); - - it('should fail if a sequenceId param is missing', () => { - const builder = new TransferBuilder().amount(amount).to(toAddress).key(key); - assert.throws(() => builder.signAndBuild()); - }); - - it('should fail if a destination param is missing', () => { - const builder = new TransferBuilder().amount(amount).contractSequenceId(2).key(key); - assert.throws(() => builder.signAndBuild()); - }); - - it('should fail if a amount param is missing', () => { - const builder = new TransferBuilder().to(toAddress).contractSequenceId(2).key(key); - assert.throws(() => builder.signAndBuild()); - }); - }); }); diff --git a/modules/sdk-coin-opeth/src/lib/transactionBuilder.ts b/modules/sdk-coin-opeth/src/lib/transactionBuilder.ts index dbebbfa8aa..18e7f6e20f 100644 --- a/modules/sdk-coin-opeth/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-opeth/src/lib/transactionBuilder.ts @@ -1,6 +1,7 @@ import { BaseCoin as CoinConfig } from '@bitgo/statics'; import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core'; import { KeyPair, Transaction, TransactionBuilder as EthLikeTransactionBuilder } from '@bitgo/abstract-eth'; + import { getCommon } from './utils'; import { TransferBuilder } from './transferBuilder'; import { walletSimpleByteCode } from './walletUtil'; diff --git a/modules/sdk-coin-opeth/src/lib/transferBuilder.ts b/modules/sdk-coin-opeth/src/lib/transferBuilder.ts index a470442578..7447c0cf3d 100644 --- a/modules/sdk-coin-opeth/src/lib/transferBuilder.ts +++ b/modules/sdk-coin-opeth/src/lib/transferBuilder.ts @@ -1,8 +1 @@ -import { TransferBuilder as EthTransferBuilder } from '@bitgo/abstract-eth'; - -export class TransferBuilder extends EthTransferBuilder { - /** @inheritdoc */ - protected getNativeOperationHashPrefix(): string { - return 'OPETH'; - } -} +export { TransferBuilder } from '@bitgo/abstract-eth'; diff --git a/modules/sdk-coin-opeth/test/resources.ts b/modules/sdk-coin-opeth/test/resources.ts index 9fd3907d7e..879b76eee6 100644 --- a/modules/sdk-coin-opeth/test/resources.ts +++ b/modules/sdk-coin-opeth/test/resources.ts @@ -35,3 +35,24 @@ export const PRIVATE_KEY_2 = export const PUBLIC_KEY_2 = 'xpub661MyMwAqRbcFXrZT6GpipqTPdFdhYX5DFDGv5ws1iW8ViCWSTK3zVhVntpiKDjPqXrqP1hhQscJZhVpbmZt9qygxgPdmN9RAZVKqovPfLw'; + +export const SEND_FUNDS_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004106935b57df83e029901bb12028bcd86feff0724699ae71bee552dd5ed99766b2794d4cc6dfd8f94949f21fea3188c2cd502c89bd0adb939bcf9dd7e79e8f7aca1b00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_SEQUENCE_ZERO_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041d731e3bd73bff7f803cbd38a2d96b1b3474a58e31afd25f8f900f284963ae29305924d5445bc17c54aea5bb20947cfaadaa642812dc5bec4e9709a4b3de777d11b00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_AMOUNT_ZERO_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041861d26b3e1451d9445c71c7bfd51b98297840e5ff39649a936f05b0c4b30f30c6d2a8ecb1ab7764dd895241bab17a9d4cab04d453102eef90e5d1d61642c33d51c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_OPETH_TERC_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000e9df68a54bba438c8a6192e95f0f2c53ac93d997000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041db3a377de4367e5a77a337b71967fd815ab0a1b29da576adc6e0d74420d2d16d6d49c5bbeefc49f29e9164a665234c7b3664e8e66899f6b0d3b0d855504d52e81c00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_TOKEN_SEQUENCE_ZERO_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000e9df68a54bba438c8a6192e95f0f2c53ac93d997000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004194dd7978c79c9edf402c9ae09adb721d42aadd621b79908ec52eafb79b3d536c2498fdb30c59d29529dc0c19c6c1f26cd281257c7ff02f7d0dd02eccf7923cc21b00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_TOKEN_AMOUNT_ZERO_DATA = + '0x0dcd7a6c0000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e9df68a54bba438c8a6192e95f0f2c53ac93d997000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041dff2b15181f683875e6463ac5abf9be92ed3aae99a5351028610264d3ae724cf04e69d4ab31fb4e127885fc63712e9a8950a204660a1a4d65bfc8de98fcfc1571b00000000000000000000000000000000000000000000000000000000000000'; + +export const SEND_FUNDS_NO_KEY_DATA = + '0x391252150000000000000000000000007325a3f7d4f9e86ae62cf742426078c3755730d5000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005ec6ab34000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; diff --git a/modules/sdk-coin-opeth/test/unit/opeth.ts b/modules/sdk-coin-opeth/test/unit/opeth.ts index 82da66ad03..61584823d6 100644 --- a/modules/sdk-coin-opeth/test/unit/opeth.ts +++ b/modules/sdk-coin-opeth/test/unit/opeth.ts @@ -168,7 +168,8 @@ describe('Optimism', function () { builder.counter(1); builder.type(TransactionType.Send); builder.contract(account_1.address); - builder.transfer().amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); + const transferBuilder = builder.transfer() as TransferBuilder; + transferBuilder.coin('topeth').amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); const unsignedTx = await builder.build(); const unsignedTxForBroadcasting = unsignedTx.toBroadcastFormat(); @@ -203,7 +204,8 @@ describe('Optimism', function () { builder.counter(1); builder.type(TransactionType.Send); builder.contract(account_1.address); - builder.transfer().amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); + const transferBuilder = builder.transfer() as TransferBuilder; + transferBuilder.coin('topeth').amount('1').to(account_2.address).expirationTime(10000).contractSequenceId(1); const unsignedTx = await builder.build(); const unsignedTxForBroadcasting = unsignedTx.toBroadcastFormat(); diff --git a/modules/sdk-coin-opeth/test/unit/transferBuilder.ts b/modules/sdk-coin-opeth/test/unit/transferBuilder.ts new file mode 100644 index 0000000000..e12bf8f233 --- /dev/null +++ b/modules/sdk-coin-opeth/test/unit/transferBuilder.ts @@ -0,0 +1,107 @@ +import should from 'should'; +import { KeyPair, TransferBuilder } from '../../src'; + +import * as testData from '../resources'; + +describe('Eth send multi sig builder', function () { + const toAddress = '0x7325A3F7d4f9E86AE62Cf742426078C3755730d5'; + const xprv = + 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2'; + const key = new KeyPair({ prv: xprv }).getKeys().prv as string; + const amount = '100000000000000000'; // equivalent to 0.1 ether + + describe('should build', () => { + it('native coin transfer should succeed', async () => { + const builder = new TransferBuilder() + .coin('topeth') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .key(key) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_DATA); + }); + + it('native coin transfer with sequenceId zero should succeed', async () => { + const builder = new TransferBuilder() + .coin('topeth') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(0) + .key(key) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_SEQUENCE_ZERO_DATA); + }); + + it('native coin transfer with amount 0 should succeed', async () => { + const builder = new TransferBuilder() + .coin('topeth') + .expirationTime(1590078260) + .amount('0') + .to(toAddress) + .contractSequenceId(2) + .key(key) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_AMOUNT_ZERO_DATA); + }); + + it('ERC20 token transfer should succeed', async () => { + const builder = new TransferBuilder() + .coin('topeth:terc18dp') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_OPETH_TERC_DATA); + }); + + it('erc20 transfer should succeed', async () => { + const builder = new TransferBuilder() + .coin('topeth:terc18dp') + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(0) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_TOKEN_SEQUENCE_ZERO_DATA); + }); + + it('erc20 transfer with amount 0 should succeed', async () => { + const builder = new TransferBuilder() + .coin('topeth:terc18dp') + .expirationTime(1590078260) + .amount('0') + .to(toAddress) + .contractSequenceId(2) + .key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_TOKEN_AMOUNT_ZERO_DATA); + }); + + it('should build without a signature set', () => { + const builder = new TransferBuilder() + .expirationTime(1590078260) + .amount(amount) + .to(toAddress) + .contractSequenceId(2) + .data('0x'); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_NO_KEY_DATA); + }); + + it('should build from a non signed serialized data', () => { + const builder = new TransferBuilder(testData.SEND_FUNDS_NO_KEY_DATA); + builder.coin('topeth').key(key); + const result = builder.signAndBuild(); + should.equal(result, testData.SEND_FUNDS_DATA); + }); + }); +}); diff --git a/modules/sdk-coin-polygon/src/lib/transactionBuilder.ts b/modules/sdk-coin-polygon/src/lib/transactionBuilder.ts index 80b01c0d93..a718ecf6e2 100644 --- a/modules/sdk-coin-polygon/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-polygon/src/lib/transactionBuilder.ts @@ -1,6 +1,7 @@ import { TransactionBuilder as EthLikeTransactionBuilder } from '@bitgo/abstract-eth'; import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core'; import { BaseCoin as CoinConfig } from '@bitgo/statics'; + import { getCommon } from './utils'; import { walletSimpleByteCode } from './walletUtil'; import { Transaction, TransferBuilder } from './'; diff --git a/modules/sdk-coin-rbtc/package.json b/modules/sdk-coin-rbtc/package.json index a2da94e3c7..61c1031e7d 100644 --- a/modules/sdk-coin-rbtc/package.json +++ b/modules/sdk-coin-rbtc/package.json @@ -45,8 +45,7 @@ "@bitgo/sdk-core": "^18.0.0", "@bitgo/statics": "^39.0.0", "@ethereumjs/common": "^2.6.5", - "ethereumjs-abi": "^0.6.5", - "ethereumjs-util": "7.1.5" + "ethereumjs-abi": "^0.6.5" }, "devDependencies": { "@bitgo/sdk-api": "^1.34.0", diff --git a/modules/sdk-coin-rbtc/test/unit/transactionBuilder/send.ts b/modules/sdk-coin-rbtc/test/unit/transactionBuilder/send.ts index c236c69d20..ff22628c8d 100644 --- a/modules/sdk-coin-rbtc/test/unit/transactionBuilder/send.ts +++ b/modules/sdk-coin-rbtc/test/unit/transactionBuilder/send.ts @@ -1,10 +1,8 @@ import should from 'should'; -import * as ethUtil from 'ethereumjs-util'; -import EthereumAbi from 'ethereumjs-abi'; -import { BaseTransaction, TransactionType } from '@bitgo/sdk-core'; -import { KeyPair, TransactionBuilder } from '../../../src'; +import { TransactionType } from '@bitgo/sdk-core'; + +import { TransactionBuilder } from '../../../src'; import * as testData from '../../resources'; -import { decodeTransferData } from '@bitgo/sdk-coin-eth'; import { coins } from '@bitgo/statics'; describe('Rbtc send transaction', function () { @@ -21,23 +19,6 @@ describe('Rbtc send transaction', function () { txBuilder.contract(contractAddress); }; - const getOperationHash = function (tx: BaseTransaction): string { - const { data } = tx.toJson(); - const { tokenContractAddress, expireTime, sequenceId, amount, to } = decodeTransferData(data); - const operationParams = [ - ['string', 'address', 'uint', 'address', 'uint', 'uint'], - [ - 'RSK-ERC20', - new ethUtil.BN(ethUtil.stripHexPrefix(to), 16), - amount, - new ethUtil.BN(ethUtil.stripHexPrefix(tokenContractAddress || ''), 16), - expireTime, - sequenceId, - ], - ]; - return EthereumAbi.soliditySHA3(...operationParams); - }; - const key = testData.KEYPAIR_PRV.getKeys().prv as string; it('a send funds transaction', async () => { @@ -75,62 +56,6 @@ describe('Rbtc send transaction', function () { const tx = await txBuilder.build(); should.equal(tx.toBroadcastFormat(), testData.SEND_TX_AMOUNT_ZERO_BROADCAST); }); - - it('a send token transaction', async () => { - const recipient = '0x72c2c8e08bf91d755cd7d26b49a2ee3dc99de1b9'; - const contractAddress = '0xdf7decb1baa8f529f0c8982cbb4be50357195299'; - const amount = '100'; - initTxBuilder(); - txBuilder.contract(contractAddress); - txBuilder - .transfer() - .coin('trif') - .amount(amount) - .to(recipient) - .expirationTime(1590066728) - .contractSequenceId(5) - .key(key); - txBuilder.sign({ - key: testData.PRIVATE_KEY_1, - }); - const tx = await txBuilder.build(); - should.equal(tx.toBroadcastFormat(), testData.SEND_TOKEN_TX_BROADCAST); - should.equal(tx.signature.length, 2); - should.equal(tx.inputs.length, 1); - should.equal(tx.inputs[0].address, contractAddress); - should.equal(tx.inputs[0].value, amount); - should.equal(tx.inputs[0].coin, 'trif'); - - should.equal(tx.outputs.length, 1); - should.equal(tx.outputs[0].address, recipient); - should.equal(tx.outputs[0].value, amount); - should.equal(tx.outputs[0].coin, 'trif'); - - const { signature } = decodeTransferData(tx.toJson().data); - const operationHash = getOperationHash(tx); - - const { v, r, s } = ethUtil.fromRpcSig(signature); - const senderPubKey = ethUtil.ecrecover(Buffer.from(operationHash, 'hex'), v, r, s); - const senderAddress = ethUtil.pubToAddress(senderPubKey); - const senderKey = new KeyPair({ prv: testData.PRIVATE_KEY_1 }); - ethUtil.bufferToHex(senderAddress).should.equal(senderKey.getAddress()); - }); - - it('a send token transactions from serialized', async () => { - const txBuilder = new TransactionBuilder(coins.get('trbtc')); - txBuilder.from(testData.SEND_TOKEN_TX_BROADCAST); - const tx = await txBuilder.build(); - should.equal(tx.toBroadcastFormat(), testData.SEND_TOKEN_TX_BROADCAST); - - const { signature } = decodeTransferData(tx.toJson().data); - const operationHash = getOperationHash(tx); - - const { v, r, s } = ethUtil.fromRpcSig(signature); - const senderPubKey = ethUtil.ecrecover(Buffer.from(operationHash || ''), v, r, s); - const senderAddress = ethUtil.pubToAddress(senderPubKey); - const senderKey = new KeyPair({ prv: testData.PRIVATE_KEY_1 }); - ethUtil.bufferToHex(senderAddress).should.equal(senderKey.getAddress()); - }); }); describe('should sign and build from serialized', () => { diff --git a/modules/sdk-coin-zketh/src/lib/transferBuilder.ts b/modules/sdk-coin-zketh/src/lib/transferBuilder.ts index daa68d86cd..7447c0cf3d 100644 --- a/modules/sdk-coin-zketh/src/lib/transferBuilder.ts +++ b/modules/sdk-coin-zketh/src/lib/transferBuilder.ts @@ -1,23 +1 @@ -import { TransferBuilder as EthTransferBuilder } from '@bitgo/abstract-eth'; - -export class TransferBuilder extends EthTransferBuilder { - /** - * Get the prefix used in generating an operation hash for sending native coins - * See https://github.com/BitGo/eth-multisig-v4/blob/master/contracts/coins/ZkethWalletSimple.sol - * - * @returns the string prefix - */ - protected getNativeOperationHashPrefix(): string { - return 'ZKETH'; - } - - /** - * Get the prefix used in generating an operation hash for sending tokens - * See https://github.com/BitGo/eth-multisig-v4/blob/master/contracts/coins/ZkethWalletSimple.sol - * - * @returns the string prefix - */ - protected getTokenOperationHashPrefix(): string { - return 'ZKETH-ERC20'; - } -} +export { TransferBuilder } from '@bitgo/abstract-eth'; diff --git a/modules/statics/src/networks.ts b/modules/statics/src/networks.ts index 1f0f1755a6..065f38330a 100644 --- a/modules/statics/src/networks.ts +++ b/modules/statics/src/networks.ts @@ -148,8 +148,8 @@ class Arbitrum extends Mainnet implements EthereumNetwork { explorerUrl = 'https://arbiscan.io/tx/'; accountExplorerUrl = 'https://arbiscan.io/address/'; chainId = 42161; - nativeCoinOperationHashPrefix = 'ARBETH'; - tokenOperationHashPrefix = 'ARBETH-ERC20'; + nativeCoinOperationHashPrefix = '42161'; + tokenOperationHashPrefix = '42161-ERC20'; } class ArbitrumTestnet extends Testnet implements EthereumNetwork { @@ -158,8 +158,8 @@ class ArbitrumTestnet extends Testnet implements EthereumNetwork { explorerUrl = 'https://sepolia.arbiscan.io/tx/'; accountExplorerUrl = 'https://sepolia.arbiscan.io/address/'; chainId = 421614; - nativeCoinOperationHashPrefix = 'ARBETH'; - tokenOperationHashPrefix = 'ARBETH-ERC20'; + nativeCoinOperationHashPrefix = '421614'; + tokenOperationHashPrefix = '421614-ERC20'; } class AvalancheC extends Mainnet implements AccountNetwork { @@ -375,6 +375,8 @@ class Celo extends Mainnet implements EthereumNetwork { explorerUrl = 'https://explorer.celo.org/tx/'; accountExplorerUrl = 'https://explorer.celo.org/address/'; chainId = 42220; + nativeCoinOperationHashPrefix = 'CELO'; + tokenOperationHashPrefix = 'CELO-ERC20'; } class CeloTestnet extends Testnet implements EthereumNetwork { @@ -383,6 +385,8 @@ class CeloTestnet extends Testnet implements EthereumNetwork { explorerUrl = 'https://alfajores-blockscout.celo-testnet.org/tx/'; accountExplorerUrl = 'https://alfajores-blockscout.celo-testnet.org/address/'; chainId = 44787; + nativeCoinOperationHashPrefix = 'CELO'; + tokenOperationHashPrefix = 'CELO-ERC20'; } // TODO update explorerUrl STLX-1657 @@ -493,6 +497,8 @@ class EthereumClassic extends Mainnet implements EthereumNetwork { accountExplorerUrl = 'https://blockscout.com/etc/mainnet/address/'; // from https://chainid.network/chains/ chainId = 61; + nativeCoinOperationHashPrefix = 'ETC'; + tokenOperationHashPrefix = 'ETC-ERC20'; } class EthereumClassicTestnet extends Testnet implements EthereumNetwork { @@ -502,6 +508,8 @@ class EthereumClassicTestnet extends Testnet implements EthereumNetwork { accountExplorerUrl = 'https://blockscout.com/etc/mordor/address/'; // from https://chainid.network/chains/ chainId = 63; + nativeCoinOperationHashPrefix = 'ETC'; + tokenOperationHashPrefix = 'ETC-ERC20'; } class Eos extends Mainnet implements AccountNetwork { @@ -560,6 +568,8 @@ class Rbtc extends Mainnet implements EthereumNetwork { explorerUrl = 'https://explorer.rsk.co/tx/'; accountExplorerUrl = 'https://explorer.rsk.co/address/'; chainId = 30; + nativeCoinOperationHashPrefix = 'RSK'; + tokenOperationHashPrefix = 'RSK-ERC20'; } class RbtcTestnet extends Testnet implements EthereumNetwork { @@ -568,6 +578,8 @@ class RbtcTestnet extends Testnet implements EthereumNetwork { explorerUrl = 'https://explorer.testnet.rsk.co/tx/'; accountExplorerUrl = 'https://explorer.testnet.rsk.co/address/'; chainId = 31; + nativeCoinOperationHashPrefix = 'RSK'; + tokenOperationHashPrefix = 'RSK-ERC20'; } class Stellar extends Mainnet implements AccountNetwork { @@ -910,8 +922,8 @@ class Optimism extends Mainnet implements EthereumNetwork { explorerUrl = 'https://optimistic.etherscan.io/tx/'; accountExplorerUrl = 'https://optimistic.etherscan.io/address/'; chainId = 10; - nativeCoinOperationHashPrefix = 'OPETH'; - tokenOperationHashPrefix = 'OPETH-ERC20'; + nativeCoinOperationHashPrefix = '10'; + tokenOperationHashPrefix = '10-ERC20'; } class OptimismTestnet extends Testnet implements EthereumNetwork { @@ -920,8 +932,8 @@ class OptimismTestnet extends Testnet implements EthereumNetwork { explorerUrl = 'https://sepolia-optimism.etherscan.io/tx/'; accountExplorerUrl = 'https://sepolia-optimism.etherscan.io/address/'; chainId = 11155420; - nativeCoinOperationHashPrefix = 'OPETH'; - tokenOperationHashPrefix = 'OPETH-ERC20'; + nativeCoinOperationHashPrefix = '11155420'; + tokenOperationHashPrefix = '11155420-ERC20'; } class ZkSync extends Mainnet implements EthereumNetwork {