From 1c1cd6fb2a809cd027ce9758980c734424601b93 Mon Sep 17 00:00:00 2001 From: Rohit Saw Date: Fri, 6 Feb 2026 12:23:55 +0530 Subject: [PATCH] feat: override isValidAddress for hbarevm ticket: WIN-8724 --- modules/sdk-coin-evm/package.json | 4 + modules/sdk-coin-evm/src/evmCoin.ts | 11 ++- modules/sdk-coin-evm/test/unit/evmCoin.ts | 112 ++++++++++++++++++++++ yarn.lock | 8 +- 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 modules/sdk-coin-evm/test/unit/evmCoin.ts diff --git a/modules/sdk-coin-evm/package.json b/modules/sdk-coin-evm/package.json index 314f9413f4..3b9c67cc10 100644 --- a/modules/sdk-coin-evm/package.json +++ b/modules/sdk-coin-evm/package.json @@ -22,6 +22,10 @@ "@ethereumjs/common": "^2.6.5", "superagent": "^9.0.1" }, + "devDependencies": { + "@bitgo/sdk-api": "^1.73.4", + "@bitgo/sdk-test": "^9.1.25" + }, "author": "BitGo SDK Team ", "license": "MIT", "repository": { diff --git a/modules/sdk-coin-evm/src/evmCoin.ts b/modules/sdk-coin-evm/src/evmCoin.ts index 93c1d2755e..94b8f9190a 100644 --- a/modules/sdk-coin-evm/src/evmCoin.ts +++ b/modules/sdk-coin-evm/src/evmCoin.ts @@ -13,7 +13,7 @@ import { VerifyEthTransactionOptions, } from '@bitgo/abstract-eth'; import { TransactionBuilder } from './lib'; -import { recovery_HBAREVM_BlockchainExplorerQuery } from './lib/utils'; +import { recovery_HBAREVM_BlockchainExplorerQuery, validateHederaAccountId } from './lib/utils'; import assert from 'assert'; export class EvmCoin extends AbstractEthLikeNewCoins { @@ -135,4 +135,13 @@ export class EvmCoin extends AbstractEthLikeNewCoins { // If validation passes, consider it verified return true; } + + /** @inheritDoc */ + isValidAddress(address: string, isAlternateAddress?: boolean): boolean { + if (isAlternateAddress && this.getFamily() === CoinFamily.HBAREVM) { + const { valid } = validateHederaAccountId(address); + return valid; + } + return super.isValidAddress(address); + } } diff --git a/modules/sdk-coin-evm/test/unit/evmCoin.ts b/modules/sdk-coin-evm/test/unit/evmCoin.ts new file mode 100644 index 0000000000..710987cd2a --- /dev/null +++ b/modules/sdk-coin-evm/test/unit/evmCoin.ts @@ -0,0 +1,112 @@ +import 'should'; +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { EvmCoin } from '../../src/evmCoin'; +import { coins } from '@bitgo/statics'; + +describe('EvmCoin', function () { + let bitgo: TestBitGoAPI; + let evmCoin: EvmCoin; + + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' }); + }); + + describe('isValidAddress', function () { + beforeEach(function () { + // Create EvmCoin instance for testing + const staticsCoin = coins.get('hbarevm'); + evmCoin = new (EvmCoin as any)(bitgo, staticsCoin); + }); + + it('should validate standard Ethereum addresses (backward compatible)', function () { + const validAddresses = [ + '0x5aAeb6053f3E94C9b9A09f33669435E7Ef1BeAed', + '0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359', + '0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB', + '0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', + '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1', + ]; + + validAddresses.forEach((address) => { + evmCoin.isValidAddress(address).should.be.true(`${address} should be valid`); + }); + }); + + it('should validate addresses without 0x prefix (backward compatible)', function () { + const validAddresses = ['5aAeb6053f3E94C9b9A09f33669435E7Ef1BeAed', 'fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359']; + + validAddresses.forEach((address) => { + evmCoin.isValidAddress(address).should.be.true(`${address} should be valid`); + }); + }); + + it('should reject invalid Ethereum addresses (backward compatible)', function () { + const invalidAddresses = [ + '0xInvalidAddress', + '0x123', + 'not-an-address', + '', + '0x', + '0xzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz', + ]; + + invalidAddresses.forEach((address) => { + evmCoin.isValidAddress(address).should.be.false(`${address} should be invalid`); + }); + }); + + it('should work with first parameter only (backward compatible)', function () { + const address = '0x5aAeb6053f3E94C9b9A09f33669435E7Ef1BeAed'; + evmCoin.isValidAddress(address).should.be.true(); + }); + + describe('with optional isAlternateAddress parameter for HBAREVM', function () { + it('should validate Hedera account IDs when isAlternateAddress is true', function () { + const validHederaAccountIds = ['0.0.123456', '0.0.1234567890', '0.0.1']; + + validHederaAccountIds.forEach((accountId) => { + evmCoin.isValidAddress(accountId, true).should.be.true(`${accountId} should be valid`); + }); + }); + + it('should reject invalid Hedera account IDs when isAlternateAddress is true', function () { + const invalidHederaAccountIds = ['0.0', '0.0.abc', '0.0.-123', 'not-an-account-id']; + + invalidHederaAccountIds.forEach((accountId) => { + evmCoin.isValidAddress(accountId, true).should.be.false(`${accountId} should be invalid`); + }); + }); + + it('should still validate Ethereum addresses when isAlternateAddress is false', function () { + const ethAddress = '0x5aAeb6053f3E94C9b9A09f33669435E7Ef1BeAed'; + evmCoin.isValidAddress(ethAddress, false).should.be.true(); + }); + + it('should validate Ethereum addresses when isAlternateAddress is undefined (default)', function () { + const ethAddress = '0x5aAeb6053f3E94C9b9A09f33669435E7Ef1BeAed'; + evmCoin.isValidAddress(ethAddress, undefined).should.be.true(); + }); + }); + + describe('for non-HBAREVM coins', function () { + beforeEach(function () { + // Create a non-HBAREVM coin instance + const staticsCoin = coins.get('polygon'); + evmCoin = new (EvmCoin as any)(bitgo, staticsCoin); + }); + + it('should validate standard Ethereum addresses regardless of isAlternateAddress flag', function () { + const ethAddress = '0x5aAeb6053f3E94C9b9A09f33669435E7Ef1BeAed'; + evmCoin.isValidAddress(ethAddress).should.be.true(); + evmCoin.isValidAddress(ethAddress, false).should.be.true(); + evmCoin.isValidAddress(ethAddress, true).should.be.true(); + }); + + it('should not validate Hedera account IDs even with isAlternateAddress=true', function () { + const hederaAccountId = '0.0.123456'; + evmCoin.isValidAddress(hederaAccountId, true).should.be.false(); + }); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index ad6e4c5bd2..c9d2472932 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3080,10 +3080,10 @@ resolved "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz" integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== +"@isaacs/brace-expansion@5.0.1", "@isaacs/brace-expansion@^5.0.0": + version "5.0.1" + resolved "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz#0ef5a92d91f2fff2a37646ce54da9e5f599f6eff" + integrity sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ== dependencies: "@isaacs/balanced-match" "^4.0.1"