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 4, 2024
1 parent 7a8da64 commit e2b28f1
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 3 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.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@
"@sentry/browser": "^8.33.1",
"@sentry/types": "^8.33.1",
"@sentry/utils": "^8.33.1",
"@solana/web3.js": "^1.95.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": true
}
},
"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,
},
};
17 changes: 16 additions & 1 deletion 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,6 +62,20 @@ 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])(
Expand All @@ -87,7 +102,7 @@ describe('multichain', () => {
'returns ChainType.Ethereum for non-supported address: %s',
(address: string) => {
expect(getCaipNamespaceFromAddress(address)).toBe(
KnownCaipNamespace.Eip155,
KnownCaipNamespace.Solana,
);
},
);
Expand Down
25 changes: 25 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 { PublicKey } from '@solana/web3.js';

/**
* Returns whether an address is on the Bitcoin mainnet.
Expand Down Expand Up @@ -28,6 +29,25 @@ 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 {
try {
const solanaPublicKey = new PublicKey(address);
const isOnCurve = PublicKey.isOnCurve(solanaPublicKey);

return isOnCurve;
} catch (error) {
return false;
}
}

/**
* Returns the associated chain's type for the given address.
*
Expand All @@ -38,6 +58,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;
}
24 changes: 24 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8064,6 +8064,29 @@ __metadata:
languageName: node
linkType: hard

"@solana/web3.js@npm:^1.95.4":
version: 1.95.4
resolution: "@solana/web3.js@npm:1.95.4"
dependencies:
"@babel/runtime": "npm:^7.25.0"
"@noble/curves": "npm:^1.4.2"
"@noble/hashes": "npm:^1.4.0"
"@solana/buffer-layout": "npm:^4.0.1"
agentkeepalive: "npm:^4.5.0"
bigint-buffer: "npm:^1.1.5"
bn.js: "npm:^5.2.1"
borsh: "npm:^0.7.0"
bs58: "npm:^4.0.1"
buffer: "npm:6.0.3"
fast-stable-stringify: "npm:^1.0.0"
jayson: "npm:^4.1.1"
node-fetch: "npm:^2.7.0"
rpc-websockets: "npm:^9.0.2"
superstruct: "npm:^2.0.2"
checksum: 10/353e04ac1110035ff108f16af4029c7a98f71cce841d45877c9bc4a354cdc58a051681603c92289b81e3dc5ef6b1567c6f866e4ba56a434db145e38a5a41d276
languageName: node
linkType: hard

"@sovpro/delimited-stream@npm:^1.1.0":
version: 1.1.0
resolution: "@sovpro/delimited-stream@npm:1.1.0"
Expand Down Expand Up @@ -26489,6 +26512,7 @@ __metadata:
"@sentry/cli": "npm:^2.19.4"
"@sentry/types": "npm:^8.33.1"
"@sentry/utils": "npm:^8.33.1"
"@solana/web3.js": "npm:^1.95.4"
"@storybook/addon-a11y": "npm:^7.6.20"
"@storybook/addon-actions": "npm:^7.6.20"
"@storybook/addon-designs": "npm:^7.0.9"
Expand Down

0 comments on commit e2b28f1

Please sign in to comment.