Skip to content

Commit 700d9d6

Browse files
Merge pull request #5935 from BitGo/WIN-4884-Unsigned
feat: unsigned sweep for flare and xdc
2 parents a253512 + 0a29d5e commit 700d9d6

File tree

14 files changed

+380
-7
lines changed

14 files changed

+380
-7
lines changed

modules/sdk-coin-coredao/src/coredao.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { BaseCoin, BitGoBase, common, MPCAlgorithm, MultisigType, multisigTypes } from '@bitgo/sdk-core';
22
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
3-
import { AbstractEthLikeNewCoins, optionalDeps, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth';
3+
import {
4+
AbstractEthLikeNewCoins,
5+
optionalDeps,
6+
recoveryBlockchainExplorerQuery,
7+
UnsignedSweepTxMPCv2,
8+
RecoverOptions,
9+
OfflineVaultTxInfo,
10+
} from '@bitgo/abstract-eth';
411
import { TransactionBuilder } from './lib';
512
import BN from 'bn.js';
613

@@ -38,6 +45,10 @@ export class Coredao extends AbstractEthLikeNewCoins {
3845
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken);
3946
}
4047

48+
protected async buildUnsignedSweepTxnTSS(params: RecoverOptions): Promise<OfflineVaultTxInfo | UnsignedSweepTxMPCv2> {
49+
return this.buildUnsignedSweepTxnMPCv2(params);
50+
}
51+
4152
/** @inheritDoc */
4253
async queryAddressBalance(address: string): Promise<BN> {
4354
const result = await this.recoveryBlockchainExplorerQuery({

modules/sdk-coin-coredao/src/lib/transactionBuilder.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { TransferBuilder } from './transferBuilder';
66

77
export class TransactionBuilder extends AbstractTransactionBuilder {
88
protected _transfer: TransferBuilder;
9+
private _signatures: any;
910

1011
constructor(_coinConfig: Readonly<CoinConfig>) {
1112
super(_coinConfig);
@@ -24,6 +25,11 @@ export class TransactionBuilder extends AbstractTransactionBuilder {
2425
return this._transfer;
2526
}
2627

28+
addSignature(publicKey, signature) {
29+
this._signatures = [];
30+
this._signatures.push({ publicKey, signature });
31+
}
32+
2733
protected getContractData(addresses: string[]): string {
2834
throw new Error('Method not implemented.');
2935
}

modules/sdk-coin-flr/src/flr.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99

1010
import { BaseCoin, BitGoBase, common, MPCAlgorithm, MultisigType, multisigTypes } from '@bitgo/sdk-core';
1111
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
12-
import { AbstractEthLikeNewCoins, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth';
12+
import {
13+
AbstractEthLikeNewCoins,
14+
recoveryBlockchainExplorerQuery,
15+
UnsignedSweepTxMPCv2,
16+
RecoverOptions,
17+
OfflineVaultTxInfo,
18+
} from '@bitgo/abstract-eth';
1319
import { TransactionBuilder } from './lib';
1420

1521
export class Flr extends AbstractEthLikeNewCoins {
@@ -40,6 +46,10 @@ export class Flr extends AbstractEthLikeNewCoins {
4046
return 'ecdsa';
4147
}
4248

49+
protected async buildUnsignedSweepTxnTSS(params: RecoverOptions): Promise<OfflineVaultTxInfo | UnsignedSweepTxMPCv2> {
50+
return this.buildUnsignedSweepTxnMPCv2(params);
51+
}
52+
4353
async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<Record<string, unknown>> {
4454
const apiToken = common.Environments[this.bitgo.getEnv()].flrExplorerApiToken;
4555
const explorerUrl = common.Environments[this.bitgo.getEnv()].flrExplorerBaseUrl;

modules/sdk-coin-flr/src/lib/transactionBuilder.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { TransferBuilder } from './transferBuilder';
66

77
export class TransactionBuilder extends AbstractTransactionBuilder {
88
protected _transfer: TransferBuilder;
9+
private _signatures: any;
910

1011
constructor(_coinConfig: Readonly<CoinConfig>) {
1112
super(_coinConfig);
@@ -24,6 +25,11 @@ export class TransactionBuilder extends AbstractTransactionBuilder {
2425
return this._transfer;
2526
}
2627

28+
addSignature(publicKey, signature) {
29+
this._signatures = [];
30+
this._signatures.push({ publicKey, signature });
31+
}
32+
2733
protected getContractData(addresses: string[]): string {
2834
throw new Error('Method not implemented.');
2935
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const getTxListRequestUnsignedSweep: Record<string, string> = {
2+
module: 'account',
3+
action: 'txlist',
4+
address: '0x1469e6e519ff8bf398b76b4be0b50701b999f14c',
5+
};
6+
7+
const getTxListResponseUnsignedSweep: Record<string, unknown> = {
8+
status: '1',
9+
result: [
10+
{
11+
hash: '0xede855d43d70ea1bb75db63d4f75113dae0845f0d4bdb0b2d8bda55249c70812',
12+
nonce: '23',
13+
from: '0x1469e6e519ff8bf398b76b4be0b50701b999f14c',
14+
},
15+
],
16+
message: 'OK',
17+
};
18+
19+
const getBalanceRequestUnsignedSweep: Record<string, string> = {
20+
module: 'account',
21+
action: 'balance',
22+
address: '0x1469e6e519ff8bf398b76b4be0b50701b999f14c',
23+
};
24+
25+
const getBalanceResponseUnsignedSweep: Record<string, unknown> = {
26+
status: '1',
27+
result: '100000000000000000',
28+
message: 'OK',
29+
};
30+
31+
export const mockDataUnsignedSweep = {
32+
userKey:
33+
'038412b0e79372ca618978f2bc9fc944c504e828050a55a19fdfeca93cff5ec6562ae94f204a3f99e87334f812be8a54927ff24572bc666c5436887d2e42c0997d',
34+
backupKey:
35+
'038412b0e79372ca618978f2bc9fc944c504e828050a55a19fdfeca93cff5ec6562ae94f204a3f99e87334f812be8a54927ff24572bc666c5436887d2e42c0997d',
36+
derivationPath: 'm/0',
37+
derivationSeed: '',
38+
walletBaseAddress: '0x1469e6e519ff8bf398b76b4be0b50701b999f14c',
39+
recoveryDestination: '0x07efb1aa5e41b70b21facd3d287548ebf632a165',
40+
getTxListRequest: getTxListRequestUnsignedSweep,
41+
getTxListResponse: getTxListResponseUnsignedSweep,
42+
getBalanceRequest: getBalanceRequestUnsignedSweep,
43+
getBalanceResponse: getBalanceResponseUnsignedSweep,
44+
};

modules/sdk-coin-flr/test/unit/flr.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import 'should';
1+
import * as should from 'should';
22

33
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
44
import { BitGoAPI } from '@bitgo/sdk-api';
55

66
import { Flr, Tflr } from '../../src/index';
7+
import { UnsignedSweepTxMPCv2 } from '@bitgo/abstract-eth';
8+
import { mockDataUnsignedSweep } from '../resources';
9+
import nock from 'nock';
10+
import { common } from '@bitgo/sdk-core';
711

812
const bitgo: TestBitGoAPI = TestBitGo.decorate(BitGoAPI, { env: 'test' });
913

@@ -40,3 +44,59 @@ describe('flr', function () {
4044
});
4145
});
4246
});
47+
48+
describe('Build Unsigned Sweep for Self-Custody Cold Wallets - (MPCv2)', function () {
49+
const bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
50+
const explorerUrl = common.Environments[bitgo.getEnv()].flrExplorerBaseUrl as string;
51+
const maxFeePerGasvalue = 30000000000;
52+
const maxPriorityFeePerGasValue = 15000000000;
53+
const chain_id = 114;
54+
const gasLimitvalue = 500000;
55+
56+
it('should generate an unsigned sweep without derivation path', async () => {
57+
nock(explorerUrl)
58+
.get('/api')
59+
.twice()
60+
.query(mockDataUnsignedSweep.getTxListRequest)
61+
.reply(200, mockDataUnsignedSweep.getTxListResponse);
62+
nock(explorerUrl)
63+
.get('/api')
64+
.query(mockDataUnsignedSweep.getBalanceRequest)
65+
.reply(200, mockDataUnsignedSweep.getBalanceResponse);
66+
67+
const baseCoin: any = bitgo.coin('tflr');
68+
const transaction = (await baseCoin.recover({
69+
userKey: mockDataUnsignedSweep.userKey,
70+
backupKey: mockDataUnsignedSweep.backupKey,
71+
walletContractAddress: mockDataUnsignedSweep.walletBaseAddress,
72+
recoveryDestination: mockDataUnsignedSweep.recoveryDestination,
73+
isTss: true,
74+
eip1559: { maxFeePerGas: maxFeePerGasvalue, maxPriorityFeePerGas: maxPriorityFeePerGasValue },
75+
gasLimit: gasLimitvalue,
76+
replayProtectionOptions: {
77+
chain: chain_id,
78+
hardfork: 'london',
79+
},
80+
})) as UnsignedSweepTxMPCv2;
81+
should.exist(transaction);
82+
transaction.should.have.property('txRequests');
83+
transaction.txRequests.length.should.equal(1);
84+
const txRequest = transaction.txRequests[0];
85+
txRequest.should.have.property('walletCoin');
86+
txRequest.walletCoin.should.equal('tflr');
87+
txRequest.should.have.property('transactions');
88+
txRequest.transactions.length.should.equal(1);
89+
const tx = txRequest.transactions[0];
90+
tx.should.have.property('nonce');
91+
tx.should.have.property('unsignedTx');
92+
tx.unsignedTx.should.have.property('serializedTxHex');
93+
tx.unsignedTx.should.have.property('signableHex');
94+
tx.unsignedTx.should.have.property('derivationPath');
95+
tx.unsignedTx.should.have.property('feeInfo');
96+
tx.unsignedTx.feeInfo?.should.have.property('fee');
97+
tx.unsignedTx.feeInfo?.should.have.property('feeString');
98+
tx.unsignedTx.should.have.property('parsedTx');
99+
tx.unsignedTx.parsedTx?.should.have.property('spendAmount');
100+
tx.unsignedTx.parsedTx?.should.have.property('outputs');
101+
});
102+
});

modules/sdk-coin-oas/src/lib/transactionBuilder.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { TransferBuilder } from './transferBuilder';
66

77
export class TransactionBuilder extends AbstractTransactionBuilder {
88
protected _transfer: TransferBuilder;
9+
private _signatures: any;
910

1011
constructor(_coinConfig: Readonly<CoinConfig>) {
1112
super(_coinConfig);
@@ -24,6 +25,11 @@ export class TransactionBuilder extends AbstractTransactionBuilder {
2425
return this._transfer;
2526
}
2627

28+
addSignature(publicKey, signature) {
29+
this._signatures = [];
30+
this._signatures.push({ publicKey, signature });
31+
}
32+
2733
protected getContractData(addresses: string[]): string {
2834
throw new Error('Method not implemented.');
2935
}

modules/sdk-coin-oas/src/oas.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
AbstractEthLikeNewCoins,
88
recoveryBlockchainExplorerQuery,
99
TransactionBuilder as EthLikeTransactionBuilder,
10+
UnsignedSweepTxMPCv2,
11+
RecoverOptions,
12+
OfflineVaultTxInfo,
1013
} from '@bitgo/abstract-eth';
1114
import { TransactionBuilder } from './lib';
1215

@@ -38,6 +41,10 @@ export class Oas extends AbstractEthLikeNewCoins {
3841
return 'ecdsa';
3942
}
4043

44+
protected async buildUnsignedSweepTxnTSS(params: RecoverOptions): Promise<OfflineVaultTxInfo | UnsignedSweepTxMPCv2> {
45+
return this.buildUnsignedSweepTxnMPCv2(params);
46+
}
47+
4148
/**
4249
* Make a query to Oasys chain explorer for information such as balance, token balance, solidity calls
4350
* @param {Object} query key-value pairs of parameters to append after /api
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const getTxListRequestUnsignedSweep: Record<string, string> = {
2+
module: 'account',
3+
action: 'txlist',
4+
address: '0x7f3f0386b3e17d24e7d7b6bcfffa3d92b8bf8a68',
5+
};
6+
7+
const getTxListResponseUnsignedSweep: Record<string, unknown> = {
8+
status: '1',
9+
result: [
10+
{
11+
hash: '0xede855d43d70ea1bb75db63d4f75113dae0845f0d4bdb0b2d8bda55249c70812',
12+
nonce: '23',
13+
from: '0x7f3f0386b3e17d24e7d7b6bcfffa3d92b8bf8a68',
14+
},
15+
],
16+
message: 'OK',
17+
};
18+
19+
const getBalanceRequestUnsignedSweep: Record<string, string> = {
20+
module: 'account',
21+
action: 'balance',
22+
address: '0x7f3f0386b3e17d24e7d7b6bcfffa3d92b8bf8a68',
23+
};
24+
25+
const getBalanceResponseUnsignedSweep: Record<string, unknown> = {
26+
status: '1',
27+
result: '100000000000000000',
28+
message: 'OK',
29+
};
30+
31+
export const mockDataUnsignedSweep = {
32+
userKey:
33+
'029dd2bdb90da985dc9c2c2bdf0f805ac21832462db53cd99ddf41d2f2dd2a31f2860c724001470a59b38bf67de19481e2811731d4efcd5b496de2ce3fe9baa18e',
34+
backupKey:
35+
'029dd2bdb90da985dc9c2c2bdf0f805ac21832462db53cd99ddf41d2f2dd2a31f2860c724001470a59b38bf67de19481e2811731d4efcd5b496de2ce3fe9baa18e',
36+
derivationPath: 'm/0',
37+
derivationSeed: '',
38+
walletBaseAddress: '0x7f3f0386b3e17d24e7d7b6bcfffa3d92b8bf8a68',
39+
recoveryDestination: '0xb986fd8081b8ca18cc343881bec0a70757309187',
40+
getTxListRequest: getTxListRequestUnsignedSweep,
41+
getTxListResponse: getTxListResponseUnsignedSweep,
42+
getBalanceRequest: getBalanceRequestUnsignedSweep,
43+
getBalanceResponse: getBalanceResponseUnsignedSweep,
44+
};

modules/sdk-coin-oas/test/unit/oas.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import 'should';
2-
1+
import * as should from 'should';
32
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
43
import { BitGoAPI } from '@bitgo/sdk-api';
54

65
import { Oas, Toas } from '../../src/index';
6+
import { UnsignedSweepTxMPCv2 } from '@bitgo/abstract-eth';
7+
import { mockDataUnsignedSweep } from '../resources';
8+
import nock from 'nock';
9+
import { common } from '@bitgo/sdk-core';
710

811
const bitgo: TestBitGoAPI = TestBitGo.decorate(BitGoAPI, { env: 'test' });
912

@@ -40,3 +43,59 @@ describe('OASYS chain', function () {
4043
});
4144
});
4245
});
46+
47+
describe('Build Unsigned Sweep for Self-Custody Cold Wallets - (MPCv2)', function () {
48+
const bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
49+
const explorerUrl = common.Environments[bitgo.getEnv()].oasExplorerBaseUrl as string;
50+
const maxFeePerGasvalue = 20000000000;
51+
const maxPriorityFeePerGasValue = 10000000000;
52+
const chain_id = 9372;
53+
const gasLimitvalue = 500000;
54+
55+
it('should generate an unsigned sweep without derivation path', async () => {
56+
nock(explorerUrl)
57+
.get('/api')
58+
.twice()
59+
.query(mockDataUnsignedSweep.getTxListRequest)
60+
.reply(200, mockDataUnsignedSweep.getTxListResponse);
61+
nock(explorerUrl)
62+
.get('/api')
63+
.query(mockDataUnsignedSweep.getBalanceRequest)
64+
.reply(200, mockDataUnsignedSweep.getBalanceResponse);
65+
66+
const baseCoin: any = bitgo.coin('toas');
67+
const transaction = (await baseCoin.recover({
68+
userKey: mockDataUnsignedSweep.userKey,
69+
backupKey: mockDataUnsignedSweep.backupKey,
70+
walletContractAddress: mockDataUnsignedSweep.walletBaseAddress,
71+
recoveryDestination: mockDataUnsignedSweep.recoveryDestination,
72+
isTss: true,
73+
eip1559: { maxFeePerGas: maxFeePerGasvalue, maxPriorityFeePerGas: maxPriorityFeePerGasValue },
74+
gasLimit: gasLimitvalue,
75+
replayProtectionOptions: {
76+
chain: chain_id,
77+
hardfork: 'london',
78+
},
79+
})) as UnsignedSweepTxMPCv2;
80+
should.exist(transaction);
81+
transaction.should.have.property('txRequests');
82+
transaction.txRequests.length.should.equal(1);
83+
const txRequest = transaction.txRequests[0];
84+
txRequest.should.have.property('walletCoin');
85+
txRequest.walletCoin.should.equal('toas');
86+
txRequest.should.have.property('transactions');
87+
txRequest.transactions.length.should.equal(1);
88+
const tx = txRequest.transactions[0];
89+
tx.should.have.property('nonce');
90+
tx.should.have.property('unsignedTx');
91+
tx.unsignedTx.should.have.property('serializedTxHex');
92+
tx.unsignedTx.should.have.property('signableHex');
93+
tx.unsignedTx.should.have.property('derivationPath');
94+
tx.unsignedTx.should.have.property('feeInfo');
95+
tx.unsignedTx.feeInfo?.should.have.property('fee');
96+
tx.unsignedTx.feeInfo?.should.have.property('feeString');
97+
tx.unsignedTx.should.have.property('parsedTx');
98+
tx.unsignedTx.parsedTx?.should.have.property('spendAmount');
99+
tx.unsignedTx.parsedTx?.should.have.property('outputs');
100+
});
101+
});

0 commit comments

Comments
 (0)