Skip to content

Commit a3065e1

Browse files
committed
feat: address verification for near
TICKET: WP-7082
1 parent 77e100a commit a3065e1

File tree

2 files changed

+142
-4
lines changed

2 files changed

+142
-4
lines changed

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

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import {
1818
EDDSAMethods,
1919
EDDSAMethodTypes,
2020
Environments,
21+
InvalidAddressError,
2122
KeyPair,
22-
MethodNotImplementedError,
2323
MPCAlgorithm,
2424
MPCRecoveryOptions,
2525
MPCSweepRecoveryOptions,
@@ -38,7 +38,9 @@ import {
3838
TokenEnablementConfig,
3939
TransactionParams,
4040
TransactionType,
41-
VerifyAddressOptions,
41+
TssVerifyAddressOptions,
42+
UnexpectedAddressError,
43+
verifyMPCWalletAddress,
4244
VerifyTransactionOptions,
4345
} from '@bitgo/sdk-core';
4446
import { BaseCoin as StaticsBaseCoin, CoinFamily, coins, Nep141Token, Networks } from '@bitgo/statics';
@@ -82,6 +84,15 @@ export interface NearParseTransactionOptions extends BaseParseTransactionOptions
8284
};
8385
}
8486

87+
/**
88+
* Options for verifying NEAR TSS/MPC wallet addresses.
89+
* Extends base TssVerifyAddressOptions with NEAR-specific fields.
90+
*/
91+
export interface TssVerifyNearAddressOptions extends TssVerifyAddressOptions {
92+
/** The root address of the wallet (for root address verification) */
93+
rootAddress?: string;
94+
}
95+
8596
interface TransactionOutput {
8697
address: string;
8798
amount: string;
@@ -984,8 +995,41 @@ export class Near extends BaseCoin {
984995
};
985996
}
986997

987-
async isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
988-
throw new MethodNotImplementedError();
998+
/**
999+
* Verify if an address belongs to a NEAR wallet using EdDSA TSS MPC derivation.
1000+
* For NEAR, the address is the public key directly (implicit accounts).
1001+
*
1002+
* @param {TssVerifyAddressOptions} params - Verification parameters
1003+
* @returns {Promise<boolean>} True if address belongs to wallet
1004+
* @throws {InvalidAddressError} If address format is invalid or doesn't match derived address
1005+
* @throws {Error} If invalid parameters
1006+
*/
1007+
async isWalletAddress(params: TssVerifyNearAddressOptions): Promise<boolean> {
1008+
const { address, rootAddress } = params;
1009+
1010+
if (!this.isValidAddress(address)) {
1011+
throw new InvalidAddressError(`invalid address: ${address}`);
1012+
}
1013+
1014+
const isVerifyingRootAddress = rootAddress && address === rootAddress;
1015+
if (isVerifyingRootAddress) {
1016+
const index = typeof params.index === 'string' ? parseInt(params.index, 10) : params.index;
1017+
if (index !== 0) {
1018+
throw new Error(`Root address verification requires index 0, but got index ${params.index}.`);
1019+
}
1020+
}
1021+
1022+
const result = await verifyMPCWalletAddress(
1023+
{ ...params, keyCurve: 'ed25519' },
1024+
this.isValidAddress.bind(this),
1025+
(pubKey) => pubKey
1026+
);
1027+
1028+
if (!result) {
1029+
throw new UnexpectedAddressError(`address validation failure: address ${address} is not a wallet address`);
1030+
}
1031+
1032+
return true;
9891033
}
9901034

9911035
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {

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

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,100 @@ describe('NEAR:', function () {
151151
});
152152
});
153153

154+
describe('Address verification', () => {
155+
const addressVerificationData = {
156+
commonKeychain:
157+
'43d3f6a94d7e3faf4dd390a7e26f554eaa98c8f0813e3f0ae959d61d8acd012e0504e552a5c311260f2fbaef3a817dfa5b85b984cd43b161bebad9ded25764cc',
158+
rootAddress: '98908af363d3e99d87b1d6dce4f80a28bbfe64fee22dbb8a36dada25ba30d027',
159+
receiveAddress: '6aa21569736f6ebaf925fef8ece219c2b703098cc358ce34a97f2c2a2e099659',
160+
receiveAddressIndex: 2,
161+
};
162+
163+
let keychains;
164+
165+
before(function () {
166+
keychains = [
167+
{ commonKeychain: addressVerificationData.commonKeychain },
168+
{ commonKeychain: addressVerificationData.commonKeychain },
169+
{ commonKeychain: addressVerificationData.commonKeychain },
170+
];
171+
});
172+
173+
it('should verify a valid TSS root address (index 0)', async function () {
174+
const params = {
175+
address: addressVerificationData.rootAddress,
176+
rootAddress: addressVerificationData.rootAddress,
177+
keychains: keychains,
178+
index: 0,
179+
};
180+
const result = await basecoin.isWalletAddress(params);
181+
result.should.equal(true);
182+
});
183+
184+
it('should verify a valid TSS receive address (index > 0)', async function () {
185+
const params = {
186+
address: addressVerificationData.receiveAddress,
187+
rootAddress: addressVerificationData.rootAddress,
188+
keychains: keychains,
189+
index: addressVerificationData.receiveAddressIndex,
190+
};
191+
const result = await basecoin.isWalletAddress(params);
192+
result.should.equal(true);
193+
});
194+
195+
it('should throw error for invalid address format', async function () {
196+
const invalidAddress = 'invalid-address';
197+
const params = {
198+
address: invalidAddress,
199+
keychains: keychains,
200+
index: 0,
201+
};
202+
await basecoin.isWalletAddress(params).should.be.rejected();
203+
});
204+
205+
it('should throw error when verifying root address with wrong index', async function () {
206+
const params = {
207+
address: addressVerificationData.rootAddress,
208+
rootAddress: addressVerificationData.rootAddress,
209+
keychains: keychains,
210+
index: 1,
211+
};
212+
await basecoin
213+
.isWalletAddress(params)
214+
.should.be.rejectedWith('Root address verification requires index 0, but got index 1.');
215+
});
216+
217+
it('should throw error when keychains is missing', async function () {
218+
const params = {
219+
address: addressVerificationData.rootAddress,
220+
keychains: [],
221+
index: 0,
222+
};
223+
await basecoin.isWalletAddress(params).should.be.rejectedWith('missing required param keychains');
224+
});
225+
226+
it('should throw error for address that does not match derivation', async function () {
227+
const wrongAddress = '0000000000000000000000000000000000000000000000000000000000000000';
228+
const params = {
229+
address: wrongAddress,
230+
keychains: keychains,
231+
index: 0,
232+
};
233+
await basecoin.isWalletAddress(params).should.be.rejected();
234+
});
235+
236+
it('should handle string index', async function () {
237+
const params = {
238+
address: addressVerificationData.rootAddress,
239+
rootAddress: addressVerificationData.rootAddress,
240+
keychains: keychains,
241+
index: '0',
242+
};
243+
const result = await basecoin.isWalletAddress(params);
244+
result.should.equal(true);
245+
});
246+
});
247+
154248
describe('Verify transaction: ', () => {
155249
const amount = '1000000';
156250
const gas = '125000000000000';

0 commit comments

Comments
 (0)