diff --git a/modules/sdk-coin-vet/package.json b/modules/sdk-coin-vet/package.json index 2711b18a49..5622796823 100644 --- a/modules/sdk-coin-vet/package.json +++ b/modules/sdk-coin-vet/package.json @@ -46,7 +46,10 @@ "@bitgo/secp256k1": "^1.3.3", "ethereumjs-util": "7.1.5", "bignumber.js": "^9.1.1", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "lodash": "^4.17.21", + "@vechain/sdk-core": "^1.2.0-rc.3", + "@noble/curves": "1.8.1" }, "devDependencies": { "@bitgo/sdk-api": "^1.63.3", diff --git a/modules/sdk-coin-vet/src/lib/iface.ts b/modules/sdk-coin-vet/src/lib/iface.ts index 546ef821e8..e5b6b0af67 100644 --- a/modules/sdk-coin-vet/src/lib/iface.ts +++ b/modules/sdk-coin-vet/src/lib/iface.ts @@ -1,6 +1,16 @@ +import { + TransactionExplanation as BaseTransactionExplanation, + TransactionType as BitGoTransactionType, +} from '@bitgo/sdk-core'; + /** * The transaction data returned from the toJson() function of a transaction */ export interface TxData { id: string; } + +export interface VetTransactionExplanation extends BaseTransactionExplanation { + sender?: string; + type?: BitGoTransactionType; +} diff --git a/modules/sdk-coin-vet/src/lib/index.ts b/modules/sdk-coin-vet/src/lib/index.ts index 0394ae4730..cdba777f69 100644 --- a/modules/sdk-coin-vet/src/lib/index.ts +++ b/modules/sdk-coin-vet/src/lib/index.ts @@ -3,8 +3,8 @@ import * as Utils from './utils'; import * as Interface from './iface'; export { KeyPair } from './keyPair'; -export { Transaction } from './transaction'; -export { TransactionBuilder } from './transactionBuilder'; -export { TransferBuilder } from './transferBuilder'; +export { Transaction } from './transaction/transaction'; +export { TransactionBuilder } from './transactionBuilder/transactionBuilder'; +export { TransferBuilder } from './transactionBuilder/transferBuilder'; export { TransactionBuilderFactory } from './transactionBuilderFactory'; export { Constants, Utils, Interface }; diff --git a/modules/sdk-coin-vet/src/lib/transaction.ts b/modules/sdk-coin-vet/src/lib/transaction.ts deleted file mode 100644 index 431c397b49..0000000000 --- a/modules/sdk-coin-vet/src/lib/transaction.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BaseKey, BaseTransaction } from '@bitgo/sdk-core'; -import { TxData } from './iface'; - -export class Transaction extends BaseTransaction { - canSign(key: BaseKey): boolean { - return false; - } - - toBroadcastFormat(): string { - throw new Error('Method not implemented.'); - } - - toJson(): TxData { - throw new Error('Method not implemented.'); - } -} diff --git a/modules/sdk-coin-vet/src/lib/transaction/transaction.ts b/modules/sdk-coin-vet/src/lib/transaction/transaction.ts new file mode 100644 index 0000000000..68f9800be7 --- /dev/null +++ b/modules/sdk-coin-vet/src/lib/transaction/transaction.ts @@ -0,0 +1,525 @@ +import BigNumber from 'bignumber.js'; +import { + BaseTransaction, + TransactionType, + InvalidTransactionError, + TransactionRecipient, + PublicKey, +} from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { + TransactionBody, + TransactionClause, + Transaction as VetTransaction, + Secp256k1, + HexUInt, +} from '@vechain/sdk-core'; +import * as nc_utils from '@noble/curves/abstract/utils'; +import utils from '../utils'; +import { VetTransactionExplanation } from '../iface'; + +export interface VetTransactionData { + id: string; + chainTag: number; + blockRef: string; + expiration: number; + gasPriceCoef: number; + gas: number; + dependsOn: string | null; + nonce: number; + sender: string; + feePayer: string; + recipients: TransactionRecipient[]; +} + +const gasPrice = 1e13; + +export class Transaction extends BaseTransaction { + protected _rawTransaction: VetTransaction; + protected _type: TransactionType; + protected _recipients: TransactionRecipient[]; + private _chainTag: number; + private _blockRef: string; + private _expiration: number; + private _clauses: TransactionClause[]; + private _gasPriceCoef: number; + private _gas: number; + private _dependsOn: string | null; + private _nonce: number; + private _sender: string; + private _senderSignature: Buffer; + private _feePayerAddress: string; + private _feePayerSignature: Buffer; + private _feePayerPubKey: PublicKey; + + static EMPTY_PUBLIC_KEY = Buffer.alloc(32); + static EMPTY_SIGNATURE = Buffer.alloc(64); + + constructor(_coinConfig: Readonly) { + super(_coinConfig); + this._type = TransactionType.Send; + this._chainTag = 0x27; // Initialize to 39 (0x27) + this._blockRef = '0x0'; + this._expiration = 64; + this._clauses = []; + this._gasPriceCoef = 128; + this._gas = 0; + this._dependsOn = null; + this._nonce = 0x0; + this._recipients = []; + this._feePayerPubKey = { + pub: HexUInt.of(Transaction.EMPTY_PUBLIC_KEY).toString(), + }; + this._senderSignature = Transaction.EMPTY_SIGNATURE; + this._feePayerSignature = Transaction.EMPTY_SIGNATURE; + } + + public get id(): string { + this.generateTxnId(); + return this._id ?? 'UNAVAILABLE'; + } + + get rawTransaction(): VetTransaction { + return this._rawTransaction; + } + + set rawTransaction(rawTx: VetTransaction) { + this._rawTransaction = rawTx; + } + + get type(): TransactionType { + return this._type; + } + + set type(type: TransactionType) { + this._type = type; + } + + get sender(): string { + return this._sender; + } + + set sender(address: string) { + this._sender = address; + } + + get senderSignature(): Uint8Array | undefined { + return this._senderSignature; + } + + set senderSignature(sig: Buffer) { + this._senderSignature = sig; + } + + get feePayerAddress(): string { + return this._feePayerAddress; + } + + set feePayerAddress(address: string) { + this._feePayerAddress = address; + } + + get feePayerSignature(): Uint8Array { + return this._feePayerSignature; + } + + set feePayerSignature(sig: Buffer) { + this._feePayerSignature = sig; + } + + get chainTag(): number { + return this._chainTag; + } + + set chainTag(tag: number) { + this._chainTag = tag; + } + + get blockRef(): string { + return this._blockRef; + } + + set blockRef(ref: string) { + this._blockRef = ref; + } + + get expiration(): number { + return this._expiration; + } + + set expiration(exp: number) { + this._expiration = exp; + } + + get recipients(): TransactionRecipient[] { + return this._recipients; + } + + set recipients(recipients: TransactionRecipient[]) { + this._recipients = recipients; + } + + get clauses(): TransactionClause[] { + return this._clauses; + } + + set clauses(clauses: TransactionClause[]) { + this._clauses = clauses; + } + + get gasPriceCoef(): number { + return this._gasPriceCoef; + } + + set gasPriceCoef(coef: number) { + this._gasPriceCoef = coef; + } + + get gas(): number { + return this._gas; + } + + set gas(g: number) { + this._gas = g; + } + + get dependsOn(): string | null { + return this._dependsOn; + } + + set dependsOn(dep: string | null) { + this._dependsOn = dep; + } + + get nonce(): number { + return this._nonce; + } + + set nonce(n: number) { + this._nonce = n; + } + + get feePayerPubKey(): PublicKey { + return this._feePayerPubKey; + } + + set feePayerPubKey(pubKey: PublicKey) { + this._feePayerPubKey = pubKey; + } + + /** + * Get all signatures associated with this transaction + * Required by BaseTransaction + */ + get signature(): string[] { + const sigs: string[] = []; + if (this._senderSignature) { + sigs.push(Buffer.from(this._senderSignature).toString('hex')); + } + if (this._feePayerSignature) { + sigs.push(Buffer.from(this._feePayerSignature).toString('hex')); + } + return sigs; + } + + /** + * Set signatures for this transaction + * Required by BaseTransaction + * Note: This is mainly for compatibility with the base class. + * Prefer using senderSignature and feePayerSignature directly. + */ + set signature(sigs: string[]) { + if (sigs.length > 0) { + this._senderSignature = Buffer.from(sigs[0], 'hex'); + } + if (sigs.length > 1) { + this._feePayerSignature = Buffer.from(sigs[1], 'hex'); + } + } + + /** @inheritdoc */ + canSign(): boolean { + return true; + } + + /** + * Check if this is a fee delegated transaction + */ + isFeeDelegated(): boolean { + return !!this._feePayerAddress; + } + + addRecipients(clauses: TransactionClause[]): void { + this._recipients = clauses.map((clause) => ({ + address: (clause.to || '0x0').toString().toLowerCase(), + amount: (clause.value || '0').toString(), + })); + } + + buildClauses(): void { + this._clauses = this._recipients.map((recipient) => ({ + to: recipient.address, + value: recipient.amount, + data: '0x', + })); + } + + /** + * Populates the transaction object with data from a deserialized VeChain transaction + * @param signedTx - The deserialized VeChain transaction containing body, signatures, and other transaction data + * @throws {InvalidTransactionError} When the transaction is invalid or missing required data + */ + fromDeserializedSignedTransaction(signedTx: VetTransaction): void { + try { + if (!signedTx || !signedTx.body) { + throw new InvalidTransactionError('Invalid transaction: missing transaction body'); + } + + // Store the raw transaction + this.rawTransaction = signedTx; + + // Set transaction body properties + const body = signedTx.body; + this.chainTag = typeof body.chainTag === 'number' ? body.chainTag : 0; + this.blockRef = body.blockRef || '0x0'; + this.expiration = typeof body.expiration === 'number' ? body.expiration : 64; + this.clauses = body.clauses || []; + this.gasPriceCoef = typeof body.gasPriceCoef === 'number' ? body.gasPriceCoef : 128; + this.gas = typeof body.gas === 'number' ? body.gas : Number(body.gas) || 0; + this.dependsOn = body.dependsOn || null; + this.nonce = typeof body.nonce === 'number' ? body.nonce : Number(body.nonce) || 0; + // Set recipients from clauses + this.recipients = body.clauses.map((clause) => ({ + address: (clause.to || '0x0').toString().toLowerCase(), + amount: Number(clause.value).toString(), + })); + this.loadInputsAndOutputs(); + + // Set sender address + if (signedTx.origin) { + this.sender = signedTx.origin.toString().toLowerCase(); + } + + // Set signatures if present + if (signedTx.signature) { + // First signature is sender's signature + this.senderSignature = Buffer.from(signedTx.signature.slice(0, Secp256k1.SIGNATURE_LENGTH)); + + // If there's additional signature data, it's the fee payer's signature + if (signedTx.signature.length > Secp256k1.SIGNATURE_LENGTH) { + this.feePayerSignature = Buffer.from(signedTx.signature.slice(Secp256k1.SIGNATURE_LENGTH)); + } + } + } catch (e) { + throw new InvalidTransactionError(`Failed to deserialize transaction: ${e.message}`); + } + } + + addSenderSignature(signature: Buffer): void { + this.senderSignature = signature; + } + + getFeePayerPubKey(): string { + return this.feePayerPubKey.pub; + } + + addFeePayerSignature(publicKey: PublicKey, signature: Buffer): void { + this.feePayerPubKey = publicKey; + this.feePayerSignature = signature; + } + + async build(): Promise { + this.buildClauses(); + await this.buildRawTransaction(); + this.generateTxnId(); + this.loadInputsAndOutputs(); + } + + /** + * Sets the transaction ID from the raw transaction if it is signed + * @private + */ + private generateTxnId(): void { + // Check if we have a raw transaction + if (!this.rawTransaction) { + return; + } + + // Check if the transaction is signed by verifying signature exists + if (!this.senderSignature || !this.feePayerSignature) { + return; + } + + const halfSignedTransaction: VetTransaction = VetTransaction.of(this.rawTransaction.body, this.senderSignature); + if (!halfSignedTransaction.signature) { + return; + } + const fullSignedTransaction: VetTransaction = VetTransaction.of( + halfSignedTransaction.body, + nc_utils.concatBytes( + // Drop any previous gas payer signature. + halfSignedTransaction.signature.slice(0, Secp256k1.SIGNATURE_LENGTH), + this.feePayerSignature + ) + ); + if (!fullSignedTransaction.signature) { + return; + } + try { + this._id = fullSignedTransaction.id.toString(); + } catch (e) { + return; + } + } + + protected async buildRawTransaction(): Promise { + const transactionBody: TransactionBody = { + chainTag: this.chainTag, + blockRef: this.blockRef, + expiration: 64, //move this value to constants + clauses: this.clauses, + gasPriceCoef: this.gasPriceCoef, + gas: this.gas, + dependsOn: null, + nonce: this.nonce, + reserved: { + features: 1, // mark transaction as delegated i.e. will use gas payer + }, + }; + + this.rawTransaction = VetTransaction.of(transactionBody); + } + + loadInputsAndOutputs(): void { + const totalAmount = this._recipients.reduce( + (accumulator, current) => accumulator.plus(current.amount), + new BigNumber('0') + ); + this._inputs = [ + { + address: this.sender, + value: totalAmount.toString(), + coin: this._coinConfig.name, + }, + ]; + this._outputs = this._recipients.map((recipient) => { + return { + address: recipient.address, + value: recipient.amount as string, + coin: this._coinConfig.name, + }; + }); + } + + fromRawTransaction(rawTransaction: string): void { + let signedTxn: VetTransaction; + try { + signedTxn = utils.deserializeTransaction(rawTransaction); + } catch (e) { + throw new Error('invalid raw transaction'); + } + this.fromDeserializedSignedTransaction(signedTxn); + } + + /** @inheritdoc */ + toJson(): VetTransactionData { + const json: VetTransactionData = { + id: this.id, + chainTag: this.chainTag, + blockRef: this.blockRef, + expiration: this.expiration, + recipients: this.recipients, + gasPriceCoef: this.gasPriceCoef, + gas: this.gas, + dependsOn: this.dependsOn, + nonce: this.nonce, + sender: this.sender, + feePayer: this.feePayerAddress, + }; + + return json; + } + + public getFee(): string { + return new BigNumber(this.gas) + .multipliedBy(gasPrice) + .multipliedBy(new BigNumber(this.gasPriceCoef).dividedBy(255).plus(1)) + .toString() + .slice(0, 18); + } + + public get signablePayload(): Buffer { + return Buffer.from(this.rawTransaction.getTransactionHash().bytes); + } + + /** @inheritdoc */ + toBroadcastFormat(): string { + if (!this.rawTransaction) { + throw new InvalidTransactionError('Empty Transaction'); + } + return this.serialize(); + } + + serialize(): string { + const halfSignedTransaction: VetTransaction = VetTransaction.of(this.rawTransaction.body, this.senderSignature); + if (!halfSignedTransaction.signature) { + throw new InvalidTransactionError('Invalid sender signature in half-signed transaction'); + } + if (!this.feePayerSignature) { + throw new InvalidTransactionError('Missing fee payer signature'); + } + const fullSignedTransaction: VetTransaction = VetTransaction.of( + halfSignedTransaction.body, + nc_utils.concatBytes( + // Drop any previous gas payer signature. + halfSignedTransaction.signature.slice(0, Secp256k1.SIGNATURE_LENGTH), + this.feePayerSignature + ) + ); + return HexUInt.of(fullSignedTransaction.encoded).toString(); + } + + static deserializeTransaction(rawTx: string): VetTransaction { + try { + if (!rawTx) { + throw new InvalidTransactionError('Raw transaction string cannot be empty'); + } + return utils.deserializeTransaction(rawTx); + } catch (e) { + if (e instanceof InvalidTransactionError) { + throw e; + } + throw new InvalidTransactionError(`Failed to deserialize transaction: ${e.message}`); + } + } + + explainTransaction(): VetTransactionExplanation { + const displayOrder = [ + 'id', + 'outputs', + 'outputAmount', + 'changeOutputs', + 'changeAmount', + 'fee', + 'withdrawAmount', + 'sender', + 'type', + ]; + + const outputs: TransactionRecipient[] = this._recipients; + const outputAmount = outputs + .reduce((accumulator, current) => accumulator.plus(current.amount), new BigNumber('0')) + .toString(); + return { + displayOrder, + id: this.id, + outputs, + outputAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: this.getFee() }, + sender: this.sender, + type: this.type, + }; + } +} diff --git a/modules/sdk-coin-vet/src/lib/transactionBuilder.ts b/modules/sdk-coin-vet/src/lib/transactionBuilder.ts deleted file mode 100644 index 04175935ff..0000000000 --- a/modules/sdk-coin-vet/src/lib/transactionBuilder.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - BaseAddress, - BaseKey, - BaseTransactionBuilder, - BuildTransactionError, - FeeOptions, - PublicKey as BasePublicKey, - Signature, - TransactionType, -} from '@bitgo/sdk-core'; -import { Transaction } from './transaction'; -import utils from './utils'; -import BigNumber from 'bignumber.js'; - -export abstract class TransactionBuilder extends BaseTransactionBuilder { - protected _transaction: Transaction; - private _signatures: Signature[] = []; - - // get and set region - /** - * The transaction type. - */ - protected abstract get transactionType(): TransactionType; - - /** @inheritdoc */ - protected get transaction(): Transaction { - return this._transaction; - } - - /** @inheritdoc */ - protected set transaction(transaction: Transaction) { - this._transaction = transaction; - } - - /** @inheritdoc */ - protected signImplementation(key: BaseKey): Transaction { - throw new Error('Method not implemented.'); - } - - /** @inheritDoc */ - addSignature(publicKey: BasePublicKey, signature: Buffer): void { - this._signatures.push({ publicKey, signature }); - } - - /** - * Sets the sender of this transaction. - * This account will be responsible for paying transaction fees. - * - * @param {string} senderAddress the account that is sending this transaction - * @returns {TransactionBuilder} This transaction builder - */ - sender(senderAddress: string): this { - throw new Error('Method not implemented.'); - } - - fee(feeOptions: FeeOptions): this { - throw new Error('Method not implemented.'); - } - - /** @inheritdoc */ - protected fromImplementation(rawTransaction: string): Transaction { - throw new Error('Method not implemented.'); - } - - /** @inheritdoc */ - protected async buildImplementation(): Promise { - throw new Error('Method not implemented.'); - } - - // region Validators - /** @inheritdoc */ - validateAddress(address: BaseAddress, addressFormat?: string): void { - if (!utils.isValidAddress(address.address)) { - throw new BuildTransactionError('Invalid address ' + address.address); - } - } - - /** @inheritdoc */ - validateKey(key: BaseKey): void { - throw new Error('Method not implemented.'); - } - - /** @inheritdoc */ - validateRawTransaction(rawTransaction: string): void { - throw new Error('Method not implemented.'); - } - - /** @inheritdoc */ - validateTransaction(transaction?: Transaction): void { - throw new Error('Method not implemented.'); - } - - /** @inheritdoc */ - validateValue(value: BigNumber): void { - if (value.isLessThan(0)) { - throw new BuildTransactionError('Value cannot be less than zero'); - } - } -} diff --git a/modules/sdk-coin-vet/src/lib/transactionBuilder/transactionBuilder.ts b/modules/sdk-coin-vet/src/lib/transactionBuilder/transactionBuilder.ts new file mode 100644 index 0000000000..68263b786f --- /dev/null +++ b/modules/sdk-coin-vet/src/lib/transactionBuilder/transactionBuilder.ts @@ -0,0 +1,188 @@ +import BigNumber from 'bignumber.js'; +import { + BaseKey, + BaseTransactionBuilder, + BaseAddress, + BuildTransactionError, + Recipient, + TransactionType, + PublicKey, + ParseTransactionError, +} from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { Transaction } from '../transaction/transaction'; +import utils from '../utils'; +import { TransactionClause } from '@vechain/sdk-core'; + +export abstract class TransactionBuilder extends BaseTransactionBuilder { + protected _transaction: Transaction; + + constructor(_coinConfig: Readonly) { + super(_coinConfig); + } + + protected abstract get transactionType(): TransactionType; + + getNonce(): number { + return this.transaction.nonce; + } + + initBuilder(tx: Transaction): void { + this._transaction = tx; + } + + /** @inheritdoc */ + get transaction(): Transaction { + return this._transaction; + } + + /** @inheritdoc */ + protected set transaction(transaction: Transaction) { + this._transaction = transaction; + } + + /** + * Sets the sender of this transaction. + * + * @param {string} senderAddress the account that is sending this transaction + * @returns {TransactionBuilder} This transaction builder + */ + sender(senderAddress: string): this { + this.validateAddress({ address: senderAddress }); + this.transaction.sender = senderAddress; + return this; + } + + recipients(recipients: Recipient[]): this { + for (const recipient of recipients) { + this.validateAddress({ address: recipient.address }); + this.validateValue(new BigNumber(recipient.amount)); + } + this.transaction.recipients = recipients; + return this; + } + + gas(g: number): this { + this.validateValue(new BigNumber(g)); + this.transaction.gas = g; + return this; + } + + nonce(n: number): this { + this.transaction.nonce = n; + return this; + } + + expiration(exp: number): this { + this.transaction.expiration = exp; + return this; + } + + dependsOn(dep: string | null): this { + this.transaction.dependsOn = dep; + return this; + } + + blockRef(ref: string): this { + this.transaction.blockRef = ref; + return this; + } + + gasPriceCoef(coef: number): this { + this.transaction.gasPriceCoef = coef; + return this; + } + + /** @inheritDoc */ + addSenderSignature(signature: Buffer): void { + this.transaction.addSenderSignature(signature); + } + + addFeePayerSignature(publicKey: PublicKey, signature: Buffer): void { + this.transaction.addFeePayerSignature(publicKey, signature); + } + + /** @inheritdoc */ + protected fromImplementation(rawTransaction: string): Transaction { + this.transaction.fromRawTransaction(rawTransaction); + this.transaction.type = this.transactionType; + return this.transaction; + } + + /** @inheritdoc */ + protected async buildImplementation(): Promise { + this.transaction.type = this.transactionType; + await this.transaction.build(); + return this.transaction; + } + + /** @inheritdoc */ + validateAddress(address: BaseAddress, addressFormat?: string): void { + if (!utils.isValidAddress(address.address)) { + throw new BuildTransactionError('Invalid address ' + address.address); + } + } + + protected abstract isValidTransactionClauses(clauses: TransactionClause[]): boolean; + + /** @inheritdoc */ + validateTransaction(transaction?: Transaction): void { + if (!transaction) { + throw new Error('transaction not defined'); + } + this.validateAddress({ address: transaction.sender }); + for (const recipient of transaction.recipients) { + this.validateAddress({ address: recipient.address }); + this.validateValue(new BigNumber(recipient.amount)); + } + } + + isValidRawTransaction(rawTransaction: string): boolean { + try { + const tx = utils.deserializeTransaction(rawTransaction); + return this.isValidTransactionClauses(tx.body.clauses); + } catch (e) { + return false; + } + } + + /** @inheritdoc */ + validateRawTransaction(rawTransaction: string): void { + if (!rawTransaction) { + throw new ParseTransactionError('Invalid raw transaction: Undefined'); + } + if (!this.isValidRawTransaction(rawTransaction)) { + throw new ParseTransactionError('Invalid raw transaction'); + } + } + + validateValue(value: BigNumber): void { + if (value.isNaN()) { + throw new BuildTransactionError('Invalid amount format'); + } else if (value.isLessThan(0)) { + throw new BuildTransactionError('Value cannot be less than zero'); + } + } + + private validateGas(gas: number): void { + this.validateValue(new BigNumber(gas)); + } + + addFeePayerAddress(address: string): void { + this.transaction.feePayerAddress = address; + } + + getFeePayerPubKey(): string { + return this.transaction.getFeePayerPubKey(); + } + + /** @inheritdoc */ + protected signImplementation(key: BaseKey): Transaction { + throw new Error('Method not implemented'); + } + + /** @inheritdoc */ + validateKey(key: BaseKey): void { + throw new Error('Method not implemented.'); + } +} diff --git a/modules/sdk-coin-vet/src/lib/transactionBuilder/transferBuilder.ts b/modules/sdk-coin-vet/src/lib/transactionBuilder/transferBuilder.ts new file mode 100644 index 0000000000..a56a70f73d --- /dev/null +++ b/modules/sdk-coin-vet/src/lib/transactionBuilder/transferBuilder.ts @@ -0,0 +1,46 @@ +import BigNumber from 'bignumber.js'; +import { TransactionClause } from '@vechain/sdk-core'; + +import { TransactionType } from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { TransactionBuilder } from './transactionBuilder'; +import utils from '../utils'; +import { Transaction } from '../transaction/transaction'; + +export class TransferBuilder extends TransactionBuilder { + constructor(_coinConfig: Readonly) { + super(_coinConfig); + this.transaction = new Transaction(_coinConfig); + } + + protected get transactionType(): TransactionType { + return TransactionType.Send; + } + + protected isValidTransactionClauses(clauses: TransactionClause[]): boolean { + try { + if (!clauses || !Array.isArray(clauses) || clauses.length === 0) { + return false; + } + + return clauses.every((clause) => { + if (!clause.to || !utils.isValidAddress(clause.to)) { + return false; + } + + if (!clause.value || new BigNumber(clause.value).isLessThanOrEqualTo(0)) { + return false; + } + + // For native VET transfers, data must be exactly '0x' + if (clause.data !== '0x') { + return false; + } + + return true; + }); + } catch (e) { + return false; + } + } +} diff --git a/modules/sdk-coin-vet/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-vet/src/lib/transactionBuilderFactory.ts index b963ec4b4d..5934675958 100644 --- a/modules/sdk-coin-vet/src/lib/transactionBuilderFactory.ts +++ b/modules/sdk-coin-vet/src/lib/transactionBuilderFactory.ts @@ -1,20 +1,59 @@ -import { BaseTransactionBuilderFactory } from '@bitgo/sdk-core'; -import { TransactionBuilder } from './transactionBuilder'; -import { TransferBuilder } from './transferBuilder'; +import { Transaction as VetTransaction } from '@vechain/sdk-core'; +import { BaseTransactionBuilderFactory, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { TransactionBuilder } from './transactionBuilder/transactionBuilder'; +import { TransferBuilder } from './transactionBuilder/transferBuilder'; +import { Transaction } from './transaction/transaction'; +import utils from './utils'; export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { + constructor(_coinConfig: Readonly) { + super(_coinConfig); + } + /** @inheritdoc */ - from(raw: string): TransactionBuilder { - throw new Error('Method not implemented.'); + from(signedRawTx: string): TransactionBuilder { + try { + const signedTx = Transaction.deserializeTransaction(signedRawTx); + const type = this.getTransactionTypeFromSignedTxn(signedTx); + switch (type) { + case TransactionType.Send: + const transferTx = new Transaction(this._coinConfig); + transferTx.fromDeserializedSignedTransaction(signedTx); + return this.getTransferBuilder(transferTx); + default: + throw new InvalidTransactionError('Invalid transaction type'); + } + } catch (e) { + throw e; + } } /** @inheritdoc */ - getTransferBuilder(): TransferBuilder { - throw new Error('Method not implemented.'); + getTransferBuilder(tx?: Transaction): TransferBuilder { + return this.initializeBuilder(tx, new TransferBuilder(this._coinConfig)); } /** @inheritdoc */ getWalletInitializationBuilder(): void { throw new Error('Method not implemented.'); } + + getTransactionTypeFromSignedTxn(signedTxn: VetTransaction): TransactionType { + return utils.getTransactionTypeFromClause(signedTxn.body.clauses); + } + + /** + * Initialize the builder with the given transaction + * + * @param {Transaction | undefined} tx - the transaction used to initialize the builder + * @param {TransactionBuilder} builder - the builder to be initialized + * @returns {TransactionBuilder} the builder initialized + */ + private initializeBuilder(tx: Transaction | undefined, builder: T): T { + if (tx) { + builder.initBuilder(tx); + } + return builder; + } } diff --git a/modules/sdk-coin-vet/src/lib/transferBuilder.ts b/modules/sdk-coin-vet/src/lib/transferBuilder.ts deleted file mode 100644 index b7ce748c92..0000000000 --- a/modules/sdk-coin-vet/src/lib/transferBuilder.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { TransactionBuilder } from './transactionBuilder'; -import { BaseCoin as CoinConfig } from '@bitgo/statics'; -import { TransactionType } from '@bitgo/sdk-core'; - -export class TransferBuilder extends TransactionBuilder { - constructor(_coinConfig: Readonly) { - super(_coinConfig); - } - - protected get transactionType(): TransactionType { - return TransactionType.Send; - } -} diff --git a/modules/sdk-coin-vet/src/lib/types.ts b/modules/sdk-coin-vet/src/lib/types.ts new file mode 100644 index 0000000000..5fe2eaac31 --- /dev/null +++ b/modules/sdk-coin-vet/src/lib/types.ts @@ -0,0 +1,9 @@ +import { ParseTransactionOptions } from '@bitgo/sdk-core'; + +export interface ExplainTransactionOptions { + txHex: string; +} + +export interface VetParseTransactionOptions extends ParseTransactionOptions { + txHex: string; +} diff --git a/modules/sdk-coin-vet/src/lib/utils.ts b/modules/sdk-coin-vet/src/lib/utils.ts index 03c3891911..22a3fd41da 100644 --- a/modules/sdk-coin-vet/src/lib/utils.ts +++ b/modules/sdk-coin-vet/src/lib/utils.ts @@ -1,6 +1,7 @@ -import { BaseUtils } from '@bitgo/sdk-core'; +import { BaseUtils, TransactionType } from '@bitgo/sdk-core'; import { VET_TRANSACTION_ID_LENGTH } from './constants'; import { KeyPair } from './keyPair'; +import { HexUInt, Transaction, TransactionClause } from '@vechain/sdk-core'; export class Utils implements BaseUtils { isValidAddress(address: string): boolean { @@ -41,6 +42,27 @@ export class Utils implements BaseUtils { const regex = new RegExp(`^(0x|0X)[a-fA-F0-9]{${length}}$`); return regex.test(value); } + + deserializeTransaction(serializedTransaction: string): Transaction { + const txBytes = HexUInt.of(serializedTransaction).bytes; + try { + return Transaction.decode(txBytes, false); + } catch (err) { + if (err.message?.includes('Expected 9 items, but got 10')) { + // Likely signed, so retry with isSigned = true + return Transaction.decode(txBytes, true); + } + throw err; + } + } + + getTransactionTypeFromClause(clauses: TransactionClause[]): TransactionType { + if (clauses[0].data === '0x') { + return TransactionType.Send; + } else { + return TransactionType.SendToken; + } + } } const utils = new Utils(); diff --git a/modules/sdk-coin-vet/src/vet.ts b/modules/sdk-coin-vet/src/vet.ts index 0cf41ec338..ab665c8b65 100644 --- a/modules/sdk-coin-vet/src/vet.ts +++ b/modules/sdk-coin-vet/src/vet.ts @@ -1,3 +1,5 @@ +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; import { AuditDecryptedKeyParams, BaseCoin, @@ -19,6 +21,9 @@ import utils from './lib/utils'; import { bip32 } from '@bitgo/secp256k1'; import { randomBytes } from 'crypto'; import { KeyPair as EthKeyPair } from '@bitgo/abstract-eth'; +import { TransactionBuilderFactory } from './lib'; +import { ExplainTransactionOptions, VetParseTransactionOptions } from './lib/types'; +import { VetTransactionExplanation } from './lib/iface'; /** * Full Name: Vechain @@ -79,7 +84,40 @@ export class Vet extends BaseCoin { } async verifyTransaction(params: VerifyTransactionOptions): Promise { - throw new Error('Method not implemented'); + const { txPrebuild: txPrebuild, txParams: txParams } = params; + const txHex = txPrebuild.txHex; + if (!txHex) { + throw new Error('missing required tx prebuild property txHex'); + } + const explainedTx = await this.explainTransaction({ txHex }); + if (!explainedTx) { + throw new Error('failed to explain transaction'); + } + if (txParams.recipients !== undefined) { + const filteredRecipients = txParams.recipients?.map((recipient) => { + return { + address: recipient.address, + amount: BigInt(recipient.amount), + }; + }); + const filteredOutputs = explainedTx.outputs.map((output) => { + return { + address: output.address, + amount: BigInt(output.amount), + }; + }); + if (!_.isEqual(filteredOutputs, filteredRecipients)) { + throw new Error('Tx outputs does not match with expected txParams recipients'); + } + let totalAmount = new BigNumber(0); + 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: VerifyAddressOptions): Promise { @@ -91,16 +129,39 @@ export class Vet extends BaseCoin { return true; } - async parseTransaction(): Promise { - throw new Error('Method not implemented'); + async parseTransaction(params: VetParseTransactionOptions): Promise { + const transactionExplanation = await this.explainTransaction({ txHex: params.txHex }); + if (!transactionExplanation) { + throw new Error('Invalid transaction'); + } + return { + inputs: [ + { + address: transactionExplanation.sender, + amount: transactionExplanation.outputAmount, + }, + ], + outputs: [ + { + address: transactionExplanation.outputs[0].address, + amount: transactionExplanation.outputs[0].amount, + }, + ], + }; } /** * Explain a Vechain transaction * @param params */ - async explainTransaction(params): Promise { - throw new Error('Method not implemented'); + async explainTransaction(params: ExplainTransactionOptions): Promise { + let rebuiltTransaction: BaseTransaction; + try { + rebuiltTransaction = await this.rebuildTransaction(params.txHex); + } catch { + return undefined; + } + return rebuiltTransaction.explainTransaction(); } generateKeyPair(seed?: Buffer): KeyPair { @@ -136,16 +197,22 @@ export class Vet extends BaseCoin { throw new Error('Method not implemented.'); } - protected getTxBuilderFactory() { - throw new Error('Method not implemented.'); + protected getTxBuilderFactory(): TransactionBuilderFactory { + return new TransactionBuilderFactory(this._staticsCoin); } protected async rebuildTransaction(txHex: string): Promise { - throw new Error('Method not implemented'); + const txBuilderFactory = this.getTxBuilderFactory(); + try { + const txBuilder = txBuilderFactory.from(txHex); + return txBuilder.transaction; + } catch { + throw new Error('Failed to rebuild transaction'); + } } /** @inheritDoc */ - auditDecryptedKey(params: AuditDecryptedKeyParams) { + auditDecryptedKey(params: AuditDecryptedKeyParams): void { /** https://bitgoinc.atlassian.net/browse/COIN-4213 */ throw new Error('Method not implemented.'); } diff --git a/modules/sdk-coin-vet/test/resources/vet.ts b/modules/sdk-coin-vet/test/resources/vet.ts index b9dac6b324..bc9364b7a6 100644 --- a/modules/sdk-coin-vet/test/resources/vet.ts +++ b/modules/sdk-coin-vet/test/resources/vet.ts @@ -1,450 +1,58 @@ -export const TEST_ACCOUNT = { - pubAddress: 'bbn1897xa4swxx9dr7z0zut0mfs7efplx80q86kd8q', - compressedPublicKey: '02bcdbd054a73aa6097c1926c87eb7d66142d2ea710584c4b3e9844e1dab1538f0', - compressedPublicKeyTwo: '02001fda4568760a99e58ee295b4a51edcc6a689297a71f7d1571cf4e1253abcde', - uncompressedPublicKey: - '04bcdbd054a73aa6097c1926c87eb7d66142d2ea710584c4b3e9844e1dab1538f0a088291e83cb3438431deb5e251439e338d8edc4389ea2004f442a73cc97afc8', - privateKey: '3f020639e98e5c4953abb23d01a7c892e83b57593a7f46f37298b58cbf1cacd5', - extendedPrv: - 'xprv9s21ZrQH143K3n6kDURwkfkBxB58Fxoz7cFCbpQeFws4K5iXaSUcpq18cCqJQ74MnqNrnLBHfE7YvUgKpnckmpsBLExGSRK55Ud5uuxGrxL', - extendedPub: - 'xpub661MyMwAqRbcGGBDKVxx7ogvWCucfRXqUqAoQCpFpHQ3Bt3g7ynsNdKcTWvGGYrpq6VYPPgxjMfKaszXMhKmmCZEnhg9RpqcGeP9uCnZsD9', -}; +import { Recipient } from '@bitgo/sdk-core'; -export const TEST_SEND_TX = { - hash: 'F91AD3FF898461BCE40A97C3D4A1CD6993D64599450A1C08185FA7DA446D4632', - signature: 'GxwY0ko3vlfPsU6gYZ2iqt3Acg+GBBrsSPUHTnP5Xv8GHgbQH2OfCdI/61hqEPx3LMqRTl9zEcKfBkHtB7cg4Q==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'CooBCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwEmUKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQME+MaK7sQPg5Csp69Sx+wjOtEcEey6eSJ8EOSEmjdR8BIECgIIARgGEhEKCwoEdWJibhIDMjAwEKCNBhpAGxwY0ko3vlfPsU6gYZ2iqt3Acg+GBBrsSPUHTnP5Xv8GHgbQH2OfCdI/61hqEPx3LMqRTl9zEcKfBkHtB7cg4Q==', - sender: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - recipient: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', - chainId: 'bbn-test-5', - accountNumber: 6, - sequence: 6, - sendAmount: '10000', - feeAmount: '200', - sendMessage: { - typeUrl: '/cosmos.bank.v1beta1.MsgSend', - value: { - amount: [ - { - denom: 'ubbn', - amount: '10000', - }, - ], - toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', - fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - }, - }, - gasBudget: { - amount: [{ denom: 'ubbn', amount: '200' }], - gasLimit: 100000, - }, -}; +export const AMOUNT = 100000000000000000; // 0.1 VET in base units -export const TEST_SEND_TX2 = { - hash: 'F3F54170A03449385C5E1CD14CD8F0D23931186618C76A3491C4B4C5D03B4DD8', - signature: '9tD0slY4loMya9rocQROMVQAw0eWfiTlMZFr9VQ8RAg+q4YFgDhteHvFZvKQPn6Y9SCSlUkBr0I+ZlIj8v7oug==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'CooBCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwEmUKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQME+MaK7sQPg5Csp69Sx+wjOtEcEey6eSJ8EOSEmjdR8BIECgIIARgJEhEKCwoEdWJibhIDMjAwEKCNBhpA9tD0slY4loMya9rocQROMVQAw0eWfiTlMZFr9VQ8RAg+q4YFgDhteHvFZvKQPn6Y9SCSlUkBr0I+ZlIj8v7oug==', - sender: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - recipient: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', - chainId: 'bbn-test-5', - accountNumber: 9, - sequence: 9, - sendAmount: '10000', - feeAmount: '200', - sendMessage: { - typeUrl: '/cosmos.bank.v1beta1.MsgSend', - value: { - amount: [ - { - denom: 'ubbn', - amount: '10000', - }, - ], - toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', - fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - }, - }, - gasBudget: { - amount: [{ denom: 'ubbn', amount: '200' }], - gasLimit: 100000, - }, -}; +export const SPONSORED_TRANSACTION = + '0xf8bc2788014e9cad44bade0940e0df94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec88016345785d8a0000808180825208808302d6b5c101b882ee76129c1259eb0c8a2b3b3e5f2b089cd11068da1c0db32a9e22228db83dd4be5c721858bc813514141fbd5cf641d0972ce47ceb9be61133fa2ebf0ea37c1f290011fdce201f56d639d827035a5ed8bcef42a42f6eb562bc76a6d95c7736cf8cf340122d1e2fb034668dc491d47b7d3bb10724ba2338a6e79df87bce9617fdce9c00'; -export const TEST_SEND_MANY_TX = { - hash: 'CEA0A14B0BE20834B0A193B9327FCAAB21A539BEE44B89299848280ED371A2DC', - signature: '5AHfov+3gmMBS/cLiM0kRBuyBhqpEOqyFPg3ANhzAdNB2Yfp7eR2BFep+xQaB/5r+3ISLrIdUv0V97uU8ENtwQ==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'CpQCCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMWM5bnB0MHhod2Z2bXRuaHlxM2pxZmhxMDg4OWw4dzA0cWF5M2RzGg0KBHViYm4SBTEwMDAwEmUKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQME+MaK7sQPg5Csp69Sx+wjOtEcEey6eSJ8EOSEmjdR8BIECgIIARgIEhEKCwoEdWJibhIDMjAwEKCNBhpA5AHfov+3gmMBS/cLiM0kRBuyBhqpEOqyFPg3ANhzAdNB2Yfp7eR2BFep+xQaB/5r+3ISLrIdUv0V97uU8ENtwQ==', - sender: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - chainId: 'bbn-test-5', - accountNumber: 8, - sequence: 8, - memo: '', - sendMessages: [ - { - typeUrl: '/cosmos.bank.v1beta1.MsgSend', - value: { - amount: [ - { - denom: 'ubbn', - amount: '10000', - }, - ], - toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', - fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - }, - }, - { - typeUrl: '/cosmos.bank.v1beta1.MsgSend', - value: { - amount: [ - { - denom: 'ubbn', - amount: '10000', - }, - ], - toAddress: 'bbn1c9npt0xhwfvmtnhyq3jqfhq0889l8w04qay3ds', - fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - }, - }, - ], - gasBudget: { - amount: [{ denom: 'ubbn', amount: '200' }], - gasLimit: 100000, - }, -}; +export const UNSIGNED_TRANSACTION = + '0xf8772788014eabfe2b8fcc1440dddc94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec85e8d4a51000808180825208808301bff7c0b8414f4b195e2dd666a01c1186df341ff3c0cbe2d6b4a58dca3c8c484f3eac05c4100a165a2730fe67b4c11b4a6c78ea8ab0757bf59735adef327a36737e1694536b00'; -export const TEST_TX_WITH_MEMO = { - hash: '75CD78D6C4C413750B43D91553B0BED47CB779F23B9D0CE8F94C47F82F97F069', - signature: 'YQNm391A/NqX5RoJFSwqfv9VX0GrWxk6rJMDxGjtCaJrUtsc6JnlM66cw1miDajaQlZu5LBi6JgHmrN7Nq39JA==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'Co8BCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwEgMyNDESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1HwEgQKAggBGAcSEQoLCgR1YmJuEgMyMDAQoI0GGkBhA2bf3UD82pflGgkVLCp+/1VfQatbGTqskwPEaO0JomtS2xzomeUzrpzDWaINqNpCVm7ksGLomAeas3s2rf0k', - from: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - to: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', - chainId: 'bbn-test-5', - accountNumber: 7, - sequence: 7, - feeAmount: '200', - sendAmount: '10000', - sendMessage: { - typeUrl: '/cosmos.bank.v1beta1.MsgSend', - value: { - amount: [ - { - denom: 'ubbn', - amount: '10000', - }, - ], - fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', - }, - }, - memo: '241', - gasBudget: { - amount: [{ denom: 'ubbn', amount: '200' }], - gasLimit: 100000, - }, -}; +export const UNSIGNED_TRANSACTION_2 = + '0xf72788014ead140e77bbc140e0df94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec88016345785d8a00008081808252088082faf8c101'; -export const TEST_DELEGATE_TX = { - hash: 'EC11EACC2692B1B741D0CEC74FD4E5DA2D9E8875623713ED0EAE63ED43FB5A91', - signature: 'BkwhIYhaVq5UPGEpCUPBCdq59j/JrHCQHn86UiREjL5BqvvPaQZM+hexBpSNoXDokiwoQDQK53VSO4iUumCUGQ==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'Cp4BCpsBCicvYmFieWxvbi5lcG9jaGluZy52MS5Nc2dXcmFwcGVkRGVsZWdhdGUScApuCipiYm4xMjc0ZXA4cG5ybGVqNXZnbXR3cHB5c3luemNkNGZoeGMza3UwdDMSMWJibnZhbG9wZXIxemw3OGV6aGd3NmZxeHlkZWNubWNteWRzbTBndHJqZHRhbm03bnIaDQoEdWJibhIFMTAwMDASZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1HwEgQKAggBGB0SEQoLCgR1YmJuEgM1MDAQwJoMGkAGTCEhiFpWrlQ8YSkJQ8EJ2rn2P8mscJAefzpSJESMvkGq+89pBkz6F7EGlI2hcOiSLChANArndVI7iJS6YJQZ', - from: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - to: 'bbnvaloper1zl78ezhgw6fqxydecnmcmydsm0gtrjdtanm7nr', - chainId: 'bbn-test-5', - accountNumber: 59235, - sequence: 29, - sendAmount: '10000', - feeAmount: '500', - sendMessage: { - typeUrl: '/babylon.epoching.v1.MsgWrappedDelegate', - value: { - delegatorAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - validatorAddress: 'bbnvaloper1zl78ezhgw6fqxydecnmcmydsm0gtrjdtanm7nr', - amount: { - denom: 'ubbn', - amount: '10000', - }, - }, - }, - gasBudget: { - amount: [ - { - denom: 'ubbn', - amount: '500', - }, - ], - gasLimit: 200000, - }, -}; +export const INVALID_TRANSACTION = + '0xf8bc2788014ea060b5b5997e40e0df94e5f4eec44adf19c4cbeec88016345785d8a000080818082520880830bf84fc101b882418e212a40b29da685a7312829e8d1d3708b654f4ddb4388a7a80f5af3e5423b455451901d9b837fe18501e6ea5ec7d3d2711f00073d553aabe40e0260ec8a6f00da557d0a1af66b82b457324bc6fc86c7f0362e76c15b64432e66b6fa62fca38c7d208a604d1a7ec5356c95fec7bc6f332d16718a91a53e0e99e3a81ae3205ab400'; -export const TEST_UNDELEGATE_TX = { - hash: '009D9A8F17B4DB99B86F1EBAFC801AA2A887E89D4D20EAE2FE30203484A3273E', - signature: 'm8XIY/88A66uBWShNLGFxLqgftFF18dpqni4ChvS6Wsn+xZCqZhcrpoJbmLzaFTRp+YnPvLawLy0QEAkff5NyA==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'CqABCp0BCikvYmFieWxvbi5lcG9jaGluZy52MS5Nc2dXcmFwcGVkVW5kZWxlZ2F0ZRJwCm4KKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIxYmJudmFsb3BlcjF6bDc4ZXpoZ3c2ZnF4eWRlY25tY215ZHNtMGd0cmpkdGFubTduchoNCgR1YmJuEgUxMDAwMBJlClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDBPjGiu7ED4OQrKevUsfsIzrRHBHsunkifBDkhJo3UfASBAoCCAEYHhIRCgsKBHViYm4SAzUwMBDAmgwaQJvFyGP/PAOurgVkoTSxhcS6oH7RRdfHaap4uAob0ulrJ/sWQqmYXK6aCW5i82hU0afmJz7y2sC8tEBAJH3+Tcg=', - from: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - to: 'bbnvaloper1zl78ezhgw6fqxydecnmcmydsm0gtrjdtanm7nr', - chainId: 'bbn-test-5', - accountNumber: 59235, - sequence: 30, - sendAmount: '10000', - feeAmount: '500', - sendMessage: { - typeUrl: '/babylon.epoching.v1.MsgWrappedUndelegate', - value: { - delegatorAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - validatorAddress: 'bbnvaloper1zl78ezhgw6fqxydecnmcmydsm0gtrjdtanm7nr', - amount: { - denom: 'ubbn', - amount: '10000', - }, - }, +export const TRANSFER_CLAUSE = [ + { + to: '0xe59f1cea4e0fef511e3d0f4eec44adf19c4cbeec', + value: 1000000000000, + data: '0x', }, - gasBudget: { - amount: [ - { - denom: 'ubbn', - amount: '500', - }, - ], - gasLimit: 200000, - }, -}; +]; -export const TEST_REDELEGATE_TX = { - hash: 'CC45369A75E76D3287A3831853369EEE6C5D1B5FB4277CC4FA0D4D06A0DFA661', - signature: 'bJlHHrfB08ypEZkSzy6wbqvNJhGB6OgPd2quqW5GqNYPtBniVHiS0jjVwgoldkM0cLdj8EilWP663Donqci8Aw==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'CtoBCtcBCi4vYmFieWxvbi5lcG9jaGluZy52MS5Nc2dXcmFwcGVkQmVnaW5SZWRlbGVnYXRlEqQBCqEBCipiYm4xMjc0ZXA4cG5ybGVqNXZnbXR3cHB5c3luemNkNGZoeGMza3UwdDMSMWJibnZhbG9wZXIxemw3OGV6aGd3NmZxeHlkZWNubWNteWRzbTBndHJqZHRhbm03bnIaMWJibnZhbG9wZXIxMmptOXo2aHdtNGV4N2MwejhodGV5M3loeWZoeDQ0em43MGE1bDYiDQoEdWJibhIFMTAwMDASZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1HwEgQKAggBGB8SEQoLCgR1YmJuEgM1MDAQwJoMGkBsmUcet8HTzKkRmRLPLrBuq80mEYHo6A93aq6pbkao1g+0GeJUeJLSONXCCiV2QzRwt2PwSKVY/rrcOiepyLwD', - delegator: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - validator: 'bbnvaloper12jm9z6hwm4ex7c0z8htey3yhyfhx44zn70a5l6', - chainId: 'bbn-test-5', - accountNumber: 59235, - sequence: 31, - sendAmount: '10000', - feeAmount: '500', - sendMessage: { - typeUrl: '/babylon.epoching.v1.MsgWrappedBeginRedelegate', - value: { - delegatorAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - validatorSrcAddress: 'bbnvaloper1zl78ezhgw6fqxydecnmcmydsm0gtrjdtanm7nr', - validatorDstAddress: 'bbnvaloper12jm9z6hwm4ex7c0z8htey3yhyfhx44zn70a5l6', - amount: { - denom: 'ubbn', - amount: '10000', - }, - }, - }, - gasBudget: { - amount: [ - { - denom: 'ubbn', - amount: '500', - }, - ], - gasLimit: 200000, - }, +export const addresses = { + validAddresses: ['0x7ca00e3bc8a836026c2917c6c7c6d049e52099dd', '0xe59f1cea4e0fef511e3d0f4eec44adf19c4cbeec'], + invalidAddresses: [ + 'randomString', + '0xc4173a804406a365e69dfb297ddfgsdcvf', + '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', + ], }; -export const TEST_WITHDRAW_REWARDS_TX = { - hash: 'EA4ECA4DA17B158238242572AD9A7E7734D7085BF679555EE90FA4F0EF3CFA79', - signature: 'vyVU/1zVhp6m0uMF5mLaKr3o/Oe36O+EF3E7Dgw7smlUjOW5Ttwgnl44zO1vro4MU2frc/KV4Ji2IqWKi8QlLA==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'Cp0BCpoBCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEl8KKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIxYmJudmFsb3BlcjF6bDc4ZXpoZ3c2ZnF4eWRlY25tY215ZHNtMGd0cmpkdGFubTduchJlClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDBPjGiu7ED4OQrKevUsfsIzrRHBHsunkifBDkhJo3UfASBAoCCAEYIBIRCgsKBHViYm4SAzUwMBDAmgwaQL8lVP9c1YaeptLjBeZi2iq96Pznt+jvhBdxOw4MO7JpVIzluU7cIJ5eOMztb66ODFNn63PyleCYtiKliovEJSw=', - from: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - to: 'bbnvaloper1zl78ezhgw6fqxydecnmcmydsm0gtrjdtanm7nr', - chainId: 'bbn-test-5', - accountNumber: 59235, - sequence: 32, - sendAmount: '10000', - feeAmount: '500', - sendMessage: { - typeUrl: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward', - value: { - delegatorAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - validatorAddress: 'bbnvaloper1zl78ezhgw6fqxydecnmcmydsm0gtrjdtanm7nr', - }, +export const invalidRecipients: Recipient[] = [ + { + address: addresses.invalidAddresses[0], + amount: AMOUNT.toString(), }, - gasBudget: { - amount: [ - { - denom: 'ubbn', - amount: '500', - }, - ], - gasLimit: 200000, + { + address: addresses.validAddresses[0], + amount: '-919191', }, -}; - -const u8 = (data: string) => new Uint8Array(Buffer.from(data, 'hex')); -export const TEST_CUSTOM_MsgCreateBTCDelegation_TX = { - hash: '', - signature: 'cuHdHOHdvc1pkJ/6EFCJZLAnIH8tybynNFGBZ23F/zQRXJhpJpLeiGajImQf91t59V4l1yfy/L6+Z5LEjKwVtQ==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'CvoGCvcGCi0vYmFieWxvbi5idGNzdGFraW5nLnYxLk1zZ0NyZWF0ZUJUQ0RlbGVnYXRpb24SxQYKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxJkCAISYHfXPH3u933nX23HnN2vfH+POu293NNveH9tNdN9+vXX29tGue+3HnHeefPPdOemt+n/dHWuPG2n3HueeunPdOHetG3G3NW9N3tt9uO+dHX+3fPXG9fHdX+n3G3dOt9N2xoge2oIUExGM2+YWleHpUI+sYwQsUn+8pzTMWrYb1Zc0rkiINI8LCXh/Pj9HCG5pALBni4wnlMeRekvsemAW2BWsMx2KJBOMIOyAzp9AgAAAAEREREREREREREREREREREREREREREREREREREREREREQAAAAAA/////wID2QAAAAAAACJRIPGHH8KsMFC1PcNdUCqdlDiPbzc7hvOQVmeUWoQwy9Y5v1EJAAAAAAAWABSJbxumXerrBFuzEh4g5XROZsoOSAAAAABKfQIAAAABZNoUBQ/9DmGGxopZ83GgrclcyEsFAv4JFb5+uj93G7MAAAAAAP////8C2QoAAAAAAAAWABRb4SYk0IorQkCV18ByIcM0UNFL8aK6AAAAAAAAIlEg1SOFMOIj7xvTSoqY7hcI9qiofnolidiN5nALmXP//0YAAAAAUkDxCl5tcitFnofXE7+a8RE/pet6HkH/VzjecEn88RO+ql8BIors8n65QEWpkAsJIJGmPb9Ub9fWwF0y0yz4rVkdWPAHYl4CAAAAAWTaFAUP/Q5hhsaKWfNxoK3JXMhLBQL+CRW+fro/dxuzAAAAAAD/////ATPRAAAAAAAAIlEgTc6gyAIjYRBgLXscwudu9fHwUXCwWGhbTK9DZ6yEnWsAAAAAaLOiA3J9AgAAAAHAR8WQRUIBfP7lf9s1DWv3dqXvjEa8Az9FSmS0ltCrnQAAAAAA/////wJ1CgAAAAAAABYAFFvhJiTQiitCQJXXwHIhwzRQ0UvxNrMAAAAAAAAiUSDVI4Uw4iPvG9NKipjuFwj2qKh+eiWJ2I3mcAuZc///RgAAAAB6QFv5vtY3+9EJ1Txg30CTsWa6BqJ7kMa30FvrkSShn8YtHRdSKjCdTdfY3C7tlU3LP2KPRK6YN8ArkVRfoxPebhcSZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1HwEgQKAggBGCESEQoLCgR1YmJuEgM1MDAQwJoMGkBy4d0c4d29zWmQn/oQUIlksCcgfy3JvKc0UYFnbcX/NBFcmGkmkt6IZqMiZB/3W3n1XiXXJ/L8vr5nksSMrBW1', - from: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - to: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - chainId: 'bbn-test-5', - accountNumber: 59235, - sequence: 33, - sendAmount: '10000', - feeAmount: '500', - sendMessage: { - typeUrl: '/babylon.btcstaking.v1.MsgCreateBTCDelegation', - value: { - _kind: 'CreateBtcDelegation', - stakerAddr: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - pop: { - btcSigType: 2, - btcSig: u8( - '77d73c7deef77de75f6dc79cddaf7c7f8f3aedbddcd36f787f6d35d37dfaf5d7dbdb46b9efb71e71de79f3cf74e7a6b7e9ff7475ae3c6da7dc7b9e7ae9cf74e1deb46dc6dcd5bd377b6df6e3be7475feddf3d71bd7c7757fa7dc6ddd3adf4ddb' - ), - }, - btcPk: u8('7b6a08504c46336f985a5787a5423eb18c10b149fef29cd3316ad86f565cd2b9'), - fpBtcPkList: [u8('d23c2c25e1fcf8fd1c21b9a402c19e2e309e531e45e92fb1e9805b6056b0cc76')], - stakingTime: 10000, - stakingValue: 55555, - stakingTx: u8( - '020000000111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0203d9000000000000225120f1871fc2ac3050b53dc35d502a9d94388f6f373b86f3905667945a8430cbd639bf51090000000000160014896f1ba65deaeb045bb3121e20e5744e66ca0e4800000000' - ), - slashingTx: u8( - '020000000164da14050ffd0e6186c68a59f371a0adc95cc84b0502fe0915be7eba3f771bb30000000000ffffffff02d90a0000000000001600145be12624d08a2b424095d7c07221c33450d14bf1a2ba000000000000225120d5238530e223ef1bd34a8a98ee1708f6a8a87e7a2589d88de6700b9973ffff4600000000' - ), - delegatorSlashingSig: u8( - 'f10a5e6d722b459e87d713bf9af1113fa5eb7a1e41ff5738de7049fcf113beaa5f01228aecf27eb94045a9900b092091a63dbf546fd7d6c05d32d32cf8ad591d' - ), - unbondingTime: 1008, - unbondingTx: u8( - '020000000164da14050ffd0e6186c68a59f371a0adc95cc84b0502fe0915be7eba3f771bb30000000000ffffffff0133d10000000000002251204dcea0c802236110602d7b1cc2e76ef5f1f05170b058685b4caf4367ac849d6b00000000' - ), - unbondingValue: 53555, - unbondingSlashingTx: u8( - '0200000001c047c5904542017cfee57fdb350d6bf776a5ef8c46bc033f454a64b496d0ab9d0000000000ffffffff02750a0000000000001600145be12624d08a2b424095d7c07221c33450d14bf136b3000000000000225120d5238530e223ef1bd34a8a98ee1708f6a8a87e7a2589d88de6700b9973ffff4600000000' - ), - delegatorUnbondingSlashingSig: u8( - '5bf9bed637fbd109d53c60df4093b166ba06a27b90c6b7d05beb9124a19fc62d1d17522a309d4dd7d8dc2eed954dcb3f628f44ae9837c02b91545fa313de6e17' - ), - }, + { + address: addresses.validAddresses[0], + amount: 'invalidAmount', }, - gasBudget: { - amount: [ - { - denom: 'ubbn', - amount: '500', - }, - ], - gasLimit: 200000, - }, - inputs: [], - outputs: [], -}; +]; -export const TEST_CUSTOM_MsgWithdrawReward_TX = { - hash: '', - signature: 'KGFmGSxicrY4WjDoJPOaVyzd4TJNFtxVI6vLXCr1X7MaOylWJTh+2pkIJc5Cm/kUFkDqWsyHdUzexbt1ThyZ2A==', - pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', - privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', - signedTxBase64: - 'CmkKZwokL2JhYnlsb24uaW5jZW50aXZlLk1zZ1dpdGhkcmF3UmV3YXJkEj8KEWZpbmFsaXR5X3Byb3ZpZGVyEipiYm4xMjc0ZXA4cG5ybGVqNXZnbXR3cHB5c3luemNkNGZoeGMza3UwdDMSZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1HwEgQKAggBGDsSEQoLCgR1YmJuEgM1MDAQwJoMGkAoYWYZLGJytjhaMOgk85pXLN3hMk0W3FUjq8tcKvVfsxo7KVYlOH7amQglzkKb+RQWQOpazId1TN7Fu3VOHJnY', - from: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - to: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - chainId: 'bbn-test-5', - accountNumber: 59235, - sequence: 59, - sendAmount: '0', - feeAmount: '500', - sendMessage: { - typeUrl: '/babylon.incentive.MsgWithdrawReward', - value: { - _kind: 'WithdrawReward', - type: 'finality_provider', - address: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - }, +export const recipients: Recipient[] = [ + { + address: addresses.validAddresses[1], + amount: AMOUNT.toString(), }, - gasBudget: { - amount: [ - { - denom: 'ubbn', - amount: '500', - }, - ], - gasLimit: 200000, - }, - inputs: [ - { - address: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - value: 'UNAVAILABLE', - coin: 'tbaby', - }, - ], - outputs: [ - { - address: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - value: 'UNAVAILABLE', - coin: 'tbaby', - }, - ], -}; - -export const address = { - address1: 'bbn1897xa4swxx9dr7z0zut0mfs7efplx80q86kd8q', - address2: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - address3: 'bbn19syhqw5qeuas365cw9pvdadzax7durfm7hlfn8', - address4: 'bbn1ukzj0eme6srq8rtghy6zatk3gshw0ysr3pmjy9', - address5: 'bbn1pa3s79kuppz7d0aq3rh9c439k4pwtfhxaea969', - address6: 'bbn1c9npt0xhwfvmtnhyq3jqfhq0889l8w04qay3ds', - validatorAddress1: 'bbnvaloper109x4ruspxarwt62puwcenhclw36l9v7j92f0ex', - validatorAddress2: 'bbnvaloper1fa0c7df8v25mv926wey4m9kunhhm7svnp6tezt', - validatorAddress3: 'bbnvaloper15rrsv9439j60o7wwv8l6zkv8tp5lrqy7quzx87', - validatorAddress4: 'bbnvaloper1rlrjlm6rec6h2nvzakyj39d5fv7p7vgs3kq472', - noMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', - validMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=2', - validMemoIdAddress2: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=xyz123', - invalidMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=-1', - invalidMemoIdAddress2: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=1.23', - multipleMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=3&memoId=12', - finalityProviderAddress: 'e4889630fa8695dae630c41cd9b85ef165ccc2dc5e5935d5a24393a9defee9ef', -}; - -export const blockHash = { - hash1: '4CF6A4005A3DEFB73B8AAC90D2FDF31E941E4F58D3D183E5F959E6CBC0AB4103', - hash2: 'B3A416BA5A8C916279858861AEC61A79546D06FD0A541621E8054AA498311066', -}; - -export const txIds = { - hash1: 'F91AD3FF898461BCE40A97C3D4A1CD6993D64599450A1C08185FA7DA446D4632', - hash2: 'A7FB4EB5000BCFDB8A481E85027907A54BD05EFF9ED83CEBC5F53B1C77812896', - hash3: '61E0C20DAD6C6077F3B07519CF03133FE77C98780E299DA5DDBED7FE523ED9E2', -}; +]; -export const coinAmounts = { - amount1: { amount: '100000', denom: 'ubbn' }, - amount2: { amount: '1000000', denom: 'ubbn' }, - amount3: { amount: '10000000', denom: 'ubbn' }, - amount4: { amount: '-1', denom: 'ubbn' }, - amount5: { amount: '1000000000', denom: 'mbbn' }, +export const feePayer = { + address: '0xdc9fef0b84a0ccf3f1bd4b84e41743e3e051a083', }; diff --git a/modules/sdk-coin-vet/test/transactionBuilder/transferBuilder.ts b/modules/sdk-coin-vet/test/transactionBuilder/transferBuilder.ts new file mode 100644 index 0000000000..c9f2cb9a37 --- /dev/null +++ b/modules/sdk-coin-vet/test/transactionBuilder/transferBuilder.ts @@ -0,0 +1,163 @@ +import { TransactionBuilderFactory, Transaction } from '../../src'; +import { coins } from '@bitgo/statics'; +import * as testData from '../resources/vet'; +import should from 'should'; +import { TransactionType } from '@bitgo/sdk-core'; + +describe('Vet Transfer Transaction', () => { + const factory = new TransactionBuilderFactory(coins.get('tvet')); + + describe('Vet Coin Transfer Transaction', () => { + describe('Succeed', () => { + it('should build a transfer tx', async function () { + const transaction = new Transaction(coins.get('tvet')); + const txBuilder = factory.getTransferBuilder(transaction); + txBuilder.sender(testData.addresses.validAddresses[0]); + txBuilder.recipients(testData.recipients); + txBuilder.gas(21000); + txBuilder.nonce(64248); + txBuilder.blockRef('0x014ead140e77bbc1'); + txBuilder.addFeePayerAddress(testData.feePayer.address); + txBuilder.expiration(64); + txBuilder.gasPriceCoef(128); + const tx = (await txBuilder.build()) as Transaction; + should.equal(tx.sender, testData.addresses.validAddresses[0]); + should.equal(tx.recipients[0].address, testData.recipients[0].address); + should.equal(tx.recipients[0].amount, testData.recipients[0].amount); + should.equal(tx.gas, 21000); + should.equal(tx.getFee(), '315411764705882352'); + should.equal(tx.nonce, 64248); + should.equal(tx.expiration, 64); + should.equal(tx.type, TransactionType.Send); + tx.inputs.length.should.equal(1); + tx.inputs[0].should.deepEqual({ + address: testData.addresses.validAddresses[0], + value: testData.recipients[0].amount, + coin: 'tvet', + }); + tx.outputs.length.should.equal(1); + tx.outputs[0].should.deepEqual({ + address: testData.recipients[0].address, + value: testData.recipients[0].amount, + coin: 'tvet', + }); + const rawTx = tx.toBroadcastFormat(); + should.equal(txBuilder.isValidRawTransaction(rawTx), true); + rawTx.should.equal(testData.UNSIGNED_TRANSACTION_2); + }); + + it('should build and send a signed tx', async function () { + const txBuilder = factory.from(testData.SPONSORED_TRANSACTION); + txBuilder.getNonce().should.equal(186037); + + const tx = (await txBuilder.build()) as Transaction; + should.equal(tx.type, TransactionType.Send); + tx.inputs.length.should.equal(1); + tx.inputs[0].should.deepEqual({ + address: testData.addresses.validAddresses[0], + value: testData.AMOUNT.toString(), + coin: 'tvet', + }); + tx.outputs.length.should.equal(1); + tx.outputs[0].should.deepEqual({ + address: testData.recipients[0].address, + value: testData.AMOUNT.toString(), + coin: 'tvet', + }); + should.equal(tx.id, '0x6d842d5dc5d59d4e8f0a8ec2757b430d1f19c06766fbc5b3db5ebac8a067a439'); + should.equal(tx.gas, 21000); + should.equal(tx.getFee(), '315411764705882352'); + should.equal(tx.nonce, 186037); + should.equal(tx.expiration, 64); + should.equal(tx.type, TransactionType.Send); + const rawTx = tx.toBroadcastFormat(); + should.equal(txBuilder.isValidRawTransaction(rawTx), true); + should.equal(rawTx, testData.SPONSORED_TRANSACTION); + }); + + it('should succeed to validate a valid signablePayload', async function () { + const transaction = new Transaction(coins.get('tvet')); + const txBuilder = factory.getTransferBuilder(transaction); + txBuilder.sender(testData.addresses.validAddresses[0]); + txBuilder.recipients(testData.recipients); + txBuilder.gas(21000); + txBuilder.nonce(64248); + txBuilder.expiration(64); + txBuilder.blockRef('0x014ead140e77bbc1'); + txBuilder.gasPriceCoef(128); + txBuilder.addFeePayerAddress(testData.feePayer.address); + const tx = (await txBuilder.build()) as Transaction; + const signablePayload = tx.signablePayload; + should.equal( + signablePayload.toString('hex'), + '90c5cd3e79059f65b32088c7d807b4c989c5c3051d5392827ec817ce2037c947' + ); + }); + + it('should build a unsigned tx and validate its toJson', async function () { + const transaction = new Transaction(coins.get('tvet')); + const txBuilder = factory.getTransferBuilder(transaction); + txBuilder.sender(testData.addresses.validAddresses[0]); + txBuilder.recipients(testData.recipients); + txBuilder.gas(21000); + txBuilder.nonce(64248); + txBuilder.expiration(64); + txBuilder.blockRef('0x014ead140e77bbc1'); + txBuilder.gasPriceCoef(128); + txBuilder.addFeePayerAddress(testData.feePayer.address); + const tx = (await txBuilder.build()) as Transaction; + const toJson = tx.toJson(); + should.equal(toJson.sender, testData.addresses.validAddresses[0]); + should.deepEqual(toJson.recipients, [ + { + address: testData.recipients[0].address, + amount: testData.recipients[0].amount, + }, + ]); + should.equal(toJson.nonce, 64248); + should.equal(toJson.gas, 21000); + should.equal(toJson.gasPriceCoef, 128); + should.equal(toJson.expiration, 64); + should.equal(toJson.feePayer, testData.feePayer.address); + }); + + it('should build a signed tx and validate its toJson', async function () { + const txBuilder = factory.from(testData.SPONSORED_TRANSACTION); + const tx = (await txBuilder.build()) as Transaction; + const toJson = tx.toJson(); + should.equal(toJson.id, '0x6d842d5dc5d59d4e8f0a8ec2757b430d1f19c06766fbc5b3db5ebac8a067a439'); + should.equal(toJson.sender, testData.addresses.validAddresses[0]); + should.deepEqual(toJson.recipients, [ + { + address: testData.addresses.validAddresses[1], + amount: testData.AMOUNT.toString(), + }, + ]); + should.equal(toJson.nonce, 186037); + should.equal(toJson.gas, 21000); + should.equal(toJson.gasPriceCoef, 128); + should.equal(toJson.expiration, 64); + }); + }); + + describe('Fail', () => { + it('should fail for invalid sender', async function () { + const transaction = new Transaction(coins.get('tvet')); + const builder = factory.getTransferBuilder(transaction); + should(() => builder.sender('randomString')).throwError('Invalid address randomString'); + }); + + it('should fail for invalid recipient', async function () { + const builder = factory.getTransferBuilder(); + should(() => builder.recipients([testData.invalidRecipients[0]])).throwError('Invalid address randomString'); + should(() => builder.recipients([testData.invalidRecipients[1]])).throwError('Value cannot be less than zero'); + should(() => builder.recipients([testData.invalidRecipients[2]])).throwError('Invalid amount format'); + }); + + it('should fail for invalid gas amount', async function () { + const builder = factory.getTransferBuilder(); + should(() => builder.gas(-1)).throwError('Value cannot be less than zero'); + }); + }); + }); +}); diff --git a/modules/sdk-coin-vet/test/unit/keyPair.ts b/modules/sdk-coin-vet/test/unit/keyPair.ts index 29791e318f..c79f76d8b6 100644 --- a/modules/sdk-coin-vet/test/unit/keyPair.ts +++ b/modules/sdk-coin-vet/test/unit/keyPair.ts @@ -1,8 +1,8 @@ +import * as nacl from 'tweetnacl'; import assert from 'assert'; import should from 'should'; -import { KeyPair } from '../../src/lib'; -import * as nacl from 'tweetnacl'; +import { KeyPair } from '../../src'; describe('VeChain KeyPair', function () { // these are all encodings of the same key so the test suite will show that they we can interchange between them @@ -32,11 +32,13 @@ describe('VeChain KeyPair', function () { it('from an xprv', () => { const keyPair = new KeyPair({ prv: xprv }); const defaultKeys = keyPair.getKeys(); - defaultKeys.prv!.should.equal(prv); + assert(defaultKeys.prv); + defaultKeys.prv.should.equal(prv); defaultKeys.pub.should.equal(pub); const extendedKeys = keyPair.getExtendedKeys(); - extendedKeys.xprv!.should.equal(xprv); + assert(extendedKeys.xprv); + extendedKeys.xprv.should.equal(xprv); extendedKeys.xpub.should.equal(xpub); }); @@ -60,7 +62,8 @@ describe('VeChain KeyPair', function () { it('from a raw private key', () => { const keyPair = new KeyPair({ prv }); const defaultKeys = keyPair.getKeys(); - defaultKeys.prv!.should.equal(prv); + assert(defaultKeys.prv); + defaultKeys.prv.should.equal(prv); defaultKeys.pub.should.equal(uncompressedPub); assert.throws(() => keyPair.getExtendedKeys()); @@ -70,8 +73,10 @@ describe('VeChain KeyPair', function () { const keyPair = new KeyPair(); should.exists(keyPair.getKeys().prv); should.exists(keyPair.getKeys().pub); - should.equal(keyPair.getKeys().prv!.length, 64); - should.equal(keyPair.getKeys().pub.length, 66); + const keys = keyPair.getKeys(); + assert(keys.prv); + should.equal(keys.prv.length, 64); + should.equal(keys.pub.length, 66); }); it('from seed', () => { @@ -138,7 +143,8 @@ describe('VeChain KeyPair', function () { it('should get the keys in extended format from xprv', () => { const keyPair = new KeyPair({ prv: xprv }); const { xprv: calculatedXprv, xpub: calculatedXpub } = keyPair.getExtendedKeys(); - calculatedXprv!.should.equal(xprv); + assert(calculatedXprv); + calculatedXprv.should.equal(xprv); calculatedXpub.should.equal(xpub); }); diff --git a/modules/sdk-coin-vet/test/unit/utils.ts b/modules/sdk-coin-vet/test/unit/utils.ts new file mode 100644 index 0000000000..035db97cf1 --- /dev/null +++ b/modules/sdk-coin-vet/test/unit/utils.ts @@ -0,0 +1,51 @@ +import should from 'should'; +import { Transaction as VetTransaction } from '@vechain/sdk-core'; +import * as testData from '../resources/vet'; +import utils from '../../src/lib/utils'; +import { TransactionType } from '@bitgo/sdk-core'; + +describe('Vechain util library', function () { + describe('isValidAddress', function () { + it('should succeed to validate raw transactoin', function () { + for (const address of testData.addresses.validAddresses) { + should.equal(utils.isValidAddress(address), true); + } + }); + + it('should fail to validate invalid addresses', function () { + for (const address of testData.addresses.invalidAddresses) { + should.doesNotThrow(() => utils.isValidAddress(address)); + should.equal(utils.isValidAddress(address), false); + } + // @ts-expect-error Testing for missing param, should not throw an error + should.doesNotThrow(() => utils.isValidAddress(undefined)); + // @ts-expect-error Testing for missing param, should return false + should.equal(utils.isValidAddress(undefined), false); + }); + }); + + describe('isValidDeserialize', function () { + it('should succeed to correctly deserialize sponsored signed serialized transaction', function () { + const signedTxn: VetTransaction = utils.deserializeTransaction(testData.SPONSORED_TRANSACTION); + should.equal(signedTxn.gasPayer.toString().toLowerCase(), '0xdc9fef0b84a0ccf3f1bd4b84e41743e3e051a083'); + }); + it('should succeed to correctly deserialize unsigned serialized transaction', function () { + const signedTxn: VetTransaction = utils.deserializeTransaction(testData.UNSIGNED_TRANSACTION); + should.equal(signedTxn.origin.toString().toLowerCase(), '0x7ca00e3bc8a836026c2917c6c7c6d049e52099dd'); + }); + it('should fail to deserialize an invalid serialized transaction', function () { + should.throws(() => utils.deserializeTransaction(testData.INVALID_TRANSACTION)); + }); + }); + + it('should get correct transaction type from clause', function () { + should.equal(TransactionType.Send, utils.getTransactionTypeFromClause(testData.TRANSFER_CLAUSE)); + }); + + it('is valid public key', function () { + // with 0x prefix + should.equal(false, utils.isValidPublicKey('0x9b4e96086d111500259f9b38680b0509a405c1904da18976455a20c691d3bb07')); + // without 0x prefix + should.equal(true, utils.isValidPublicKey('029831d82c36a58a69b31177b73d852e260a37769561450dab6ed234d5d965ef0b')); + }); +}); diff --git a/modules/sdk-coin-vet/test/unit/vet.ts b/modules/sdk-coin-vet/test/unit/vet.ts index 15f257bede..63dbbd3fe1 100644 --- a/modules/sdk-coin-vet/test/unit/vet.ts +++ b/modules/sdk-coin-vet/test/unit/vet.ts @@ -1,14 +1,39 @@ +import sinon from 'sinon'; +import assert from 'assert'; +import _ from 'lodash'; import { BitGoAPI } from '@bitgo/sdk-api'; import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; -import { Vet, Tvet } from '../../src'; +import { coins } from '@bitgo/statics'; +import { Vet, Tvet, Transaction } from '../../src'; +import * as testData from '../resources/vet'; describe('Vechain', function () { let bitgo: TestBitGoAPI; + let basecoin; + let newTxPrebuild; + let newTxParams; + + const txPreBuild = { + txHex: testData.SPONSORED_TRANSACTION, + txInfo: {}, + }; + + const txParams = { + recipients: testData.recipients, + }; + before(function () { bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); bitgo.safeRegister('vet', Vet.createInstance); bitgo.safeRegister('tvet', Tvet.createInstance); bitgo.initializeTestVars(); + basecoin = bitgo.coin('tvet'); + newTxPrebuild = () => { + return _.cloneDeep(txPreBuild); + }; + newTxParams = () => { + return _.cloneDeep(txParams); + }; }); it('should return the right info', function () { @@ -34,4 +59,137 @@ describe('Vechain', function () { vet.isValidAddress('0x690fFcefa92876C772E85d4B5963807C2152e08d').should.true(); vet.isValidAddress('0xe59F1cea4e0FEf511e3d0f4EEc44ADf19C4cbeEC').should.true(); }); + + it('is valid pub', function () { + // with 0x prefix + basecoin.isValidPub('0x9b4e96086d111500259f9b38680b0509a405c1904da18976455a20c691d3bb07').should.equal(false); + // without 0x prefix + basecoin.isValidPub('027c10f30d5c874cee3d193a321c82926d905998cb4852880935da4ee1820bd7d5').should.equal(true); + }); + + describe('Verify transaction: ', () => { + it('should succeed to verify transaction', async function () { + const txPrebuild = newTxPrebuild(); + const txParams = newTxParams(); + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification }); + isTransactionVerified.should.equal(true); + }); + + it('should succeed to verify transaction when recipients amount are numbers', async function () { + const txPrebuild = newTxPrebuild(); + const txParamsWithNumberAmounts = newTxParams(); + txParamsWithNumberAmounts.recipients = txParamsWithNumberAmounts.recipients.map(({ address, amount }) => { + return { address, amount: Number(amount) }; + }); + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ + txParams: txParamsWithNumberAmounts, + txPrebuild, + verification, + }); + isTransactionVerified.should.equal(true); + }); + + it('should fail to verify transaction with invalid param', async function () { + const txPrebuild = {}; + const txParams = newTxParams(); + txParams.recipients = undefined; + await basecoin + .verifyTransaction({ + txParams, + txPrebuild, + }) + .should.rejectedWith('missing required tx prebuild property txHex'); + }); + }); + + describe('Parse and Explain Transactions: ', () => { + const transferInputsResponse = [ + { + address: testData.addresses.validAddresses[0], + amount: testData.AMOUNT.toString(), + }, + ]; + + const transferOutputsResponse = [ + { + address: testData.recipients[0].address, + amount: testData.recipients[0].amount, + }, + ]; + + it('should parse a transfer transaction', async function () { + const parsedTransaction = await basecoin.parseTransaction({ + txHex: testData.SPONSORED_TRANSACTION, + }); + + parsedTransaction.should.deepEqual({ + inputs: transferInputsResponse, + outputs: transferOutputsResponse, + }); + }); + + it('should explain a transfer transaction', async function () { + const rawTx = newTxPrebuild().txHex; + const transaction = new Transaction(coins.get('tvet')); + transaction.fromRawTransaction(rawTx); + const explainedTx = transaction.explainTransaction(); + explainedTx.should.deepEqual({ + displayOrder: [ + 'id', + 'outputs', + 'outputAmount', + 'changeOutputs', + 'changeAmount', + 'fee', + 'withdrawAmount', + 'sender', + 'type', + ], + id: '0x6d842d5dc5d59d4e8f0a8ec2757b430d1f19c06766fbc5b3db5ebac8a067a439', + outputs: [ + { + address: '0xe59f1cea4e0fef511e3d0f4eec44adf19c4cbeec', + amount: '100000000000000000', + }, + ], + outputAmount: '100000000000000000', + changeOutputs: [], + changeAmount: '0', + fee: { fee: '315411764705882352' }, + sender: '0x7ca00e3bc8a836026c2917c6c7c6d049e52099dd', + type: 0, + }); + }); + + it('should fail to explain a invalid raw transaction', async function () { + const rawTx = 'invalidRawTransaction'; + const transaction = new Transaction(coins.get('tvet')); + await assert.rejects(async () => transaction.fromRawTransaction(rawTx), { + message: 'invalid raw transaction', + }); + }); + + it('should fail to parse a transfer transaction when explainTransaction response is undefined', async function () { + const stub = sinon.stub(Vet.prototype, 'explainTransaction'); + stub.resolves(undefined); + await basecoin + .parseTransaction({ txHex: testData.INVALID_TRANSACTION }) + .should.be.rejectedWith('Invalid transaction'); + stub.restore(); + }); + }); + + describe('address validation', () => { + it('should return true when validating a well formatted address prefixed with 0x', async function () { + const address = testData.addresses.validAddresses[0]; + basecoin.isValidAddress(address).should.equal(true); + }); + + it('should return false when validating an incorrectly formatted', async function () { + const address = 'wrongaddress'; + basecoin.isValidAddress(address).should.equal(false); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 1619963ec3..f8b17e314f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,16 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -1759,6 +1769,11 @@ resolved "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== +"@ethereumjs/rlp@^5.0.2": + version "5.0.2" + resolved "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz#c89bd82f2f3bec248ab2d517ae25f5bbc4aac842" + integrity sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA== + "@ethereumjs/tx@^3.3.0": version "3.5.2" resolved "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz#197b9b6299582ad84f9527ca961466fce2296c1c" @@ -3456,6 +3471,18 @@ dependencies: bs58 "^5.0.0" +"@noble/ciphers@^1.1.1", "@noble/ciphers@^1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" + integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== + +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/curves@1.3.0", "@noble/curves@~1.3.0": version "1.3.0" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" @@ -3477,6 +3504,13 @@ dependencies: "@noble/hashes" "1.7.1" +"@noble/curves@1.9.1", "@noble/curves@^1.6.0", "@noble/curves@~1.9.0": + version "1.9.1" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz#9654a0bc6c13420ae252ddcf975eaf0f58f0a35c" + integrity sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA== + dependencies: + "@noble/hashes" "1.8.0" + "@noble/curves@^1.2.0", "@noble/curves@^1.7.0": version "1.9.0" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz#13e0ca8be4a0ce66c113693a94514e5599f40cfc" @@ -3489,6 +3523,11 @@ resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@1.3.3", "@noble/hashes@~1.3.3": version "1.3.3" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" @@ -3504,7 +3543,7 @@ resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== -"@noble/hashes@1.8.0", "@noble/hashes@^1.1.5": +"@noble/hashes@1.8.0", "@noble/hashes@^1.1.5", "@noble/hashes@^1.5.0", "@noble/hashes@~1.8.0": version "1.8.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== @@ -4698,6 +4737,11 @@ resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.4.tgz#002eb571a35d69bdb4c214d0995dff76a8dcd2a9" integrity sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ== +"@scure/base@^1.2.4", "@scure/base@~1.2.5": + version "1.2.6" + resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz#ca917184b8231394dd8847509c67a0be522e59f6" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + "@scure/base@~1.1.0", "@scure/base@~1.1.5", "@scure/base@~1.1.6": version "1.1.9" resolved "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" @@ -4721,6 +4765,15 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip32@1.7.0", "@scure/bip32@^1.5.0": + version "1.7.0" + resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz#b8683bab172369f988f1589640e53c4606984219" + integrity sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw== + dependencies: + "@noble/curves" "~1.9.0" + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + "@scure/bip32@^1.3.1", "@scure/bip32@^1.4.0": version "1.6.2" resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz#093caa94961619927659ed0e711a6e4bf35bffd0" @@ -4746,6 +4799,14 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip39@1.6.0", "@scure/bip39@^1.4.0", "@scure/bip39@^1.5.1": + version "1.6.0" + resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz#475970ace440d7be87a6086cbee77cb8f1a684f9" + integrity sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A== + dependencies: + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + "@scure/bip39@^1.2.1", "@scure/bip39@^1.3.0": version "1.5.4" resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz#07fd920423aa671be4540d59bdd344cc1461db51" @@ -5626,6 +5687,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + "@types/node@^10.12.18": version "10.17.60" resolved "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" @@ -6057,6 +6125,37 @@ expo-modules-autolinking "^0.0.3" invariant "^2.2.4" +"@vechain/sdk-core@^1.2.0-rc.3": + version "1.2.0-rc.3" + resolved "https://registry.npmjs.org/@vechain/sdk-core/-/sdk-core-1.2.0-rc.3.tgz#cc14f6ebaff34bc3c7643ef5e60a5dcadd9078d8" + integrity sha512-DvMhuKmNgKAEhn/WladUhMe/luMMS2lqy/PhEWLIaiaVHwAzwRGmlujK5+YUylCgQqt/frw9SZSPTTOJ9Fs5Yg== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + "@noble/ciphers" "^1.1.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/base" "^1.2.4" + "@scure/bip32" "^1.4.0" + "@scure/bip39" "^1.5.1" + "@vechain/sdk-errors" "1.2.0-rc.3" + "@vechain/sdk-logging" "1.2.0-rc.3" + abitype "^1.0.6" + ethers "6.13.4" + fast-json-stable-stringify "^2.1.0" + viem "^2.21.45" + +"@vechain/sdk-errors@1.2.0-rc.3": + version "1.2.0-rc.3" + resolved "https://registry.npmjs.org/@vechain/sdk-errors/-/sdk-errors-1.2.0-rc.3.tgz#c03723474418a0a117702e3307af2b242296a4fc" + integrity sha512-zanvUB5yFVqvKGf6gBExc2A9+KIj11xw5BjycNhNCp78jLgjGo/fMLCOgt3FMXE5jhvPyIvSfTzd3MOAOnmK/Q== + +"@vechain/sdk-logging@1.2.0-rc.3": + version "1.2.0-rc.3" + resolved "https://registry.npmjs.org/@vechain/sdk-logging/-/sdk-logging-1.2.0-rc.3.tgz#4045e8133f62456dedcf6f39d613b8cfcec17c00" + integrity sha512-wvNnkNcJaP/h+NwkC/OQ5kgzasmXNldnHkyCfFLbdB70xTiDvSqfIH6TAzpfwegKUSiHXtRo02snfOI+WOThWQ== + dependencies: + "@vechain/sdk-errors" "1.2.0-rc.3" + "@vue/compiler-core@3.5.13": version "3.5.13" resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05" @@ -6307,6 +6406,11 @@ abbrev@1, abbrev@^1.0.0: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abitype@1.0.8, abitype@^1.0.6: + version "1.0.8" + resolved "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" + integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -6378,6 +6482,11 @@ aes-js@3.0.0: resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + after@0.8.2: version "0.8.2" resolved "https://registry.npmjs.org/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" @@ -10673,6 +10782,19 @@ ethers@5.6.9: "@ethersproject/web" "5.6.1" "@ethersproject/wordlists" "5.6.1" +ethers@6.13.4: + version "6.13.4" + resolved "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz#bd3e1c3dc1e7dc8ce10f9ffb4ee40967a651b53c" + integrity sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "22.7.5" + aes-js "4.0.0-beta.5" + tslib "2.7.0" + ws "8.17.1" + ethers@^5.1.3, ethers@^5.4.4, ethers@^5.7.2: version "5.8.0" resolved "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz#97858dc4d4c74afce83ea7562fe9493cedb4d377" @@ -11032,7 +11154,7 @@ fast-glob@^3.2.5, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.8" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -13171,6 +13293,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isows@1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz#1c06400b7eed216fbba3bcbd68f12490fc342915" + integrity sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -15740,6 +15867,20 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" +ox@0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/ox/-/ox-0.7.1.tgz#fb23a770dd966c051ad916d4e2e655a6f995e1cf" + integrity sha512-+k9fY9PRNuAMHRFIUbiK9Nt5seYHHzSQs9Bj+iMETcGtlpS7SmBzcGSVUQO3+nqGLEiNK4598pHNFlVRaZbRsg== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/ciphers" "^1.3.0" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -19606,6 +19747,11 @@ tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tslib@^1, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -19902,6 +20048,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -20216,6 +20367,20 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +viem@^2.21.45: + version "2.30.6" + resolved "https://registry.npmjs.org/viem/-/viem-2.30.6.tgz#fffe69aa4d16ab1802b21b44856ae09bacf10f9b" + integrity sha512-N3vGy3pZ+EVgQRuWqQhZPFXxQE8qMRrBd3uM+KLc1Ub2w6+vkyr7umeWQCM4c+wlsCdByUgh2630MDMLquMtpg== + dependencies: + "@noble/curves" "1.9.1" + "@noble/hashes" "1.8.0" + "@scure/bip32" "1.7.0" + "@scure/bip39" "1.6.0" + abitype "1.0.8" + isows "1.0.7" + ox "0.7.1" + ws "8.18.2" + vlq@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz#6057b85729245b9829e3cc7755f95b228d4fe041" @@ -20940,11 +21105,21 @@ ws@7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.17.1: + version "8.17.1" + resolved "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + ws@8.18.0: version "8.18.0" resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== +ws@8.18.2: + version "8.18.2" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" + integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== + ws@8.8.0: version "8.8.0" resolved "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769"