diff --git a/src/constants/assets.ts b/src/constants/assets.ts index 71191e7..8ceb7c3 100644 --- a/src/constants/assets.ts +++ b/src/constants/assets.ts @@ -153,6 +153,40 @@ export const initialAssets: Asset[] = [ 'https://rawcdn.githack.com/trustwallet/assets/master/blockchains/solana/info/logo.png', networkColor: '#9971d8', }, + { + assetId: 'tron:0x2b6653dc/slip44:195', + chainId: 'tron:0x2b6653dc', + symbol: 'TRX', + name: 'Tron', + precision: 6, + color: '#EB0029', + icon: 'https://rawcdn.githack.com/trustwallet/assets/master/blockchains/tron/info/logo.png', + explorer: 'https://tronscan.org', + explorerAddressLink: 'https://tronscan.org/#/address/', + explorerTxLink: 'https://tronscan.org/#/transaction/', + relatedAssetKey: null, + networkName: 'Tron', + networkColor: '#EB0029', + networkIcon: + 'https://rawcdn.githack.com/trustwallet/assets/master/blockchains/tron/info/logo.png', + }, + { + assetId: 'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', + chainId: 'tron:0x2b6653dc', + symbol: 'USDT', + name: 'Tether', + precision: 6, + color: '#24A37B', + icon: 'https://rawcdn.githack.com/trustwallet/assets/master/blockchains/tron/assets/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/logo.png', + explorer: 'https://tronscan.org', + explorerAddressLink: 'https://tronscan.org/#/address/', + explorerTxLink: 'https://tronscan.org/#/transaction/', + relatedAssetKey: 'eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7', + networkName: 'Tron', + networkColor: '#EB0029', + networkIcon: + 'https://rawcdn.githack.com/trustwallet/assets/master/blockchains/tron/info/logo.png', + }, ] export const initialAssetsById = initialAssets.reduce>((acc, asset) => { diff --git a/src/constants/caip.ts b/src/constants/caip.ts index 9a7c928..3be517d 100644 --- a/src/constants/caip.ts +++ b/src/constants/caip.ts @@ -1,60 +1,36 @@ -// Subset of web caip constants with Chainflip-supported assets only -// We need to redeclare things here as @shapeshiftoss/caip is actually a monorepo project and not published on npm +// Chainflip-supported caip constants, grouped by chain (chainId, then native asset, then tokens). import type { AssetId, ChainId } from '@shapeshiftoss/caip' +// Bitcoin +export const btcChainId: ChainId = 'bip122:000000000019d6689c085ae165831e93' export const btcAssetId: AssetId = 'bip122:000000000019d6689c085ae165831e93/slip44:0' +// Ethereum +export const ethChainId: ChainId = 'eip155:1' export const ethAssetId: AssetId = 'eip155:1/slip44:60' -export const arbitrumAssetId: AssetId = 'eip155:42161/slip44:60' -export const solAssetId: AssetId = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' -export const wrappedSolAssetId: AssetId = - 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:So11111111111111111111111111111111111111112' -export const usdtAssetId: AssetId = 'eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7' export const usdcAssetId: AssetId = 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' -export const usdcOnArbitrumOneAssetId: AssetId = - 'eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831' -export const usdcOnSolanaAssetId: AssetId = - 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' +export const usdtAssetId: AssetId = 'eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7' export const flipAssetId: AssetId = 'eip155:1/erc20:0x826180541412d574cf1336d22c0c0a287822678a' -export const btcChainId: ChainId = 'bip122:000000000019d6689c085ae165831e93' - -export const ethChainId: ChainId = 'eip155:1' +// Arbitrum export const arbitrumChainId: ChainId = 'eip155:42161' +export const arbitrumAssetId: AssetId = 'eip155:42161/slip44:60' +export const usdcOnArbitrumOneAssetId: AssetId = + 'eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831' + +// Base export const baseChainId: ChainId = 'eip155:8453' +// Solana export const solanaChainId: ChainId = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' +export const solAssetId: AssetId = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' +export const wrappedSolAssetId: AssetId = + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:So11111111111111111111111111111111111111112' +export const usdcOnSolanaAssetId: AssetId = + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' -export const CHAIN_NAMESPACE = { - Evm: 'eip155', - Utxo: 'bip122', - CosmosSdk: 'cosmos', - Solana: 'solana', -} as const - -export const CHAIN_REFERENCE = { - EthereumMainnet: '1', - BitcoinMainnet: '000000000019d6689c085ae165831e93', - ArbitrumMainnet: '42161', - ArbitrumNovaMainnet: '42170', - BaseMainnet: '8453', - SolanaMainnet: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', -} as const - -export const ASSET_NAMESPACE = { - erc20: 'erc20', - erc721: 'erc721', - erc1155: 'erc1155', - slip44: 'slip44', - splToken: 'token', -} as const - -export const ASSET_REFERENCE = { - Bitcoin: '0', - Ethereum: '60', - Arbitrum: '60', // evm chain which uses ethereum derivation path as common practice - Solana: '501', -} as const - -export const FEE_ASSET_IDS = [ethAssetId, btcAssetId, arbitrumAssetId, solAssetId] +// Tron +export const tronChainId: ChainId = 'tron:0x2b6653dc' +export const trxAssetId: AssetId = 'tron:0x2b6653dc/slip44:195' +export const usdtOnTronAssetId: AssetId = 'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t' diff --git a/src/lib/validation.test.ts b/src/lib/validation.test.ts new file mode 100644 index 0000000..59cedde --- /dev/null +++ b/src/lib/validation.test.ts @@ -0,0 +1,67 @@ +import { btcChainId, ethChainId, solanaChainId, tronChainId } from 'constants/caip' +import { describe, expect, it } from 'vitest' + +import { isValidAddressSync } from './validation' + +describe('isValidAddressSync', () => { + it('treats an empty address as valid (nothing entered yet)', () => { + expect(isValidAddressSync('', ethChainId)).toBe(true) + expect(isValidAddressSync('', tronChainId)).toBe(true) + }) + + it('returns false for an unsupported chain', () => { + expect(isValidAddressSync('anything', 'cosmos:cosmoshub-4')).toBe(false) + }) + + describe('evm', () => { + it('accepts a valid checksummed address', () => { + expect(isValidAddressSync('0xdAC17F958D2ee523a2206206994597C13D831ec7', ethChainId)).toBe( + true, + ) + }) + + it('rejects malformed addresses', () => { + expect(isValidAddressSync('0x123', ethChainId)).toBe(false) + expect(isValidAddressSync('not-an-address', ethChainId)).toBe(false) + }) + }) + + describe('solana', () => { + it('accepts a valid base58 pubkey', () => { + expect(isValidAddressSync('So11111111111111111111111111111111111111112', solanaChainId)).toBe( + true, + ) + }) + + it('rejects malformed addresses', () => { + expect(isValidAddressSync('not-an-address', solanaChainId)).toBe(false) + }) + }) + + describe('bitcoin', () => { + it('accepts a valid address', () => { + expect(isValidAddressSync('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', btcChainId)).toBe(true) + }) + + it('rejects malformed addresses', () => { + expect(isValidAddressSync('not-an-address', btcChainId)).toBe(false) + }) + }) + + describe('tron', () => { + it('accepts valid base58 addresses', () => { + expect(isValidAddressSync('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', tronChainId)).toBe(true) + expect(isValidAddressSync('TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7', tronChainId)).toBe(true) + }) + + it('rejects malformed addresses', () => { + expect(isValidAddressSync('not-an-address', tronChainId)).toBe(false) + }) + + it('rejects an evm address on the tron chain', () => { + expect(isValidAddressSync('0xdAC17F958D2ee523a2206206994597C13D831ec7', tronChainId)).toBe( + false, + ) + }) + }) +}) diff --git a/src/lib/validation.ts b/src/lib/validation.ts index 2e75ecf..42ffa65 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -1,13 +1,13 @@ import { btcChainId } from '@shapeshiftoss/caip' import { PublicKey } from '@solana/web3.js' -import { solanaChainId } from 'constants/caip' +import { solanaChainId, tronChainId } from 'constants/caip' import debounce from 'lodash/debounce' import WAValidator from 'multicoin-address-validator' import { isAddress } from 'viem' import { isEvmChainId } from './utils' -const isValidAddressSync = (address: string, chainId: string): boolean => { +export const isValidAddressSync = (address: string, chainId: string): boolean => { if (!address) { return true } @@ -30,6 +30,10 @@ const isValidAddressSync = (address: string, chainId: string): boolean => { const isValid = WAValidator.validate(address, 'BTC') return isValid } + case tronChainId: { + const isValid = WAValidator.validate(address, 'TRX') + return isValid + } default: console.log('Unsupported chain:', chainId) return false diff --git a/src/queries/chainflip/assets.test.ts b/src/queries/chainflip/assets.test.ts index 945faf3..3de5c96 100644 --- a/src/queries/chainflip/assets.test.ts +++ b/src/queries/chainflip/assets.test.ts @@ -1,4 +1,10 @@ -import { btcAssetId, ethAssetId, usdcOnSolanaAssetId } from 'constants/caip' +import { + btcAssetId, + ethAssetId, + trxAssetId, + usdcOnSolanaAssetId, + usdtOnTronAssetId, +} from 'constants/caip' import { describe, expect, it } from 'vitest' import { transformChainflipAssets } from './assets' @@ -66,6 +72,44 @@ describe('transformChainflipAssets', () => { expect(transformChainflipAssets(mockResponse)).toEqual([usdcOnSolanaAssetId]) }) + it('should transform tron chainflip assets to assetIds', () => { + const mockResponse: ChainflipAssetsResponse = { + assets: [ + { + id: 'trx.tron', + direction: 'both', + ticker: 'TRX', + name: 'Tron', + network: 'Tron', + networkLogo: '/networks/trx/logo.svg', + assetLogo: '/assets/trx.tron/logo.svg', + decimals: 6, + minimalAmount: 10, + minimalAmountNative: '10000000', + usdPrice: 0.27, + usdPriceNative: '270000', + }, + { + id: 'usdt.tron', + direction: 'both', + ticker: 'USDT', + name: 'Tether', + network: 'Tron', + contractAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', + networkLogo: '/networks/trx/logo.svg', + assetLogo: '/assets/usdt.tron/logo.svg', + decimals: 6, + minimalAmount: 10, + minimalAmountNative: '10000000', + usdPrice: 1.0001, + usdPriceNative: '1000100', + }, + ], + } + + expect(transformChainflipAssets(mockResponse)).toEqual([trxAssetId, usdtOnTronAssetId]) + }) + it('should return empty array for unsupported assets', () => { const mockResponse: ChainflipAssetsResponse = { assets: [ diff --git a/src/queries/chainflip/assets.ts b/src/queries/chainflip/assets.ts index 8706414..7ce1666 100644 --- a/src/queries/chainflip/assets.ts +++ b/src/queries/chainflip/assets.ts @@ -6,10 +6,12 @@ import { ethAssetId, flipAssetId, solAssetId, + trxAssetId, usdcAssetId, usdcOnArbitrumOneAssetId, usdcOnSolanaAssetId, usdtAssetId, + usdtOnTronAssetId, } from 'constants/caip' import mirror from 'lodash/invert' @@ -27,6 +29,8 @@ const assetIdToChainflipId: Record = { [usdcAssetId]: 'usdc.eth', [usdcOnSolanaAssetId]: 'usdc.sol', [usdtAssetId]: 'usdt.eth', + [trxAssetId]: 'trx.tron', + [usdtOnTronAssetId]: 'usdt.tron', } // Map Chainflip internal asset IDs to CAIPs diff --git a/src/queries/marketData/index.ts b/src/queries/marketData/index.ts index 57b900c..f8778c1 100644 --- a/src/queries/marketData/index.ts +++ b/src/queries/marketData/index.ts @@ -6,10 +6,12 @@ import { ethAssetId, flipAssetId, solAssetId, + trxAssetId, usdcAssetId, usdcOnArbitrumOneAssetId, usdcOnSolanaAssetId, usdtAssetId, + usdtOnTronAssetId, } from 'constants/caip' import { reactQueries } from '../react-queries' @@ -28,6 +30,8 @@ const ASSET_ID_TO_COINGECKO_ID: Record = { [usdcOnArbitrumOneAssetId]: 'usd-coin', [solAssetId]: 'solana', [usdcOnSolanaAssetId]: 'usd-coin', + [trxAssetId]: 'tron', + [usdtOnTronAssetId]: 'tether', } export const findByAssetId = async (assetId: string): Promise => { diff --git a/vitest.config.ts b/vitest.config.ts index f931763..3edacb7 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,14 +12,5 @@ export default defineConfig({ environment: 'happy-dom', setupFiles: [], clearMocks: true, - poolOptions: { - isolate: false, - threads: { - singleThread: true, - }, - forks: { - isolate: false, - }, - }, }, })