Skip to content

Commit

Permalink
chore: add Solana shared utilities and constants
Browse files Browse the repository at this point in the history
  • Loading branch information
ulissesferreira committed Nov 6, 2024
1 parent d970c25 commit 6b6fc64
Show file tree
Hide file tree
Showing 7 changed files with 667 additions and 16 deletions.
13 changes: 13 additions & 0 deletions app/images/solana-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@
"@metamask/snaps-utils": "^8.5.1",
"@metamask/transaction-controller": "^38.1.0",
"@metamask/user-operation-controller": "^13.0.0",
"@metamask/utils": "^9.3.0",
"@metamask/utils": "^10.0.1",
"@ngraveio/bc-ur": "^1.1.12",
"@noble/hashes": "^1.3.3",
"@popperjs/core": "^2.4.0",
Expand All @@ -357,6 +357,7 @@
"@sentry/browser": "^8.33.1",
"@sentry/types": "^8.33.1",
"@sentry/utils": "^8.33.1",
"@solana/web3.js": "^2.0.0-rc.4",
"@swc/core": "1.4.11",
"@trezor/connect-web": "^9.4.0",
"@zxing/browser": "^0.1.4",
Expand Down Expand Up @@ -745,7 +746,8 @@
"resolve-url-loader>es6-iterator>d>es5-ext": false,
"resolve-url-loader>es6-iterator>d>es5-ext>esniff>es5-ext": false,
"level>classic-level": false,
"jest-preview": false
"jest-preview": false,
"@solana/web3.js>bigint-buffer": false
}
},
"packageManager": "yarn@4.4.1"
Expand Down
5 changes: 5 additions & 0 deletions shared/constants/multichain/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { MultichainNetworks } from './networks';

export const MULTICHAIN_NATIVE_CURRENCY_TO_CAIP19 = {
BTC: `${MultichainNetworks.BITCOIN}/slip44:0`,
SOL: `${MultichainNetworks.SOLANA}/slip44:501`,
} as const;

export enum MultichainNativeAssets {
BITCOIN = `${MultichainNetworks.BITCOIN}/slip44:0`,
BITCOIN_TESTNET = `${MultichainNetworks.BITCOIN_TESTNET}/slip44:0`,

SOLANA = `${MultichainNetworks.SOLANA}/slip44:501`,
SOLANA_DEVNET = `${MultichainNetworks.SOLANA_DEVNET}/slip44:501`,
SOLANA_TESTNET = `${MultichainNetworks.SOLANA_TESTNET}/slip44:501`,
}
70 changes: 69 additions & 1 deletion shared/constants/multichain/networks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { CaipChainId } from '@metamask/utils';
import { isBtcMainnetAddress, isBtcTestnetAddress } from '../../lib/multichain';
import {
isBtcMainnetAddress,
isBtcTestnetAddress,
isSolanaAddress,
} from '../../lib/multichain';

export type ProviderConfigWithImageUrl = {
rpcUrl?: string;
Expand All @@ -21,24 +25,39 @@ export type MultichainProviderConfig = ProviderConfigWithImageUrl & {
export enum MultichainNetworks {
BITCOIN = 'bip122:000000000019d6689c085ae165831e93',
BITCOIN_TESTNET = 'bip122:000000000933ea01ad0ee984209779ba',

SOLANA = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
SOLANA_DEVNET = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
SOLANA_TESTNET = 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z',
}

export const BITCOIN_TOKEN_IMAGE_URL = './images/bitcoin-logo.svg';
export const SOLANA_TOKEN_IMAGE_URL = './images/solana-logo.svg';

export const MULTICHAIN_NETWORK_BLOCK_EXPLORER_URL_MAP = {
[MultichainNetworks.BITCOIN]: 'https://blockstream.info/address',
[MultichainNetworks.BITCOIN_TESTNET]:
'https://blockstream.info/testnet/address',

[MultichainNetworks.SOLANA]: 'https://explorer.solana.com/',
[MultichainNetworks.SOLANA_DEVNET]:
'https://explorer.solana.com/?cluster=devnet',
[MultichainNetworks.SOLANA_TESTNET]:
'https://explorer.solana.com/?cluster=testnet',
} as const;

export const MULTICHAIN_TOKEN_IMAGE_MAP = {
[MultichainNetworks.BITCOIN]: BITCOIN_TOKEN_IMAGE_URL,
[MultichainNetworks.SOLANA]: SOLANA_TOKEN_IMAGE_URL,
} as const;

export const MULTICHAIN_PROVIDER_CONFIGS: Record<
CaipChainId,
MultichainProviderConfig
> = {
/**
* Bitcoin
*/
[MultichainNetworks.BITCOIN]: {
chainId: MultichainNetworks.BITCOIN,
rpcUrl: '', // not used
Expand Down Expand Up @@ -69,4 +88,53 @@ export const MULTICHAIN_PROVIDER_CONFIGS: Record<
},
isAddressCompatible: isBtcTestnetAddress,
},
/**
* Solana
*/
[MultichainNetworks.SOLANA]: {
chainId: MultichainNetworks.SOLANA,
rpcUrl: '', // not used
ticker: 'SOL',
nickname: 'Solana',
id: 'solana-mainnet',
type: 'rpc',
rpcPrefs: {
imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.SOLANA],
blockExplorerUrl:
MULTICHAIN_NETWORK_BLOCK_EXPLORER_URL_MAP[MultichainNetworks.SOLANA],
},
isAddressCompatible: isSolanaAddress,
},
[MultichainNetworks.SOLANA_DEVNET]: {
chainId: MultichainNetworks.SOLANA_DEVNET,
rpcUrl: '', // not used
ticker: 'SOL',
nickname: 'Solana (devnet)',
id: 'solana-devnet',
type: 'rpc',
rpcPrefs: {
imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.SOLANA],
blockExplorerUrl:
MULTICHAIN_NETWORK_BLOCK_EXPLORER_URL_MAP[
MultichainNetworks.SOLANA_DEVNET
],
},
isAddressCompatible: isSolanaAddress,
},
[MultichainNetworks.SOLANA_TESTNET]: {
chainId: MultichainNetworks.SOLANA_TESTNET,
rpcUrl: '', // not used
ticker: 'SOL',
nickname: 'Solana (testnet)',
id: 'solana-testnet',
type: 'rpc',
rpcPrefs: {
imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.SOLANA],
blockExplorerUrl:
MULTICHAIN_NETWORK_BLOCK_EXPLORER_URL_MAP[
MultichainNetworks.SOLANA_TESTNET
],
},
isAddressCompatible: isSolanaAddress,
},
};
23 changes: 19 additions & 4 deletions shared/lib/multichain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getCaipNamespaceFromAddress,
isBtcMainnetAddress,
isBtcTestnetAddress,
isSolanaAddress,
} from './multichain';

