diff --git a/src/lib/api/orders.ts b/src/lib/api/orders.ts index 5aa147c1..84d4f56b 100644 --- a/src/lib/api/orders.ts +++ b/src/lib/api/orders.ts @@ -304,7 +304,7 @@ async function collectProcessedOrderPages({ * request stream and converts them to ProcessedQuotes. */ export async function fetchAndQuotePaymentTokenOrders( - networkId: number = 8453, + networkId: number, overridePaymentToken?: Token, signal?: AbortSignal, fetchOrders: OrdersQueryFetcher = apiQueryOrders @@ -361,13 +361,14 @@ export async function fetchAndQuoteOwnerOrders( ): Promise { const { paymentToken, allTokens } = resolveNetworkTokens(networkId, overridePaymentToken); const chainId = networks.find((n) => n.id === networkId)?.chainId; + if (chainId === undefined) throw new Error(`Unknown network ${networkId}`); const processedQuotes: ProcessedQuote[] = []; const seen = new Set(); let page = 1; let hasMore = true; while (hasMore && page <= MAX_ORDER_PAGES) { - const response = await apiGetOrdersByOwner(ownerAddress, { page, pageSize: 50 }); + const response = await apiGetOrdersByOwner(ownerAddress, chainId, { page, pageSize: 50 }); for (const order of response.orders) { if (chainId !== undefined && order.chainId !== chainId) continue; if (seen.has(order.orderHash)) continue; @@ -402,9 +403,11 @@ export async function fetchAndQuoteTokenOrders( overridePaymentToken?: Token ) { const { paymentToken, allTokens } = resolveNetworkTokens(networkId, overridePaymentToken); + const chainId = networks.find((network) => network.id === networkId)?.chainId; + if (chainId === undefined) throw new Error(`Unknown network ${networkId}`); return collectProcessedOrderPages({ - fetchPage: (page) => apiGetOrdersByToken(tokenAddress, { page, pageSize: 50 }), + fetchPage: (page) => apiGetOrdersByToken(tokenAddress, chainId, { page, pageSize: 50 }), paymentToken, allTokens, networkId, diff --git a/src/lib/api/st0xApi.ts b/src/lib/api/st0xApi.ts index 8601717f..f26f7398 100644 --- a/src/lib/api/st0xApi.ts +++ b/src/lib/api/st0xApi.ts @@ -32,12 +32,14 @@ export interface ApiToken extends ApiTokenRef { // ============================================================================ export interface ApiSwapQuoteRequest { + chainId: number; inputToken: string; outputToken: string; outputAmount: string; } export interface ApiSwapQuoteResponse { + chainId: number; inputToken: string; outputToken: string; outputAmount: string; @@ -60,6 +62,7 @@ export interface ApiApproval { } export interface ApiSwapCalldataResponse { + chainId: number; to: string; data: string; value: string; @@ -70,6 +73,7 @@ export interface ApiSwapCalldataResponse { export type ApiSwapCalldataMode = 'buyUpTo' | 'spendExact' | 'spendUpTo'; export interface ApiSwapV2RequestCommon { + chainId: number; inputToken: string; outputToken: string; mode: ApiSwapCalldataMode; @@ -156,6 +160,7 @@ export interface ApiOrdersQueryRequest { // ============================================================================ export interface ApiTradeByAddress { + chainId: number; txHash: string; inputAmount: string; outputAmount: string; @@ -180,6 +185,7 @@ export interface ApiTradesByAddressResponse { } export interface ApiTradeByTxEntry { + chainId: number; orderHash: string; orderOwner: string; request: { @@ -196,6 +202,7 @@ export interface ApiTradeByTxEntry { } export interface ApiTradesByTxResponse { + chainId: number; txHash: string; blockNumber: number; timestamp: number; @@ -225,7 +232,7 @@ export interface ApiTradesBatchResponse { export interface ApiTradesOrderHashesQueryRequest { orderHashes: string[]; tokenAddresses?: string[]; - chainId?: number; + chainId: number; startTime?: number; endTime?: number; denomination?: 'wrapped' | 'unwrapped'; @@ -268,6 +275,7 @@ export interface ApiTokenProofReceipt { } export interface ApiTokenProofsResponse { + chainId: number; address: string; metadata: MetaV1S[]; schemas: ApiTokenProofSchema[]; @@ -279,11 +287,13 @@ export interface ApiTokenProofsResponse { // ============================================================================ export interface ApiTokenDetailsError { + chainId: number; address: string; message: string; } export interface ApiTokenDetailsSummary { + chainId: number; address: string; deployTimestamp?: number; receiptContractAddress?: string | null; @@ -329,6 +339,7 @@ export interface ApiTokenDetailsListResponse { // ============================================================================ export interface ApiWrapRatio { + chainId: number; shareAddress: string; assetAddress: string; assetsPerShare: string; @@ -338,6 +349,7 @@ export interface ApiWrapRatio { } export interface ApiWrapRatioError { + chainId: number; shareAddress: string; message: string; } @@ -356,6 +368,7 @@ export interface ApiWrapRatioHistoryEvent { } export interface ApiWrapRatioHistoryResponse { + chainId: number; shareAddress: string; assetAddress: string; events: ApiWrapRatioHistoryEvent[]; @@ -440,6 +453,7 @@ function apiUrl(path: string, params?: Record { assertBrowser('apiGetOrdersByToken'); - const url = apiUrl(`/v1/orders/token/${tokenAddress}`, { + const url = apiUrl(`/v2/orders/token/${tokenAddress}`, { + chainId, page: options?.page, pageSize: options?.pageSize, side: options?.side, @@ -465,7 +480,7 @@ export async function apiQueryOrders( signal?: AbortSignal ): Promise { assertBrowser('apiQueryOrders'); - return fetchJson(apiUrl('/v1/orders/query'), { + return fetchJson(apiUrl('/v2/orders/query'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), @@ -476,25 +491,28 @@ export async function apiQueryOrders( /** * Fetch the canonical supported token list from the REST API. */ -export async function apiGetTokens(): Promise { +export async function apiGetTokens(chainId?: number): Promise { assertBrowser('apiGetTokens'); - return fetchJson(apiUrl('/v1/tokens')); + return fetchJson(apiUrl('/v2/tokens', { chainId })); } /** * Fetch raw proof/attestation metadata for a tokenized asset. */ -export async function apiGetTokenProofs(address: string): Promise { +export async function apiGetTokenProofs( + address: string, + chainId: number +): Promise { assertBrowser('apiGetTokenProofs'); - return fetchJson(apiUrl(`/v1/tokens/${address}/proofs`)); + return fetchJson(apiUrl(`/v2/tokens/${address}/proofs`, { chainId })); } /** * Fetch ST0x token detail summaries from the REST API. */ -export async function apiGetTokenDetails(): Promise { +export async function apiGetTokenDetails(chainId?: number): Promise { assertBrowser('apiGetTokenDetails'); - return fetchJson(apiUrl('/v1/tokens/details')); + return fetchJson(apiUrl('/v2/tokens/details', { chainId })); } /** @@ -502,11 +520,13 @@ export async function apiGetTokenDetails(): Promise */ export async function apiGetTokenDetailsByAddress( address: string, + chainId: number, options?: { activityLimit?: number } ): Promise { assertBrowser('apiGetTokenDetailsByAddress'); return fetchJson( - apiUrl(`/v1/tokens/${address}/details`, { + apiUrl(`/v2/tokens/${address}/details`, { + chainId, activityLimit: options?.activityLimit }) ); @@ -515,17 +535,22 @@ export async function apiGetTokenDetailsByAddress( /** * Fetch current wrap ratios for supported wrapped tokens. */ -export async function apiGetWrapRatios(): Promise { +export async function apiGetWrapRatios(chainId?: number): Promise { assertBrowser('apiGetWrapRatios'); - return fetchJson(apiUrl('/v1/tokens/wrap-ratio')); + return fetchJson(apiUrl('/v2/tokens/wrap-ratio', { chainId })); } /** * Fetch current wrap ratio for a single wrapped token. */ -export async function apiGetWrapRatio(wrappedTokenAddress: string): Promise { +export async function apiGetWrapRatio( + wrappedTokenAddress: string, + chainId: number +): Promise { assertBrowser('apiGetWrapRatio'); - return fetchJson(apiUrl(`/v1/tokens/wrap-ratio/${wrappedTokenAddress}`)); + return fetchJson( + apiUrl(`/v2/tokens/wrap-ratio/${wrappedTokenAddress}`, { chainId }) + ); } /** @@ -533,11 +558,13 @@ export async function apiGetWrapRatio(wrappedTokenAddress: string): Promise { assertBrowser('apiGetWrapRatioHistory'); return fetchJson( - apiUrl(`/v1/tokens/wrap-ratio/${wrappedTokenAddress}/history`, { + apiUrl(`/v2/tokens/wrap-ratio/${wrappedTokenAddress}/history`, { + chainId, page: options?.page, pageSize: options?.pageSize }) @@ -549,7 +576,7 @@ export async function apiGetWrapRatioHistory( */ export async function apiGetSwapQuote(request: ApiSwapQuoteRequest): Promise { assertBrowser('apiGetSwapQuote'); - return fetchJson(apiUrl('/v1/swap/quote'), { + return fetchJson(apiUrl('/v2/swap/quote'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) @@ -578,7 +605,7 @@ export async function apiGetSwapCalldata( request: ApiSwapCalldataRequest ): Promise { assertBrowser('apiGetSwapCalldata'); - return fetchJson(apiUrl('/v1/swap/calldata'), { + return fetchJson(apiUrl('/v2/swap/calldata'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) @@ -605,10 +632,12 @@ export async function apiGetSwapCalldataV2( */ export async function apiGetTradesByAddress( address: string, + chainId: number, options?: { page?: number; pageSize?: number; startTime?: number; endTime?: number } ): Promise { assertBrowser('apiGetTradesByAddress'); - const url = apiUrl(`/v1/trades/${address}`, { + const url = apiUrl(`/v2/trades/${address}`, { + chainId, page: options?.page, pageSize: options?.pageSize, startTime: options?.startTime, @@ -622,10 +651,12 @@ export async function apiGetTradesByAddress( */ export async function apiGetTakerTrades( address: string, + chainId: number, options?: { page?: number; pageSize?: number } ): Promise { assertBrowser('apiGetTakerTrades'); - const url = apiUrl(`/v1/trades/taker/${address}`, { + const url = apiUrl(`/v2/trades/taker/${address}`, { + chainId, page: options?.page, pageSize: options?.pageSize }); @@ -633,9 +664,14 @@ export async function apiGetTakerTrades( } /** Fetch the indexed trade totals for one confirmed transaction. */ -export async function apiGetTradesByTx(txHash: string): Promise { +export async function apiGetTradesByTx( + txHash: string, + chainId: number +): Promise { assertBrowser('apiGetTradesByTx'); - return fetchJson(apiUrl(`/v1/trades/tx/${txHash}`), { retries: 0 }); + return fetchJson(apiUrl(`/v2/trades/tx/${txHash}`, { chainId }), { + retries: 0 + }); } /** @@ -643,10 +679,12 @@ export async function apiGetTradesByTx(txHash: string): Promise { assertBrowser('apiGetOrdersByOwner'); - const url = apiUrl(`/v1/orders/owner/${ownerAddress}`, { + const url = apiUrl(`/v2/orders/owner/${ownerAddress}`, { + chainId, page: options?.page, pageSize: options?.pageSize, state: options?.state @@ -675,7 +713,7 @@ export async function apiQueryTrades( signal?: AbortSignal ): Promise { assertBrowser('apiQueryTrades'); - return fetchJson(apiUrl('/v1/trades/query'), { + return fetchJson(apiUrl('/v2/trades/query'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), @@ -687,8 +725,11 @@ export async function apiQueryTrades( * Fetch trades for multiple orders in a single query request. * Used to compute filled amounts for a user's deployed orders. */ -export async function apiGetTradesBatch(orderHashes: string[]): Promise { - return apiQueryTrades({ orderHashes }); +export async function apiGetTradesBatch( + orderHashes: string[], + chainId: number +): Promise { + return apiQueryTrades({ orderHashes, chainId }); } /** @@ -696,6 +737,7 @@ export async function apiGetTradesBatch(orderHashes: string[]): Promise { assertBrowser('apiGetTradesByToken'); const params = new URLSearchParams({ + chainId: String(chainId), page: String(page), pageSize: String(pageSize) }); if (startTime !== undefined) params.set('startTime', String(startTime)); if (endTime !== undefined) params.set('endTime', String(endTime)); return fetchJson( - `/api/st0x/v1/trades/token/${tokenAddress}?${params}` + `/api/st0x/v2/trades/token/${tokenAddress}?${params}` ); } diff --git a/src/lib/clients/raindexSettings.ts b/src/lib/clients/raindexSettings.ts index d17b2888..09220beb 100644 --- a/src/lib/clients/raindexSettings.ts +++ b/src/lib/clients/raindexSettings.ts @@ -21,7 +21,7 @@ function resolveRegistryManifestUrl(): string { return new URL(REGISTRY_MANIFEST_URL, `${appOrigin()}/`).toString(); } -function settingsUrlFromManifest(manifest: string, manifestUrl: string): string { +export function settingsUrlFromManifest(manifest: string, manifestUrl: string): string { const settingsEntry = manifest .split(/\r?\n/) .map((line) => line.trim()) diff --git a/src/lib/components/DepositModal.svelte b/src/lib/components/DepositModal.svelte index 45c28e31..7918cb84 100644 --- a/src/lib/components/DepositModal.svelte +++ b/src/lib/components/DepositModal.svelte @@ -53,11 +53,13 @@ } } - // Per UI-SPEC §DepositModal: payment token displayed in body copy. - // Network's default payment token on Base is USDC. - $: paymentToken = 'USDC'; - $: networkName = $currentNetwork?.displayName ?? 'Base'; - $: basescanUrl = $walletAddress ? `https://basescan.org/address/${$walletAddress}` : ''; + // Per UI-SPEC §DepositModal: registry-backed network and payment token in body copy. + $: paymentToken = $currentNetwork?.defaultPaymentToken?.symbol ?? 'payment token'; + $: networkName = $currentNetwork?.displayName ?? 'the selected network'; + $: explorerUrl = + $walletAddress && $currentNetwork + ? `${$currentNetwork.blockExplorer}/address/${$walletAddress}` + : ''; @@ -109,7 +111,7 @@ - +
- + diff --git a/src/lib/components/LowFundsBanner.svelte b/src/lib/components/LowFundsBanner.svelte index dab28e76..08c68624 100644 --- a/src/lib/components/LowFundsBanner.svelte +++ b/src/lib/components/LowFundsBanner.svelte @@ -11,12 +11,13 @@ let dismissed = false; $: apiTokensQuery = createApiTokensQuery($currentNetwork?.chainId); $: paymentTokens = ($apiTokensQuery.data ?? []).filter((token) => token.category === 'CRYPTO'); + $: primaryPaymentToken = $currentNetwork?.defaultPaymentToken; // Share the same query key as dashboard to avoid duplicate RPC calls // Uses same settings as dashboard - invalidation happens after transactions $: usdcBalanceQuery = createQuery({ queryKey: [ - 'usdcWalletBalance', + 'paymentTokenWalletBalance', $walletAddress, $currentNetwork?.chainId, paymentTokens.map((token) => token.address).join(',') @@ -30,14 +31,15 @@ ), staleTime: 30_000, // Consider data fresh for 30 seconds queryFn: async () => { - if (paymentTokens.length === 0 || !$walletAddress) return []; + if (paymentTokens.length === 0 || !$walletAddress || !$currentNetwork) return []; // Build multicall contracts array const contracts = paymentTokens.map((token) => ({ abi: erc20Abi, address: token.address as `0x${string}`, functionName: 'balanceOf' as const, - args: [$walletAddress as `0x${string}`] + args: [$walletAddress as `0x${string}`], + chainId: $currentNetwork.chainId })); try { @@ -60,15 +62,17 @@ }) .filter((b): b is NonNullable => b !== null); } catch (e) { - console.error('Multicall failed for USDC balance:', e); + console.error('Multicall failed for payment-token balances:', e); return []; } } }); - // Check if USDC balance is 0 - $: usdcBalance = $usdcBalanceQuery.data?.find((t) => t.symbol === 'USDC')?.walletBalance ?? 0n; - $: hasNoFunds = usdcBalance === 0n && $usdcBalanceQuery.isSuccess; + $: primaryPaymentBalance = + $usdcBalanceQuery.data?.find( + (token) => token.address.toLowerCase() === primaryPaymentToken?.address.toLowerCase() + )?.walletBalance ?? 0n; + $: hasNoFunds = primaryPaymentBalance === 0n && $usdcBalanceQuery.isSuccess; $: showBanner = $isAuthenticated && hasNoFunds && !dismissed; function handleDeposit() { @@ -91,7 +95,7 @@ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> - No USDC found in your wallet. + No {primaryPaymentToken?.symbol ?? 'payment tokens'} found in your wallet.