Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/sdk-coin-xrp/src/ripple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const signWithPrivateKey = function (txHex, privateKey, options) {
} catch (e) {
try {
tx = JSON.parse(txHex);
} catch (e) {
} catch (e2) {
Comment thread
0xPrabh marked this conversation as resolved.
throw new Error('txHex needs to be either hex or JSON string for XRP');
}
}
Expand Down
24 changes: 20 additions & 4 deletions modules/sdk-coin-xrp/src/xrp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
UnexpectedAddressError,
VerifyTransactionOptions,
} from '@bitgo/sdk-core';
import { coins, BaseCoin as StaticsBaseCoin, XrpCoin } from '@bitgo/statics';
import { coins, BaseCoin as StaticsBaseCoin, XrpCoin, XrpMptCoin } from '@bitgo/statics';
import * as rippleKeypairs from 'ripple-keypairs';
import * as xrpl from 'xrpl';

Expand Down Expand Up @@ -132,6 +132,8 @@ export class Xrp extends BaseCoin {
return {
requiresTokenEnablement: true,
supportsMultipleTokenEnablements: false,
getEnableTokenType: (tokenName: string) =>
coins.has(tokenName) && coins.get(tokenName) instanceof XrpMptCoin ? 'enableMpt' : 'enabletoken',
};
}

Expand Down Expand Up @@ -219,7 +221,7 @@ export class Xrp extends BaseCoin {
try {
transaction = JSON.parse(txHex);
txHex = rippleBinaryCodec.encode(transaction);
} catch (e) {
} catch (e2) {
throw new Error('txHex needs to be either hex or JSON string for XRP');
}
}
Expand Down Expand Up @@ -329,18 +331,25 @@ export class Xrp extends BaseCoin {
} catch (e) {
try {
transaction = JSON.parse(txHex);
} catch (e) {
} catch (e2) {
throw new Error('txHex needs to be either hex or JSON string for XRP');
}
}

return transaction.TransactionType;
}

verifyTxType(txPrebuildDecoded: TransactionExplanation, txHexPrebuild: string | undefined): void {
verifyTxType(txPrebuildDecoded: TransactionExplanation, txHexPrebuild: string | undefined, isMpt = false): void {
if (!txHexPrebuild) throw new Error('Missing txHexPrebuild to verify token type for enabletoken tx');
const transactionType = this.getTransactionTypeRawTxHex(txHexPrebuild);
if (transactionType === undefined) throw new Error('Missing TransactionType on token enablement tx');

if (isMpt) {
if (transactionType !== XrpTransactionType.MPTokenAuthorize)
throw new Error(`tx type ${transactionType} does not match expected type MPTokenAuthorize`);
return;
}

if (transactionType !== XrpTransactionType.TrustSet)
throw new Error(`tx type ${transactionType} does not match expected type TrustSet`);
// decoded payload type could come as undefined or any of the enabletoken like types but never as something else like Send, etc
Expand Down Expand Up @@ -438,6 +447,13 @@ export class Xrp extends BaseCoin {

// Explaining a tx strips out certain data, for extra measurement we're checking vs the explained tx
// but also vs the tx pre explained.
if (txParams.type === 'enableMpt') {
if (verification?.verifyTokenEnablement) {
this.verifyTxType(explanation, txPrebuild.txHex, true);
}
return true;
}

if (txParams.type === 'enabletoken' && verification?.verifyTokenEnablement) {
this.verifyTxType(explanation, txPrebuild.txHex);
this.verifyActivationAddress(txParams, explanation);
Expand Down
55 changes: 55 additions & 0 deletions modules/sdk-coin-xrp/test/unit/xrp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,38 @@ describe('XRP:', function () {
unsignedExplanation.fee.fee.should.equal('45');
});

it('should explain an MPTokenAuthorize transaction from hex', async function () {
const factory = getMptBuilderFactory(testData.MPT_ISSUANCE_ID);
const sender = testData.TEST_MULTI_SIG_ACCOUNT.address.split('?')[0];

const builder = factory.getMPTokenAuthorizeBuilder();
builder.sender(sender);
builder.mptIssuanceId(testData.MPT_ISSUANCE_ID);
builder.sequence(1600000);
builder.fee('12');
builder.flags(2147483648);

const txHex = (await builder.build()).toBroadcastFormat();
const explanation = await basecoin.explainTransaction({ txHex });

explanation.id.should.be.a.String();
explanation.fee.fee.should.equal('12');
});

it('should explain an MPTokenAuthorize transaction from JSON string', async function () {
const txJson = JSON.stringify({
TransactionType: 'MPTokenAuthorize',
Account: 'rBSpCz8PafXTJHppDcNnex7dYnbe3tSuFG',
MPTokenIssuanceID: testData.MPT_ISSUANCE_ID,
Fee: '12',
Sequence: 1600000,
Flags: 2147483648,
});
const explanation = await basecoin.explainTransaction({ txHex: txJson });

explanation.fee.fee.should.equal('12');
});

it('should be able to sign an XRP transaction', async function () {
const txPrebuild = {
txHex:
Expand Down Expand Up @@ -1038,4 +1070,27 @@ describe('XRP:', function () {
);
});
});

describe('getTokenEnablementConfig', function () {
it('should return enableMpt for a registered MPT token', function () {
const config = basecoin.getTokenEnablementConfig();
config.getEnableTokenType('txrp:feesec').should.equal('enableMpt');
});

it('should return enabletoken for a registered IOU token', function () {
const config = basecoin.getTokenEnablementConfig();
config.getEnableTokenType('txrp:rlusd').should.equal('enabletoken');
});

it('should return enabletoken for an unknown token name', function () {
const config = basecoin.getTokenEnablementConfig();
config.getEnableTokenType('txrp:unknown-token').should.equal('enabletoken');
});

it('should require token enablement and not support multiple enablements in one tx', function () {
const config = basecoin.getTokenEnablementConfig();
config.requiresTokenEnablement.should.equal(true);
config.supportsMultipleTokenEnablements.should.equal(false);
});
});
});
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ export interface TokenEnablementConfig {
requiresTokenEnablement: boolean;
supportsMultipleTokenEnablements: boolean;
validateWallet?: (walletType: string) => void;
getEnableTokenType?: (tokenName: string) => string;
}

export interface MessagePrep {
Expand Down
3 changes: 2 additions & 1 deletion modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3975,7 +3975,8 @@ export class Wallet implements IWallet {

const buildParams: PrebuildTransactionOptions = _.pick(params, this.prebuildWhitelistedParams());
if (!buildParams.type) {
buildParams.type = 'enabletoken';
const tokenName = params.enableTokens[0]?.name;
buildParams.type = (tokenName && teConfig.getEnableTokenType?.(tokenName)) ?? 'enabletoken';
}
// Check if we build with intent
if (this._wallet.multisigType === 'tss') {
Expand Down
Loading