|
1 | 1 | import { Ada } from './ada'; |
2 | | -import { BitGoBase, CoinConstructor, NamedCoinConstructor } from '@bitgo/sdk-core'; |
| 2 | +import { |
| 3 | + BitGoBase, |
| 4 | + CoinConstructor, |
| 5 | + NamedCoinConstructor, |
| 6 | + VerifyTransactionOptions, |
| 7 | + NodeEnvironmentError, |
| 8 | +} from '@bitgo/sdk-core'; |
3 | 9 | import { coins, tokens, AdaTokenConfig } from '@bitgo/statics'; |
| 10 | +import { Transaction } from './lib'; |
| 11 | +import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs'; |
| 12 | +import assert from 'assert'; |
4 | 13 |
|
5 | 14 | export class AdaToken extends Ada { |
6 | 15 | public readonly tokenConfig: AdaTokenConfig; |
@@ -85,4 +94,72 @@ export class AdaToken extends Ada { |
85 | 94 | get contractAddress() { |
86 | 95 | return this.tokenConfig.contractAddress; |
87 | 96 | } |
| 97 | + |
| 98 | + /** |
| 99 | + * Verify that a token transaction prebuild complies with the original intention. |
| 100 | + * For token transfers, we need to verify the token amount in multiAssets, not the ADA amount. |
| 101 | + * For token consolidation, we verify all outputs go to the base address. |
| 102 | + * |
| 103 | + * @param params.txPrebuild prebuild transaction |
| 104 | + * @param params.txParams transaction parameters |
| 105 | + * @param params.verification verification options (includes consolidationToBaseAddress flag) |
| 106 | + * @param params.wallet wallet object for getting base address |
| 107 | + * @return true if verification success |
| 108 | + */ |
| 109 | + async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> { |
| 110 | + try { |
| 111 | + const coinConfig = coins.get(this.getBaseChain()); |
| 112 | + const { txPrebuild, txParams, verification, wallet } = params; |
| 113 | + const transaction = new Transaction(coinConfig); |
| 114 | + assert(txPrebuild.txHex, new Error('missing required tx prebuild property txHex')); |
| 115 | + |
| 116 | + transaction.fromRawTransaction(txPrebuild.txHex); |
| 117 | + const txJson = transaction.toJson(); |
| 118 | + |
| 119 | + if (txParams.recipients !== undefined) { |
| 120 | + // assetName in tokenConfig is ASCII (e.g. 'WATER'), convert to hex for comparison |
| 121 | + const asciiEncodedAssetName = Buffer.from(this.tokenConfig.assetName).toString('hex'); |
| 122 | + |
| 123 | + // ASCII encoded asset name may be appended to the policy ID (consistent with crypto compare) |
| 124 | + // But cardano sdk requires only the policy ID (28 bytes = 56 hex chars) for ScriptHash |
| 125 | + let policyId = this.tokenConfig.policyId; |
| 126 | + if (policyId.endsWith(asciiEncodedAssetName)) { |
| 127 | + policyId = policyId.substring(0, policyId.length - asciiEncodedAssetName.length); |
| 128 | + } |
| 129 | + |
| 130 | + const policyScriptHash = CardanoWasm.ScriptHash.from_hex(policyId); |
| 131 | + const assetName = CardanoWasm.AssetName.new(Buffer.from(asciiEncodedAssetName, 'hex')); |
| 132 | + |
| 133 | + for (const recipient of txParams.recipients) { |
| 134 | + const found = txJson.outputs.some((output) => { |
| 135 | + if (recipient.address !== output.address || !output.multiAssets) { |
| 136 | + return false; |
| 137 | + } |
| 138 | + const multiAssets = output.multiAssets as CardanoWasm.MultiAsset; |
| 139 | + const tokenQty = multiAssets.get_asset(policyScriptHash, assetName); |
| 140 | + return tokenQty && tokenQty.to_str() === recipient.amount; |
| 141 | + }); |
| 142 | + |
| 143 | + if (!found) { |
| 144 | + throw new Error('cannot find recipient in expected output'); |
| 145 | + } |
| 146 | + } |
| 147 | + } else if (verification?.consolidationToBaseAddress) { |
| 148 | + // For token consolidation, verify all outputs go to the base address |
| 149 | + const baseAddress = wallet?.coinSpecific()?.baseAddress || wallet?.coinSpecific()?.rootAddress; |
| 150 | + |
| 151 | + for (const output of txJson.outputs) { |
| 152 | + if (output.address !== baseAddress) { |
| 153 | + throw new Error('tx outputs does not match with expected address'); |
| 154 | + } |
| 155 | + } |
| 156 | + } |
| 157 | + } catch (e) { |
| 158 | + if (e instanceof NodeEnvironmentError) { |
| 159 | + return true; |
| 160 | + } |
| 161 | + throw e; |
| 162 | + } |
| 163 | + return true; |
| 164 | + } |
88 | 165 | } |
0 commit comments