Skip to content

Commit 776f886

Browse files
Merge pull request #5325 from BitGo/manas/WIN-4102-destination-tag-check-for-address
feat: destination tag validation in xrp isWalletAddress method
2 parents 0cbf14a + eaa62ae commit 776f886

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,34 @@ export class Xrp extends BaseCoin {
358358
throw new InvalidAddressError(`address verification failure: address "${address}" is not valid`);
359359
}
360360

361+
const accountInfoParams = {
362+
method: 'account_info',
363+
params: [
364+
{
365+
account: address,
366+
ledger_index: 'current',
367+
queue: true,
368+
strict: true,
369+
signer_lists: true,
370+
},
371+
],
372+
};
373+
374+
const accountInfo = (await this.bitgo.post(this.getRippledUrl()).send(accountInfoParams)).body;
375+
376+
if (accountInfo?.result?.account_data?.Flags == null) {
377+
throw new Error('Invalid account information: Flags field is missing.');
378+
}
379+
380+
const flags = xrpl.parseAccountRootFlags(accountInfo.result.account_data.Flags);
381+
361382
const addressDetails = utils.getAddressDetails(address);
362383
const rootAddressDetails = utils.getAddressDetails(rootAddress);
363384

385+
if (flags.lsfRequireDestTag && addressDetails.destinationTag == null) {
386+
throw new InvalidAddressError(`Invalid Address: Destination Tag is required for address "${address}".`);
387+
}
388+
364389
if (addressDetails.address !== rootAddressDetails.address) {
365390
throw new UnexpectedAddressError(
366391
`address validation failure: ${addressDetails.address} vs. ${rootAddressDetails.address}`

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

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ describe('XRP:', function () {
2323
let basecoin;
2424
let token;
2525

26+
afterEach(function () {
27+
sinon.restore();
28+
});
29+
2630
before(function () {
2731
XrpToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
2832
bitgo.safeRegister(name, coinConstructor);
@@ -83,6 +87,18 @@ describe('XRP:', function () {
8387
makeArgs('r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8?dt=4294967295', 'rDgocL7QpZh8ZhrPsax4zVqbGGxeAsiBoh'),
8488
];
8589

90+
sinon.stub(basecoin.bitgo, 'post').returns({
91+
send: sinon.stub().resolves({
92+
body: {
93+
result: {
94+
account_data: {
95+
Flags: 0, // Mock Flags value
96+
},
97+
},
98+
},
99+
}),
100+
});
101+
86102
for (const nonThrowingArg of nonThrowingArgs) {
87103
await basecoin.verifyAddress(nonThrowingArg);
88104
}
@@ -474,4 +490,111 @@ describe('XRP:', function () {
474490
await token.verifyTransaction({ txParams, txPrebuild }).should.be.rejectedWith('txrp:usd is not supported');
475491
});
476492
});
493+
494+
describe('Unit Tests for isWalletAddress function', function () {
495+
it('should return true for valid wallet address with matching root address', async function () {
496+
sinon.stub(basecoin.bitgo, 'post').returns({
497+
send: sinon.stub().resolves({
498+
body: {
499+
result: {
500+
account_data: {
501+
Flags: 0, // Include the Flags field
502+
},
503+
},
504+
},
505+
}),
506+
});
507+
508+
const validAddress = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8?dt=1893500718';
509+
const rootAddress = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8';
510+
511+
const result = await basecoin.isWalletAddress({ address: validAddress, rootAddress });
512+
result.should.be.true();
513+
});
514+
515+
it('should throw InvalidAddressError if the address is invalid', async function () {
516+
const invalidAddress = 'invalidAddress';
517+
const rootAddress = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8';
518+
519+
sinon.stub(basecoin, 'isValidAddress').returns(false);
520+
521+
await assert.rejects(async () => basecoin.isWalletAddress({ address: invalidAddress, rootAddress }), {
522+
name: 'InvalidAddressError',
523+
message: `address verification failure: address "${invalidAddress}" is not valid`,
524+
});
525+
});
526+
527+
it('should throw InvalidAddressError if destinationTag is required but not provided', async function () {
528+
const addressWithoutTag = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8';
529+
const rootAddress = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8';
530+
531+
sinon.stub(basecoin, 'isValidAddress').returns(true);
532+
sinon.stub(basecoin.bitgo, 'post').returns({
533+
send: sinon.stub().resolves({
534+
body: {
535+
result: {
536+
account_data: {
537+
Flags: 0x00020000, // lsfRequireDestTag
538+
},
539+
},
540+
},
541+
}),
542+
});
543+
544+
await assert.rejects(async () => basecoin.isWalletAddress({ address: addressWithoutTag, rootAddress }), {
545+
name: 'InvalidAddressError',
546+
message: 'Invalid Address: Destination Tag is required for address "r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8".',
547+
});
548+
});
549+
550+
it('should throw UnexpectedAddressError if the root address does not match', async function () {
551+
sinon.stub(basecoin.bitgo, 'post').returns({
552+
send: sinon.stub().resolves({
553+
body: {
554+
result: {
555+
account_data: {
556+
Flags: 0, // Include the Flags field
557+
},
558+
},
559+
},
560+
}),
561+
});
562+
563+
const validAddress = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8?dt=1893500718';
564+
const mismatchedRootAddress = 'rBfhJ6HopLW69xK83nyShdNxC3uggjs46K';
565+
566+
await assert.rejects(
567+
async () => basecoin.isWalletAddress({ address: validAddress, rootAddress: mismatchedRootAddress }),
568+
{
569+
name: 'UnexpectedAddressError',
570+
message: `address validation failure: ${validAddress.split('?')[0]} vs. ${mismatchedRootAddress}`,
571+
}
572+
);
573+
});
574+
575+
it('should handle missing Flags field gracefully', async function () {
576+
const validAddress = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8?dt=1893500718';
577+
const rootAddress = 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8';
578+
579+
sinon.stub(basecoin, 'isValidAddress').returns(true);
580+
sinon.stub(basecoin.bitgo, 'post').returns({
581+
send: sinon.stub().resolves({
582+
body: {
583+
result: {
584+
account_data: {}, // Flags field is missing
585+
},
586+
},
587+
}),
588+
});
589+
590+
await assert.rejects(async () => basecoin.isWalletAddress({ address: validAddress, rootAddress }), {
591+
name: 'Error',
592+
message: 'Invalid account information: Flags field is missing.',
593+
});
594+
});
595+
596+
afterEach(function () {
597+
sinon.restore();
598+
});
599+
});
477600
});

0 commit comments

Comments
 (0)