Skip to content

Commit c60c8c5

Browse files
committed
feat: recover function for vet recovery
Ticket: COIN-6042
1 parent 2882978 commit c60c8c5

8 files changed

Lines changed: 551 additions & 5 deletions

File tree

modules/bitgo/test/v2/lib/recovery-nocks.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,96 @@ module.exports.nockEthLikeRecovery = function (bitgo, nockData = nockEthData) {
416416
});
417417
};
418418

419+
module.exports.nockVetRecovery = function (bitgo) {
420+
// nock for account balance
421+
const url = Environments[bitgo.getEnv()].vetNodeUrl;
422+
nock(url).get('/accounts/0x88c2ab227908d39f6afdb85203dca3e937bb77af').reply(200, {
423+
balance: '0x8ac7230489e80000',
424+
energy: '0x5969b539627800',
425+
hasCode: false,
426+
});
427+
428+
nock(url).get('/blocks/best').reply(200, {
429+
number: 23107826,
430+
id: '0x016098f2a6779c3ad2bb52ef0a3f57c770af55a77bfa1b2837266f752118ad8d',
431+
size: 368,
432+
parentID: '0x016098f1acffb0125ffeca9b3e2491d31574d14b55a15e912e45e8081e063e0e',
433+
timestamp: 1761116630,
434+
gasLimit: 40000000,
435+
beneficiary: '0xae99cb89767a09d53e589a40cb4016974aba4b94',
436+
gasUsed: 0,
437+
totalScore: 218523577,
438+
txsRoot: '0x45b0cfc220ceec5b7c1c62c4d4193d38e4eba48e8815729ce75f9c0ab0e4c1c0',
439+
txsFeatures: 1,
440+
stateRoot: '0x7a5e7b3b8b89958e7fdd5e14acbc79dbc419672e84d02376a43b3beebe555e33',
441+
receiptsRoot: '0x45b0cfc220ceec5b7c1c62c4d4193d38e4eba48e8815729ce75f9c0ab0e4c1c0',
442+
com: true,
443+
signer: '0xae99cb89767a09d53e589a40cb4016974aba4b94',
444+
isTrunk: true,
445+
isFinalized: false,
446+
baseFeePerGas: '0x9184e72a000',
447+
transactions: [],
448+
});
449+
450+
nock(url)
451+
.post('/accounts/*', {
452+
clauses: [
453+
{
454+
to: '0xac05da78464520aa7c9d4c19bd7a440b111b3054',
455+
value: '10000000000000000000',
456+
data: '0x',
457+
},
458+
],
459+
caller: '0x88c2ab227908d39f6afdb85203dca3e937bb77af',
460+
})
461+
.reply(200, [
462+
{
463+
data: '0x',
464+
events: [],
465+
transfers: [
466+
{
467+
sender: '0x880ff4718587d678e78fc7803b3634bd12ecf019',
468+
recipient: '0xac05da78464520aa7c9d4c19bd7a440b111b3054',
469+
amount: '0x8ac7230489e80000',
470+
},
471+
],
472+
gasUsed: 0,
473+
reverted: false,
474+
vmError: '',
475+
},
476+
]);
477+
// nock for vtho balance for gas
478+
nock(url)
479+
.post('/accounts/*', {
480+
clauses: [
481+
{
482+
to: '0x0000000000000000000000000000456E65726779',
483+
value: '0x0',
484+
data: '0x70a0823100000000000000000000000088c2ab227908d39f6afdb85203dca3e937bb77af',
485+
},
486+
],
487+
})
488+
.reply(200, [
489+
{
490+
data: '0x000000000000000000000000000000000000000000000007e982789f8fe0cf0a',
491+
events: [],
492+
transfers: [],
493+
gasUsed: 870,
494+
reverted: false,
495+
vmError: '',
496+
},
497+
]);
498+
499+
nock(url)
500+
.post('/transactions', {
501+
raw: /^0x[0-9a-f]+$/i,
502+
})
503+
.reply(200, {
504+
id: '0x' + 'a'.repeat(64), // A fake transaction ID
505+
reverted: false,
506+
});
507+
};
508+
419509
module.exports.nockEtherscanRateLimitError = function () {
420510
const response = {
421511
status: '0',

modules/bitgo/test/v2/unit/recovery.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,4 +1457,75 @@ describe('Recovery:', function () {
14571457
(output.txRequests[0].transactions[0].unsignedTx.parsedTx as { outputs: any[] }).should.have.property('outputs');
14581458
});
14591459
});
1460+
1461+
describe('RecoverVet', function () {
1462+
beforeEach(() => {
1463+
nock.cleanAll();
1464+
});
1465+
let recoveryParams;
1466+
1467+
it('should construct a recovery tx with MPCv2 TSS', async function () {
1468+
recoveryNocks.nockVetRecovery(bitgo);
1469+
const basecoin = bitgo.coin('tvet');
1470+
const baseAddress = ethLikeDKLSKeycard.senderAddress;
1471+
recoveryParams = {
1472+
userKey: ethLikeDKLSKeycard.userKey,
1473+
backupKey: ethLikeDKLSKeycard.backupKey,
1474+
walletContractAddress: baseAddress,
1475+
recoveryDestination: ethLikeDKLSKeycard.destinationAddress,
1476+
walletPassphrase: ethLikeDKLSKeycard.walletPassphrase,
1477+
isTss: true,
1478+
};
1479+
1480+
const recovery = await basecoin.recover(recoveryParams);
1481+
1482+
should.exist(recovery);
1483+
recovery.should.have.property('id');
1484+
recovery.should.have.property('tx');
1485+
});
1486+
1487+
// it('should construct an unsigned sweep tx with TSS', async function () {
1488+
// recoveryNocks.nockEthLikeRecovery(bitgo, nockUnsignedSweepTSSData);
1489+
//
1490+
// const basecoin = bitgo.coin('hteth');
1491+
//
1492+
// const userKey =
1493+
// '0234eb39b22fed523ece7c78da29ba1f1de5b64a6e48013e0914de793bc1df0570e779de04758732734d97e54b782c8b336283811af6a2c57bd81438798e1c2446';
1494+
// const backupKey =
1495+
// '0234eb39b22fed523ece7c78da29ba1f1de5b64a6e48013e0914de793bc1df0570e779de04758732734d97e54b782c8b336283811af6a2c57bd81438798e1c2446';
1496+
//
1497+
// recoveryParams = {
1498+
// userKey: userKey,
1499+
// backupKey: backupKey,
1500+
// walletContractAddress: '0xe7406dc43d13f698fb41a345c7783d39a4c2d191',
1501+
// recoveryDestination: '0xac05da78464520aa7c9d4c19bd7a440b111b3054',
1502+
// walletPassphrase: TestBitGo.V2.TEST_RECOVERY_PASSCODE,
1503+
// isTss: true,
1504+
// gasPrice: '20000000000',
1505+
// gasLimit: '500000',
1506+
// replayProtectionOptions: {
1507+
// chain: 42,
1508+
// hardfork: 'london',
1509+
// },
1510+
// };
1511+
//
1512+
// const transaction = await basecoin.recover(recoveryParams);
1513+
// should.exist(transaction);
1514+
// const output = transaction as unknown as UnsignedSweepTxMPCv2;
1515+
// output.should.have.property('txRequests');
1516+
// output.txRequests.should.have.length(1);
1517+
// output.txRequests[0].should.have.property('transactions');
1518+
// output.txRequests[0].transactions.should.have.length(1);
1519+
// output.txRequests[0].should.have.property('walletCoin');
1520+
// output.txRequests[0].transactions[0].should.have.property('unsignedTx');
1521+
// output.txRequests[0].transactions[0].unsignedTx.should.have.property('serializedTxHex');
1522+
// output.txRequests[0].transactions[0].unsignedTx.should.have.property('signableHex');
1523+
// output.txRequests[0].transactions[0].unsignedTx.should.have.property('derivationPath');
1524+
// output.txRequests[0].transactions[0].unsignedTx.should.have.property('feeInfo');
1525+
// output.txRequests[0].transactions[0].unsignedTx.should.have.property('parsedTx');
1526+
// const parsedTx = output.txRequests[0].transactions[0].unsignedTx.parsedTx as { spendAmount: string };
1527+
// parsedTx.should.have.property('spendAmount');
1528+
// (output.txRequests[0].transactions[0].unsignedTx.parsedTx as { outputs: any[] }).should.have.property('outputs');
1529+
// });
1530+
});
14601531
});

