Skip to content

Commit 3d5ce1a

Browse files
feat(abstract-eth): add extra methods in transfer builder
TICKET: COIN-3248
1 parent 58d2811 commit 3d5ce1a

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

modules/abstract-eth/src/lib/transferBuilder.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1+
import assert from 'assert';
12
import * as ethUtil from 'ethereumjs-util';
23
import EthereumAbi from 'ethereumjs-abi';
34
import BN from 'bn.js';
45
import { coins, BaseCoin, ContractAddressDefinedToken, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics';
56
import { BuildTransactionError, InvalidParameterValueError } from '@bitgo/sdk-core';
67
import { decodeTransferData, sendMultiSigData, sendMultiSigTokenData, isValidEthAddress, isValidAmount } from './utils';
78
import { defaultAbiCoder, keccak256 } from 'ethers/lib/utils';
9+
import { sendMultiSigTokenTypes, sendMultiSigTypes } from './walletUtil';
810

911
/** ETH transfer builder */
1012
export class TransferBuilder {
1113
private readonly _EMPTY_HEX_VALUE = '0x';
1214
protected _amount: string;
1315
protected _toAddress: string;
1416
protected _sequenceId: number;
15-
protected _signKey: string;
17+
protected _signKey: string | null;
1618
protected _expirationTime: number;
1719
protected _signature: string;
1820
private _data: string;
@@ -111,6 +113,12 @@ export class TransferBuilder {
111113
return this;
112114
}
113115

116+
setSignature(signature: string): TransferBuilder {
117+
this._signKey = null;
118+
this._signature = signature;
119+
return this;
120+
}
121+
114122
signAndBuild(chainId: string, coinUsesNonPackedEncodingForTxData?: boolean): string {
115123
this._chainId = chainId;
116124

@@ -254,6 +262,7 @@ export class TransferBuilder {
254262

255263
protected ethSignMsgHash(): string {
256264
const data = this.getOperationHash();
265+
assert(this._signKey);
257266
const keyBuffer = Buffer.from(ethUtil.padToEven(this._signKey), 'hex');
258267
if (keyBuffer.length !== 32) {
259268
throw new Error('private key length is invalid');
@@ -289,4 +298,17 @@ export class TransferBuilder {
289298
this._tokenContractAddress = transferData.tokenContractAddress;
290299
}
291300
}
301+
302+
public getSignatureData(): Buffer<ArrayBuffer> {
303+
const method = this._tokenContractAddress
304+
? EthereumAbi.methodID('sendMultiSigToken', sendMultiSigTokenTypes)
305+
: EthereumAbi.methodID('sendMultiSig', sendMultiSigTypes);
306+
const operationData = this.getOperationData();
307+
const rawEncodedOperationData = EthereumAbi.rawEncode(...operationData);
308+
return Buffer.concat([
309+
method,
310+
rawEncodedOperationData,
311+
Buffer.from([this._coinUsesNonPackedEncodingForTxData ? 1 : 0]),
312+
]);
313+
}
292314
}

modules/sdk-coin-ethlike/test/fixtures/ethlikeCoin.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,22 @@ export const encryptedUserKey =
7272
':"ccm","adata":"","cipher":"aes","salt":"p+fkHuLa/8k=","ct":"hYG7pvljLIgCjZ\n' +
7373
'53PBlCde5KZRmlUKKHLtDMk+HJfuU46hW+x+C9WsIAO4gFPnTCvFVmQ8x7czCtcNFub5AO2otOG\n' +
7474
'OsX4GE2gXOEmCl1TpWwwNhm7yMUjGJUpgW6ZZgXSXdDitSKi4V/hk78SGSzjFOBSPYRa6I="}\n';
75+
76+
export const custodialHot = {
77+
hteth: {
78+
signatureData:
79+
'0dcd7a6c00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000b9f62c71d5f6949cfb211a67fb13ccf079cc760b0000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000e4ab69c077896252fafbd49efd26b5d171a324100000000000000000000000000000000000000000000000000000000067f415e100000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000005455243323000000000000000000000000000000000000000000000000000000000',
80+
signature:
81+
'0xa9c3ead37547fa56b694e4eccc9352225b7458ef08e4e961ca5774d398abd55b5986a7446259374d2098012923583935189739f18dd117c4d15221b2a26f50911c',
82+
signedTxHex:
83+
'0xf9016a80830186a082520894702cf81e03aa310ec9481d814e3d04a20b04b50580b901440dcd7a6c000000000000000000000000b9f62c71d5f6949cfb211a67fb13ccf079cc760b0000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000e4ab69c077896252fafbd49efd26b5d171a324100000000000000000000000000000000000000000000000000000000067f415e1000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041a9c3ead37547fa56b694e4eccc9352225b7458ef08e4e961ca5774d398abd55b5986a7446259374d2098012923583935189739f18dd117c4d15221b2a26f50911c000000000000000000000000000000000000000000000000000000000000008284f38080',
84+
},
85+
tarbeth: {
86+
signatureData:
87+
'0dcd7a6c00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000b9f62c71d5f6949cfb211a67fb13ccf079cc760b0000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000e4ab69c077896252fafbd49efd26b5d171a324100000000000000000000000000000000000000000000000000000000067f415e10000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000c3432313631342d4552433230000000000000000000000000000000000000000000',
88+
signature:
89+
'0xa9c3ead37547fa56b694e4eccc9352225b7458ef08e4e961ca5774d398abd55b5986a7446259374d2098012923583935189739f18dd117c4d15221b2a26f50911c',
90+
signedTxHex:
91+
'0xf9016b80830186a082520894702cf81e03aa310ec9481d814e3d04a20b04b50580b901440dcd7a6c000000000000000000000000b9f62c71d5f6949cfb211a67fb13ccf079cc760b0000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000e4ab69c077896252fafbd49efd26b5d171a324100000000000000000000000000000000000000000000000000000000067f415e1000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041a9c3ead37547fa56b694e4eccc9352225b7458ef08e4e961ca5774d398abd55b5986a7446259374d2098012923583935189739f18dd117c4d15221b2a26f50911c00000000000000000000000000000000000000000000000000000000000000830cddff8080',
92+
},
93+
};

modules/sdk-coin-ethlike/test/unit/ethlikeCoin.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import assert from 'assert';
22
import { BitGoAPI } from '@bitgo/sdk-api';
33
import { common, FullySignedTransaction, HalfSignedTransaction, TransactionType } from '@bitgo/sdk-core';
4-
import { OfflineVaultTxInfo } from '@bitgo/abstract-eth';
4+
import { OfflineVaultTxInfo, TransferBuilder } from '@bitgo/abstract-eth';
55
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
66
import { bip32 } from '@bitgo/secp256k1';
77
import nock from 'nock';
@@ -93,6 +93,37 @@ describe('EthLike coin tests', function () {
9393
assert(result.txHex);
9494
result.txHex.should.equal(mockData.ccr[coin.name].txHex);
9595
});
96+
97+
it('should generate signature data for custodial hot wallet and sign using hsm signature', async function () {
98+
const baseAddress = '0x702cf81e03aa310ec9481d814e3d04a20b04b505';
99+
const destinationAddress = '0xb9f62c71d5f6949cfb211a67fb13ccf079cc760b';
100+
const tokenContractAddress = '0xe4ab69c077896252fafbd49efd26b5d171a32410';
101+
const txBuilder = getBuilder(coin.name, coin.common) as EthLikeTransactionBuilder;
102+
103+
txBuilder.contract(baseAddress);
104+
txBuilder.contractCounter(0);
105+
txBuilder.fee({
106+
fee: '100000',
107+
gasLimit: '21000',
108+
});
109+
110+
const transferBuilder = txBuilder.transfer() as TransferBuilder;
111+
transferBuilder
112+
.coin(coin.name)
113+
.amount('100000000')
114+
.contractSequenceId(100)
115+
.expirationTime(1744049633)
116+
.to(destinationAddress)
117+
.tokenContractAddress(tokenContractAddress);
118+
const signatureData = transferBuilder.getSignatureData();
119+
assert.strictEqual(signatureData.toString('hex'), mockData.custodialHot[coin.name].signatureData);
120+
121+
// Set HSM Signature
122+
transferBuilder.setSignature(mockData.custodialHot[coin.name].signature);
123+
const tx = await txBuilder.build();
124+
const txHex = tx.toBroadcastFormat();
125+
assert.strictEqual(txHex, mockData.custodialHot[coin.name].signedTxHex);
126+
});
96127
});
97128
});
98129
});

0 commit comments

Comments
 (0)