const BTC_MAINNET_ADDRESSES = [
Expand Down Expand Up @@ -61,10 +62,24 @@ describe('multichain', () => {
);
});

describe('isSolanaAddress', () => {
// @ts-expect-error This is missing from the Mocha type definitions
it.each(SOL_ADDRESSES)(
'returns true if address is a valid Solana address: %s',
(address: string) => {
expect(isSolanaAddress(address)).toBe(true);
},
);

it('should return false for invalid Solana addresses', () => {
expect(isSolanaAddress('invalid')).toBe(false);
});
});

describe('getChainTypeFromAddress', () => {
// @ts-expect-error This is missing from the Mocha type definitions
it.each([...BTC_MAINNET_ADDRESSES, ...BTC_TESTNET_ADDRESSES])(
'returns ChainType.Bitcoin for bitcoin address: %s',
'returns KnownCaipNamespace.Bitcoin for bitcoin address: %s',
(address: string) => {
expect(getCaipNamespaceFromAddress(address)).toBe(
KnownCaipNamespace.Bip122,
Expand All @@ -74,7 +89,7 @@ describe('multichain', () => {

// @ts-expect-error This is missing from the Mocha type definitions
it.each(ETH_ADDRESSES)(
'returns ChainType.Ethereum for ethereum address: %s',
'returns KnownCaipNamespace.Ethereum for ethereum address: %s',
(address: string) => {
expect(getCaipNamespaceFromAddress(address)).toBe(
KnownCaipNamespace.Eip155,
Expand All @@ -84,10 +99,10 @@ describe('multichain', () => {

// @ts-expect-error This is missing from the Mocha type definitions
it.each(SOL_ADDRESSES)(
'returns ChainType.Ethereum for non-supported address: %s',
'returns KnownCaipNamespace.Solana for non-supported address: %s',
(address: string) => {
expect(getCaipNamespaceFromAddress(address)).toBe(
KnownCaipNamespace.Eip155,
KnownCaipNamespace.Solana,
);
},
);
Expand Down
18 changes: 18 additions & 0 deletions shared/lib/multichain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CaipNamespace, KnownCaipNamespace } from '@metamask/utils';
import { validate, Network } from 'bitcoin-address-validation';
import { isAddress } from '@solana/addresses';

/**
* Returns whether an address is on the Bitcoin mainnet.
Expand Down Expand Up @@ -28,6 +29,18 @@ export function isBtcTestnetAddress(address: string): boolean {
return validate(address, Network.testnet);
}

/**
* Returns whether an address is a valid Solana address, specifically an account's.
* Derived addresses (like Program's) will return false.
* See: https://stackoverflow.com/questions/71200948/how-can-i-validate-a-solana-wallet-address-with-web3js
*
* @param address - The address to check.
* @returns `true` if the address is a valid Solana address, `false` otherwise.
*/
export function isSolanaAddress(address: string): boolean {
return isAddress(address);
}

/**
* Returns the associated chain's type for the given address.
*
Expand All @@ -38,6 +51,11 @@ export function getCaipNamespaceFromAddress(address: string): CaipNamespace {
if (isBtcMainnetAddress(address) || isBtcTestnetAddress(address)) {
return KnownCaipNamespace.Bip122;
}

if (isSolanaAddress(address)) {
return KnownCaipNamespace.Solana;
}

// Defaults to "Ethereum" for all other cases for now.
return KnownCaipNamespace.Eip155;
}
Loading

0 comments on commit 6b6fc64

Please sign in to comment.