modules/sdk-coin-vet/src/lib/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,9 @@ export const STARGATE_DELEGATION_ADDRESS = '0x4cb1c9ef05b529c093371264fab2c93cc6
1515

1616
export const STARGATE_NFT_ADDRESS_TESTNET = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
1717
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';
18+
19+
export const AVG_GAS_UNITS = '21000';
20+
export const EXPIRATION = 400;
21+
export const GAS_PRICE_COEF = '128';
22+
export const GAS_UNIT_PRICE = '10000000000000'; // vechain has fixed gas unit price of 10^13 wei
23+
export const COEF_DIVISOR = '255';

modules/sdk-coin-vet/src/lib/transaction/transaction.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class Transaction extends BaseTransaction {
3232
private _senderSignature: Buffer | null;
3333
private _feePayerAddress: string;
3434
private _feePayerSignature: Buffer | null;
35+
private _isRecovery: boolean;
3536

3637
constructor(_coinConfig: Readonly<CoinConfig>) {
3738
super(_coinConfig);
@@ -47,6 +48,7 @@ export class Transaction extends BaseTransaction {
4748
this._recipients = [];
4849
this._senderSignature = null;
4950
this._feePayerSignature = null;
51+
this._isRecovery = false;
5052
}
5153

5254
public get id(): string {
@@ -198,6 +200,14 @@ export class Transaction extends BaseTransaction {
198200
this._transactionData = transactionData;
199201
}
200202

203+
get isRecovery(): boolean {
204+
return this._isRecovery;
205+
}
206+
207+
set isRecovery(isRecovery: boolean) {
208+
this._isRecovery = isRecovery;
209+
}
210+
201211
/**
202212
* Get all signatures associated with this transaction
203213
* Required by BaseTransaction
@@ -337,6 +347,9 @@ export class Transaction extends BaseTransaction {
337347
if (halfSignedTransaction.signature) {
338348
this._rawTransaction = halfSignedTransaction;
339349
this._sender = halfSignedTransaction.origin.toString().toLowerCase();
350+
if (this.isRecovery) {
351+
this._id = halfSignedTransaction.id.toString();
352+
}
340353
} else {
341354
return;
342355
}
@@ -372,7 +385,7 @@ export class Transaction extends BaseTransaction {
372385
};
373386

374387
if (
375-
this.type === TransactionType.Send ||
388+
(this.type === TransactionType.Send && !this.isRecovery) ||
376389
this.type === TransactionType.SendToken ||
377390
this.type === TransactionType.SendNFT ||
378391
this.type === TransactionType.ContractCall ||

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
4545
return this;
4646
}
4747

48+
isRecovery(isRecovery: boolean): this {
49+
this._transaction.isRecovery = isRecovery;
50+
return this;
51+
}
52+
4853
/**
4954
* Sets the sender of this transaction.
5055
*

modules/sdk-coin-vet/src/lib/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,22 @@ export interface ClaimRewardsData {
1515
claimBaseRewards?: boolean;
1616
claimStakingRewards?: boolean;
1717
}
18+
19+
export type RecoverOptions = {
20+
userKey: string;
21+
backupKey: string;
22+
walletPassphrase?: string;
23+
walletContractAddress: string; // use this as walletBaseAddress for TSS
24+
recoveryDestination: string;
25+
isUnsignedSweep?: boolean; // specify if this is an unsigned recovery
26+
};
27+
28+
export interface RecoveryTransaction {
29+
id: string;
30+
tx: string;
31+
}
32+
33+
export interface UnsignedSweepRecoveryTransaction {
34+
txHex: string;
35+
coin: string;
36+
}

0 commit comments

Comments
 (0)