diff --git a/modules/sdk-coin-sol/src/sol.ts b/modules/sdk-coin-sol/src/sol.ts index 4b502079c6..3afb2bc696 100644 --- a/modules/sdk-coin-sol/src/sol.ts +++ b/modules/sdk-coin-sol/src/sol.ts @@ -2,10 +2,14 @@ * @prettier */ +import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token'; import BigNumber from 'bignumber.js'; import * as base58 from 'bs58'; +import * as _ from 'lodash'; +import * as request from 'superagent'; import { + AuditDecryptedKeyParams, BaseBroadcastTransactionOptions, BaseBroadcastTransactionResult, BaseCoin, @@ -27,9 +31,13 @@ import { MPCTx, MPCTxs, MPCUnsignedTx, + MultisigType, + multisigTypes, OvcInput, OvcOutput, ParsedTransaction, + PopulatedIntent, + PrebuildTransactionWithIntentOptions, PresignTransactionOptions, PublicKey, RecoveryTxRequest, @@ -40,16 +48,9 @@ import { TransactionRecipient, VerifyAddressOptions, VerifyTransactionOptions, - MultisigType, - multisigTypes, - AuditDecryptedKeyParams, - PopulatedIntent, - PrebuildTransactionWithIntentOptions, } from '@bitgo/sdk-core'; import { auditEddsaPrivateKey, getDerivationPath } from '@bitgo/sdk-lib-mpc'; import { BaseNetwork, CoinFamily, coins, BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; -import * as _ from 'lodash'; -import * as request from 'superagent'; import { KeyPair as SolKeyPair, Transaction, TransactionBuilder, TransactionBuilderFactory } from './lib'; import { getAssociatedTokenAccountAddress, @@ -60,7 +61,6 @@ import { isValidPublicKey, validateRawTransaction, } from './lib/utils'; -import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token'; export const DEFAULT_SCAN_FACTOR = 20; // default number of receive addresses to scan for funds @@ -173,6 +173,8 @@ export interface SolConsolidationRecoveryOptions extends MPCConsolidationRecover } const HEX_REGEX = /^[0-9a-fA-F]+$/; +const BLIND_SIGNING_TX_TYPES_TO_CHECK = { enabletoken: 'AssociatedTokenAccountInitialization' }; +const BLIND_SIGNING_TX_TYPES_WITH_EMPTY_OUTPUTS = ['enabletoken']; export class Sol extends BaseCoin { protected readonly _staticsCoin: Readonly; @@ -233,6 +235,32 @@ export class Sol extends BaseCoin { return Math.pow(10, this._staticsCoin.decimalPlaces); } + verifyTxType(expectedTypeFromUserParams: string | undefined, actualTypeFromDecoded: string | undefined): void { + // do nothing, let the tx fail way down as always + if (expectedTypeFromUserParams === undefined || actualTypeFromDecoded === undefined) return; + + // ex: user param comes with 'enabletoken' included in BLIND SIGNING -> if fails later in the IF check + // ex: user param comes with 'transfer' non included in BLIND SIGNING-> correctPrebuildTxType goes undefined and IF later goes false so it pass + // check the unit tests for this. + const correctPrebuildTxType = BLIND_SIGNING_TX_TYPES_TO_CHECK[expectedTypeFromUserParams]; + + if (correctPrebuildTxType && correctPrebuildTxType !== actualTypeFromDecoded) { + throw new Error( + `Tx type "${actualTypeFromDecoded}" does not match expected txParams type "${expectedTypeFromUserParams}"` + ); + } + } + + verifyNoOutputs(expectedTypeFromUserParams: string | undefined, decodedTxHex: TransactionExplanation): void { + if ( + expectedTypeFromUserParams && + BLIND_SIGNING_TX_TYPES_WITH_EMPTY_OUTPUTS.includes(expectedTypeFromUserParams) && + decodedTxHex.outputs?.length > 0 + ) { + throw new Error(`Tx outputs were found when none were expected for sol enablement tokens`); + } + } + async verifyTransaction(params: SolVerifyTransactionOptions): Promise { // asset name to transfer amount map const totalAmount: Record = {}; @@ -261,6 +289,9 @@ export class Sol extends BaseCoin { transaction.fromRawTransaction(rawTxBase64); const explainedTx = transaction.explainTransaction(); + this.verifyNoOutputs(txParams.type, explainedTx); + this.verifyTxType(txParams.type, explainedTx.type); + // users do not input recipients for consolidation requests as they are generated by the server if (txParams.recipients !== undefined) { const filteredRecipients = txParams.recipients?.map((recipient) => diff --git a/modules/sdk-coin-sol/test/fixtures/sol.ts b/modules/sdk-coin-sol/test/fixtures/sol.ts index 6985b1681b..b285a472b9 100644 --- a/modules/sdk-coin-sol/test/fixtures/sol.ts +++ b/modules/sdk-coin-sol/test/fixtures/sol.ts @@ -1017,3 +1017,385 @@ export const wrwUser = { walletAddress4: 'E7Z6pFfUhjx2dFjdB9Ws2KnKepXoq62TeF5uaCVSvqQV', walletAddress5: '8V34g2KeJXChECTWHfcJ5NeyWwyb7HvKpu66DD3YPNcf', }; + +export const enableTokenFixtures = { + txParams: { + apiVersion: 'full', + enableTokens: [ + { + name: 'tsol:ray', + address: 'A8DzSnggNN84nwPgVvLtdo1yZ3VscLGDi4FdEQXzUyYy', + }, + ], + type: 'enabletoken', + walletPassphrase: 'test-password', + prebuildTx: { + walletId: '68bafee43eb5cd22aca2afd6b13ec7ad', + wallet: { + id: '68bafee43eb5cd22aca2afd6b13ec7ad', + users: [ + { + user: '65f9c6797236825d1b4e1f82a1035b46', + permissions: ['admin', 'spend', 'view'], + }, + ], + coin: 'tsol', + label: 'SOL BLIND SIGNING', + m: 2, + n: 3, + keys: [ + '68bafed588671cf94ed8a5dbba882ad3', + '68bafed52d6d95694cf596c2a153868b', + '68bafed42d6d95694cf5956ed640853a', + ], + keySignatures: {}, + enterprise: '66632c6b42b03d265a939048beaaee55', + organization: '66632c6d42b03d265a939107d2f586e5', + bitgoOrg: 'BitGo Trust', + tags: ['68bafee43eb5cd22aca2afd6b13ec7ad', '66632c6b42b03d265a939048beaaee55'], + disableTransactionNotifications: false, + freeze: {}, + deleted: false, + approvalsRequired: 1, + isCold: false, + coinSpecific: { + rootAddress: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + pendingChainInitialization: false, + minimumFunding: 2447136, + lastChainIndex: { + 0: 1, + 1: -1, + }, + nonceExpiresAt: '2025-09-05T12:54:16.219Z', + trustedTokens: [ + { + token: 'tsol:gari', + state: 'active', + }, + { + token: 'tsol:gmt', + state: 'active', + }, + { + token: 'tsol:orca', + state: 'active', + }, + ], + }, + admin: { + policy: { + date: '2025-09-05T15:16:52.376Z', + id: '68bafee43eb5cd22aca2afd9e699a3ac', + label: 'default', + rules: [], + version: 0, + latest: true, + }, + }, + clientFlags: [], + walletFlags: [], + allowBackupKeySigning: false, + recoverable: true, + startDate: '2025-09-05T15:16:52.000Z', + type: 'hot', + buildDefaults: {}, + customChangeKeySignatures: {}, + hasLargeNumberOfAddresses: false, + multisigType: 'tss', + hasReceiveTransferPolicy: false, + creator: '65f9c6797236825d1b4e1f82a1035b46', + walletFullyCreated: true, + config: {}, + balanceString: '997865920', + confirmedBalanceString: '997865920', + spendableBalanceString: '985212384', + reservedBalanceString: '2447136', + receiveAddress: { + id: '68bafee43eb5cd22aca2afe431d4272b', + address: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + chain: 0, + index: 0, + coin: 'tsol', + wallet: '68bafee43eb5cd22aca2afd6b13ec7ad', + coinSpecific: { + rootAddress: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + minimumFunding: 2447136, + type: 'native', + pendingChainInitialization: false, + trustedTokens: [ + { + token: 'tsol:gari', + state: 'active', + }, + { + token: 'tsol:gmt', + state: 'active', + }, + { + token: 'tsol:orca', + state: 'active', + }, + ], + }, + }, + pendingApprovals: [], + }, + txRequestId: '17b5d895-c380-4973-9ab2-191a04d545f8', + txHex: + '0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c70c9bb38230c6577dd46ae67975420585747a3be7fe2e8f744508f8bbd9122eda991f45c8031d1bd218998602870078164671e82d8020bd9caa36387163cc010201070be8e579a7198c71ee38b3ba2ee56af88cb16995a8f0bf46ab2a3702603cb732e41c96172044f1217c3784e8f02f49e2c8fc3591e81294ab54394f9d22fd7b7a8f3566d43c990eb025f022d68c80888882e9cbd00ed6267cff400cc065be79b9d2c41f3ee4163b631730fea44aaf49b2b5eb7ff29a52a8292fae6f45f7529c93c1000000000000000000000000000000000000000000000000000000000000000081f74cefd3fb844f62ca7ed6f1418b3f86faf0886647d980259bf0aafbe77b6c8792efb2b156bf73ac31e3022e9b98cdc80afa50d34c7f005fe4b84b540336e68c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f85906a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a0000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9b10993465fa6fd2e70caebcc7220931c696e877c5c4f1ee3d7bf3f1874b4ddb70204030308010404000000070700020605040a0900', + buildParams: { + apiVersion: 'full', + enableTokens: [ + { + name: 'tsol:ray', + address: 'A8DzSnggNN84nwPgVvLtdo1yZ3VscLGDi4FdEQXzUyYy', + }, + ], + type: 'enabletoken', + }, + feeInfo: { + fee: 2049280, + feeString: '2049280', + }, + }, + }, + + txPrebuildRaw: { + walletId: '68bafee43eb5cd22aca2afd6b13ec7ad', + wallet: { + id: '68bafee43eb5cd22aca2afd6b13ec7ad', + users: [ + { + user: '65f9c6797236825d1b4e1f82a1035b46', + permissions: ['admin', 'spend', 'view'], + }, + ], + coin: 'tsol', + label: 'SOL BLIND SIGNING', + m: 2, + n: 3, + keys: [ + '68bafed588671cf94ed8a5dbba882ad3', + '68bafed52d6d95694cf596c2a153868b', + '68bafed42d6d95694cf5956ed640853a', + ], + keySignatures: {}, + enterprise: '66632c6b42b03d265a939048beaaee55', + organization: '66632c6d42b03d265a939107d2f586e5', + bitgoOrg: 'BitGo Trust', + tags: ['68bafee43eb5cd22aca2afd6b13ec7ad', '66632c6b42b03d265a939048beaaee55'], + disableTransactionNotifications: false, + freeze: {}, + deleted: false, + approvalsRequired: 1, + isCold: false, + coinSpecific: { + rootAddress: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + pendingChainInitialization: false, + minimumFunding: 2447136, + lastChainIndex: { + 0: 1, + 1: -1, + }, + nonceExpiresAt: '2025-09-05T12:54:16.219Z', + trustedTokens: [ + { + token: 'tsol:gari', + state: 'active', + }, + { + token: 'tsol:gmt', + state: 'active', + }, + { + token: 'tsol:orca', + state: 'active', + }, + ], + }, + admin: { + policy: { + date: '2025-09-05T15:16:52.376Z', + id: '68bafee43eb5cd22aca2afd9e699a3ac', + label: 'default', + rules: [], + version: 0, + latest: true, + }, + }, + clientFlags: [], + walletFlags: [], + allowBackupKeySigning: false, + recoverable: true, + startDate: '2025-09-05T15:16:52.000Z', + type: 'hot', + buildDefaults: {}, + customChangeKeySignatures: {}, + hasLargeNumberOfAddresses: false, + multisigType: 'tss', + hasReceiveTransferPolicy: false, + creator: '65f9c6797236825d1b4e1f82a1035b46', + walletFullyCreated: true, + config: {}, + balanceString: '997865920', + confirmedBalanceString: '997865920', + spendableBalanceString: '985212384', + reservedBalanceString: '2447136', + receiveAddress: { + id: '68bafee43eb5cd22aca2afe431d4272b', + address: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + chain: 0, + index: 0, + coin: 'tsol', + wallet: '68bafee43eb5cd22aca2afd6b13ec7ad', + coinSpecific: { + rootAddress: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + minimumFunding: 2447136, + type: 'native', + pendingChainInitialization: false, + trustedTokens: [ + { + token: 'tsol:gari', + state: 'active', + }, + { + token: 'tsol:gmt', + state: 'active', + }, + { + token: 'tsol:orca', + state: 'active', + }, + ], + }, + }, + pendingApprovals: [], + }, + txRequestId: '17b5d895-c380-4973-9ab2-191a04d545f8', + txHex: + '0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c70c9bb38230c6577dd46ae67975420585747a3be7fe2e8f744508f8bbd9122eda991f45c8031d1bd218998602870078164671e82d8020bd9caa36387163cc010201070be8e579a7198c71ee38b3ba2ee56af88cb16995a8f0bf46ab2a3702603cb732e41c96172044f1217c3784e8f02f49e2c8fc3591e81294ab54394f9d22fd7b7a8f3566d43c990eb025f022d68c80888882e9cbd00ed6267cff400cc065be79b9d2c41f3ee4163b631730fea44aaf49b2b5eb7ff29a52a8292fae6f45f7529c93c1000000000000000000000000000000000000000000000000000000000000000081f74cefd3fb844f62ca7ed6f1418b3f86faf0886647d980259bf0aafbe77b6c8792efb2b156bf73ac31e3022e9b98cdc80afa50d34c7f005fe4b84b540336e68c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f85906a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a0000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9b10993465fa6fd2e70caebcc7220931c696e877c5c4f1ee3d7bf3f1874b4ddb70204030308010404000000070700020605040a0900', + buildParams: { + apiVersion: 'full', + enableTokens: [ + { + name: 'tsol:ray', + address: 'A8DzSnggNN84nwPgVvLtdo1yZ3VscLGDi4FdEQXzUyYy', + }, + ], + type: 'enabletoken', + }, + feeInfo: { + fee: 2049280, + feeString: '2049280', + }, + }, + walletData: { + id: '68bafee43eb5cd22aca2afd6b13ec7ad', + users: [ + { + user: '65f9c6797236825d1b4e1f82a1035b46', + permissions: ['admin', 'spend', 'view'], + }, + ], + coin: 'tsol', + label: 'SOL BLIND SIGNING', + m: 2, + n: 3, + keys: ['68bafed588671cf94ed8a5dbba882ad3', '68bafed52d6d95694cf596c2a153868b', '68bafed42d6d95694cf5956ed640853a'], + keySignatures: {}, + enterprise: '66632c6b42b03d265a939048beaaee55', + organization: '66632c6d42b03d265a939107d2f586e5', + bitgoOrg: 'BitGo Trust', + tags: ['68bafee43eb5cd22aca2afd6b13ec7ad', '66632c6b42b03d265a939048beaaee55'], + disableTransactionNotifications: false, + freeze: {}, + deleted: false, + approvalsRequired: 1, + isCold: false, + coinSpecific: { + rootAddress: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + pendingChainInitialization: false, + minimumFunding: 2447136, + lastChainIndex: { + 0: 1, + 1: -1, + }, + nonceExpiresAt: '2025-09-05T12:54:16.219Z', + trustedTokens: [ + { + token: 'tsol:gari', + state: 'active', + }, + { + token: 'tsol:gmt', + state: 'active', + }, + { + token: 'tsol:orca', + state: 'active', + }, + ], + }, + admin: { + policy: { + date: '2025-09-05T15:16:52.376Z', + id: '68bafee43eb5cd22aca2afd9e699a3ac', + label: 'default', + rules: [], + version: 0, + latest: true, + }, + }, + clientFlags: [], + walletFlags: [], + allowBackupKeySigning: false, + recoverable: true, + startDate: '2025-09-05T15:16:52.000Z', + type: 'hot', + buildDefaults: {}, + customChangeKeySignatures: {}, + hasLargeNumberOfAddresses: false, + multisigType: 'tss', + hasReceiveTransferPolicy: false, + creator: '65f9c6797236825d1b4e1f82a1035b46', + walletFullyCreated: true, + config: {}, + balanceString: '997865920', + confirmedBalanceString: '997865920', + spendableBalanceString: '985212384', + reservedBalanceString: '2447136', + receiveAddress: { + id: '68bafee43eb5cd22aca2afe431d4272b', + address: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + chain: 0, + index: 0, + coin: 'tsol', + wallet: '68bafee43eb5cd22aca2afd6b13ec7ad', + coinSpecific: { + rootAddress: 'Gg8Y2TYjUiW4MdUJU3qR3CTDKG8izuKYpXobDj3uzi1y', + minimumFunding: 2447136, + type: 'native', + pendingChainInitialization: false, + trustedTokens: [ + { + token: 'tsol:gari', + state: 'active', + }, + { + token: 'tsol:gmt', + state: 'active', + }, + { + token: 'tsol:orca', + state: 'active', + }, + ], + }, + }, + pendingApprovals: [], + }, + sendTxHex: + '020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000091d9945020da975c1908af823d1f3fb024362fc9c2b42126542e53945b2e58107286ccf6de77449a5dee338aebb2f47a11690d918e1d70d4bf948a7ed708430802010206e8e579a7198c71ee38b3ba2ee56af88cb16995a8f0bf46ab2a3702603cb732e41c96172044f1217c3784e8f02f49e2c8fc3591e81294ab54394f9d22fd7b7a8f8401c3f67cfa52518b34a09b08f4ea77e1c4fb9d89bfaccdc33cf8b8a9cf8d7bc5c538dc718dd6601193893098aa50838d76868be6561eeb39bca11197dc2f82000000000000000000000000000000000000000000000000000000000000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000039b6464b8771903ac1721b28ab9e19ae5f034543572c7034d282c493769f9bd40204030305010404000000040200020c020000000a00000000000000', + tamperedTxHexWithOutputs: + '020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000091d9945020da975c1908af823d1f3fb024362fc9c2b42126542e53945b2e58107286ccf6de77449a5dee338aebb2f47a11690d918e1d70d4bf948a7ed708430802010206e8e579a7198c71ee38b3ba2ee56af88cb16995a8f0bf46ab2a3702603cb732e41c96172044f1217c3784e8f02f49e2c8fc3591e81294ab54394f9d22fd7b7a8f8401c3f67cfa52518b34a09b08f4ea77e1c4fb9d89bfaccdc33cf8b8a9cf8d7bc5c538dc718dd6601193893098aa50838d76868be6561eeb39bca11197dc2f82000000000000000000000000000000000000000000000000000000000000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000039b6464b8771903ac1721b28ab9e19ae5f034543572c7034d282c493769f9bd40204030305010404000000040200020c020000000a00000000000000', +}; diff --git a/modules/sdk-coin-sol/test/unit/sol.ts b/modules/sdk-coin-sol/test/unit/sol.ts index 7fa8918ab4..ba726c0d56 100644 --- a/modules/sdk-coin-sol/test/unit/sol.ts +++ b/modules/sdk-coin-sol/test/unit/sol.ts @@ -1,8 +1,8 @@ +import assert from 'assert'; import * as _ from 'lodash'; +import nock from 'nock'; import * as should from 'should'; import * as sinon from 'sinon'; -import nock from 'nock'; -import assert from 'assert'; import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; @@ -15,6 +15,7 @@ import { MPCSweepTxs, MPCTx, MPCTxs, + TransactionExplanation, TransactionPrebuild, TssUtils, TxRequest, @@ -23,14 +24,14 @@ import { } from '@bitgo/sdk-core'; import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; import { coins } from '@bitgo/statics'; -import { KeyPair, Sol, Tsol } from '../../src'; +import { KeyPair, Sol, SolVerifyTransactionOptions, Tsol } from '../../src'; import { Transaction } from '../../src/lib'; import { AtaInit, InstructionParams, TokenTransfer } from '../../src/lib/iface'; import { getAssociatedTokenAccountAddress } from '../../src/lib/utils'; import * as testData from '../fixtures/sol'; import * as resources from '../resources/sol'; -import { getBuilderFactory } from './getBuilderFactory'; import { solBackupKey } from './fixtures/solBackupKey'; +import { getBuilderFactory } from './getBuilderFactory'; describe('SOL:', function () { let bitgo: TestBitGoAPI; @@ -3215,4 +3216,97 @@ describe('SOL:', function () { ); }); }); + + describe('blind signing token enablement protection', () => { + let originalExplain: (this: Transaction) => TransactionExplanation; + let explainStub: sinon.SinonStub; + beforeEach(() => { + originalExplain = Transaction.prototype.explainTransaction; + }); + + afterEach(() => { + // Restore the stub to avoid affecting other tests in cascade as explainTransaction + // is used in multiple places + if (explainStub) explainStub.restore(); + }); + it('should verify as valid the enabletoken intent when prebuild tx matchs user intent ', async function () { + const { txParams, txPrebuildRaw, walletData } = testData.enableTokenFixtures; + const wallet = new Wallet(bitgo, basecoin, walletData); + const sameIntentTx = await basecoin.verifyTransaction({ + txParams, + txPrebuild: txPrebuildRaw, + wallet, + } as unknown as SolVerifyTransactionOptions); + + sameIntentTx.should.equal(true); + }); + + it('should thrown an error when tampered prebuild tx type ', async function () { + const { txParams, txPrebuildRaw, sendTxHex, walletData } = testData.enableTokenFixtures; + const tamperedTxPrebuild = { ...txPrebuildRaw, txHex: sendTxHex }; + + const wallet = new Wallet(bitgo, basecoin, walletData); + // Necessary fluff in order to reuse the same txHex for the next test (as it autogenerate outputs) but removing the outputs + // to make it fail by the type instead of the outputs array. + explainStub = sinon.stub(Transaction.prototype, 'explainTransaction').callsFake(function (this: Transaction) { + const result = originalExplain.call(this) as unknown as Record; + const outputs = Array.isArray(result.outputs) ? [] : []; + const durableNonce = + result.durableNonce && typeof result.durableNonce === 'object' + ? (result.durableNonce as import('../../src/lib/iface').DurableNonceParams) + : undefined; + const memo = typeof result.memo === 'string' ? result.memo : undefined; + const stakingAuthorize = + result.stakingAuthorize && typeof result.stakingAuthorize === 'object' + ? (result.stakingAuthorize as import('../../src/lib/iface').StakingAuthorizeParams) + : undefined; + const stakingDelegate = + result.stakingDelegate && typeof result.stakingDelegate === 'object' + ? (result.stakingDelegate as import('../../src/lib/iface').StakingDelegateParams) + : undefined; + return { + displayOrder: Array.isArray(result.displayOrder) ? result.displayOrder : [], + id: typeof result.id === 'string' ? result.id : '', + outputs, + outputAmount: typeof result.outputAmount === 'string' ? result.outputAmount : '0', + changeOutputs: Array.isArray(result.changeOutputs) ? result.changeOutputs : [], + changeAmount: typeof result.changeAmount === 'string' ? result.changeAmount : '0', + fee: typeof result.fee === 'object' && result.fee !== null ? (result.fee as { fee: string }) : { fee: '0' }, + type: typeof result.type === 'string' ? result.type : '', + blockhash: typeof result.blockhash === 'string' ? result.blockhash : '', + durableNonce, + memo, + stakingAuthorize, + stakingDelegate, + }; + }); + + await assert.rejects( + async () => + await basecoin.verifyTransaction({ + txParams, + txPrebuild: tamperedTxPrebuild, + wallet, + } as unknown as SolVerifyTransactionOptions), + { message: 'Tx type "Send" does not match expected txParams type "enabletoken"' } + ); + }); + + it('should throw an error when outputs is not empty', async function () { + const { txParams, txPrebuildRaw, tamperedTxHexWithOutputs, walletData } = testData.enableTokenFixtures; + const tamperedTxPrebuild = { ...txPrebuildRaw, txHex: tamperedTxHexWithOutputs }; + + const wallet = new Wallet(bitgo, basecoin, walletData); + // tamperedTxPrebuild enables a token that contains outputs + await assert.rejects( + async () => + basecoin.verifyTransaction({ + txParams, + txPrebuild: tamperedTxPrebuild, + wallet, + } as unknown as SolVerifyTransactionOptions), + { message: 'Tx outputs were found when none were expected for sol enablement tokens' } + ); + }); + }); });