Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions modules/bitgo/test/v2/lib/recovery-nocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,96 @@ module.exports.nockEthLikeRecovery = function (bitgo, nockData = nockEthData) {
});
};

module.exports.nockVetRecovery = function (bitgo, baseAddress) {
// nock for account balance
const url = Environments[bitgo.getEnv()].vetNodeUrl;
nock(url).get(`/accounts/${baseAddress}`).reply(200, {
balance: '0x8ac7230489e80000',
energy: '0x5969b539627800',
hasCode: false,
});

nock(url).get('/blocks/best').reply(200, {
number: 23107826,
id: '0x016098f2a6779c3ad2bb52ef0a3f57c770af55a77bfa1b2837266f752118ad8d',
size: 368,
parentID: '0x016098f1acffb0125ffeca9b3e2491d31574d14b55a15e912e45e8081e063e0e',
timestamp: 1761116630,
gasLimit: 40000000,
beneficiary: '0xae99cb89767a09d53e589a40cb4016974aba4b94',
gasUsed: 0,
totalScore: 218523577,
txsRoot: '0x45b0cfc220ceec5b7c1c62c4d4193d38e4eba48e8815729ce75f9c0ab0e4c1c0',
txsFeatures: 1,
stateRoot: '0x7a5e7b3b8b89958e7fdd5e14acbc79dbc419672e84d02376a43b3beebe555e33',
receiptsRoot: '0x45b0cfc220ceec5b7c1c62c4d4193d38e4eba48e8815729ce75f9c0ab0e4c1c0',
com: true,
signer: '0xae99cb89767a09d53e589a40cb4016974aba4b94',
isTrunk: true,
isFinalized: false,
baseFeePerGas: '0x9184e72a000',
transactions: [],
});

nock(url)
.post('/accounts/*', {
clauses: [
{
to: '0xac05da78464520aa7c9d4c19bd7a440b111b3054',
value: '10000000000000000000',
data: '0x',
},
],
caller: `${baseAddress}`,
})
.reply(200, [
{
data: '0x',
events: [],
transfers: [
{
sender: `${baseAddress}`,
recipient: '0xac05da78464520aa7c9d4c19bd7a440b111b3054',
amount: '0x8ac7230489e80000',
},
],
gasUsed: 0,
reverted: false,
vmError: '',
},
]);
// nock for vtho balance for gas
nock(url)
.post('/accounts/*', {
clauses: [
{
to: '0x0000000000000000000000000000456E65726779',
value: '0x0',
data: `0x70a08231000000000000000000000000${baseAddress.slice(2)}`,
},
],
})
.reply(200, [
{
data: '0x000000000000000000000000000000000000000000000007e982789f8fe0cf0a',
events: [],
transfers: [],
gasUsed: 870,
reverted: false,
vmError: '',
},
]);

nock(url)
.post('/transactions', {
raw: /^0x[0-9a-f]+$/i,
})
.reply(200, {
id: '0x' + 'a'.repeat(64), // A fake transaction ID
reverted: false,
});
};

module.exports.nockEtherscanRateLimitError = function () {
const response = {
status: '0',
Expand Down
44 changes: 44 additions & 0 deletions modules/bitgo/test/v2/unit/recovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1457,4 +1457,48 @@ describe('Recovery:', function () {
(output.txRequests[0].transactions[0].unsignedTx.parsedTx as { outputs: any[] }).should.have.property('outputs');
});
});

