@@ -21,14 +21,16 @@ import {
2121 ParseTransactionOptions ,
2222 promiseProps ,
2323 TokenEnablementConfig ,
24+ TransactionParams ,
2425 UnexpectedAddressError ,
2526 VerifyTransactionOptions ,
2627} from '@bitgo/sdk-core' ;
27- import { BaseCoin as StaticsBaseCoin , coins , XrpCoin } from '@bitgo/statics' ;
28+ import { coins , BaseCoin as StaticsBaseCoin , XrpCoin } from '@bitgo/statics' ;
2829import * as rippleBinaryCodec from 'ripple-binary-codec' ;
2930import * as rippleKeypairs from 'ripple-keypairs' ;
3031import * as xrpl from 'xrpl' ;
3132
33+ import { TokenTransferBuilder , TransactionBuilderFactory , TransferBuilder } from './lib' ;
3234import {
3335 ExplainTransactionOptions ,
3436 FeeInfo ,
@@ -40,11 +42,11 @@ import {
4042 SupplementGenerateWalletOptions ,
4143 TransactionExplanation ,
4244 VerifyAddressOptions ,
45+ XrpTransactionType ,
4346} from './lib/iface' ;
4447import { KeyPair as XrpKeyPair } from './lib/keyPair' ;
4548import utils from './lib/utils' ;
4649import ripple from './ripple' ;
47- import { TokenTransferBuilder , TransactionBuilderFactory , TransferBuilder } from './lib' ;
4850
4951export class Xrp extends BaseCoin {
5052 protected _staticsCoin : Readonly < StaticsBaseCoin > ;
@@ -298,19 +300,121 @@ export class Xrp extends BaseCoin {
298300 } ;
299301 }
300302
303+ getTransactionTypeRawTxHex ( txHex : string ) : XrpTransactionType | undefined {
304+ let transaction ;
305+ if ( ! txHex ) {
306+ throw new Error ( 'missing required param txHex' ) ;
307+ }
308+ try {
309+ transaction = rippleBinaryCodec . decode ( txHex ) ;
310+ } catch ( e ) {
311+ try {
312+ transaction = JSON . parse ( txHex ) ;
313+ } catch ( e ) {
314+ throw new Error ( 'txHex needs to be either hex or JSON string for XRP' ) ;
315+ }
316+ }
317+
318+ return transaction . TransactionType ;
319+ }
320+
321+ verifyTxType ( txPrebuildDecoded : TransactionExplanation , txHexPrebuild : string | undefined ) : void {
322+ if ( ! txHexPrebuild ) throw new Error ( 'Missing txHexPrebuild to verify token type for enabletoken tx' ) ;
323+ const transactionType = this . getTransactionTypeRawTxHex ( txHexPrebuild ) ;
324+ if ( transactionType === undefined ) throw new Error ( 'Missing TransactionType on token enablement tx' ) ;
325+ if ( transactionType !== XrpTransactionType . TrustSet )
326+ throw new Error ( `tx type ${ transactionType } does not match expected type TrustSet` ) ;
327+ // decoded payload type could come as undefined or any of the enabletoken like types but never as something else like Send, etc
328+ const actualTypeFromDecoded =
329+ 'type' in txPrebuildDecoded && typeof txPrebuildDecoded . type === 'string' ? txPrebuildDecoded . type : undefined ;
330+ if (
331+ ! actualTypeFromDecoded ||
332+ actualTypeFromDecoded === 'enabletoken' ||
333+ actualTypeFromDecoded === 'AssociatedTokenAccountInitialization'
334+ )
335+ return ;
336+
337+ throw new Error ( `tx type ${ actualTypeFromDecoded } does not match the expected type enabletoken` ) ;
338+ }
339+
340+ verifyTokenName (
341+ txParams : TransactionParams ,
342+ txPrebuildDecoded : TransactionExplanation ,
343+ txHexPrebuild : string | undefined ,
344+ coinConfig : XrpCoin
345+ ) : void {
346+ if ( ! txHexPrebuild ) throw new Error ( 'Missing txHexPrebuild param required for token enablement.' ) ;
347+ if ( ! txParams . recipients || txParams . recipients . length === 0 )
348+ throw new Error ( 'Missing recipients param for token enablement.' ) ;
349+ const fullTokenName = txParams . recipients [ 0 ] . tokenName ;
350+ if ( fullTokenName === undefined )
351+ throw new Error ( 'Param tokenName is required for token enablement. Recipient must include a token name.' ) ;
352+
353+ if ( ! ( 'limitAmount' in txPrebuildDecoded ) ) throw new Error ( 'Missing limitAmount param for token enablement.' ) ;
354+
355+ // we check currency on both the txHex but also the explained payload
356+ const expectedCurrency = utils . getXrpCurrencyFromTokenName ( fullTokenName ) . currency ;
357+ if ( coinConfig . isToken && expectedCurrency !== txPrebuildDecoded . limitAmount . currency )
358+ throw new Error ( 'Invalid token issuer or currency on token enablement tx' ) ;
359+ }
360+
361+ verifyActivationAddress ( txParams : TransactionParams , txPrebuildDecoded : TransactionExplanation ) : void {
362+ if ( txParams . recipients === undefined || txParams . recipients . length === 0 )
363+ throw new Error ( 'Missing recipients param for token enablement.' ) ;
364+
365+ if ( txParams . recipients ?. length !== 1 ) {
366+ throw new Error (
367+ `${ this . getChain ( ) } doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
368+ ) ;
369+ }
370+ if ( ! ( 'account' in txPrebuildDecoded ) ) throw new Error ( 'missing account on token enablement tx' ) ;
371+
372+ const activationAddress = txParams . recipients [ 0 ] . address ;
373+ const accountAddress = txPrebuildDecoded . account ;
374+ if ( activationAddress !== accountAddress ) throw new Error ( "Account address doesn't match with activation address." ) ;
375+ }
376+
377+ verifyTokenIssuer ( txParams : TransactionParams , txPrebuildDecoded : TransactionExplanation ) : void {
378+ if ( txPrebuildDecoded === undefined || ! ( 'limitAmount' in txPrebuildDecoded ) )
379+ throw new Error ( 'missing token issuer on token enablement tx' ) ;
380+ const { issuer, currency } = txPrebuildDecoded . limitAmount ;
381+ if ( ! utils . getXrpToken ( issuer , currency ) )
382+ throw new Error ( 'Invalid token issuer or currency on token enablement tx' ) ;
383+ }
384+
385+ verifyRequiredKeys ( txParams : TransactionParams , txPrebuildDecoded : TransactionExplanation ) : void {
386+ if (
387+ ! ( 'account' in txPrebuildDecoded ) ||
388+ ! ( 'limitAmount' in txPrebuildDecoded ) ||
389+ ! txPrebuildDecoded . limitAmount . currency
390+ ) {
391+ throw new Error ( 'Explanation is missing required keys (account or limitAmount with currency)' ) ;
392+ }
393+ }
394+
301395 /**
302396 * Verify that a transaction prebuild complies with the original intention
303397 * @param txParams params object passed to send
304398 * @param txPrebuild prebuild object returned by server
305399 * @param wallet
306400 * @returns {boolean }
307401 */
308- public async verifyTransaction ( { txParams, txPrebuild } : VerifyTransactionOptions ) : Promise < boolean > {
402+ public async verifyTransaction ( { txParams, txPrebuild, verification } : VerifyTransactionOptions ) : Promise < boolean > {
309403 const coinConfig = coins . get ( this . getChain ( ) ) as XrpCoin ;
310404 const explanation = await this . explainTransaction ( {
311405 txHex : txPrebuild . txHex ,
312406 } ) ;
313407
408+ // Explaining a tx strips out certain data, for extra measurement we're checking vs the explained tx
409+ // but also vs the tx pre explained.
410+ if ( txParams . type === 'enabletoken' && verification ?. verifyTokenEnablement ) {
411+ this . verifyTxType ( explanation , txPrebuild . txHex ) ;
412+ this . verifyActivationAddress ( txParams , explanation ) ;
413+ this . verifyTokenIssuer ( txParams , explanation ) ;
414+ this . verifyTokenName ( txParams , explanation , txPrebuild . txHex , coinConfig ) ;
415+ this . verifyRequiredKeys ( txParams , explanation ) ;
416+ }
417+
314418 const output = [ ...explanation . outputs , ...explanation . changeOutputs ] [ 0 ] ;
315419 const expectedOutput = txParams . recipients && txParams . recipients [ 0 ] ;
316420
@@ -331,32 +435,6 @@ export class Xrp extends BaseCoin {
331435 throw new Error ( 'transaction prebuild does not match expected output' ) ;
332436 }
333437
334- if ( txParams . type === 'enabletoken' ) {
335- if ( txParams . recipients ?. length !== 1 ) {
336- throw new Error (
337- `${ this . getChain ( ) } doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
338- ) ;
339- }
340- const recipient = txParams . recipients [ 0 ] ;
341- if ( ! recipient . tokenName ) {
342- throw new Error ( 'Recipient must include a token name.' ) ;
343- }
344- const recipientCurrency = utils . getXrpCurrencyFromTokenName ( recipient . tokenName ) . currency ;
345- if ( coinConfig . isToken ) {
346- if ( recipientCurrency !== coinConfig . currencyCode ) {
347- throw new Error ( 'Incorrect token name specified in recipients' ) ;
348- }
349- }
350- if ( ! ( 'account' in explanation ) || ! ( 'limitAmount' in explanation ) || ! explanation . limitAmount . currency ) {
351- throw new Error ( 'Explanation is missing required keys (account or limitAmount with currency)' ) ;
352- }
353- const baseAddress = explanation . account ;
354- const currency = explanation . limitAmount . currency ;
355-
356- if ( recipient . address !== baseAddress || recipientCurrency !== currency ) {
357- throw new Error ( 'Tx outputs does not match with expected txParams recipients' ) ;
358- }
359- }
360438 return true ;
361439 }
362440
0 commit comments