Skip to content
Open
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
37 changes: 33 additions & 4 deletions modules/sdk-coin-near/src/near.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
EDDSAMethodTypes,
Environments,
KeyPair,
MethodNotImplementedError,
MPCAlgorithm,
MPCRecoveryOptions,
MPCSweepRecoveryOptions,
Expand All @@ -38,7 +37,9 @@ import {
TokenEnablementConfig,
TransactionParams,
TransactionType,
VerifyAddressOptions,
TssVerifyAddressOptions,
UnexpectedAddressError,
verifyEddsaTssWalletAddress,
VerifyTransactionOptions,
} from '@bitgo/sdk-core';
import { BaseCoin as StaticsBaseCoin, CoinFamily, coins, Nep141Token, Networks } from '@bitgo/statics';
Expand Down Expand Up @@ -82,6 +83,15 @@ export interface NearParseTransactionOptions extends BaseParseTransactionOptions
};
}

/**
* Options for verifying NEAR TSS/MPC wallet addresses.
* Extends base TssVerifyAddressOptions with NEAR-specific fields.
*/
export interface TssVerifyNearAddressOptions extends TssVerifyAddressOptions {
/** The root address of the wallet (for root address verification) */
rootAddress?: string;
}

interface TransactionOutput {
address: string;
amount: string;
Expand Down Expand Up @@ -984,8 +994,27 @@ export class Near extends BaseCoin {
};
}

async isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
throw new MethodNotImplementedError();
/**
* Verifies if the given address belongs to a TSS wallet for NEAR.
* For NEAR, the address is the public key directly (implicit accounts).
*
* @param {TssVerifyNearAddressOptions} params - Verification parameters
* @returns {Promise<boolean>} True if address belongs to wallet
* @throws {UnexpectedAddressError} If address doesn't match derived address
* @throws {Error} If invalid parameters or root address verification with wrong index
*/
async isWalletAddress(params: TssVerifyNearAddressOptions): Promise<boolean> {
const result = await verifyEddsaTssWalletAddress(
params,
(address) => this.isValidAddress(address),
(publicKey) => publicKey
);

if (!result) {
throw new UnexpectedAddressError(`address validation failure`);
}

return true;
}

async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
Expand Down
92 changes: 92 additions & 0 deletions modules/sdk-coin-near/test/unit/near.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,98 @@ describe('NEAR:', function () {
});
});

describe('Address verification', () => {
const addressVerificationData = {
commonKeychain:
'43d3f6a94d7e3faf4dd390a7e26f554eaa98c8f0813e3f0ae959d61d8acd012e0504e552a5c311260f2fbaef3a817dfa5b85b984cd43b161bebad9ded25764cc',
rootAddress: '98908af363d3e99d87b1d6dce4f80a28bbfe64fee22dbb8a36dada25ba30d027',
receiveAddress: '6aa21569736f6ebaf925fef8ece219c2b703098cc358ce34a97f2c2a2e099659',
receiveAddressIndex: 2,
};

let keychains;

before(function () {
keychains = [
{ commonKeychain: addressVerificationData.commonKeychain },
{ commonKeychain: addressVerificationData.commonKeychain },
{ commonKeychain: addressVerificationData.commonKeychain },
];
});

it('should verify a valid TSS root address (index 0)', async function () {
const params = {
address: addressVerificationData.rootAddress,
rootAddress: addressVerificationData.rootAddress,
keychains: keychains,
index: 0,
};
const result = await basecoin.isWalletAddress(params);
result.should.equal(true);
});

it('should verify a valid TSS receive address (index > 0)', async function () {
const params = {
address: addressVerificationData.receiveAddress,
rootAddress: addressVerificationData.rootAddress,
keychains: keychains,
index: addressVerificationData.receiveAddressIndex,
};
const result = await basecoin.isWalletAddress(params);
result.should.equal(true);
});

it('should throw error for invalid address format', async function () {
const invalidAddress = 'invalid-address';
const params = {
address: invalidAddress,
keychains: keychains,
index: 0,
};
await basecoin.isWalletAddress(params).should.be.rejectedWith('address validation failure');
});

it('should throw error when verifying root address with wrong index', async function () {
const params = {
address: addressVerificationData.rootAddress,
rootAddress: addressVerificationData.rootAddress,
keychains: keychains,
index: 1,
};
await basecoin.isWalletAddress(params).should.be.rejectedWith('address validation failure');
});

it('should throw error when keychains is missing', async function () {
const params = {
address: addressVerificationData.rootAddress,
keychains: [],
index: 0,
};
await basecoin.isWalletAddress(params).should.be.rejectedWith('missing required param keychains');
});

it('should throw error for address that does not match derivation', async function () {
const wrongAddress = '0000000000000000000000000000000000000000000000000000000000000000';
const params = {
address: wrongAddress,
keychains: keychains,
index: 0,
};
await basecoin.isWalletAddress(params).should.be.rejectedWith('address validation failure');
});

it('should handle string index', async function () {
const params = {
address: addressVerificationData.rootAddress,
rootAddress: addressVerificationData.rootAddress,
keychains: keychains,
index: '0',
};
const result = await basecoin.isWalletAddress(params);
result.should.equal(true);
});
});

describe('Verify transaction: ', () => {
const amount = '1000000';
const gas = '125000000000000';
Expand Down