describe('RecoverVet', function () {
beforeEach(() => {
nock.cleanAll();
});
let recoveryParams;

it('should construct a recovery tx with MPCv2 TSS', async function () {
const basecoin = bitgo.coin('tvet');
const baseAddress = ethLikeDKLSKeycard.senderAddress;
recoveryNocks.nockVetRecovery(bitgo, baseAddress);
recoveryParams = {
userKey: ethLikeDKLSKeycard.userKey,
backupKey: ethLikeDKLSKeycard.backupKey,
walletContractAddress: baseAddress,
recoveryDestination: ethLikeDKLSKeycard.destinationAddress,
walletPassphrase: ethLikeDKLSKeycard.walletPassphrase,
isTss: true,
};

const recovery = await basecoin.recover(recoveryParams);

should.exist(recovery);
recovery.should.have.property('id');
recovery.should.have.property('tx');
});

it('should construct an unsigned sweep tx with TSS', async function () {
recoveryNocks.nockVetRecovery(bitgo, '0xad848d2c97a08b2cd5e7f28f76ecd45dd0f82e0e');
const basecoin = bitgo.coin('tvet');

const unsignedSweepRecoveryParams = {
bitgoKey:
'03f54983c529802697d9a2320ded23eb7f15118fcba01156356c2264f04d32b4caa77fcf8cf3f73547078e984f28787c4c1e694586214b609e45b6de9cc32ad6e5',
recoveryDestination: ethLikeDKLSKeycard.destinationAddress,
};

const recovery = await basecoin.recover(unsignedSweepRecoveryParams);
should.exist(recovery);
recovery.should.have.property('txHex');
recovery.should.have.property('coin');
recovery.coin.should.equal('tvet');
});
});
});
1 change: 1 addition & 0 deletions modules/sdk-coin-vet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@bitgo/sdk-core": "^36.15.0",
"@bitgo/secp256k1": "^1.6.0",
"@bitgo/statics": "^58.7.0",
"@bitgo/sdk-lib-mpc": "^10.8.1",
"@noble/curves": "1.8.1",
"@vechain/sdk-core": "^1.2.0-rc.3",
"bignumber.js": "^9.1.1",
Expand Down
6 changes: 6 additions & 0 deletions modules/sdk-coin-vet/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ export const STARGATE_DELEGATION_ADDRESS = '0x4cb1c9ef05b529c093371264fab2c93cc6

export const STARGATE_NFT_ADDRESS_TESTNET = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';

export const AVG_GAS_UNITS = '21000';
export const EXPIRATION = 400;
export const GAS_PRICE_COEF = '128';
export const GAS_UNIT_PRICE = '10000000000000'; // vechain has fixed gas unit price of 10^13 wei
export const COEF_DIVISOR = '255';
15 changes: 14 additions & 1 deletion modules/sdk-coin-vet/src/lib/transaction/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class Transaction extends BaseTransaction {
private _senderSignature: Buffer | null;
private _feePayerAddress: string;
private _feePayerSignature: Buffer | null;
private _isRecovery: boolean;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
Expand All @@ -47,6 +48,7 @@ export class Transaction extends BaseTransaction {
this._recipients = [];
this._senderSignature = null;
this._feePayerSignature = null;
this._isRecovery = false;
}

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

get isRecovery(): boolean {
return this._isRecovery;
}

set isRecovery(isRecovery: boolean) {
this._isRecovery = isRecovery;
}

/**
* Get all signatures associated with this transaction
* Required by BaseTransaction
Expand Down Expand Up @@ -337,6 +347,9 @@ export class Transaction extends BaseTransaction {
if (halfSignedTransaction.signature) {
this._rawTransaction = halfSignedTransaction;
this._sender = halfSignedTransaction.origin.toString().toLowerCase();
if (this.isRecovery) {
this._id = halfSignedTransaction.id.toString();
}
} else {
return;
}
Expand Down Expand Up @@ -372,7 +385,7 @@ export class Transaction extends BaseTransaction {
};

if (
this.type === TransactionType.Send ||
(this.type === TransactionType.Send && !this.isRecovery) ||
this.type === TransactionType.SendToken ||
this.type === TransactionType.SendNFT ||
this.type === TransactionType.ContractCall ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
return this;
}

isRecovery(isRecovery: boolean): this {
this._transaction.isRecovery = isRecovery;
return this;
}

/**
* Sets the sender of this transaction.
*
Expand Down
19 changes: 19 additions & 0 deletions modules/sdk-coin-vet/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,22 @@ export interface ClaimRewardsData {
claimBaseRewards?: boolean;
claimStakingRewards?: boolean;
}

export type RecoverOptions = {
userKey?: string;
backupKey?: string;
walletPassphrase?: string;
recoveryDestination: string;
isUnsignedSweep?: boolean; // specify if this is an unsigned recovery
bitgoKey?: string;
};

export interface RecoveryTransaction {
id: string;
tx: string;
}

export interface UnsignedSweepRecoveryTransaction {
txHex: string;
coin: string;
}
Loading