Skip to content
Merged
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
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
/modules/sdk-coin-near/ @BitGo/ethalt-team
/modules/sdk-coin-opeth/ @BitGo/ethalt-team
/modules/sdk-coin-polygon/ @BitGo/ethalt-team
/modules/sdk-coin-rune/ @BitGo/ethalt-team
/modules/sdk-coin-sol/ @BitGo/ethalt-team
/modules/sdk-coin-stx/ @BitGo/ethalt-team
/modules/sdk-coin-sui/ @BitGo/ethalt-team
Expand Down
91 changes: 68 additions & 23 deletions modules/sdk-coin-rune/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,8 @@ export class RuneUtils extends CosmosUtils {
const value = this.registry.decode(message);
return {
value: {
fromAddress:
this.networkType === NetworkType.TESTNET
? bech32.encode(TESTNET_ADDRESS_PREFIX, value.fromAddress)
: bech32.encode(MAINNET_ADDRESS_PREFIX, value.fromAddress),
toAddress:
this.networkType === NetworkType.TESTNET
? bech32.encode(TESTNET_ADDRESS_PREFIX, value.toAddress)
: bech32.encode(MAINNET_ADDRESS_PREFIX, value.toAddress),
fromAddress: this.getEncodedAddress(value.fromAddress),
toAddress: this.getEncodedAddress(value.toAddress),
amount: value.amount,
},
typeUrl: message.typeUrl,
Expand All @@ -39,23 +33,74 @@ export class RuneUtils extends CosmosUtils {

/** @inheritdoc */
isValidAddress(address: string | Buffer): boolean {
if (address === undefined) {
if (address === undefined || address === null) {
return false;
}
if (typeof address !== 'string') {
const encodedAddress =
this.networkType === NetworkType.TESTNET
? bech32.encode(TESTNET_ADDRESS_PREFIX, address)
: bech32.encode(MAINNET_ADDRESS_PREFIX, address);
if (this.networkType === NetworkType.TESTNET) {
return this.isValidCosmosLikeAddressWithMemoId(encodedAddress, constants.testnetAccountAddressRegex);
}
return this.isValidCosmosLikeAddressWithMemoId(encodedAddress, constants.mainnetAccountAddressRegex);
} else {
if (this.networkType === NetworkType.TESTNET) {
return this.isValidCosmosLikeAddressWithMemoId(address, constants.testnetAccountAddressRegex);
}
return this.isValidCosmosLikeAddressWithMemoId(address, constants.mainnetAccountAddressRegex);
if (address instanceof Uint8Array) {
return this.isValidDecodedAddress(address);
}
if (typeof address === 'string') {
return this.isValidEncodedAddress(address);
}
return false;
}

/**
* Validates a decoded address in `Buffer` form by encoding it and
* checking if the encoded version is valid
*
* @param address - The decoded address as a `Buffer`.
* @returns `true` if the encoded address is valid, `false` otherwise.
*/
private isValidDecodedAddress(address: Buffer): boolean {
const encodedAddress = this.getEncodedAddress(address);
return this.isValidEncodedAddress(encodedAddress);
}

/**
* Validates an encoded address string against network-specific criteria.
*
* @param address - The encoded address as a `string`.
* @returns `true` if the address meets network-specific validation criteria, `false` otherwise.
*/
private isValidEncodedAddress(address: string): boolean {
if (this.networkType === NetworkType.TESTNET) {
return this.isValidCosmosLikeAddressWithMemoId(address, constants.testnetAccountAddressRegex);
}
return this.isValidCosmosLikeAddressWithMemoId(address, constants.mainnetAccountAddressRegex);
}

/**
* Encodes a given address `Buffer` into a bech32 string format, based on the current network type.
* Primarily serves as a utility to convert a `Buffer`-type address to a bech32 encoded string
*
* @param address - The address to be encoded, provided as a `Buffer`.
* @returns A bech32-encoded string representing the address.
* @throws Error - Throws an error if encoding fails
*/
getEncodedAddress(address: Buffer): string {
try {
return this.networkType === NetworkType.TESTNET
? bech32.encode(TESTNET_ADDRESS_PREFIX, address)
: bech32.encode(MAINNET_ADDRESS_PREFIX, address);
} catch (error) {
throw new Error(`Failed to encode address: ${error instanceof Error ? error.message : String(error)}`);
}
}

/**
* Decodes a bech32-encoded address string back into a `Buffer`.
* Primarily serves as a utility to convert a string-type address into its binary representation,
*
* @param address - The bech32-encoded address as a `string`.
* @returns The decoded address as a `Buffer`.
* @throws Error - Throws an error if decoding fails
*/
getDecodedAddress(address: string): Buffer {
try {
return bech32.decode(address).data;
} catch (error) {
throw new Error(`Failed to decode address: ${error instanceof Error ? error.message : String(error)}`);
}
}

Expand Down
55 changes: 53 additions & 2 deletions modules/sdk-coin-rune/test/unit/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { NetworkType } from '@bitgo/statics';
import should from 'should';
import { RuneUtils } from '../../src/lib/utils';
import { blockHash, mainnetCoinAmounts, txIds } from '../resources/rune';
import { testnetCoinAmounts } from '../resources/trune';
import { MAINNET_ADDRESS_PREFIX, TESTNET_ADDRESS_PREFIX } from '../../src/lib/constants';
import { blockHash, mainnetCoinAmounts, txIds, mainnetAddress } from '../resources/rune';
import { testnetCoinAmounts, testnetAddress } from '../resources/trune';
const bech32 = require('bech32-buffer');

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

const testnetDecodedAddress = bech32.decode(testnetAddress.address1).data;
const mainnetDecodedAddress = bech32.decode(mainnetAddress.address1).data;

it('should validate block hash correctly', () => {
should.equal(mainnetUtils.isValidBlockId(blockHash.hash1), true);
should.equal(mainnetUtils.isValidBlockId(blockHash.hash2), true);
Expand Down Expand Up @@ -57,4 +62,50 @@ describe('utils', () => {
'transactionBuilder: validateAmount: Invalid denom: ' + testnetCoinAmounts.amount5.denom
);
});

it('should validate mainnet address', () => {
should.equal(mainnetUtils.isValidAddress(mainnetAddress.address1), true);
should.equal(mainnetUtils.isValidAddress(mainnetAddress.validMemoIdAddress), true);
should.equal(mainnetUtils.isValidAddress(mainnetDecodedAddress), true);
should.equal(mainnetUtils.isValidAddress(mainnetAddress.invalidMemoIdAddress), false);
should.equal(mainnetUtils.isValidAddress(testnetAddress.address1), false);
should.equal(mainnetUtils.isValidAddress('12345'), false);
});

it('should validate testnet address', () => {
should.equal(testnetUtils.isValidAddress(testnetAddress.address1), true);
should.equal(testnetUtils.isValidAddress(testnetAddress.validMemoIdAddress), true);
should.equal(mainnetUtils.isValidAddress(testnetDecodedAddress), true);
should.equal(testnetUtils.isValidAddress(testnetAddress.invalidMemoIdAddress), false);
should.equal(testnetUtils.isValidAddress(mainnetAddress.address1), false);
should.equal(testnetUtils.isValidAddress('12345'), false);
});

it('should convert string type testnet address to Uint8Array', () => {
const decodedAddress = testnetUtils.getDecodedAddress(testnetAddress.address1);
should.equal(decodedAddress instanceof Uint8Array, true);
should.equal(decodedAddress.length, 20);
});

it('should convert string type mainnet address to Uint8Array', () => {
const decodedAddress = mainnetUtils.getDecodedAddress(mainnetAddress.address1);
should.equal(decodedAddress instanceof Uint8Array, true);
should.equal(decodedAddress.length, 20);
});

it('should convert Uint8Array type testnet address to string', () => {
const encodedAddress = testnetUtils.getEncodedAddress(testnetDecodedAddress);
should.equal(encodedAddress, testnetAddress.address1);
should.equal(typeof encodedAddress, 'string');
should.equal(encodedAddress.length, 44);
should.equal(encodedAddress.startsWith(TESTNET_ADDRESS_PREFIX), true);
});

it('should convert Uint8Array type mainnet address to string', () => {
const encodedAddress = mainnetUtils.getEncodedAddress(mainnetDecodedAddress);
should.equal(encodedAddress, mainnetAddress.address1);
should.equal(typeof encodedAddress, 'string');
should.equal(encodedAddress.length, 43);
should.equal(encodedAddress.startsWith(MAINNET_ADDRESS_PREFIX), true);
});
});
Loading