diff --git a/.env b/.env
index dadb86b..e1d2cdf 100644
--- a/.env
+++ b/.env
@@ -7,3 +7,8 @@ VITE_FOO=bar
# chat woot
VITE_CHATWOOT_TOKEN=jmoXp9BPMSPEYHeJX5YKT15Q
VITE_CHATWOOT_URL=https://app.chatwoot.com
+
+# Chainflip
+
+VITE_CHAINFLIP_API_KEY=09bc0796ff40435482c0a54fa6ae2784
+VITE_CHAINFLIP_API_URL=https://chainflip-broker.io
diff --git a/package.json b/package.json
index d46b46e..4b33caa 100644
--- a/package.json
+++ b/package.json
@@ -16,8 +16,10 @@
"@chakra-ui/react": "^2.10.3",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
+ "@lukemorales/query-key-factory": "^1.3.4",
"@shapeshiftoss/caip": "^8.15.0",
"@shapeshiftoss/types": "^8.6.0",
+ "@tanstack/react-query": "^5.65.1",
"axios": "^1.6.5",
"framer-motion": "^10.17.9",
"match-sorter": "^6.3.1",
diff --git a/src/App.tsx b/src/App.tsx
index f070c74..d9d47fe 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,12 +1,15 @@
import './App.css'
import { Center } from '@chakra-ui/react'
+import { QueryClientProvider } from '@tanstack/react-query'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { ChatwootButton } from 'components/Chatwoot'
import { SelectPair } from 'components/SelectPair'
import { Status } from 'components/Status/Status'
import { TradeInput } from 'components/TradeInput'
+import { queryClient } from './config/react-query'
+
const router = createBrowserRouter([
{
path: '/',
@@ -26,10 +29,12 @@ function App() {
console.log(import.meta.env.VITE_FOO)
return (
-
-
-
-
+
+
+
+
+
+
)
}
diff --git a/src/config/react-query.ts b/src/config/react-query.ts
new file mode 100644
index 0000000..9f69260
--- /dev/null
+++ b/src/config/react-query.ts
@@ -0,0 +1,3 @@
+import { QueryClient } from '@tanstack/react-query'
+
+export const queryClient = new QueryClient()
diff --git a/src/constants/caip.ts b/src/constants/caip.ts
new file mode 100644
index 0000000..9a7c928
--- /dev/null
+++ b/src/constants/caip.ts
@@ -0,0 +1,60 @@
+// 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
+
+import type { AssetId, ChainId } from '@shapeshiftoss/caip'
+
+export const btcAssetId: AssetId = 'bip122:000000000019d6689c085ae165831e93/slip44:0'
+
+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 flipAssetId: AssetId = 'eip155:1/erc20:0x826180541412d574cf1336d22c0c0a287822678a'
+
+export const btcChainId: ChainId = 'bip122:000000000019d6689c085ae165831e93'
+
+export const ethChainId: ChainId = 'eip155:1'
+export const arbitrumChainId: ChainId = 'eip155:42161'
+export const baseChainId: ChainId = 'eip155:8453'
+
+export const solanaChainId: ChainId = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'
+
+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]
diff --git a/src/env.d.ts b/src/env.d.ts
index 2278f39..b37958a 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -7,6 +7,8 @@ interface ImportMetaEnv {
readonly VITE_CHATWOOT_URL: string
readonly VITE_CHATWOOT_TOKEN: string
readonly VITE_FEATURE_CHATWOOT: boolean
+ readonly VITE_CHAINFLIP_API_KEY: string
+ readonly VITE_CHAINFLIP_API_URL: string
}
interface ImportMeta {
diff --git a/src/queries/chainflip/assets.test.ts b/src/queries/chainflip/assets.test.ts
new file mode 100644
index 0000000..945faf3
--- /dev/null
+++ b/src/queries/chainflip/assets.test.ts
@@ -0,0 +1,91 @@
+import { btcAssetId, ethAssetId, usdcOnSolanaAssetId } from 'constants/caip'
+import { describe, expect, it } from 'vitest'
+
+import { transformChainflipAssets } from './assets'
+import type { ChainflipAssetsResponse } from './types'
+
+describe('transformChainflipAssets', () => {
+ it('should transform multiple chainflip assets to assetIds', () => {
+ const mockResponse: ChainflipAssetsResponse = {
+ assets: [
+ {
+ id: 'btc.btc',
+ direction: 'both',
+ ticker: 'BTC',
+ name: 'Bitcoin',
+ network: 'Bitcoin',
+ networkLogo: '/networks/btc/logo.svg',
+ assetLogo: '/assets/btc.btc/logo.svg',
+ decimals: 8,
+ minimalAmount: 0.0007,
+ minimalAmountNative: '70000',
+ usdPrice: 102680.1024594243,
+ usdPriceNative: '102680102459',
+ },
+ {
+ id: 'eth.eth',
+ direction: 'both',
+ ticker: 'ETH',
+ name: 'Ethereum',
+ network: 'Ethereum',
+ networkLogo: '/networks/eth/logo.svg',
+ assetLogo: '/assets/eth.eth/logo.svg',
+ decimals: 18,
+ minimalAmount: 0.01,
+ minimalAmountNative: '10000000000000000',
+ usdPrice: 3175.43595522135,
+ usdPriceNative: '3175435955',
+ },
+ ],
+ }
+
+ expect(transformChainflipAssets(mockResponse)).toEqual([btcAssetId, ethAssetId])
+ })
+
+ it('should transform single chainflip asset to assetId', () => {
+ const mockResponse: ChainflipAssetsResponse = {
+ assets: [
+ {
+ id: 'usdc.sol',
+ direction: 'both',
+ ticker: 'USDC',
+ name: 'solUSDC',
+ network: 'Solana',
+ contractAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
+ networkLogo: '/networks/sol/logo.svg',
+ assetLogo: '/assets/usdc.sol/logo.svg',
+ decimals: 6,
+ minimalAmount: 10,
+ minimalAmountNative: '10000000',
+ usdPrice: 1.0014781977,
+ usdPriceNative: '1001478',
+ },
+ ],
+ }
+
+ expect(transformChainflipAssets(mockResponse)).toEqual([usdcOnSolanaAssetId])
+ })
+
+ it('should return empty array for unsupported assets', () => {
+ const mockResponse: ChainflipAssetsResponse = {
+ assets: [
+ {
+ id: 'unsupported.asset',
+ direction: 'both',
+ ticker: 'UNSUPPORTED',
+ name: 'Unsupported Asset',
+ network: 'Unknown',
+ networkLogo: '/networks/unknown/logo.svg',
+ assetLogo: '/assets/unknown.asset/logo.svg',
+ decimals: 18,
+ minimalAmount: 1,
+ minimalAmountNative: '1000000000000000000',
+ usdPrice: 1,
+ usdPriceNative: '1000000000000000000',
+ },
+ ],
+ }
+
+ expect(transformChainflipAssets(mockResponse)).toEqual([])
+ })
+})
diff --git a/src/queries/chainflip/assets.ts b/src/queries/chainflip/assets.ts
new file mode 100644
index 0000000..de6db1e
--- /dev/null
+++ b/src/queries/chainflip/assets.ts
@@ -0,0 +1,39 @@
+import { useQuery } from '@tanstack/react-query'
+import {
+ arbitrumAssetId,
+ btcAssetId,
+ ethAssetId,
+ flipAssetId,
+ solAssetId,
+ usdcAssetId,
+ usdcOnArbitrumOneAssetId,
+ usdcOnSolanaAssetId,
+ usdtAssetId,
+} from 'constants/caip'
+
+import { reactQueries } from '../react-queries'
+import type { ChainflipAssetsResponse } from './types'
+
+// Map Chainflip internal asset IDs to CAIPs
+const chainflipToAssetId: Record = {
+ 'btc.btc': btcAssetId,
+ 'eth.arb': arbitrumAssetId,
+ 'eth.eth': ethAssetId,
+ 'flip.eth': flipAssetId,
+ 'sol.sol': solAssetId,
+ 'usdc.arb': usdcOnArbitrumOneAssetId,
+ 'usdc.eth': usdcAssetId,
+ 'usdc.sol': usdcOnSolanaAssetId,
+ 'usdt.eth': usdtAssetId,
+}
+
+export const transformChainflipAssets = (data: ChainflipAssetsResponse) => {
+ return data.assets.map(asset => chainflipToAssetId[asset.id]).filter(Boolean)
+}
+
+export const useChainflipAssetsQuery = () => {
+ return useQuery({
+ ...reactQueries.chainflip.assets,
+ select: transformChainflipAssets,
+ })
+}
diff --git a/src/queries/chainflip/quote.ts b/src/queries/chainflip/quote.ts
new file mode 100644
index 0000000..5b76f92
--- /dev/null
+++ b/src/queries/chainflip/quote.ts
@@ -0,0 +1,10 @@
+import { useQuery } from '@tanstack/react-query'
+
+import { reactQueries } from '../react-queries'
+import type { ChainflipQuoteParams } from './types'
+
+export const useChainflipQuoteQuery = (params: ChainflipQuoteParams) => {
+ return useQuery({
+ ...reactQueries.chainflip.quote(params),
+ })
+}
diff --git a/src/queries/chainflip/status.ts b/src/queries/chainflip/status.ts
new file mode 100644
index 0000000..f2b5271
--- /dev/null
+++ b/src/queries/chainflip/status.ts
@@ -0,0 +1,16 @@
+import { useQuery } from '@tanstack/react-query'
+
+import { reactQueries } from '../react-queries'
+
+type ChainflipStatusQueryParams = {
+ swapId: number
+ enabled?: boolean
+}
+
+export const useChainflipStatusQuery = ({ swapId, enabled = true }: ChainflipStatusQueryParams) => {
+ return useQuery({
+ ...reactQueries.chainflip.status(swapId),
+ refetchInterval: 15000,
+ enabled,
+ })
+}
diff --git a/src/queries/chainflip/swap.ts b/src/queries/chainflip/swap.ts
new file mode 100644
index 0000000..cae8ab1
--- /dev/null
+++ b/src/queries/chainflip/swap.ts
@@ -0,0 +1,10 @@
+import { useQuery } from '@tanstack/react-query'
+
+import { reactQueries } from '../react-queries'
+import type { ChainflipSwapParams } from './types'
+
+export const useChainflipSwapQuery = (params: ChainflipSwapParams) => {
+ return useQuery({
+ ...reactQueries.chainflip.swap(params),
+ })
+}
diff --git a/src/queries/chainflip/types.ts b/src/queries/chainflip/types.ts
new file mode 100644
index 0000000..ae89630
--- /dev/null
+++ b/src/queries/chainflip/types.ts
@@ -0,0 +1,109 @@
+export type ChainflipAsset = {
+ id: string
+ direction: 'both' | 'ingress' | 'egress'
+ ticker: string
+ name: string
+ network: string
+ contractAddress?: string
+ networkLogo: string
+ assetLogo: string
+ decimals: number
+ minimalAmount: number
+ minimalAmountNative: string
+ usdPrice: number
+ usdPriceNative: string
+}
+
+export type ChainflipAssetsResponse = {
+ assets: ChainflipAsset[]
+}
+
+export type ChainflipQuoteFee = {
+ type: 'ingress' | 'network' | 'broker' | 'egress' | 'liquidity'
+ asset: string
+ amount: number
+ amountNative: string
+}
+
+export type ChainflipPoolInfo = {
+ baseAsset: string
+ quoteAsset: string
+ fee: {
+ asset: string
+ amount: number
+ amountNative: string
+ }
+}
+
+export type ChainflipQuote = {
+ type: 'regular' | 'dca'
+ ingressAsset: string
+ ingressAmount: number
+ ingressAmountNative: string
+ intermediateAsset: string | null
+ intermediateAmount: number | null
+ intermediateAmountNative: string | null
+ egressAsset: string
+ egressAmount: number
+ egressAmountNative: string
+ includedFees: ChainflipQuoteFee[]
+ recommendedSlippageTolerancePercent: number
+ lowLiquidityWarning: boolean
+ poolInfo: ChainflipPoolInfo[]
+ estimatedDurationSeconds: number
+ estimatedDurationsSeconds: {
+ deposit: number
+ swap: number
+ egress: number
+ }
+ estimatedPrice: number
+ chunkIntervalBlocks?: number | null
+ numberOfChunks?: number | null
+ boostQuote?: ChainflipQuote
+}
+
+export type ChainflipQuoteParams = {
+ sourceAsset: string
+ destinationAsset: string
+ amount: string
+ commissionBps?: number
+}
+
+export type ChainflipSwapResponse = {
+ id: number
+ address: string
+ issuedBlock: number
+ network: string
+ channelId: number
+ sourceExpiryBlock: number
+ explorerUrl: string
+ channelOpeningFee: number
+ channelOpeningFeeNative: string
+}
+
+export type ChainflipSwapParams = {
+ sourceAsset: string
+ destinationAsset: string
+ destinationAddress: string
+ minimumPrice: string
+ refundAddress: string
+ maxBoostFee?: number
+ retryDurationInBlocks?: number
+ commissionBps?: number
+ numberOfChunks?: number
+ chunkIntervalBlocks?: number
+}
+
+export type ChainflipSwapEgress = {
+ transactionReference?: string
+}
+
+export type ChainflipSwapStatus = {
+ state: 'waiting' | 'receiving' | 'swapping' | 'sending' | 'sent' | 'completed' | 'failed'
+ swapEgress?: ChainflipSwapEgress
+}
+
+export type ChainflipStatusResponse = {
+ id: number
+ status: ChainflipSwapStatus
+}
diff --git a/src/queries/marketData/index.test.ts b/src/queries/marketData/index.test.ts
new file mode 100644
index 0000000..9d9a196
--- /dev/null
+++ b/src/queries/marketData/index.test.ts
@@ -0,0 +1,111 @@
+import axios from 'axios'
+import { btcAssetId, ethAssetId } from 'constants/caip'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { findByAssetId } from '.'
+
+vi.mock('axios')
+
+describe('coingecko queries', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ describe('findByAssetId', () => {
+ it('should return market data for ETH', async () => {
+ const result = {
+ price: '3611.19',
+ marketCap: '424970837706',
+ changePercent24Hr: 2.19682,
+ volume: '21999495657',
+ supply: '120839129.44',
+ }
+
+ const market_data = {
+ current_price: {
+ usd: Number(result.price),
+ },
+ market_cap: {
+ usd: Number(result.marketCap),
+ },
+ price_change_percentage_24h: result.changePercent24Hr,
+ total_volume: {
+ usd: Number(result.volume),
+ },
+ circulating_supply: Number(result.supply),
+ max_supply: null,
+ total_supply: null,
+ }
+
+ vi.mocked(axios.get).mockResolvedValue({ data: { market_data } })
+ expect(await findByAssetId(ethAssetId)).toEqual(result)
+ })
+
+ it('should return market data with maxSupply for BTC', async () => {
+ const result = {
+ price: '54810',
+ marketCap: '1032270421549',
+ changePercent24Hr: -0.33384,
+ volume: '38267223547',
+ supply: '18840237',
+ maxSupply: '21000000',
+ }
+
+ const market_data = {
+ current_price: {
+ usd: Number(result.price),
+ },
+ market_cap: {
+ usd: Number(result.marketCap),
+ },
+ price_change_percentage_24h: result.changePercent24Hr,
+ total_volume: {
+ usd: Number(result.volume),
+ },
+ circulating_supply: Number(result.supply),
+ max_supply: Number(result.maxSupply),
+ total_supply: Number(result.maxSupply),
+ }
+
+ vi.mocked(axios.get).mockResolvedValue({ data: { market_data } })
+ expect(await findByAssetId(btcAssetId)).toEqual(result)
+ })
+
+ it('should return null if asset not found on coingecko', async () => {
+ const result = await findByAssetId('invalid-asset-id')
+ expect(result).toBeNull()
+ })
+
+ it('should throw on network error', async () => {
+ vi.mocked(axios.get).mockRejectedValue(new Error())
+ await expect(findByAssetId(ethAssetId)).rejects.toThrow(
+ 'CoinGeckoMarketService(findByAssetId): error fetching market data',
+ )
+ })
+
+ it('should return null if market data is missing', async () => {
+ vi.mocked(axios.get).mockResolvedValue({ data: {} })
+ const result = await findByAssetId(ethAssetId)
+ expect(result).toBeNull()
+ })
+
+ it('should handle missing optional fields', async () => {
+ const result = {
+ price: '0',
+ marketCap: '0',
+ changePercent24Hr: 0,
+ supply: '0',
+ }
+
+ const market_data = {
+ current_price: {},
+ market_cap: {},
+ price_change_percentage_24h: 0,
+ circulating_supply: 0,
+ }
+
+ vi.mocked(axios.get).mockResolvedValue({ data: { market_data } })
+ expect(await findByAssetId(ethAssetId)).toEqual(result)
+ })
+ })
+})
diff --git a/src/queries/marketData/index.ts b/src/queries/marketData/index.ts
new file mode 100644
index 0000000..57b900c
--- /dev/null
+++ b/src/queries/marketData/index.ts
@@ -0,0 +1,63 @@
+import { useQuery } from '@tanstack/react-query'
+import axios from 'axios'
+import {
+ arbitrumAssetId,
+ btcAssetId,
+ ethAssetId,
+ flipAssetId,
+ solAssetId,
+ usdcAssetId,
+ usdcOnArbitrumOneAssetId,
+ usdcOnSolanaAssetId,
+ usdtAssetId,
+} from 'constants/caip'
+
+import { reactQueries } from '../react-queries'
+import type { CoinGeckoAssetData, MarketData } from './types'
+
+const COINGECKO_BASE_URL = 'https://api.proxy.shapeshift.com/api/v1/markets'
+
+// i.e supported assets on Chainflip only
+const ASSET_ID_TO_COINGECKO_ID: Record = {
+ [btcAssetId]: 'bitcoin',
+ [ethAssetId]: 'ethereum',
+ [flipAssetId]: 'chainflip',
+ [usdcAssetId]: 'usd-coin',
+ [usdtAssetId]: 'tether',
+ [arbitrumAssetId]: 'ethereum',
+ [usdcOnArbitrumOneAssetId]: 'usd-coin',
+ [solAssetId]: 'solana',
+ [usdcOnSolanaAssetId]: 'usd-coin',
+}
+
+export const findByAssetId = async (assetId: string): Promise => {
+ const coingeckoId = ASSET_ID_TO_COINGECKO_ID[assetId]
+ if (!coingeckoId) return null
+
+ try {
+ const { data } = await axios.get(
+ `${COINGECKO_BASE_URL}/coins/${coingeckoId}`,
+ )
+
+ const marketData = data?.market_data
+ if (!marketData) return null
+
+ return {
+ price: marketData.current_price?.['usd']?.toString() ?? '0',
+ marketCap: marketData.market_cap?.['usd']?.toString() ?? '0',
+ volume: marketData.total_volume?.['usd']?.toString(),
+ changePercent24Hr: marketData.price_change_percentage_24h,
+ supply: marketData.circulating_supply.toString(),
+ maxSupply: marketData.max_supply?.toString() ?? marketData.total_supply?.toString(),
+ }
+ } catch (e) {
+ console.warn(e)
+ throw new Error('CoinGeckoMarketService(findByAssetId): error fetching market data')
+ }
+}
+
+export const useMarketDataByAssetIdQuery = (assetId: string) => {
+ return useQuery({
+ ...reactQueries.marketData.byAssetId(assetId),
+ })
+}
diff --git a/src/queries/marketData/types.ts b/src/queries/marketData/types.ts
new file mode 100644
index 0000000..b821a62
--- /dev/null
+++ b/src/queries/marketData/types.ts
@@ -0,0 +1,24 @@
+export type CoinGeckoMarketData = {
+ current_price: Record
+ market_cap: Record
+ total_volume?: Record
+ high_24h?: Record
+ low_24h?: Record
+ circulating_supply: number
+ total_supply?: number
+ max_supply: number
+ price_change_percentage_24h: number
+}
+
+export type CoinGeckoAssetData = {
+ market_data: CoinGeckoMarketData
+}
+
+export type MarketData = {
+ price: string
+ marketCap: string
+ volume?: string
+ changePercent24Hr: number
+ supply: string
+ maxSupply?: string
+}
diff --git a/src/queries/queryKeys.ts b/src/queries/queryKeys.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/queries/react-queries.ts b/src/queries/react-queries.ts
new file mode 100644
index 0000000..715792b
--- /dev/null
+++ b/src/queries/react-queries.ts
@@ -0,0 +1,80 @@
+import type { inferQueryKeyStore } from '@lukemorales/query-key-factory'
+import { createQueryKeyStore } from '@lukemorales/query-key-factory'
+import axios from 'axios'
+
+import type {
+ ChainflipAssetsResponse,
+ ChainflipQuote,
+ ChainflipQuoteParams,
+ ChainflipStatusResponse,
+ ChainflipSwapParams,
+ ChainflipSwapResponse,
+} from './chainflip/types'
+import { findByAssetId } from './marketData'
+
+const CHAINFLIP_API_URL = import.meta.env.VITE_CHAINFLIP_API_URL
+const CHAINFLIP_API_KEY = import.meta.env.VITE_CHAINFLIP_API_KEY
+
+export const reactQueries = createQueryKeyStore({
+ chainflip: {
+ assets: {
+ queryKey: null,
+ queryFn: async () => {
+ const { data } = await axios.get(`${CHAINFLIP_API_URL}/assets`)
+ return data
+ },
+ },
+ quote: (params: ChainflipQuoteParams) => ({
+ queryKey: [params],
+ queryFn: async () => {
+ const { data } = await axios.get(`${CHAINFLIP_API_URL}/quotes-native`, {
+ params: {
+ apiKey: CHAINFLIP_API_KEY,
+ sourceAsset: params.sourceAsset,
+ destinationAsset: params.destinationAsset,
+ amount: params.amount,
+ ...(params.commissionBps && { commissionBps: params.commissionBps }),
+ },
+ })
+ return data[0]
+ },
+ }),
+ swap: (params: ChainflipSwapParams) => ({
+ queryKey: [params],
+ queryFn: async () => {
+ const { data } = await axios.get(`${CHAINFLIP_API_URL}/swap`, {
+ params: {
+ apiKey: CHAINFLIP_API_KEY,
+ ...params,
+ boostFee: params.maxBoostFee ?? 0,
+ retryDurationInBlocks: params.retryDurationInBlocks ?? 150,
+ },
+ })
+ return data
+ },
+ }),
+ status: (swapId: number) => ({
+ queryKey: [swapId],
+ queryFn: async () => {
+ const { data } = await axios.get(
+ `${CHAINFLIP_API_URL}/status-by-id`,
+ {
+ params: {
+ apiKey: CHAINFLIP_API_KEY,
+ swapId,
+ },
+ },
+ )
+ return data
+ },
+ }),
+ },
+ marketData: {
+ byAssetId: (assetId: string) => ({
+ queryKey: [assetId],
+ queryFn: () => findByAssetId(assetId),
+ }),
+ },
+})
+
+export type QueryKeys = inferQueryKeyStore
diff --git a/yarn.lock b/yarn.lock
index 6a2afa6..714353f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2365,6 +2365,16 @@ __metadata:
languageName: node
linkType: hard
+"@lukemorales/query-key-factory@npm:^1.3.4":
+ version: 1.3.4
+ resolution: "@lukemorales/query-key-factory@npm:1.3.4"
+ peerDependencies:
+ "@tanstack/query-core": ">= 4.0.0"
+ "@tanstack/react-query": ">= 4.0.0"
+ checksum: 10c0/d4e829aad970c159e3e3d6545f4f1e47d1a02621cd692bc0e13b7cc36861541e205f801d7ab8081ed8b1c8c3fd365483e1656537ef1ab165cb14743b4b1f1d6c
+ languageName: node
+ linkType: hard
+
"@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1":
version: 5.1.1-v1
resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1"
@@ -2753,6 +2763,24 @@ __metadata:
languageName: node
linkType: hard
+"@tanstack/query-core@npm:5.65.0":
+ version: 5.65.0
+ resolution: "@tanstack/query-core@npm:5.65.0"
+ checksum: 10c0/8c957082819dc90aa162256e0cc9d7d33e90f4ba9a55ec788ef5ec36bbb2b1863663dda594f9b6eb820ec9855f8d6aecd01324a020d16b58ad012c99270fe989
+ languageName: node
+ linkType: hard
+
+"@tanstack/react-query@npm:^5.65.1":
+ version: 5.65.1
+ resolution: "@tanstack/react-query@npm:5.65.1"
+ dependencies:
+ "@tanstack/query-core": "npm:5.65.0"
+ peerDependencies:
+ react: ^18 || ^19
+ checksum: 10c0/0b0ed414c59ee1d7a5a8e72d2e2f2513f7367b59ee33d5663b114fcf5108438786b9bbbd12173ae7f8124538f313cd4dff942c7080b60d26d011c18918b1d563
+ languageName: node
+ linkType: hard
+
"@types/estree@npm:1.0.5, @types/estree@npm:^1.0.0":
version: 1.0.5
resolution: "@types/estree@npm:1.0.5"
@@ -7311,8 +7339,10 @@ __metadata:
"@chakra-ui/react": "npm:^2.10.3"
"@emotion/react": "npm:^11.11.3"
"@emotion/styled": "npm:^11.11.0"
+ "@lukemorales/query-key-factory": "npm:^1.3.4"
"@shapeshiftoss/caip": "npm:^8.15.0"
"@shapeshiftoss/types": "npm:^8.6.0"
+ "@tanstack/react-query": "npm:^5.65.1"
"@types/inquirer": "npm:^9.0.7"
"@types/mixpanel-browser": "npm:^2.48.1"
"@types/node": "npm:^20.10.7"