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
34 changes: 34 additions & 0 deletions src/constants/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<AssetId, Asset>>((acc, asset) => {
Expand Down
68 changes: 22 additions & 46 deletions src/constants/caip.ts
Original file line number Diff line number Diff line change
@@ -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'
67 changes: 67 additions & 0 deletions src/lib/validation.test.ts
Original file line number Diff line number Diff line change
@@ -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,
)
})
})
})
8 changes: 6 additions & 2 deletions src/lib/validation.ts
Original file line number Diff line number Diff line change
@@ -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
}
Expand All @@ -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
Expand Down
46 changes: 45 additions & 1 deletion src/queries/chainflip/assets.test.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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: [
Expand Down
4 changes: 4 additions & 0 deletions src/queries/chainflip/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
ethAssetId,
flipAssetId,
solAssetId,
trxAssetId,
usdcAssetId,
usdcOnArbitrumOneAssetId,
usdcOnSolanaAssetId,
usdtAssetId,
usdtOnTronAssetId,
} from 'constants/caip'
import mirror from 'lodash/invert'

Expand All @@ -27,6 +29,8 @@ const assetIdToChainflipId: Record<AssetId, string> = {
[usdcAssetId]: 'usdc.eth',
[usdcOnSolanaAssetId]: 'usdc.sol',
[usdtAssetId]: 'usdt.eth',
[trxAssetId]: 'trx.tron',
[usdtOnTronAssetId]: 'usdt.tron',
}

// Map Chainflip internal asset IDs to CAIPs
Expand Down
4 changes: 4 additions & 0 deletions src/queries/marketData/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
ethAssetId,
flipAssetId,
solAssetId,
trxAssetId,
usdcAssetId,
usdcOnArbitrumOneAssetId,
usdcOnSolanaAssetId,
usdtAssetId,
usdtOnTronAssetId,
} from 'constants/caip'

import { reactQueries } from '../react-queries'
Expand All @@ -28,6 +30,8 @@ const ASSET_ID_TO_COINGECKO_ID: Record<string, string> = {
[usdcOnArbitrumOneAssetId]: 'usd-coin',
[solAssetId]: 'solana',
[usdcOnSolanaAssetId]: 'usd-coin',
[trxAssetId]: 'tron',
[usdtOnTronAssetId]: 'tether',
}

export const findByAssetId = async (assetId: string): Promise<MarketData | null> => {
Expand Down
9 changes: 0 additions & 9 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,5 @@ export default defineConfig({
environment: 'happy-dom',
setupFiles: [],
clearMocks: true,
poolOptions: {
isolate: false,
threads: {
singleThread: true,
},
forks: {
isolate: false,
},
},
},
})
Loading