PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-sui/src
Просмотр файла: sui.ts
import {
BaseBroadcastTransactionOptions,
BaseBroadcastTransactionResult,
BaseCoin,
BaseTransaction,
BitGoBase,
EDDSAMethods,
EDDSAMethodTypes,
Environments,
InvalidAddressError,
KeyPair,
MPCAlgorithm,
MPCRecoveryOptions,
MPCConsolidationRecoveryOptions,
MPCSweepRecoveryOptions,
MPCSweepTxs,
MPCTx,
MPCTxs,
MPCUnsignedTx,
ParsedTransaction,
ParseTransactionOptions as BaseParseTransactionOptions,
RecoveryTxRequest,
SignedTransaction,
SignTransactionOptions,
TransactionExplanation,
TssVerifyAddressOptions,
VerifyTransactionOptions,
PopulatedIntent,
PrebuildTransactionWithIntentOptions,
MultisigType,
multisigTypes,
} from '@bitgo/sdk-core';
import { BaseCoin as StaticsBaseCoin, BaseNetwork, coins, SuiCoin } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import {
KeyPair as SuiKeyPair,
TokenTransferTransaction,
TransactionBuilder,
TransactionBuilderFactory,
TransferTransaction,
} from './lib';
import utils from './lib/utils';
import * as _ from 'lodash';
import { SuiObjectInfo, SuiTransactionType } from './lib/iface';
import {
DEFAULT_GAS_OVERHEAD,
DEFAULT_GAS_PRICE,
DEFAULT_SCAN_FACTOR,
MAX_GAS_BUDGET,
MAX_GAS_OBJECTS,
MAX_OBJECT_LIMIT,
TOKEN_OBJECT_LIMIT,
} from './lib/constants';
import { getDerivationPath } from '@bitgo/sdk-lib-mpc';
export interface ExplainTransactionOptions {
txHex: string;
}
export interface SuiParseTransactionOptions extends BaseParseTransactionOptions {
txHex: string;
}
interface TransactionOutput {
address: string;
amount: string;
}
type TransactionInput = TransactionOutput;
export interface SuiParsedTransaction extends ParsedTransaction {
// total assets being moved, including fees
inputs: TransactionInput[];
// where assets are moved to
outputs: TransactionOutput[];
fee: BigNumber;
}
export type SuiTransactionExplanation = TransactionExplanation;
export class Sui extends BaseCoin {
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly<StaticsBaseCoin>) {
super(bitgo);
if (!staticsCoin) {
throw new Error('missing required constructor parameter staticsCoin');
}
this._staticsCoin = staticsCoin;
}
static createInstance(bitgo: BitGoBase, staticsCoin?: Readonly<StaticsBaseCoin>): BaseCoin {
return new Sui(bitgo, staticsCoin);
}
/**
* Factor between the coin's base unit and its smallest subdivison
*/
public getBaseFactor(): number {
return 1e9;
}
public getChain(): string {
return 'sui';
}
public getFamily(): string {
return 'sui';
}
public getFullName(): string {
return 'Sui';
}
getNetwork(): BaseNetwork {
return this._staticsCoin.network;
}
/** @inheritDoc */
supportsTss(): boolean {
return true;
}
/** inherited doc */
getDefaultMultisigType(): MultisigType {
return multisigTypes.tss;
}
getMPCAlgorithm(): MPCAlgorithm {
return 'eddsa';
}
allowsAccountConsolidations(): boolean {
return true;
}
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
let totalAmount = new BigNumber(0);
const coinConfig = coins.get(this.getChain());
const { txPrebuild: txPrebuild, txParams: txParams } = params;
const transaction = new TransferTransaction(coinConfig);
const rawTx = txPrebuild.txHex;
if (!rawTx) {
throw new Error('missing required tx prebuild property txHex');
}
transaction.fromRawTransaction(Buffer.from(rawTx, 'hex').toString('base64'));
const explainedTx = transaction.explainTransaction();
if (txParams.recipients && txParams.recipients.length > 0) {
const filteredRecipients = txParams.recipients?.map((recipient) => {
const filteredRecipient = _.pick(recipient, ['address', 'amount']);
filteredRecipient.amount = new BigNumber(filteredRecipient.amount).toFixed();
return filteredRecipient;
});
const filteredOutputs = explainedTx.outputs.map((output) => {
const filteredOutput = _.pick(output, ['address', 'amount']);
filteredOutput.amount = new BigNumber(filteredOutput.amount).toFixed();
return filteredOutput;
});
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
throw new Error('Tx outputs does not match with expected txParams recipients');
}
for (const recipients of txParams.recipients) {
totalAmount = totalAmount.plus(recipients.amount);
}
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
throw new Error('Tx total amount does not match with expected total amount field');
}
}
return true;
}
async isWalletAddress(params: TssVerifyAddressOptions): Promise<boolean> {
const { address: newAddress } = params;
if (!this.isValidAddress(newAddress)) {
throw new InvalidAddressError(`invalid address: ${newAddress}`);
}
return true;
}
async parseTransaction(params: SuiParseTransactionOptions): Promise<SuiParsedTransaction> {
const transactionExplanation = await this.explainTransaction({ txHex: params.txHex });
if (!transactionExplanation) {
throw new Error('Invalid transaction');
}
let fee = new BigNumber(0);
const suiTransaction = transactionExplanation as SuiTransactionExplanation;
if (suiTransaction.outputs.length <= 0) {
return {
inputs: [],
outputs: [],
fee,
};
}
const senderAddress = suiTransaction.outputs[0].address;
if (suiTransaction.fee.fee !== '') {
fee = new BigNumber(suiTransaction.fee.fee);
}
// assume 1 sender, who is also the fee payer
const inputs = [
{
address: senderAddress,
amount: new BigNumber(suiTransaction.outputAmount).plus(fee).toFixed(),
},
];
const outputs: TransactionOutput[] = suiTransaction.outputs.map((output) => {
return {
address: output.address,
amount: new BigNumber(output.amount).toFixed(),
};
});
return {
inputs,
outputs,
fee,
};
}
generateKeyPair(seed?: Buffer): KeyPair {
const keyPair = seed ? new SuiKeyPair({ seed }) : new SuiKeyPair();
const keys = keyPair.getKeys();
if (!keys.prv) {
throw new Error('Missing prv in key generation.');
}
return {
pub: keys.pub,
prv: keys.prv,
};
}
isValidPub(_: string): boolean {
throw new Error('Method not implemented.');
}
isValidPrv(_: string): boolean {
throw new Error('Method not implemented.');
}
isValidAddress(address: string): boolean {
return utils.isValidAddress(address);
}
signTransaction(_: SignTransactionOptions): Promise<SignedTransaction> {
throw new Error('Method not implemented.');
}
/**
* Explain a Sui transaction
* @param params
*/
async explainTransaction(params: ExplainTransactionOptions): Promise<SuiTransactionExplanation> {
const factory = this.getBuilder();
let rebuiltTransaction: BaseTransaction;
try {
const transactionBuilder = factory.from(Buffer.from(params.txHex, 'hex').toString('base64'));
rebuiltTransaction = await transactionBuilder.build();
} catch {
throw new Error('Invalid transaction');
}
return rebuiltTransaction.explainTransaction();
}
private getBuilder(): TransactionBuilderFactory {
return new TransactionBuilderFactory(coins.get(this.getChain()));
}
private getAddressFromPublicKey(derivedPublicKey: string) {
// TODO(BG-59016) replace with account lib implementation
return utils.getAddressFromPublicKey(derivedPublicKey);
}
/** @inheritDoc */
async getSignablePayload(serializedTx: string): Promise<Buffer> {
const factory = this.getBuilder();
const rebuiltTransaction = await factory.from(serializedTx).build();
return rebuiltTransaction.signablePayload;
}
protected getPublicNodeUrl(): string {
return Environments[this.bitgo.getEnv()].suiNodeUrl;
}
protected async getBalance(owner: string, coinType?: string): Promise<string> {
const url = this.getPublicNodeUrl();
return await utils.getBalance(url, owner, coinType);
}
protected async getInputCoins(owner: string, coinType?: string): Promise<SuiObjectInfo[]> {
const url = this.getPublicNodeUrl();
return await utils.getInputCoins(url, owner, coinType);
}
protected async getFeeEstimate(txHex: string): Promise<BigNumber> {
const url = this.getPublicNodeUrl();
return await utils.getFeeEstimate(url, txHex);
}
/**
* Builds funds recovery transaction(s) without BitGo
*
* @param {MPCRecoveryOptions} params parameters needed to construct and
* (maybe) sign the transaction
*
* @returns {MPCTx | MPCSweepTxs} array of the serialized transaction hex strings and indices
* of the addresses being swept
*/
async recover(params: MPCRecoveryOptions): Promise<MPCTxs | MPCSweepTxs> {
if (!params.bitgoKey) {
throw new Error('missing bitgoKey');
}
if (!params.recoveryDestination || !this.isValidAddress(params.recoveryDestination)) {
throw new Error('invalid recoveryDestination');
}
const startIdx = utils.validateNonNegativeNumber(
0,
'Invalid starting index to scan for addresses',
params.startingScanIndex
);
const numIterations = utils.validateNonNegativeNumber(20, 'Invalid scanning factor', params.scan);
const endIdx = startIdx + numIterations;
const bitgoKey = params.bitgoKey.replace(/\s/g, '');
const MPC = await EDDSAMethods.getInitializedMpcInstance();
for (let idx = startIdx; idx < endIdx; idx++) {
const derivationPath = (params.seed ? getDerivationPath(params.seed) : 'm') + `/${idx}`;
const derivedPublicKey = MPC.deriveUnhardened(bitgoKey, derivationPath).slice(0, 64);
const senderAddress = this.getAddressFromPublicKey(derivedPublicKey);
let availableBalance = new BigNumber(0);
try {
availableBalance = new BigNumber(await this.getBalance(senderAddress));
} catch (e) {
continue;
}
if (availableBalance.minus(MAX_GAS_BUDGET).toNumber() <= 0) {
continue;
}
// check for possible token recovery, recover the token provide by user
if (params.tokenContractAddress) {
const token = utils.getSuiTokenFromAddress(params.tokenContractAddress!, this.getNetwork()) as SuiCoin;
if (!token) {
throw new Error(`Sui Token Package ID not supported.`);
}
const coinType = `${token.packageId}::${token.module}::${token.symbol}`;
try {
const availableTokenBalance = new BigNumber(await this.getBalance(senderAddress, coinType));
if (availableTokenBalance.toNumber() <= 0) {
continue;
}
} catch (e) {
continue;
}
return this.recoverSuiToken(params, token, senderAddress, derivationPath, derivedPublicKey, idx, bitgoKey);
}
let inputCoins = await this.getInputCoins(senderAddress);
inputCoins = inputCoins.sort((a, b) => {
return b.balance.minus(a.balance).toNumber();
});
if (inputCoins.length > MAX_OBJECT_LIMIT) {
inputCoins = inputCoins.slice(0, MAX_OBJECT_LIMIT);
}
let netAmount = inputCoins.reduce((acc, obj) => acc.plus(obj.balance), new BigNumber(0));
netAmount = netAmount.minus(MAX_GAS_BUDGET);
const recipients = [
{
address: params.recoveryDestination,
amount: netAmount.toString(),
},
];
// first build the unsigned txn
const factory = new TransactionBuilderFactory(coins.get(this.getChain()));
const txBuilder = factory
.getTransferBuilder()
.type(SuiTransactionType.Transfer)
.sender(senderAddress)
.send(recipients)
.gasData({
owner: senderAddress,
price: DEFAULT_GAS_PRICE,
budget: MAX_GAS_BUDGET,
payment: inputCoins,
});
const tempTx = (await txBuilder.build()) as TransferTransaction;
const feeEstimate = await this.getFeeEstimate(tempTx.toBroadcastFormat());
const gasBudget = Math.trunc(feeEstimate.toNumber() * DEFAULT_GAS_OVERHEAD);
netAmount = netAmount.plus(MAX_GAS_BUDGET).minus(gasBudget);
recipients[0].amount = netAmount.toString();
txBuilder.send(recipients);
txBuilder.gasData({
owner: senderAddress,
price: DEFAULT_GAS_PRICE,
budget: gasBudget,
payment: inputCoins,
});
const isUnsignedSweep = !params.userKey && !params.backupKey && !params.walletPassphrase;
if (isUnsignedSweep) {
return this.buildUnsignedSweepTransaction(txBuilder, senderAddress, bitgoKey, idx, derivationPath);
}
await this.signRecoveryTransaction(txBuilder, params, derivationPath, derivedPublicKey, false);
const tx = (await txBuilder.build()) as TransferTransaction;
return {
transactions: [
{
scanIndex: idx,
recoveryAmount: netAmount.toString(),
serializedTx: tx.toBroadcastFormat(),
signature: Buffer.from(tx.serializedSig).toString('base64'),
coin: this.getChain(),
},
],
lastScanIndex: idx,
};
}
throw new Error(
`Did not find an address with sufficient funds to recover. Please start the next scan at address index ${endIdx}. If it is token transaction, please keep sufficient Sui balance in the address for the transaction fee.`
);
}
private async recoverSuiToken(
params: MPCRecoveryOptions,
token: SuiCoin,
senderAddress: string,
derivationPath: string,
derivedPublicKey: string,
idx: number,
bitgoKey: string
): Promise<MPCTxs | MPCSweepTxs> {
const coinType = `${token.packageId}::${token.module}::${token.symbol}`;
let tokenObjects = await this.getInputCoins(senderAddress, coinType);
tokenObjects = tokenObjects.sort((a, b) => {
return b.balance.minus(a.balance).toNumber();
});
if (tokenObjects.length > TOKEN_OBJECT_LIMIT) {
tokenObjects = tokenObjects.slice(0, TOKEN_OBJECT_LIMIT);
}
const netAmount = tokenObjects.reduce((acc, obj) => acc.plus(obj.balance), new BigNumber(0));
const recipients = [
{
address: params.recoveryDestination,
amount: netAmount.toString(),
},
];
const gasAmount = new BigNumber(MAX_GAS_BUDGET);
let gasObjects = await this.getInputCoins(senderAddress);
gasObjects = utils.selectObjectsInDescOrderOfBalance(gasObjects, gasAmount);
if (gasObjects.length >= MAX_GAS_OBJECTS) {
gasObjects = gasObjects.slice(0, MAX_GAS_OBJECTS - 1);
}
// first build the unsigned txn
const factory = new TransactionBuilderFactory(token);
const txBuilder = factory
.getTokenTransferBuilder()
.type(SuiTransactionType.TokenTransfer)
.sender(senderAddress)
.send(recipients)
.inputObjects(tokenObjects)
.gasData({
owner: senderAddress,
price: DEFAULT_GAS_PRICE,
budget: MAX_GAS_BUDGET,
payment: gasObjects,
});
const tempTx = (await txBuilder.build()) as TokenTransferTransaction;
const feeEstimate = await this.getFeeEstimate(tempTx.toBroadcastFormat());
const gasBudget = Math.trunc(feeEstimate.toNumber() * DEFAULT_GAS_OVERHEAD);
txBuilder.gasData({
owner: senderAddress,
price: DEFAULT_GAS_PRICE,
budget: gasBudget,
payment: gasObjects,
});
const isUnsignedSweep = !params.userKey && !params.backupKey && !params.walletPassphrase;
if (isUnsignedSweep) {
return this.buildUnsignedSweepTransaction(txBuilder, senderAddress, bitgoKey, idx, derivationPath, token);
}
await this.signRecoveryTransaction(txBuilder, params, derivationPath, derivedPublicKey, true);
const tx = (await txBuilder.build()) as TokenTransferTransaction;
return {
transactions: [
{
scanIndex: idx,
recoveryAmount: netAmount.toString(),
serializedTx: tx.toBroadcastFormat(),
signature: Buffer.from(tx.serializedSig).toString('base64'),
coin: token.name,
},
],
lastScanIndex: idx,
};
}
private async buildUnsignedSweepTransaction(
txBuilder: TransactionBuilder,
senderAddress: string,
bitgoKey: string,
index: number,
derivationPath: string,
token?: SuiCoin
): Promise<MPCSweepTxs> {
const isTokenTransaction = !!token;
const unsignedTransaction = isTokenTransaction
? ((await txBuilder.build()) as TokenTransferTransaction)
: ((await txBuilder.build()) as TransferTransaction);
const serializedTx = unsignedTransaction.toBroadcastFormat();
const serializedTxHex = Buffer.from(serializedTx, 'base64').toString('hex');
const parsedTx = await this.parseTransaction({ txHex: serializedTxHex });
const walletCoin = isTokenTransaction ? token.name : this.getChain();
const output = parsedTx.outputs[0];
const inputs = [
{
address: senderAddress,
valueString: output.amount,
value: new BigNumber(output.amount),
},
];
const outputs = [
{
address: output.address,
valueString: output.amount,
coinName: walletCoin,
},
];
const spendAmount = output.amount;
const completedParsedTx = {
inputs: inputs,
outputs: outputs,
spendAmount: spendAmount,
type: isTokenTransaction ? SuiTransactionType.TokenTransfer : SuiTransactionType.Transfer,
};
const fee = parsedTx.fee;
const feeInfo = { fee: fee.toNumber(), feeString: fee.toString() };
const coinSpecific = { commonKeychain: bitgoKey };
const transaction: MPCTx = {
serializedTx: serializedTxHex,
scanIndex: index,
coin: walletCoin,
signableHex: unsignedTransaction.signablePayload.toString('hex'),
derivationPath,
parsedTx: completedParsedTx,
feeInfo: feeInfo,
coinSpecific: coinSpecific,
};
const unsignedTx: MPCUnsignedTx = { unsignedTx: transaction, signatureShares: [] };
const transactions: MPCUnsignedTx[] = [unsignedTx];
const txRequest: RecoveryTxRequest = {
transactions: transactions,
walletCoin: walletCoin,
};
return { txRequests: [txRequest] };
}
private async signRecoveryTransaction(
txBuilder: TransactionBuilder,
params: MPCRecoveryOptions,
derivationPath: string,
derivedPublicKey: string,
isTokenTransaction: boolean
) {
// TODO(BG-51092): This looks like a common part which can be extracted out too
const unsignedTx = isTokenTransaction
? ((await txBuilder.build()) as TokenTransferTransaction)
: ((await txBuilder.build()) as TransferTransaction);
if (!params.userKey) {
throw new Error('missing userKey');
}
if (!params.backupKey) {
throw new Error('missing backupKey');
}
if (!params.walletPassphrase) {
throw new Error('missing wallet passphrase');
}
// Clean up whitespace from entered values
const userKey = params.userKey.replace(/\s/g, '');
const backupKey = params.backupKey.replace(/\s/g, '');
// Decrypt private keys from KeyCard values
let userPrv: string;
try {
userPrv = this.bitgo.decrypt({
input: userKey,
password: params.walletPassphrase,
});
} catch (e) {
throw new Error(`Error decrypting user keychain: ${e.message}`);
}
/** TODO BG-52419 Implement Codec for parsing */
const userSigningMaterial = JSON.parse(userPrv) as EDDSAMethodTypes.UserSigningMaterial;
let backupPrv: string;
try {
backupPrv = this.bitgo.decrypt({
input: backupKey,
password: params.walletPassphrase,
});
} catch (e) {
throw new Error(`Error decrypting backup keychain: ${e.message}`);
}
const backupSigningMaterial = JSON.parse(backupPrv) as EDDSAMethodTypes.BackupSigningMaterial;
/* ********************** END ***********************************/
// add signature
const signatureHex = await EDDSAMethods.getTSSSignature(
userSigningMaterial,
backupSigningMaterial,
derivationPath,
unsignedTx
);
txBuilder.addSignature({ pub: derivedPublicKey }, signatureHex);
}
async broadcastTransaction({
transactions,
}: BaseBroadcastTransactionOptions): Promise<BaseBroadcastTransactionResult> {
const txIds: string[] = [];
const url = this.getPublicNodeUrl();
let digest = '';
if (!!transactions) {
for (const txn of transactions) {
try {
digest = await utils.executeTransactionBlock(url, txn.serializedTx, [txn.signature!]);
} catch (e) {
throw new Error(`Failed to broadcast transaction, error: ${e.message}`);
}
txIds.push(digest);
}
}
return { txIds };
}
/** inherited doc */
async createBroadcastableSweepTransaction(params: MPCSweepRecoveryOptions): Promise<MPCTxs> {
const req = params.signatureShares;
const broadcastableTransactions: MPCTx[] = [];
let lastScanIndex = 0;
for (let i = 0; i < req.length; i++) {
const MPC = await EDDSAMethods.getInitializedMpcInstance();
const transaction = req[i].txRequest.transactions[0].unsignedTx;
if (!req[i].ovc || !req[i].ovc[0].eddsaSignature) {
throw new Error('Missing signature(s)');
}
const signature = req[i].ovc[0].eddsaSignature;
if (!transaction.signableHex) {
throw new Error('Missing signable hex');
}
const messageBuffer = Buffer.from(transaction.signableHex!, 'hex');
const result = MPC.verify(messageBuffer, signature);
if (!result) {
throw new Error('Invalid signature');
}
const signatureHex = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]);
const serializedTxBase64 = Buffer.from(transaction.serializedTx, 'hex').toString('base64');
const txBuilder = this.getBuilder().from(serializedTxBase64);
if (!transaction.coinSpecific?.commonKeychain) {
throw new Error('Missing common keychain');
}
const commonKeychain = transaction.coinSpecific!.commonKeychain! as string;
if (!transaction.derivationPath) {
throw new Error('Missing derivation path');
}
const derivationPath = transaction.derivationPath as string;
const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath).slice(0, 64);
// add combined signature from ovc
txBuilder.addSignature({ pub: derivedPublicKey }, signatureHex);
const signedTransaction = (await txBuilder.build()) as TransferTransaction;
const serializedTx = signedTransaction.toBroadcastFormat();
const outputAmount = signedTransaction.explainTransaction().outputAmount;
broadcastableTransactions.push({
serializedTx: serializedTx,
scanIndex: transaction.scanIndex,
signature: Buffer.from(signedTransaction.serializedSig).toString('base64'),
recoveryAmount: outputAmount.toString(),
});
if (i === req.length - 1 && transaction.coinSpecific!.lastScanIndex) {
lastScanIndex = transaction.coinSpecific!.lastScanIndex as number;
}
}
return { transactions: broadcastableTransactions, lastScanIndex };
}
/**
* Builds native SUI recoveries of receive addresses in batch without BitGo.
* Funds will be recovered to base address first. You need to initiate another sweep txn after that.
*
* @param {MPCConsolidationRecoveryOptions} params - options for consolidation recovery.
* @param {string} [params.startingScanIndex] - receive address index to start scanning from. default to 1 (inclusive).
* @param {string} [params.endingScanIndex] - receive address index to end scanning at. default to startingScanIndex + 20 (exclusive).
*/
async recoverConsolidations(params: MPCConsolidationRecoveryOptions): Promise<MPCTxs | MPCSweepTxs> {
const isUnsignedSweep = !params.userKey && !params.backupKey && !params.walletPassphrase;
const startIdx = utils.validateNonNegativeNumber(
1,
'Invalid starting index to scan for addresses',
params.startingScanIndex
);
const endIdx = utils.validateNonNegativeNumber(
startIdx + DEFAULT_SCAN_FACTOR,
'Invalid ending index to scan for addresses',
params.endingScanIndex
);
if (startIdx < 1 || endIdx <= startIdx || endIdx - startIdx > 10 * DEFAULT_SCAN_FACTOR) {
throw new Error(
`Invalid starting or ending index to scan for addresses. startingScanIndex: ${startIdx}, endingScanIndex: ${endIdx}.`
);
}
const bitgoKey = params.bitgoKey.replace(/\s/g, '');
const MPC = await EDDSAMethods.getInitializedMpcInstance();
const derivationPath = (params.seed ? getDerivationPath(params.seed) : 'm') + '/0';
const derivedPublicKey = MPC.deriveUnhardened(bitgoKey, derivationPath).slice(0, 64);
const baseAddress = this.getAddressFromPublicKey(derivedPublicKey);
const consolidationTransactions: any[] = [];
let lastScanIndex = startIdx;
for (let idx = startIdx; idx < endIdx; idx++) {
const recoverParams = {
userKey: params.userKey,
backupKey: params.backupKey,
bitgoKey: params.bitgoKey,
walletPassphrase: params.walletPassphrase,
seed: params.seed,
tokenContractAddress: params.tokenContractAddress,
recoveryDestination: baseAddress,
startingScanIndex: idx,
scan: 1,
};
let recoveryTransaction: MPCTxs | MPCSweepTxs;
try {
recoveryTransaction = await this.recover(recoverParams);
} catch (e) {
if (e.message.startsWith('Did not find an address with sufficient funds to recover.')) {
lastScanIndex = idx;
continue;
}
throw e;
}
if (isUnsignedSweep) {
consolidationTransactions.push((recoveryTransaction as MPCSweepTxs).txRequests[0]);
} else {
consolidationTransactions.push((recoveryTransaction as MPCTxs).transactions[0]);
}
lastScanIndex = idx;
}
if (consolidationTransactions.length === 0) {
throw new Error(
`Did not find an address with sufficient funds to recover. Please start the next scan at address index ${
lastScanIndex + 1
}.`
);
}
if (isUnsignedSweep) {
// lastScanIndex will be used to inform user the last address index scanned for available funds (so they can
// appropriately adjust the scan range on the next iteration of consolidation recoveries). In the case of unsigned
// sweep consolidations, this lastScanIndex will be provided in the coinSpecific of the last txn made.
consolidationTransactions[
consolidationTransactions.length - 1
].transactions[0].unsignedTx.coinSpecific.lastScanIndex = lastScanIndex;
return { txRequests: consolidationTransactions };
}
return { transactions: consolidationTransactions, lastScanIndex };
}
/** inherited doc */
setCoinSpecificFieldsInIntent(intent: PopulatedIntent, params: PrebuildTransactionWithIntentOptions): void {
intent.unspents = params.unspents;
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!