Skip to content

Commit 1653e04

Browse files
Merge pull request #5096
2 parents 6e5a5e3 + b53a9d1 commit 1653e04

File tree

3 files changed

+122
-25
lines changed

3 files changed

+122
-25
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
/modules/sdk-coin-near/ @BitGo/ethalt-team
6161
/modules/sdk-coin-opeth/ @BitGo/ethalt-team
6262
/modules/sdk-coin-polygon/ @BitGo/ethalt-team
63+
/modules/sdk-coin-rune/ @BitGo/ethalt-team
6364
/modules/sdk-coin-sol/ @BitGo/ethalt-team
6465
/modules/sdk-coin-stx/ @BitGo/ethalt-team
6566
/modules/sdk-coin-sui/ @BitGo/ethalt-team

modules/sdk-coin-rune/src/lib/utils.ts

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,8 @@ export class RuneUtils extends CosmosUtils {
2222
const value = this.registry.decode(message);
2323
return {
2424
value: {
25-
fromAddress:
26-
this.networkType === NetworkType.TESTNET
27-
? bech32.encode(TESTNET_ADDRESS_PREFIX, value.fromAddress)
28-
: bech32.encode(MAINNET_ADDRESS_PREFIX, value.fromAddress),
29-
toAddress:
30-
this.networkType === NetworkType.TESTNET
31-
? bech32.encode(TESTNET_ADDRESS_PREFIX, value.toAddress)
32-
: bech32.encode(MAINNET_ADDRESS_PREFIX, value.toAddress),
25+
fromAddress: this.getEncodedAddress(value.fromAddress),
26+
toAddress: this.getEncodedAddress(value.toAddress),
3327
amount: value.amount,
3428
},
3529
typeUrl: message.typeUrl,
@@ -39,23 +33,74 @@ export class RuneUtils extends CosmosUtils {
3933

4034
/** @inheritdoc */
4135
isValidAddress(address: string | Buffer): boolean {
42-
if (address === undefined) {
36+
if (address === undefined || address === null) {
4337
return false;
4438
}
45-
if (typeof address !== 'string') {
46-
const encodedAddress =
47-
this.networkType === NetworkType.TESTNET
48-
? bech32.encode(TESTNET_ADDRESS_PREFIX, address)
49-
: bech32.encode(MAINNET_ADDRESS_PREFIX, address);
50-
if (this.networkType === NetworkType.TESTNET) {
51-
return this.isValidCosmosLikeAddressWithMemoId(encodedAddress, constants.testnetAccountAddressRegex);
52-
}
53-
return this.isValidCosmosLikeAddressWithMemoId(encodedAddress, constants.mainnetAccountAddressRegex);
54-
} else {
55-
if (this.networkType === NetworkType.TESTNET) {
56-
return this.isValidCosmosLikeAddressWithMemoId(address, constants.testnetAccountAddressRegex);
57-
}
58-
return this.isValidCosmosLikeAddressWithMemoId(address, constants.mainnetAccountAddressRegex);
39+
if (address instanceof Uint8Array) {
40+
return this.isValidDecodedAddress(address);
41+
}
42+
if (typeof address === 'string') {
43+
return this.isValidEncodedAddress(address);
44+
}
45+
return false;
46+
}
47+
48+
/**
49+
* Validates a decoded address in `Buffer` form by encoding it and
50+
* checking if the encoded version is valid
51+
*
52+
* @param address - The decoded address as a `Buffer`.
53+
* @returns `true` if the encoded address is valid, `false` otherwise.
54+
*/
55+
private isValidDecodedAddress(address: Buffer): boolean {
56+
const encodedAddress = this.getEncodedAddress(address);
57+
return this.isValidEncodedAddress(encodedAddress);
58+
}
59+
60+
/**
61+
* Validates an encoded address string against network-specific criteria.
62+
*
63+
* @param address - The encoded address as a `string`.
64+
* @returns `true` if the address meets network-specific validation criteria, `false` otherwise.
65+
*/
66+
private isValidEncodedAddress(address: string): boolean {
67+
if (this.networkType === NetworkType.TESTNET) {
68+
return this.isValidCosmosLikeAddressWithMemoId(address, constants.testnetAccountAddressRegex);
69+
}
70+
return this.isValidCosmosLikeAddressWithMemoId(address, constants.mainnetAccountAddressRegex);
71+
}
72+
73+
/**
74+
* Encodes a given address `Buffer` into a bech32 string format, based on the current network type.
75+
* Primarily serves as a utility to convert a `Buffer`-type address to a bech32 encoded string
76+
*
77+
* @param address - The address to be encoded, provided as a `Buffer`.
78+
* @returns A bech32-encoded string representing the address.
79+
* @throws Error - Throws an error if encoding fails
80+
*/
81+
getEncodedAddress(address: Buffer): string {
82+
try {
83+
return this.networkType === NetworkType.TESTNET
84+
? bech32.encode(TESTNET_ADDRESS_PREFIX, address)
85+
: bech32.encode(MAINNET_ADDRESS_PREFIX, address);
86+
} catch (error) {
87+
throw new Error(`Failed to encode address: ${error instanceof Error ? error.message : String(error)}`);
88+
}
89+
}
90+
91+
/**
92+
* Decodes a bech32-encoded address string back into a `Buffer`.
93+
* Primarily serves as a utility to convert a string-type address into its binary representation,
94+
*
95+
* @param address - The bech32-encoded address as a `string`.
96+
* @returns The decoded address as a `Buffer`.
97+
* @throws Error - Throws an error if decoding fails
98+
*/
99+
getDecodedAddress(address: string): Buffer {
100+
try {
101+
return bech32.decode(address).data;
102+
} catch (error) {
103+
throw new Error(`Failed to decode address: ${error instanceof Error ? error.message : String(error)}`);
59104
}
60105
}
61106

modules/sdk-coin-rune/test/unit/utils.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { NetworkType } from '@bitgo/statics';
22
import should from 'should';
33
import { RuneUtils } from '../../src/lib/utils';
4-
import { blockHash, mainnetCoinAmounts, txIds } from '../resources/rune';
5-
import { testnetCoinAmounts } from '../resources/trune';
4+
import { MAINNET_ADDRESS_PREFIX, TESTNET_ADDRESS_PREFIX } from '../../src/lib/constants';
5+
import { blockHash, mainnetCoinAmounts, txIds, mainnetAddress } from '../resources/rune';
6+
import { testnetCoinAmounts, testnetAddress } from '../resources/trune';
7+
const bech32 = require('bech32-buffer');
68

79
describe('utils', () => {
810
const mainnetUtils = new RuneUtils(NetworkType.MAINNET);
911
const testnetUtils = new RuneUtils(NetworkType.TESTNET);
1012

13+
const testnetDecodedAddress = bech32.decode(testnetAddress.address1).data;
14+
const mainnetDecodedAddress = bech32.decode(mainnetAddress.address1).data;
15+
1116
it('should validate block hash correctly', () => {
1217
should.equal(mainnetUtils.isValidBlockId(blockHash.hash1), true);
1318
should.equal(mainnetUtils.isValidBlockId(blockHash.hash2), true);
@@ -57,4 +62,50 @@ describe('utils', () => {
5762
'transactionBuilder: validateAmount: Invalid denom: ' + testnetCoinAmounts.amount5.denom
5863
);
5964
});
65+
66+
it('should validate mainnet address', () => {
67+
should.equal(mainnetUtils.isValidAddress(mainnetAddress.address1), true);
68+
should.equal(mainnetUtils.isValidAddress(mainnetAddress.validMemoIdAddress), true);
69+
should.equal(mainnetUtils.isValidAddress(mainnetDecodedAddress), true);
70+
should.equal(mainnetUtils.isValidAddress(mainnetAddress.invalidMemoIdAddress), false);
71+
should.equal(mainnetUtils.isValidAddress(testnetAddress.address1), false);
72+
should.equal(mainnetUtils.isValidAddress('12345'), false);
73+
});
74+
75+
it('should validate testnet address', () => {
76+
should.equal(testnetUtils.isValidAddress(testnetAddress.address1), true);
77+
should.equal(testnetUtils.isValidAddress(testnetAddress.validMemoIdAddress), true);
78+
should.equal(mainnetUtils.isValidAddress(testnetDecodedAddress), true);
79+
should.equal(testnetUtils.isValidAddress(testnetAddress.invalidMemoIdAddress), false);
80+
should.equal(testnetUtils.isValidAddress(mainnetAddress.address1), false);
81+
should.equal(testnetUtils.isValidAddress('12345'), false);
82+
});
83+
84+
it('should convert string type testnet address to Uint8Array', () => {
85+
const decodedAddress = testnetUtils.getDecodedAddress(testnetAddress.address1);
86+
should.equal(decodedAddress instanceof Uint8Array, true);
87+
should.equal(decodedAddress.length, 20);
88+
});
89+
90+
it('should convert string type mainnet address to Uint8Array', () => {
91+
const decodedAddress = mainnetUtils.getDecodedAddress(mainnetAddress.address1);
92+
should.equal(decodedAddress instanceof Uint8Array, true);
93+
should.equal(decodedAddress.length, 20);
94+
});
95+
96+
it('should convert Uint8Array type testnet address to string', () => {
97+
const encodedAddress = testnetUtils.getEncodedAddress(testnetDecodedAddress);
98+
should.equal(encodedAddress, testnetAddress.address1);
99+
should.equal(typeof encodedAddress, 'string');
100+
should.equal(encodedAddress.length, 44);
101+
should.equal(encodedAddress.startsWith(TESTNET_ADDRESS_PREFIX), true);
102+
});
103+
104+
it('should convert Uint8Array type mainnet address to string', () => {
105+
const encodedAddress = mainnetUtils.getEncodedAddress(mainnetDecodedAddress);
106+
should.equal(encodedAddress, mainnetAddress.address1);
107+
should.equal(typeof encodedAddress, 'string');
108+
should.equal(encodedAddress.length, 43);
109+
should.equal(encodedAddress.startsWith(MAINNET_ADDRESS_PREFIX), true);
110+
});
60111
});

0 commit comments

Comments
 (0)