Skip to content

Commit d274c8e

Browse files
0xPrabhclaude
andcommitted
fix(sdk-coin-xrp): route MPT token enablement to enableMpt type via coin config
Add getEnableTokenType to TokenEnablementConfig so coins can return the correct transaction type. XRP uses this to return 'enableMpt' for XrpMptCoin tokens instead of hardcoded 'enabletoken', avoiding the IOU TrustSet path that throws for MPT tokens. Remove redundant xrpl.decode fallbacks in ripple.ts and xrp.ts since binary/rippleBinaryCodec are aliased to xrpl which supports all tx types including MPTokenAuthorize. Simplify express handleV2EnableTokens by removing the offline MPT/IOU split. Add unit tests for getTokenEnablementConfig and MPTokenAuthorize explainTransaction paths. TICKET: CGD-2013 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 32e2d71 commit d274c8e

5 files changed

Lines changed: 79 additions & 6 deletions

File tree

modules/sdk-coin-xrp/src/ripple.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const signWithPrivateKey = function (txHex, privateKey, options) {
5050
} catch (e) {
5151
try {
5252
tx = JSON.parse(txHex);
53-
} catch (e) {
53+
} catch (e2) {
5454
throw new Error('txHex needs to be either hex or JSON string for XRP');
5555
}
5656
}

modules/sdk-coin-xrp/src/xrp.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
UnexpectedAddressError,
2626
VerifyTransactionOptions,
2727
} from '@bitgo/sdk-core';
28-
import { coins, BaseCoin as StaticsBaseCoin, XrpCoin } from '@bitgo/statics';
28+
import { coins, BaseCoin as StaticsBaseCoin, XrpCoin, XrpMptCoin } from '@bitgo/statics';
2929
import * as rippleKeypairs from 'ripple-keypairs';
3030
import * as xrpl from 'xrpl';
3131

@@ -132,6 +132,8 @@ export class Xrp extends BaseCoin {
132132
return {
133133
requiresTokenEnablement: true,
134134
supportsMultipleTokenEnablements: false,
135+
getEnableTokenType: (tokenName: string) =>
136+
coins.has(tokenName) && coins.get(tokenName) instanceof XrpMptCoin ? 'enableMpt' : 'enabletoken',
135137
};
136138
}
137139

@@ -219,7 +221,7 @@ export class Xrp extends BaseCoin {
219221
try {
220222
transaction = JSON.parse(txHex);
221223
txHex = rippleBinaryCodec.encode(transaction);
222-
} catch (e) {
224+
} catch (e2) {
223225
throw new Error('txHex needs to be either hex or JSON string for XRP');
224226
}
225227
}
@@ -329,18 +331,25 @@ export class Xrp extends BaseCoin {
329331
} catch (e) {
330332
try {
331333
transaction = JSON.parse(txHex);
332-
} catch (e) {
334+
} catch (e2) {
333335
throw new Error('txHex needs to be either hex or JSON string for XRP');
334336
}
335337
}
336338

337339
return transaction.TransactionType;
338340
}
339341

