Skip to content

Commit

Permalink
feat(sdk-coin-xrp): add support for token enablement
Browse files Browse the repository at this point in the history
TICKET: WIN-3761
  • Loading branch information
nvrakesh06 committed Nov 7, 2024
1 parent 3a3ed54 commit 683f82e
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 3 deletions.
9 changes: 9 additions & 0 deletions modules/sdk-coin-xrp/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface SupplementGenerateWalletOptions {
export type TransactionExplanation =
| BaseTransactionExplanation
| AccountSetTransactionExplanation
| TrustSetTransactionExplanation
| SignerListSetTransactionExplanation;

export interface AccountSetTransactionExplanation extends BaseTransactionExplanation {
Expand All @@ -92,6 +93,14 @@ export interface AccountSetTransactionExplanation extends BaseTransactionExplana
};
}

export interface TrustSetTransactionExplanation extends BaseTransactionExplanation {
limitAmount: {
tokenName: string;
address: string;
amount: string;
};
}

export interface SignerListSetTransactionExplanation extends BaseTransactionExplanation {
signerListSet: {
signerQuorum: number;
Expand Down
60 changes: 57 additions & 3 deletions modules/sdk-coin-xrp/src/xrp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import {
ParsedTransaction,
ParseTransactionOptions,
promiseProps,
TokenEnablementConfig,
UnexpectedAddressError,
VerifyTransactionOptions,
} from '@bitgo/sdk-core';
import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
import { BaseCoin as StaticsBaseCoin, coins, XrpCoin } from '@bitgo/statics';
import * as rippleBinaryCodec from 'ripple-binary-codec';
import * as rippleKeypairs from 'ripple-keypairs';
import * as xrpl from 'xrpl';
Expand Down Expand Up @@ -107,6 +108,13 @@ export class Xrp extends BaseCoin {
return this.bitgo.get(this.url('/public/feeinfo')).result();
}

public getTokenEnablementConfig(): TokenEnablementConfig {
return {
requiresTokenEnablement: true,
supportsMultipleTokenEnablements: false,
};
}

/**
* Assemble keychain and half-sign prebuilt transaction
* @param params
Expand Down Expand Up @@ -222,6 +230,25 @@ export class Xrp extends BaseCoin {
setFlag: transaction.SetFlag,
},
};
} else if (transaction.TransactionType === 'TrustSet') {
return {
displayOrder: ['id', 'outputAmount', 'changeAmount', 'outputs', 'changeOutputs', 'fee', 'limitAmount'],
id: id,
changeOutputs: [],
outputAmount: 0,
changeAmount: 0,
outputs: [],
fee: {
fee: transaction.Fee,
feeRate: undefined,
size: txHex.length / 2,
},
limitAmount: {
tokenName: transaction.LimitAmount.currency,
address: transaction.LimitAmount.issuer,
amount: transaction.LimitAmount.value,
},
};
}

const address =
Expand Down Expand Up @@ -254,6 +281,7 @@ export class Xrp extends BaseCoin {
* @returns {boolean}
*/
public async verifyTransaction({ txParams, txPrebuild }: VerifyTransactionOptions): Promise<boolean> {
const coinConfig = coins.get(this.getChain()) as XrpCoin;
const explanation = await this.explainTransaction({
txHex: txPrebuild.txHex,
});
Expand All @@ -270,8 +298,34 @@ export class Xrp extends BaseCoin {
return amount1.toFixed() === amount2.toFixed();
};

if (!comparator(output, expectedOutput)) {
throw new Error('transaction prebuild does not match expected output');
if (txParams.recipients) {
// for enabletoken, recipient output amount is 0
const recipients = txParams.recipients.map((recipient) => ({
...recipient,
}));
if (coinConfig.isToken) {
recipients.forEach((recipient) => {
if (
recipient.tokenName !== undefined &&
utils.getXrpCurrencyFromTokenName(recipient.tokenName).currency !== coinConfig.currencyCode
) {
throw new Error('Incorrect token name specified in recipients');
}
recipient.tokenName = coinConfig.currencyCode;
});
}

// verify recipients from params and explainedTx
const filteredRecipients = recipients?.map((recipient) => _.pick(recipient, ['address', 'amount', 'tokenName']));
const filteredOutputs = 'limitAmount' in explanation ? [explanation.limitAmount] : [];

if (!_.isEqual(filteredOutputs, filteredRecipients)) {
throw new Error('Tx outputs does not match with expected txParams recipients');
}
} else {
if (!comparator(output, expectedOutput)) {
throw new Error('transaction prebuild does not match expected output');
}
}

return true;
Expand Down
85 changes: 85 additions & 0 deletions modules/sdk-coin-xrp/test/unit/xrp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import assert from 'assert';
import * as rippleBinaryCodec from 'ripple-binary-codec';
import sinon from 'sinon';
import * as testData from '../resources/xrp';
import * as _ from 'lodash';
import { XrpToken } from '../../src';

nock.disableNetConnect();

Expand All @@ -18,10 +20,15 @@ bitgo.safeRegister('txrp', Txrp.createInstance);

describe('XRP:', function () {
let basecoin;
let token;

before(function () {
XrpToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
bitgo.safeRegister(name, coinConstructor);
});
bitgo.initializeTestVars();
basecoin = bitgo.coin('txrp');
token = bitgo.coin('txrp:rlusd');
});

after(function () {
Expand Down Expand Up @@ -294,4 +301,82 @@ describe('XRP:', function () {
);
});
});

describe('Verify Transaction', () => {
let newTxPrebuild;
let newTxParams;

const txPrebuild = {
txHex: `{"TransactionType":"TrustSet","Account":"rBSpCz8PafXTJHppDcNnex7dYnbe3tSuFG","LimitAmount":{"currency":"524C555344000000000000000000000000000000","issuer":"rnox8i6h9GoAbuwr73JtaDxXoncLLUCpXH","value":"1000000000"},"Flags":2147483648,"Fee":"45","Sequence":7}`,
};

const txParams = {
recipients: [
{
address: 'rnox8i6h9GoAbuwr73JtaDxXoncLLUCpXH',
amount: '10',
},
{
address: 'raBSn6ipeWXYe7rNbNafZSx9dV2fU3zRyP',
amount: '15',
},
],
};

before(function () {
newTxPrebuild = () => {
return _.cloneDeep(txPrebuild);
};
newTxParams = () => {
return _.cloneDeep(txParams);
};
});

it('should verify token trustline transactions', async function () {
const txParams = newTxParams();
const txPrebuild = newTxPrebuild();

txParams.recipients = [
{
address: 'rnox8i6h9GoAbuwr73JtaDxXoncLLUCpXH',
amount: '1000000000',
tokenName: 'txrp:rlusd',
},
];

const validTransaction = await token.verifyTransaction({
txParams,
txPrebuild,
});
validTransaction.should.equal(true);
});

it('should fail verify trustline transaction with mismatch recipients', async function () {
const txParams = newTxParams();
const txPrebuild = newTxPrebuild();
txParams.recipients = [
{
address: 'raBSn6ipeWXYe7rNbNafZSx9dV2fU3zRyP',
amount: '1000000000',
tokenName: 'txrp:rlusd',
},
];
await token
.verifyTransaction({ txParams, txPrebuild })
.should.be.rejectedWith('Tx outputs does not match with expected txParams recipients');
});

it('should fail to verify trustline transaction with incorrect token name', async function () {
const txParams = newTxParams();
const txPrebuild = newTxPrebuild();
txParams.recipients = [
{
address: 'rnox8i6h9GoAbuwr73JtaDxXoncLLUCpXH',
amount: '1000000000',
tokenName: 'txrp:usd',
},
];
await token.verifyTransaction({ txParams, txPrebuild }).should.be.rejectedWith('txrp:usd is not supported');
});
});
});

0 comments on commit 683f82e

Please sign in to comment.