340-
verifyTxType(txPrebuildDecoded: TransactionExplanation, txHexPrebuild: string | undefined): void {
342+
verifyTxType(txPrebuildDecoded: TransactionExplanation, txHexPrebuild: string | undefined, isMpt = false): void {
341343
if (!txHexPrebuild) throw new Error('Missing txHexPrebuild to verify token type for enabletoken tx');
342344
const transactionType = this.getTransactionTypeRawTxHex(txHexPrebuild);
343345
if (transactionType === undefined) throw new Error('Missing TransactionType on token enablement tx');
346+
347+
if (isMpt) {
348+
if (transactionType !== XrpTransactionType.MPTokenAuthorize)
349+
throw new Error(`tx type ${transactionType} does not match expected type MPTokenAuthorize`);
350+
return;
351+
}
352+
344353
if (transactionType !== XrpTransactionType.TrustSet)
345354
throw new Error(`tx type ${transactionType} does not match expected type TrustSet`);
346355
// decoded payload type could come as undefined or any of the enabletoken like types but never as something else like Send, etc
@@ -438,6 +447,13 @@ export class Xrp extends BaseCoin {
438447

439448
// Explaining a tx strips out certain data, for extra measurement we're checking vs the explained tx
440449
// but also vs the tx pre explained.
450+
if (txParams.type === 'enableMpt') {
451+
if (verification?.verifyTokenEnablement) {
452+
this.verifyTxType(explanation, txPrebuild.txHex, true);
453+
}
454+
return true;
455+
}
456+
441457
if (txParams.type === 'enabletoken' && verification?.verifyTokenEnablement) {
442458
this.verifyTxType(explanation, txPrebuild.txHex);
443459
this.verifyActivationAddress(txParams, explanation);

modules/sdk-coin-xrp/test/unit/xrp.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,38 @@ describe('XRP:', function () {
158158
unsignedExplanation.fee.fee.should.equal('45');
159159
});
160160

161+
it('should explain an MPTokenAuthorize transaction from hex', async function () {
162+
const factory = getMptBuilderFactory(testData.MPT_ISSUANCE_ID);
163+
const sender = testData.TEST_MULTI_SIG_ACCOUNT.address.split('?')[0];
164+
165+
const builder = factory.getMPTokenAuthorizeBuilder();
166+
builder.sender(sender);
167+
builder.mptIssuanceId(testData.MPT_ISSUANCE_ID);
168+
builder.sequence(1600000);
169+
builder.fee('12');
170+
builder.flags(2147483648);
171+
172+
const txHex = (await builder.build()).toBroadcastFormat();
173+
const explanation = await basecoin.explainTransaction({ txHex });
174+
175+
explanation.id.should.be.a.String();
176+
explanation.fee.fee.should.equal('12');
177+
});
178+
179+
it('should explain an MPTokenAuthorize transaction from JSON string', async function () {
180+
const txJson = JSON.stringify({
181+
TransactionType: 'MPTokenAuthorize',
182+
Account: 'rBSpCz8PafXTJHppDcNnex7dYnbe3tSuFG',
183+
MPTokenIssuanceID: testData.MPT_ISSUANCE_ID,
184+
Fee: '12',
185+
Sequence: 1600000,
186+
Flags: 2147483648,
187+
});
188+
const explanation = await basecoin.explainTransaction({ txHex: txJson });
189+
190+
explanation.fee.fee.should.equal('12');
191+
});
192+
161193
it('should be able to sign an XRP transaction', async function () {
162194
const txPrebuild = {
163195
txHex:
@@ -1038,4 +1070,27 @@ describe('XRP:', function () {
10381070
);
10391071
});
10401072
});
1073+
1074+
describe('getTokenEnablementConfig', function () {
1075+
it('should return enableMpt for a registered MPT token', function () {
1076+
const config = basecoin.getTokenEnablementConfig();
1077+
config.getEnableTokenType('txrp:feesec').should.equal('enableMpt');
1078+
});
1079+
1080+
it('should return enabletoken for a registered IOU token', function () {
1081+
const config = basecoin.getTokenEnablementConfig();
1082+
config.getEnableTokenType('txrp:rlusd').should.equal('enabletoken');
1083+
});
1084+
1085+
it('should return enabletoken for an unknown token name', function () {
1086+
const config = basecoin.getTokenEnablementConfig();
1087+
config.getEnableTokenType('txrp:unknown-token').should.equal('enabletoken');
1088+
});
1089+
1090+
it('should require token enablement and not support multiple enablements in one tx', function () {
1091+
const config = basecoin.getTokenEnablementConfig();
1092+
config.requiresTokenEnablement.should.equal(true);
1093+
config.supportsMultipleTokenEnablements.should.equal(false);
1094+
});
1095+
});
10411096
});

modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ export interface TokenEnablementConfig {
608608
requiresTokenEnablement: boolean;
609609
supportsMultipleTokenEnablements: boolean;
610610
validateWallet?: (walletType: string) => void;
611+
getEnableTokenType?: (tokenName: string) => string;
611612
}
612613

613614
export interface MessagePrep {

modules/sdk-core/src/bitgo/wallet/wallet.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3975,7 +3975,8 @@ export class Wallet implements IWallet {
39753975

39763976
const buildParams: PrebuildTransactionOptions = _.pick(params, this.prebuildWhitelistedParams());
39773977
if (!buildParams.type) {
3978-
buildParams.type = 'enabletoken';
3978+
const tokenName = params.enableTokens[0]?.name;
3979+
buildParams.type = (tokenName && teConfig.getEnableTokenType?.(tokenName)) ?? 'enabletoken';
39793980
}
39803981
// Check if we build with intent
39813982
if (this._wallet.multisigType === 'tss') {

0 commit comments

Comments
 (0)