tapimo 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +13987 -5755
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +910 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/early-access.d.ts +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +643 -118
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +45 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +3733 -2428
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +193 -2
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3575 -1358
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +230 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +963 -342
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +108 -57
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/exchanges.d.ts +952 -201
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/funding.d.ts +8 -2
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +32 -6
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +166 -173
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +14 -12
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +82 -17
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +70 -13
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +459 -479
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8606 -1681
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +23 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +5 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +88 -48
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +10 -2
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +218 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +139 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +121 -121
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +3 -0
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +197 -168
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +2 -0
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +4 -0
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +49 -49
- package/dist/apps/management/routes/orgs.d.ts +91 -91
- package/dist/apps/management/routes/projects.d.ts +115 -115
- package/dist/apps/management/routes/usage.d.ts +35 -32
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- package/dist/apps/mcp.d.ts +9 -2
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +9 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +7 -2
- package/dist/cli/Cli.js.map +1 -1
- package/dist/db/Db.d.ts +4 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +83 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +41 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +45 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +12 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +23 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +43 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +60 -45
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +69 -15
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +74 -25
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +19 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +9 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +2 -2
- package/src/App.ts +9 -1
- package/src/Client.test-d.ts +638 -6
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +175 -0
- package/src/admin/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/early-access.tsx +157 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +48 -33
- package/src/apps/data/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
- package/src/apps/data/FundingQuoteProviders.ts +257 -4
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +173 -3
- package/src/apps/data/routes/activities.ts +403 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +144 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +540 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- package/src/apps/data/routes/funding.test.ts +84 -0
- package/src/apps/data/routes/funding.ts +38 -6
- package/src/apps/data/routes/receipts.test.ts +125 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +27 -12
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +146 -4
- package/src/apps/data/routes/transfers.ts +91 -13
- package/src/apps/data/routes/valuation.test.ts +422 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +87 -0
- package/src/apps/management/routes/api-keys.ts +4 -3
- package/src/apps/management/routes/billing.test.ts +112 -1
- package/src/apps/management/routes/billing.ts +7 -2
- package/src/apps/management/routes/faucet.test.ts +79 -0
- package/src/apps/management/routes/faucet.ts +185 -0
- package/src/apps/management/routes/invitations.test.ts +172 -0
- package/src/apps/management/routes/invitations.ts +3 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +140 -88
- package/src/apps/management/routes/me.ts +6 -0
- package/src/apps/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -0
- package/src/apps/mcp.test.ts +13 -0
- package/src/apps/mcp.ts +18 -5
- package/src/cli/Cli.test.ts +36 -0
- package/src/cli/Cli.ts +7 -2
- package/src/db/Db.test.ts +19 -0
- package/src/db/Db.ts +95 -0
- package/src/db/Schema.ts +53 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/invitations.ts +26 -1
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- package/src/internal/Auth.ts +73 -45
- package/src/internal/FundingQuotes.test.ts +143 -1
- package/src/internal/FundingQuotes.ts +95 -28
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +50 -0
- package/src/internal/Log.ts +24 -1
- package/src/internal/MetricSink.test.ts +160 -0
- package/src/internal/MetricSink.ts +10 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono, type Context } from 'hono'
|
|
2
|
-
import { AbiEvent, type Hex } from 'ox'
|
|
2
|
+
import { AbiError, AbiEvent, type Hex } from 'ox'
|
|
3
3
|
import {
|
|
4
4
|
type Address,
|
|
5
5
|
BaseError,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
decodeFunctionData,
|
|
8
8
|
parseAbi,
|
|
9
9
|
} from 'viem'
|
|
10
|
-
import { Actions, Addresses, Tick } from 'viem/tempo'
|
|
10
|
+
import { Abis, Actions, Addresses, Tick } from 'viem/tempo'
|
|
11
11
|
import * as z from 'zod/mini'
|
|
12
12
|
|
|
13
13
|
import type * as App from '../../../App.js'
|
|
@@ -22,12 +22,16 @@ import * as Timing from '../../../internal/Timing.js'
|
|
|
22
22
|
import * as Ttl from '../../../internal/Ttl.js'
|
|
23
23
|
import * as Value from '../../../internal/Value.js'
|
|
24
24
|
import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
|
|
25
|
+
import * as FxOracle from '../FxOracle.js'
|
|
25
26
|
import * as Tokens from './tokens.js'
|
|
27
|
+
import * as Valuation from './valuation.js'
|
|
26
28
|
|
|
27
29
|
// Stablecoin DEX precompile. Pair lifecycle is announced by `PairCreated` from
|
|
28
30
|
// this address; the indexer pre-decodes those into the `dex_pairs` table.
|
|
29
31
|
const stablecoinDex = Addresses.stablecoinDex.toLowerCase()
|
|
30
32
|
|
|
33
|
+
const contractPausedSelector = AbiError.getSelector(AbiError.fromAbi(Abis.tip20, 'ContractPaused'))
|
|
34
|
+
|
|
31
35
|
// `OrderCancelled(uint128 orderId)` — emitted when a maker cancels a resting
|
|
32
36
|
// order. Used by the orders endpoint to exclude cancelled orders from the
|
|
33
37
|
// resting set.
|
|
@@ -71,6 +75,18 @@ const swapAbi = parseAbi([
|
|
|
71
75
|
// pathUSD on-chain). Uses a fresh `Schema.tokenAddress(...)` so the example
|
|
72
76
|
// reaches the pipe input that path-param OpenAPI generation reads from.
|
|
73
77
|
const exampleBase = '0x20c000000000000000000000b9537d11c60e8b50' as const
|
|
78
|
+
const exampleSourceToken = '0x20c0000000000000000000008f5425160ebe5525' as const
|
|
79
|
+
const exampleApprovalCall = Actions.token.approve.call({
|
|
80
|
+
amount: 1_000_000n,
|
|
81
|
+
spender: Addresses.stablecoinDex,
|
|
82
|
+
token: exampleSourceToken,
|
|
83
|
+
})
|
|
84
|
+
const exampleSwapCall = Actions.dex.sell.call({
|
|
85
|
+
amountIn: 1_000_000n,
|
|
86
|
+
minAmountOut: 995_000n,
|
|
87
|
+
tokenIn: exampleSourceToken,
|
|
88
|
+
tokenOut: exampleBase,
|
|
89
|
+
})
|
|
74
90
|
|
|
75
91
|
/**
|
|
76
92
|
* Tick spacing enforced by the stablecoin DEX precompile: orders must be
|
|
@@ -105,6 +121,14 @@ const maxDepthLevels = 200
|
|
|
105
121
|
*/
|
|
106
122
|
const maxBuckets = 500
|
|
107
123
|
|
|
124
|
+
/** Largest amount accepted by the DEX precompile's uint128 swap arguments. */
|
|
125
|
+
const maxSwapAmount = 2n ** 128n - 1n
|
|
126
|
+
|
|
127
|
+
/** Basis-point denominator used for caller-selected swap slippage. */
|
|
128
|
+
const slippageDenominator = 10_000n
|
|
129
|
+
|
|
130
|
+
const tokenFields = ['token.logoUri', 'token.verified'] as const
|
|
131
|
+
|
|
108
132
|
/** Converts the OHLC `interval` enum to a millisecond duration. */
|
|
109
133
|
function intervalMs(interval: '1m' | '5m' | '15m' | '1h' | '4h' | '1d'): number {
|
|
110
134
|
switch (interval) {
|
|
@@ -211,6 +235,195 @@ export namespace schema {
|
|
|
211
235
|
'One side of a trading pair (with metadata when requested via `include=tokens`).',
|
|
212
236
|
)
|
|
213
237
|
|
|
238
|
+
/** A token amount carrying its nominal value in the requested denomination. */
|
|
239
|
+
const ValuedAmount = z.extend(Schema.TokenAmount, {
|
|
240
|
+
valuation: z
|
|
241
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
242
|
+
.check(
|
|
243
|
+
z.describe(
|
|
244
|
+
'The amount’s nominal value in the requested `denomination`. `null` when the token is ' +
|
|
245
|
+
'unverified, its display currency has no rate, or rates were unavailable.',
|
|
246
|
+
),
|
|
247
|
+
),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
/** Schemas for creating an executable native DEX exchange quote. */
|
|
251
|
+
export namespace createQuote {
|
|
252
|
+
const DestinationTokenAddress = Schema.tokenAddress(exampleBase)
|
|
253
|
+
const SourceTokenAddress = Schema.tokenAddress(exampleSourceToken)
|
|
254
|
+
|
|
255
|
+
/** Positive token amount accepted by the DEX's uint128 arguments. */
|
|
256
|
+
export const Amount = Schema.DecimalString.check(
|
|
257
|
+
z.refine(
|
|
258
|
+
(value) => {
|
|
259
|
+
const amount = BigInt(value)
|
|
260
|
+
return amount > 0n && amount <= maxSwapAmount
|
|
261
|
+
},
|
|
262
|
+
{ error: 'Amount must be between 1 and the maximum uint128 value.' },
|
|
263
|
+
),
|
|
264
|
+
z.describe(
|
|
265
|
+
'Positive token amount in the token’s smallest unit, encoded as a decimal string.',
|
|
266
|
+
),
|
|
267
|
+
z.meta({ examples: ['1000000'] }),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
/** Optional token fields that require sources beyond RPC metadata. */
|
|
271
|
+
export const Include = z
|
|
272
|
+
.enum(tokenFields)
|
|
273
|
+
.check(z.describe('Additional token fields to include in the quote response.'))
|
|
274
|
+
|
|
275
|
+
/** Parses comma-separated optional token fields. */
|
|
276
|
+
export const includeQuery = Schema.includeQuery(
|
|
277
|
+
Include,
|
|
278
|
+
'Comma-separated token fields to include, such as `token.logoUri,token.verified`.',
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
/** Query parameters selecting the Tempo chain and optional token fields. */
|
|
282
|
+
export const Query = z
|
|
283
|
+
.strictObject({
|
|
284
|
+
chainId: Schema.ChainIdQuery,
|
|
285
|
+
denomination: Schema.Denomination,
|
|
286
|
+
include: includeQuery,
|
|
287
|
+
})
|
|
288
|
+
.check(z.describe('Query parameters for creating a native DEX exchange quote.'))
|
|
289
|
+
|
|
290
|
+
/** Quote request with one pinned side and caller-selected slippage. */
|
|
291
|
+
export const Request = z
|
|
292
|
+
.strictObject({
|
|
293
|
+
amount: Amount.check(
|
|
294
|
+
z.describe(
|
|
295
|
+
'Pinned amount in base units: source amount for `exactSource`, destination amount for `exactDestination`.',
|
|
296
|
+
),
|
|
297
|
+
),
|
|
298
|
+
destinationToken: DestinationTokenAddress.check(
|
|
299
|
+
z.describe('TIP-20 token the swap receives.'),
|
|
300
|
+
),
|
|
301
|
+
mode: z
|
|
302
|
+
.enum(['exactSource', 'exactDestination'])
|
|
303
|
+
.check(
|
|
304
|
+
z.describe('Which side of the swap keeps the requested amount exact.'),
|
|
305
|
+
z.meta({ examples: ['exactSource'] }),
|
|
306
|
+
),
|
|
307
|
+
slippageBps: z
|
|
308
|
+
.number()
|
|
309
|
+
.check(
|
|
310
|
+
z.int(),
|
|
311
|
+
z.gte(0),
|
|
312
|
+
z.lt(10_000),
|
|
313
|
+
z.describe('Allowed execution slippage in basis points, where 100 is 1%.'),
|
|
314
|
+
z.meta({ examples: [50] }),
|
|
315
|
+
),
|
|
316
|
+
sourceToken: SourceTokenAddress.check(z.describe('TIP-20 token the swap spends.')),
|
|
317
|
+
})
|
|
318
|
+
.check(
|
|
319
|
+
z.refine((request) => request.sourceToken !== request.destinationToken, {
|
|
320
|
+
error: 'Source and destination tokens must differ.',
|
|
321
|
+
}),
|
|
322
|
+
z.describe('Request for an executable quote on Tempo’s native stablecoin DEX.'),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
/** One call in the unsigned Tempo transaction plan. */
|
|
326
|
+
export const Call = z
|
|
327
|
+
.object({
|
|
328
|
+
data: Schema.Hex.check(z.describe('ABI-encoded call data.')),
|
|
329
|
+
to: Schema.Address.check(z.describe('Contract or precompile that receives the call.')),
|
|
330
|
+
value: Schema.Quantity.check(
|
|
331
|
+
z.describe('Native token value sent with the call; native DEX swap calls use `0x0`.'),
|
|
332
|
+
z.meta({ examples: ['0x0'] }),
|
|
333
|
+
),
|
|
334
|
+
})
|
|
335
|
+
.check(z.describe('One unsigned call in the Tempo transaction plan.'))
|
|
336
|
+
|
|
337
|
+
const QuoteToken = Schema.describe(
|
|
338
|
+
z.partial(z.omit(Tokens.schema.Token, { id: true }), {
|
|
339
|
+
logoUri: true,
|
|
340
|
+
verified: true,
|
|
341
|
+
}),
|
|
342
|
+
'A token referenced by an exchange quote, with RPC metadata and optional enrichments.',
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
const DestinationToken = z.extend(QuoteToken, {
|
|
346
|
+
address: DestinationTokenAddress.check(z.describe('TIP-20 token the swap receives.')),
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
const SourceToken = z.extend(QuoteToken, {
|
|
350
|
+
address: SourceTokenAddress.check(z.describe('TIP-20 token the swap spends.')),
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const Transaction = z
|
|
354
|
+
.object({
|
|
355
|
+
calls: z.array(Call).check(
|
|
356
|
+
z.length(2),
|
|
357
|
+
z.describe('Approval followed by the native DEX swap call.'),
|
|
358
|
+
z.meta({
|
|
359
|
+
examples: [
|
|
360
|
+
[
|
|
361
|
+
{
|
|
362
|
+
data: exampleApprovalCall.data,
|
|
363
|
+
to: exampleApprovalCall.to,
|
|
364
|
+
value: '0x0',
|
|
365
|
+
},
|
|
366
|
+
{ data: exampleSwapCall.data, to: exampleSwapCall.to, value: '0x0' },
|
|
367
|
+
],
|
|
368
|
+
],
|
|
369
|
+
}),
|
|
370
|
+
),
|
|
371
|
+
chainId: Schema.ChainId.check(z.describe('Tempo chain where these calls execute.')),
|
|
372
|
+
})
|
|
373
|
+
.check(z.describe('Unsigned Tempo transaction plan.'))
|
|
374
|
+
|
|
375
|
+
/** Response-level valuation rate provenance. */
|
|
376
|
+
export const Meta = z
|
|
377
|
+
.object({
|
|
378
|
+
pricing: z
|
|
379
|
+
.optional(Valuation.schema.Pricing)
|
|
380
|
+
.check(
|
|
381
|
+
z.describe(
|
|
382
|
+
'Rate provenance for amount valuations. Present when conversion rates were consulted.',
|
|
383
|
+
),
|
|
384
|
+
),
|
|
385
|
+
})
|
|
386
|
+
.check(z.describe('Response-level resources attached to this quote.'))
|
|
387
|
+
|
|
388
|
+
/** Native DEX quote plus the approval and swap calls needed to execute it. */
|
|
389
|
+
export const Response = z
|
|
390
|
+
.discriminatedUnion('mode', [
|
|
391
|
+
z.object({
|
|
392
|
+
destinationAmount: ValuedAmount.check(z.describe('Quoted destination amount.')),
|
|
393
|
+
destinationAmountMin: ValuedAmount.check(
|
|
394
|
+
z.describe('Minimum destination amount after slippage.'),
|
|
395
|
+
),
|
|
396
|
+
destinationToken: DestinationToken,
|
|
397
|
+
meta: z.optional(Meta).check(z.describe('Valuation rate provenance for this quote.')),
|
|
398
|
+
mode: z
|
|
399
|
+
.literal('exactSource')
|
|
400
|
+
.check(
|
|
401
|
+
z.describe('The source amount is fixed and the destination has a minimum.'),
|
|
402
|
+
z.meta({ examples: ['exactSource'] }),
|
|
403
|
+
),
|
|
404
|
+
sourceAmount: ValuedAmount.check(z.describe('Exact source amount.')),
|
|
405
|
+
sourceToken: SourceToken.check(z.describe('Token spent by the swap.')),
|
|
406
|
+
transaction: Transaction,
|
|
407
|
+
}),
|
|
408
|
+
z.object({
|
|
409
|
+
destinationAmount: ValuedAmount.check(z.describe('Exact destination amount.')),
|
|
410
|
+
destinationToken: DestinationToken.check(z.describe('Token received by the swap.')),
|
|
411
|
+
meta: z.optional(Meta).check(z.describe('Valuation rate provenance for this quote.')),
|
|
412
|
+
mode: z
|
|
413
|
+
.literal('exactDestination')
|
|
414
|
+
.check(
|
|
415
|
+
z.describe('The destination amount is fixed and the source has a maximum.'),
|
|
416
|
+
z.meta({ examples: ['exactDestination'] }),
|
|
417
|
+
),
|
|
418
|
+
sourceAmount: ValuedAmount.check(z.describe('Quoted source amount.')),
|
|
419
|
+
sourceAmountMax: ValuedAmount.check(z.describe('Maximum source amount after slippage.')),
|
|
420
|
+
sourceToken: SourceToken,
|
|
421
|
+
transaction: Transaction,
|
|
422
|
+
}),
|
|
423
|
+
])
|
|
424
|
+
.check(z.describe('Executable native DEX quote with slippage-protected calls.'))
|
|
425
|
+
}
|
|
426
|
+
|
|
214
427
|
/** Schemas for the getPairs operation. */
|
|
215
428
|
export namespace getPairs {
|
|
216
429
|
/**
|
|
@@ -368,23 +581,15 @@ export namespace schema {
|
|
|
368
581
|
|
|
369
582
|
/** Schemas for the getSwaps operation (the swap feed, optionally pair-scoped). */
|
|
370
583
|
export namespace getSwaps {
|
|
371
|
-
/**
|
|
372
|
-
* Optional related resource that callers opt into via `include`. Currently
|
|
373
|
-
* only `tokens` (embeds metadata for both pair tokens on each row's
|
|
374
|
-
* `sourceToken`/`destinationToken`).
|
|
375
|
-
*/
|
|
584
|
+
/** Optional token fields that require sources beyond RPC metadata. */
|
|
376
585
|
export const Include = z
|
|
377
|
-
.enum(
|
|
378
|
-
.check(z.describe('
|
|
586
|
+
.enum(tokenFields)
|
|
587
|
+
.check(z.describe('Additional token fields to include in swap responses.'))
|
|
379
588
|
|
|
380
|
-
/**
|
|
381
|
-
* Parses a comma-separated `include` query value into a list of optional
|
|
382
|
-
* resources to embed. Extra lookups (e.g. token metadata) only run when
|
|
383
|
-
* explicitly requested, keeping the base swap feed fast.
|
|
384
|
-
*/
|
|
589
|
+
/** Parses comma-separated optional token fields. */
|
|
385
590
|
export const includeQuery = Schema.includeQuery(
|
|
386
591
|
Include,
|
|
387
|
-
'Comma-separated
|
|
592
|
+
'Comma-separated token fields to include, such as `token.logoUri,token.verified`.',
|
|
388
593
|
)
|
|
389
594
|
|
|
390
595
|
/** Query parameters for swap feed requests. */
|
|
@@ -394,6 +599,7 @@ export namespace schema {
|
|
|
394
599
|
'blockNumber.to': Schema.blockNumberBound('swaps', 'to'),
|
|
395
600
|
chainId: Schema.ChainIdQuery,
|
|
396
601
|
cursor: Schema.Cursor,
|
|
602
|
+
denomination: Schema.Denomination,
|
|
397
603
|
include: includeQuery,
|
|
398
604
|
limit: Schema.Limit,
|
|
399
605
|
maker: z
|
|
@@ -428,30 +634,19 @@ export namespace schema {
|
|
|
428
634
|
),
|
|
429
635
|
)
|
|
430
636
|
|
|
431
|
-
/**
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
export const SwapSide = z
|
|
437
|
-
.extend(PairToken, {
|
|
438
|
-
amount: Schema.DecimalString.check(
|
|
439
|
-
z.describe(
|
|
440
|
-
'Amount moved on this side, returned as a decimal integer string in the token’s smallest units.',
|
|
441
|
-
),
|
|
442
|
-
z.meta({ examples: ['1000000'] }),
|
|
443
|
-
),
|
|
444
|
-
})
|
|
445
|
-
.check(z.describe('One side of a swap: the token and amount that moved.'))
|
|
637
|
+
/** One swap-side token with RPC metadata and optional curated fields. */
|
|
638
|
+
export const SwapSide = Schema.describe(
|
|
639
|
+
Tokens.schema.TokenReference,
|
|
640
|
+
'A token referenced by one side of a swap.',
|
|
641
|
+
)
|
|
446
642
|
|
|
447
643
|
/** A single maker-order fill within a swap, framed from the taker's perspective. */
|
|
448
644
|
export const Fill = z
|
|
449
645
|
.object({
|
|
450
|
-
|
|
451
|
-
z.describe(
|
|
452
|
-
'Token and amount the taker received in this fill, with token metadata when requested via `include=tokens`.',
|
|
453
|
-
),
|
|
646
|
+
destinationAmount: ValuedAmount.check(
|
|
647
|
+
z.describe('Amount the taker received in this fill.'),
|
|
454
648
|
),
|
|
649
|
+
destinationToken: SwapSide.check(z.describe('Token the taker received in this fill.')),
|
|
455
650
|
logIndex: z
|
|
456
651
|
.number()
|
|
457
652
|
.check(
|
|
@@ -479,11 +674,8 @@ export namespace schema {
|
|
|
479
674
|
),
|
|
480
675
|
z.meta({ examples: ['1.00000'] }),
|
|
481
676
|
),
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
'Token and amount the taker sent in this fill, with token metadata when requested via `include=tokens`.',
|
|
485
|
-
),
|
|
486
|
-
),
|
|
677
|
+
sourceAmount: ValuedAmount.check(z.describe('Amount the taker sent in this fill.')),
|
|
678
|
+
sourceToken: SwapSide.check(z.describe('Token the taker sent in this fill.')),
|
|
487
679
|
})
|
|
488
680
|
.check(
|
|
489
681
|
z.describe('One maker-order fill within a swap, described from the taker’s perspective.'),
|
|
@@ -506,11 +698,10 @@ export namespace schema {
|
|
|
506
698
|
z.describe('Block number where the swap was included.'),
|
|
507
699
|
z.meta({ examples: [23456789] }),
|
|
508
700
|
),
|
|
509
|
-
|
|
510
|
-
z.describe(
|
|
511
|
-
'Destination side of the swap: what the taker received, summed across the final hop.',
|
|
512
|
-
),
|
|
701
|
+
destinationAmount: ValuedAmount.check(
|
|
702
|
+
z.describe('Total amount the taker received in this swap.'),
|
|
513
703
|
),
|
|
704
|
+
destinationToken: SwapSide.check(z.describe('Destination token for the final hop.')),
|
|
514
705
|
filledAt: z.iso
|
|
515
706
|
.datetime()
|
|
516
707
|
.check(
|
|
@@ -564,9 +755,8 @@ export namespace schema {
|
|
|
564
755
|
'Token path for the swap, from source through any intermediate tokens to destination. `route.length - 1` is the hop count; two entries means a direct swap.',
|
|
565
756
|
),
|
|
566
757
|
),
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
),
|
|
758
|
+
sourceAmount: ValuedAmount.check(z.describe('Total amount the taker sent in this swap.')),
|
|
759
|
+
sourceToken: SwapSide.check(z.describe('Source token for the first hop.')),
|
|
570
760
|
taker: Schema.Address.check(z.describe('Taker address that initiated the swap.')),
|
|
571
761
|
transactionHash: Schema.Hash.check(
|
|
572
762
|
z.describe('Transaction hash for the transaction containing the swap.'),
|
|
@@ -581,9 +771,26 @@ export namespace schema {
|
|
|
581
771
|
* rows per page; a transaction batching several unrelated swaps by one
|
|
582
772
|
* taker may have its rows split across consecutive pages.
|
|
583
773
|
*/
|
|
774
|
+
/** Page-level resources: valuation rate provenance. */
|
|
775
|
+
export const Meta = z
|
|
776
|
+
.object({
|
|
777
|
+
pricing: z
|
|
778
|
+
.optional(Valuation.schema.Pricing)
|
|
779
|
+
.check(
|
|
780
|
+
z.describe(
|
|
781
|
+
'Rate provenance for amount valuations. Present when conversion rates were ' +
|
|
782
|
+
'consulted; absent when every value was identity-valued or rates were unavailable.',
|
|
783
|
+
),
|
|
784
|
+
),
|
|
785
|
+
})
|
|
786
|
+
.check(z.describe('Page-level resources attached to this response.'))
|
|
787
|
+
|
|
584
788
|
export const Response = z
|
|
585
789
|
.object({
|
|
586
790
|
data: z.array(Swap).check(z.describe('Swaps returned on this page.')),
|
|
791
|
+
meta: z
|
|
792
|
+
.optional(Meta)
|
|
793
|
+
.check(z.describe('Page-level resources, such as valuation rate provenance.')),
|
|
587
794
|
nextCursor: Schema.NextCursor,
|
|
588
795
|
})
|
|
589
796
|
.check(z.describe('A paginated list of swaps on Tempo’s built-in stablecoin exchange.'))
|
|
@@ -1309,8 +1516,238 @@ export namespace schema {
|
|
|
1309
1516
|
* `token_balances_snapshot` row attribute cleanly to one pair. Pairs whose
|
|
1310
1517
|
* DEX-escrow balance is zero are excluded from the liquidity ranking.
|
|
1311
1518
|
*/
|
|
1312
|
-
export function exchanges() {
|
|
1519
|
+
export function exchanges(options: exchanges.Options = {}) {
|
|
1520
|
+
const oracle = options.fx?.oracle ?? FxOracle.ecb()
|
|
1313
1521
|
return new Hono<App.Environment>()
|
|
1522
|
+
.post(
|
|
1523
|
+
'/v1/exchange/quotes',
|
|
1524
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
1525
|
+
OpenApi.validate('query', schema.createQuote.Query, {
|
|
1526
|
+
code: 'query_invalid',
|
|
1527
|
+
message: 'Invalid query parameters',
|
|
1528
|
+
}),
|
|
1529
|
+
OpenApi.validate('json', schema.createQuote.Request, {
|
|
1530
|
+
code: 'body_invalid',
|
|
1531
|
+
message: 'Invalid request body',
|
|
1532
|
+
}),
|
|
1533
|
+
OpenApi.describeRoute({
|
|
1534
|
+
description:
|
|
1535
|
+
'Creates a priced exchange preview on Tempo’s native stablecoin DEX with unsigned approval and swap calls. This endpoint does not submit a transaction.',
|
|
1536
|
+
operationId: 'createExchangeQuote',
|
|
1537
|
+
responses: OpenApi.responses({
|
|
1538
|
+
errors: {
|
|
1539
|
+
400: { codes: ['body_invalid', 'query_invalid', 'quote_amount_out_of_range'] },
|
|
1540
|
+
404: {
|
|
1541
|
+
codes: ['quote_not_available'],
|
|
1542
|
+
description: 'The native DEX cannot price an executable exchange for these terms.',
|
|
1543
|
+
},
|
|
1544
|
+
502: 'The Tempo RPC could not quote the swap or resolve token metadata.',
|
|
1545
|
+
},
|
|
1546
|
+
success: {
|
|
1547
|
+
description: 'A native DEX quote and the unsigned calls needed to execute it.',
|
|
1548
|
+
schema: schema.createQuote.Response,
|
|
1549
|
+
},
|
|
1550
|
+
}),
|
|
1551
|
+
summary: 'Create quote',
|
|
1552
|
+
tags: ['Exchange'],
|
|
1553
|
+
}),
|
|
1554
|
+
async (c) => {
|
|
1555
|
+
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
1556
|
+
if (OpenApi.narrowValidation)
|
|
1557
|
+
return OpenApi.validationError(c, {
|
|
1558
|
+
code: 'query_invalid',
|
|
1559
|
+
message: 'Invalid query parameters',
|
|
1560
|
+
})
|
|
1561
|
+
if (OpenApi.narrowValidation)
|
|
1562
|
+
return OpenApi.validationError(c, {
|
|
1563
|
+
code: 'body_invalid',
|
|
1564
|
+
message: 'Invalid request body',
|
|
1565
|
+
})
|
|
1566
|
+
|
|
1567
|
+
const request = c.req.valid('json')
|
|
1568
|
+
const options = c.req.valid('query')
|
|
1569
|
+
const chainId = options.chainId ?? c.get('chainId')
|
|
1570
|
+
const client = c.get('getClient')(chainId)
|
|
1571
|
+
const amount = BigInt(request.amount)
|
|
1572
|
+
const slippageBps = BigInt(request.slippageBps)
|
|
1573
|
+
|
|
1574
|
+
c.header('Cache-Control', 'no-store')
|
|
1575
|
+
try {
|
|
1576
|
+
// Value quoted amounts via their curated display currency; snapshot
|
|
1577
|
+
// or rate failures degrade the values to `null`, never the quote.
|
|
1578
|
+
const denomination = options.denomination ?? 'USD'
|
|
1579
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
1580
|
+
const rates = await Valuation.ratesFor(c, {
|
|
1581
|
+
currencies: snapshot
|
|
1582
|
+
? [request.destinationToken, request.sourceToken].flatMap((address) => {
|
|
1583
|
+
const held = snapshot.byAddress.get(address.toLowerCase())?.currency
|
|
1584
|
+
return held === undefined ? [] : [held]
|
|
1585
|
+
})
|
|
1586
|
+
: [],
|
|
1587
|
+
denomination,
|
|
1588
|
+
oracle,
|
|
1589
|
+
})
|
|
1590
|
+
const valued = (
|
|
1591
|
+
baseUnits: bigint | string,
|
|
1592
|
+
address: string,
|
|
1593
|
+
token: { currency: string; decimals: number },
|
|
1594
|
+
) => ({
|
|
1595
|
+
...Value.tokenAmount({
|
|
1596
|
+
baseUnits,
|
|
1597
|
+
currency: token.currency,
|
|
1598
|
+
decimals: token.decimals,
|
|
1599
|
+
}),
|
|
1600
|
+
valuation: Valuation.valuationFor({
|
|
1601
|
+
amount: BigInt(baseUnits),
|
|
1602
|
+
denomination,
|
|
1603
|
+
rates,
|
|
1604
|
+
token: snapshot?.byAddress.get(address.toLowerCase()),
|
|
1605
|
+
}),
|
|
1606
|
+
})
|
|
1607
|
+
|
|
1608
|
+
if (request.mode === 'exactSource') {
|
|
1609
|
+
const destinationAmount = await Actions.dex.getSellQuote(client, {
|
|
1610
|
+
amountIn: amount,
|
|
1611
|
+
tokenIn: request.sourceToken,
|
|
1612
|
+
tokenOut: request.destinationToken,
|
|
1613
|
+
})
|
|
1614
|
+
if (destinationAmount === 0n) return quoteNotAvailable(c)
|
|
1615
|
+
|
|
1616
|
+
const minimumAmount =
|
|
1617
|
+
(destinationAmount * (slippageDenominator - slippageBps) + slippageDenominator - 1n) /
|
|
1618
|
+
slippageDenominator
|
|
1619
|
+
const approve = Actions.token.approve.call(client, {
|
|
1620
|
+
amount,
|
|
1621
|
+
spender: Addresses.stablecoinDex,
|
|
1622
|
+
token: request.sourceToken,
|
|
1623
|
+
})
|
|
1624
|
+
const swap = Actions.dex.sell.call({
|
|
1625
|
+
amountIn: amount,
|
|
1626
|
+
minAmountOut: minimumAmount,
|
|
1627
|
+
tokenIn: request.sourceToken,
|
|
1628
|
+
tokenOut: request.destinationToken,
|
|
1629
|
+
})
|
|
1630
|
+
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
1631
|
+
addresses: [request.destinationToken, request.sourceToken],
|
|
1632
|
+
chainId,
|
|
1633
|
+
include: options.include,
|
|
1634
|
+
})
|
|
1635
|
+
const destinationToken = tokensByAddress.get(request.destinationToken)
|
|
1636
|
+
const sourceToken = tokensByAddress.get(request.sourceToken)
|
|
1637
|
+
if (!destinationToken || !sourceToken)
|
|
1638
|
+
throw new Error('Unable to resolve exchange quote token metadata')
|
|
1639
|
+
|
|
1640
|
+
return c.json(
|
|
1641
|
+
Response.validated(schema.createQuote.Response, {
|
|
1642
|
+
destinationAmount: valued(
|
|
1643
|
+
destinationAmount,
|
|
1644
|
+
request.destinationToken,
|
|
1645
|
+
destinationToken,
|
|
1646
|
+
),
|
|
1647
|
+
destinationAmountMin: valued(
|
|
1648
|
+
minimumAmount,
|
|
1649
|
+
request.destinationToken,
|
|
1650
|
+
destinationToken,
|
|
1651
|
+
),
|
|
1652
|
+
destinationToken: {
|
|
1653
|
+
...destinationToken,
|
|
1654
|
+
address: request.destinationToken,
|
|
1655
|
+
},
|
|
1656
|
+
...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
|
|
1657
|
+
mode: request.mode,
|
|
1658
|
+
sourceAmount: valued(request.amount, request.sourceToken, sourceToken),
|
|
1659
|
+
sourceToken: {
|
|
1660
|
+
...sourceToken,
|
|
1661
|
+
address: request.sourceToken,
|
|
1662
|
+
},
|
|
1663
|
+
transaction: {
|
|
1664
|
+
calls: [
|
|
1665
|
+
{ data: approve.data, to: approve.to, value: '0x0' },
|
|
1666
|
+
{ data: swap.data, to: swap.to, value: '0x0' },
|
|
1667
|
+
],
|
|
1668
|
+
chainId,
|
|
1669
|
+
},
|
|
1670
|
+
}),
|
|
1671
|
+
200,
|
|
1672
|
+
)
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const sourceAmount = await Actions.dex.getBuyQuote(client, {
|
|
1676
|
+
amountOut: amount,
|
|
1677
|
+
tokenIn: request.sourceToken,
|
|
1678
|
+
tokenOut: request.destinationToken,
|
|
1679
|
+
})
|
|
1680
|
+
if (sourceAmount === 0n) return quoteNotAvailable(c)
|
|
1681
|
+
|
|
1682
|
+
const maximumAmount =
|
|
1683
|
+
(sourceAmount * (slippageDenominator + slippageBps)) / slippageDenominator
|
|
1684
|
+
if (maximumAmount > maxSwapAmount)
|
|
1685
|
+
return Response.error(c, {
|
|
1686
|
+
code: 'quote_amount_out_of_range',
|
|
1687
|
+
message: 'Slippage-adjusted source amount exceeds the native DEX limit',
|
|
1688
|
+
status: 400,
|
|
1689
|
+
})
|
|
1690
|
+
|
|
1691
|
+
const approve = Actions.token.approve.call(client, {
|
|
1692
|
+
amount: maximumAmount,
|
|
1693
|
+
spender: Addresses.stablecoinDex,
|
|
1694
|
+
token: request.sourceToken,
|
|
1695
|
+
})
|
|
1696
|
+
const swap = Actions.dex.buy.call({
|
|
1697
|
+
amountOut: amount,
|
|
1698
|
+
maxAmountIn: maximumAmount,
|
|
1699
|
+
tokenIn: request.sourceToken,
|
|
1700
|
+
tokenOut: request.destinationToken,
|
|
1701
|
+
})
|
|
1702
|
+
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
1703
|
+
addresses: [request.destinationToken, request.sourceToken],
|
|
1704
|
+
chainId,
|
|
1705
|
+
include: options.include,
|
|
1706
|
+
})
|
|
1707
|
+
const destinationToken = tokensByAddress.get(request.destinationToken)
|
|
1708
|
+
const sourceToken = tokensByAddress.get(request.sourceToken)
|
|
1709
|
+
if (!destinationToken || !sourceToken)
|
|
1710
|
+
throw new Error('Unable to resolve exchange quote token metadata')
|
|
1711
|
+
|
|
1712
|
+
return c.json(
|
|
1713
|
+
Response.validated(schema.createQuote.Response, {
|
|
1714
|
+
destinationAmount: valued(request.amount, request.destinationToken, destinationToken),
|
|
1715
|
+
destinationToken: {
|
|
1716
|
+
...destinationToken,
|
|
1717
|
+
address: request.destinationToken,
|
|
1718
|
+
},
|
|
1719
|
+
...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
|
|
1720
|
+
mode: request.mode,
|
|
1721
|
+
sourceAmount: valued(sourceAmount, request.sourceToken, sourceToken),
|
|
1722
|
+
sourceAmountMax: valued(maximumAmount, request.sourceToken, sourceToken),
|
|
1723
|
+
sourceToken: {
|
|
1724
|
+
...sourceToken,
|
|
1725
|
+
address: request.sourceToken,
|
|
1726
|
+
},
|
|
1727
|
+
transaction: {
|
|
1728
|
+
calls: [
|
|
1729
|
+
{ data: approve.data, to: approve.to, value: '0x0' },
|
|
1730
|
+
{ data: swap.data, to: swap.to, value: '0x0' },
|
|
1731
|
+
],
|
|
1732
|
+
chainId,
|
|
1733
|
+
},
|
|
1734
|
+
}),
|
|
1735
|
+
200,
|
|
1736
|
+
)
|
|
1737
|
+
} catch (cause) {
|
|
1738
|
+
if (request.mode === 'exactSource' && isQuoteAmountOutOfRange(cause))
|
|
1739
|
+
return Response.error(c, {
|
|
1740
|
+
code: 'quote_amount_out_of_range',
|
|
1741
|
+
message: 'Quote amount exceeds the native DEX limit',
|
|
1742
|
+
status: 400,
|
|
1743
|
+
})
|
|
1744
|
+
if (isQuoteUnavailable(cause)) return quoteNotAvailable(c)
|
|
1745
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
1746
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
1747
|
+
return Response.upstream(c, cause)
|
|
1748
|
+
}
|
|
1749
|
+
},
|
|
1750
|
+
)
|
|
1314
1751
|
.get(
|
|
1315
1752
|
'/v1/exchange/swaps',
|
|
1316
1753
|
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
@@ -1324,7 +1761,7 @@ export function exchanges() {
|
|
|
1324
1761
|
operationId: 'getSwaps',
|
|
1325
1762
|
responses: OpenApi.responses({
|
|
1326
1763
|
errors: {
|
|
1327
|
-
502: 'The indexer could not serve the exchange data right now.',
|
|
1764
|
+
502: 'The indexer or Tempo RPC could not serve the exchange data right now.',
|
|
1328
1765
|
},
|
|
1329
1766
|
success: {
|
|
1330
1767
|
description: 'A page of swaps from Tempo’s built-in stablecoin exchange.',
|
|
@@ -1335,11 +1772,10 @@ export function exchanges() {
|
|
|
1335
1772
|
tags: ['Exchange'],
|
|
1336
1773
|
}),
|
|
1337
1774
|
Cache.response<App.Environment>({
|
|
1338
|
-
// Exchanges are a newest-first feed; refresh quickly. The `
|
|
1339
|
-
// segment isolates
|
|
1340
|
-
// per-fill pages (cursor pages cache as `immutable`).
|
|
1775
|
+
// Exchanges are a newest-first feed; refresh quickly. The `v3` cache
|
|
1776
|
+
// segment isolates this response shape from earlier swap pages.
|
|
1341
1777
|
cacheControl: Cache.policies.feed,
|
|
1342
|
-
name: 'tempo-api:exchange:
|
|
1778
|
+
name: 'tempo-api:exchange:v3:swaps',
|
|
1343
1779
|
key: (c) => Cache.urlKey(c, schema.getSwaps.Query),
|
|
1344
1780
|
}),
|
|
1345
1781
|
async (c) => {
|
|
@@ -1353,10 +1789,8 @@ export function exchanges() {
|
|
|
1353
1789
|
const options = c.req.valid('query')
|
|
1354
1790
|
const chainId = options.chainId ?? c.get('chainId')
|
|
1355
1791
|
|
|
1356
|
-
// Cursor pages
|
|
1357
|
-
|
|
1358
|
-
// default.
|
|
1359
|
-
if (options.cursor !== undefined) Cache.setPolicy(c, Cache.policies.immutable)
|
|
1792
|
+
// Cursor pages carry current valuations and curated verification data.
|
|
1793
|
+
if (options.cursor !== undefined) Cache.setPolicy(c, Cache.policies.metadata)
|
|
1360
1794
|
|
|
1361
1795
|
try {
|
|
1362
1796
|
// The pair index (base address → pair) resolves each fill's book
|
|
@@ -1380,43 +1814,94 @@ export function exchanges() {
|
|
|
1380
1814
|
transactionHash: options.transactionHash,
|
|
1381
1815
|
})
|
|
1382
1816
|
|
|
1383
|
-
//
|
|
1384
|
-
//
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1817
|
+
// `route` spans every swap and fill side, so one lookup resolves each
|
|
1818
|
+
// token once for required RPC metadata and requested curated fields.
|
|
1819
|
+
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
1820
|
+
addresses: page.data.flatMap((swap) => swap.route),
|
|
1821
|
+
chainId,
|
|
1822
|
+
include: options.include,
|
|
1823
|
+
})
|
|
1824
|
+
// Value amounts via their curated display currency; snapshot or rate
|
|
1825
|
+
// failures degrade the values to `null`, never the feed.
|
|
1826
|
+
const denomination = options.denomination ?? 'USD'
|
|
1827
|
+
const snapshot =
|
|
1828
|
+
page.data.length > 0
|
|
1829
|
+
? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
1830
|
+
: undefined
|
|
1831
|
+
const rates = await Valuation.ratesFor(c, {
|
|
1832
|
+
currencies: snapshot
|
|
1833
|
+
? page.data.flatMap((swap) =>
|
|
1834
|
+
swap.route.flatMap((address) => {
|
|
1835
|
+
const held = snapshot.byAddress.get(address.toLowerCase())?.currency
|
|
1836
|
+
return held === undefined ? [] : [held]
|
|
1837
|
+
}),
|
|
1838
|
+
)
|
|
1839
|
+
: [],
|
|
1840
|
+
denomination,
|
|
1841
|
+
oracle,
|
|
1842
|
+
})
|
|
1843
|
+
const enrichSide = (side: getSwaps.Side) => {
|
|
1844
|
+
const token = tokensByAddress.get(side.address)
|
|
1845
|
+
if (!token) throw new Error(`Token metadata unavailable for ${side.address}`)
|
|
1846
|
+
return {
|
|
1847
|
+
amount: {
|
|
1848
|
+
...Value.tokenAmount({
|
|
1849
|
+
baseUnits: side.amount,
|
|
1850
|
+
currency: token.currency,
|
|
1851
|
+
decimals: token.decimals,
|
|
1852
|
+
}),
|
|
1853
|
+
valuation: Valuation.valuationFor({
|
|
1854
|
+
amount: BigInt(side.amount),
|
|
1855
|
+
denomination,
|
|
1856
|
+
rates,
|
|
1857
|
+
token: snapshot?.byAddress.get(side.address.toLowerCase()),
|
|
1858
|
+
}),
|
|
1859
|
+
},
|
|
1860
|
+
token: {
|
|
1861
|
+
address: side.address,
|
|
1862
|
+
currency: token.currency,
|
|
1863
|
+
decimals: token.decimals,
|
|
1864
|
+
...(token.logoUri !== undefined ? { logoUri: token.logoUri } : {}),
|
|
1865
|
+
name: token.name,
|
|
1866
|
+
symbol: token.symbol,
|
|
1867
|
+
...(token.verified !== undefined ? { verified: token.verified } : {}),
|
|
1868
|
+
},
|
|
1399
1869
|
}
|
|
1400
|
-
data = data.map((swap) => ({
|
|
1401
|
-
...swap,
|
|
1402
|
-
destinationToken: spread(swap.destinationToken),
|
|
1403
|
-
fills: swap.fills.map((fill) => ({
|
|
1404
|
-
...fill,
|
|
1405
|
-
destinationToken: spread(fill.destinationToken),
|
|
1406
|
-
sourceToken: spread(fill.sourceToken),
|
|
1407
|
-
})),
|
|
1408
|
-
sourceToken: spread(swap.sourceToken),
|
|
1409
|
-
}))
|
|
1410
1870
|
}
|
|
1871
|
+
const data = page.data.map((swap) => {
|
|
1872
|
+
const destination = enrichSide(swap.destinationToken)
|
|
1873
|
+
const source = enrichSide(swap.sourceToken)
|
|
1874
|
+
return {
|
|
1875
|
+
...swap,
|
|
1876
|
+
destinationAmount: destination.amount,
|
|
1877
|
+
destinationToken: destination.token,
|
|
1878
|
+
fills: swap.fills.map((fill) => {
|
|
1879
|
+
const destination_fill = enrichSide(fill.destinationToken)
|
|
1880
|
+
const source_fill = enrichSide(fill.sourceToken)
|
|
1881
|
+
return {
|
|
1882
|
+
...fill,
|
|
1883
|
+
destinationAmount: destination_fill.amount,
|
|
1884
|
+
destinationToken: destination_fill.token,
|
|
1885
|
+
sourceAmount: source_fill.amount,
|
|
1886
|
+
sourceToken: source_fill.token,
|
|
1887
|
+
}
|
|
1888
|
+
}),
|
|
1889
|
+
sourceAmount: source.amount,
|
|
1890
|
+
sourceToken: source.token,
|
|
1891
|
+
}
|
|
1892
|
+
})
|
|
1411
1893
|
|
|
1412
1894
|
return c.json(
|
|
1413
1895
|
Response.validated(schema.getSwaps.Response, {
|
|
1414
1896
|
data,
|
|
1897
|
+
...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
|
|
1415
1898
|
nextCursor: page.nextCursor,
|
|
1416
1899
|
}),
|
|
1417
1900
|
200,
|
|
1418
1901
|
)
|
|
1419
1902
|
} catch (cause) {
|
|
1903
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
1904
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
1420
1905
|
return Response.upstream(c, cause)
|
|
1421
1906
|
}
|
|
1422
1907
|
},
|
|
@@ -1500,6 +1985,7 @@ export function exchanges() {
|
|
|
1500
1985
|
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
1501
1986
|
addresses: data.flatMap((pair) => [pair.base.address, pair.quote.address]),
|
|
1502
1987
|
chainId,
|
|
1988
|
+
include: tokenFields,
|
|
1503
1989
|
})
|
|
1504
1990
|
data = data.map((pair) => {
|
|
1505
1991
|
const base = tokensByAddress.get(pair.base.address)
|
|
@@ -1596,6 +2082,7 @@ export function exchanges() {
|
|
|
1596
2082
|
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
1597
2083
|
addresses: [target.base, target.quote],
|
|
1598
2084
|
chainId,
|
|
2085
|
+
include: tokenFields,
|
|
1599
2086
|
})
|
|
1600
2087
|
const baseMeta = tokensByAddress.get(target.base)
|
|
1601
2088
|
const quoteMeta = tokensByAddress.get(target.quote)
|
|
@@ -1704,6 +2191,7 @@ export function exchanges() {
|
|
|
1704
2191
|
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
1705
2192
|
addresses: data.flatMap((o) => [o.pair.base.address, o.pair.quote.address]),
|
|
1706
2193
|
chainId,
|
|
2194
|
+
include: tokenFields,
|
|
1707
2195
|
})
|
|
1708
2196
|
data = data.map((o) => {
|
|
1709
2197
|
const base = tokensByAddress.get(o.pair.base.address)
|
|
@@ -2027,6 +2515,7 @@ export function exchanges() {
|
|
|
2027
2515
|
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
2028
2516
|
addresses: [target.base, target.quote],
|
|
2029
2517
|
chainId,
|
|
2518
|
+
include: tokenFields,
|
|
2030
2519
|
})
|
|
2031
2520
|
const baseMeta = tokensByAddress.get(target.base)
|
|
2032
2521
|
const quoteMeta = tokensByAddress.get(target.quote)
|
|
@@ -2128,6 +2617,7 @@ export function exchanges() {
|
|
|
2128
2617
|
const tokensByAddress = await resolveTokensByAddress(c, {
|
|
2129
2618
|
addresses: [target.base, target.quote],
|
|
2130
2619
|
chainId,
|
|
2620
|
+
include: tokenFields,
|
|
2131
2621
|
})
|
|
2132
2622
|
const baseMeta = tokensByAddress.get(target.base)
|
|
2133
2623
|
const quoteMeta = tokensByAddress.get(target.quote)
|
|
@@ -2146,6 +2636,14 @@ export function exchanges() {
|
|
|
2146
2636
|
)
|
|
2147
2637
|
}
|
|
2148
2638
|
|
|
2639
|
+
export declare namespace exchanges {
|
|
2640
|
+
/** Options for the exchange handlers. */
|
|
2641
|
+
type Options = {
|
|
2642
|
+
/** FX configuration backing amount valuation. */
|
|
2643
|
+
fx?: Valuation.addresses.Fx | undefined
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2149
2647
|
// Hard cap on the curated pair index. Pair creation is rare (one event per
|
|
2150
2648
|
// pair lifetime), so a few thousand bounds today's set comfortably; raise this
|
|
2151
2649
|
// if the index ever exhausts.
|
|
@@ -2169,34 +2667,62 @@ function pairNotFoundError(
|
|
|
2169
2667
|
return Response.error(c, { code: 'pair_not_found', message, status: 404 })
|
|
2170
2668
|
}
|
|
2171
2669
|
|
|
2172
|
-
/**
|
|
2173
|
-
* Batch-resolves token metadata for a set of addresses, sharing one
|
|
2174
|
-
* verified-token snapshot and one created-at lookup across the whole set.
|
|
2175
|
-
* Best-effort: addresses whose metadata cannot be resolved are absent from
|
|
2176
|
-
* the returned map, so callers keep their bare `{ address }` shapes. Shared
|
|
2177
|
-
* by every `include=tokens` embed in this module.
|
|
2178
|
-
*/
|
|
2670
|
+
/** Resolves RPC token metadata and explicitly requested enrichments. Unresolvable addresses are omitted. */
|
|
2179
2671
|
async function resolveTokensByAddress(
|
|
2180
2672
|
c: Context<App.Environment>,
|
|
2181
2673
|
options: {
|
|
2182
2674
|
addresses: readonly z.output<typeof Schema.Address>[]
|
|
2183
2675
|
chainId: z.output<typeof Schema.ChainId>
|
|
2676
|
+
include: readonly (typeof tokenFields)[number][]
|
|
2184
2677
|
},
|
|
2185
2678
|
) {
|
|
2186
|
-
const { addresses, chainId } = options
|
|
2187
|
-
|
|
2679
|
+
const { addresses, chainId, include } = options
|
|
2680
|
+
const includeLogoUri = include.includes('token.logoUri')
|
|
2681
|
+
const includeVerified = include.includes('token.verified')
|
|
2682
|
+
type Token = Omit<z.output<typeof Tokens.schema.Token>, 'verified'> & {
|
|
2683
|
+
verified?: boolean | undefined
|
|
2684
|
+
}
|
|
2188
2685
|
const tokensByAddress = new Map<string, Token>()
|
|
2189
2686
|
const uniqueTokens = [...new Set(addresses)]
|
|
2190
2687
|
if (uniqueTokens.length === 0) return tokensByAddress
|
|
2688
|
+
const snapshot =
|
|
2689
|
+
includeLogoUri || includeVerified
|
|
2690
|
+
? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
2691
|
+
: undefined
|
|
2191
2692
|
|
|
2192
|
-
const snapshot = await VerifiedTokens.snapshot(c, chainId)
|
|
2193
2693
|
const resolved = await Timing.time(c, 'tokens', () =>
|
|
2194
2694
|
Promise.all(
|
|
2195
|
-
uniqueTokens.map((token) =>
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2695
|
+
uniqueTokens.map(async (token) => {
|
|
2696
|
+
try {
|
|
2697
|
+
const [logoUri, metadata] = await Promise.all([
|
|
2698
|
+
includeLogoUri
|
|
2699
|
+
? Timing.time(c, 'token_logo', () =>
|
|
2700
|
+
Tokens.getTokenLogo(c, { address: token, chainId }),
|
|
2701
|
+
).catch(() => undefined)
|
|
2702
|
+
: undefined,
|
|
2703
|
+
Timing.time(c, 'token_metadata', () =>
|
|
2704
|
+
Tokens.getTokenMetadata(c, { address: token, chainId }),
|
|
2705
|
+
),
|
|
2706
|
+
])
|
|
2707
|
+
return [
|
|
2708
|
+
token,
|
|
2709
|
+
{
|
|
2710
|
+
address: token,
|
|
2711
|
+
currency: metadata.currency,
|
|
2712
|
+
decimals: metadata.decimals,
|
|
2713
|
+
id: token,
|
|
2714
|
+
logoUri: includeLogoUri
|
|
2715
|
+
? (logoUri ?? snapshot?.byAddress.get(token)?.logoUri ?? metadata.logoUri)
|
|
2716
|
+
: undefined,
|
|
2717
|
+
name: metadata.name,
|
|
2718
|
+
symbol: metadata.symbol,
|
|
2719
|
+
verified: includeVerified ? snapshot?.byAddress.has(token) : undefined,
|
|
2720
|
+
},
|
|
2721
|
+
] as const
|
|
2722
|
+
} catch {
|
|
2723
|
+
return [token, undefined] as const
|
|
2724
|
+
}
|
|
2725
|
+
}),
|
|
2200
2726
|
),
|
|
2201
2727
|
)
|
|
2202
2728
|
for (const [token, metadata] of resolved) if (metadata) tokensByAddress.set(token, metadata)
|
|
@@ -2646,7 +3172,7 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
2646
3172
|
})
|
|
2647
3173
|
}
|
|
2648
3174
|
|
|
2649
|
-
type Candidate = { group: parseGroupRow.Group; swap:
|
|
3175
|
+
type Candidate = { group: parseGroupRow.Group; swap: getSwaps.Swap }
|
|
2650
3176
|
|
|
2651
3177
|
// A row's keyset position is `(block, group first_idx, its first fill
|
|
2652
3178
|
// logIndex)`; rows at or before the active cursor's row anchor were already
|
|
@@ -2949,6 +3475,36 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
|
|
|
2949
3475
|
}
|
|
2950
3476
|
|
|
2951
3477
|
declare namespace getSwaps {
|
|
3478
|
+
/** Swap side before RPC metadata and structured amount enrichment. */
|
|
3479
|
+
type Side = {
|
|
3480
|
+
address: z.output<typeof Schema.TokenAddress>
|
|
3481
|
+
amount: z.output<typeof Schema.DecimalString>
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
/** Maker fill before RPC metadata and structured amount enrichment. */
|
|
3485
|
+
type Fill = Omit<
|
|
3486
|
+
z.output<typeof schema.getSwaps.Fill>,
|
|
3487
|
+
'destinationAmount' | 'destinationToken' | 'sourceAmount' | 'sourceToken'
|
|
3488
|
+
> & {
|
|
3489
|
+
/** Destination token and raw base-unit amount. */
|
|
3490
|
+
destinationToken: Side
|
|
3491
|
+
/** Source token and raw base-unit amount. */
|
|
3492
|
+
sourceToken: Side
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
/** Logical swap before RPC metadata and structured amount enrichment. */
|
|
3496
|
+
type Swap = Omit<
|
|
3497
|
+
z.output<typeof schema.getSwaps.Swap>,
|
|
3498
|
+
'destinationAmount' | 'destinationToken' | 'fills' | 'sourceAmount' | 'sourceToken'
|
|
3499
|
+
> & {
|
|
3500
|
+
/** Destination token and raw base-unit amount. */
|
|
3501
|
+
destinationToken: Side
|
|
3502
|
+
/** Maker fills that compose this swap. */
|
|
3503
|
+
fills: Fill[]
|
|
3504
|
+
/** Source token and raw base-unit amount. */
|
|
3505
|
+
sourceToken: Side
|
|
3506
|
+
}
|
|
3507
|
+
|
|
2952
3508
|
/** Options for the swap page query. */
|
|
2953
3509
|
type Options = {
|
|
2954
3510
|
chainId: z.output<typeof Schema.ChainId>
|
|
@@ -3317,7 +3873,7 @@ declare namespace resolveSwapModes {
|
|
|
3317
3873
|
type Options = {
|
|
3318
3874
|
chainId: z.output<typeof Schema.ChainId>
|
|
3319
3875
|
/** The final page's swap rows; `mode` is mutated in place. */
|
|
3320
|
-
swaps:
|
|
3876
|
+
swaps: getSwaps.Swap[]
|
|
3321
3877
|
}
|
|
3322
3878
|
}
|
|
3323
3879
|
|
|
@@ -3392,13 +3948,13 @@ declare namespace resolveBookQuotes {
|
|
|
3392
3948
|
function buildSwapRows(
|
|
3393
3949
|
group: parseGroupRow.Group,
|
|
3394
3950
|
options: buildSwapRows.Options,
|
|
3395
|
-
):
|
|
3951
|
+
): getSwaps.Swap[] {
|
|
3396
3952
|
const { order, quoteByBase, states } = options
|
|
3397
3953
|
const priceScale = BigInt(Tick.priceScale)
|
|
3398
3954
|
|
|
3399
3955
|
type Oriented = {
|
|
3400
3956
|
destination: { address: z.output<typeof Schema.TokenAddress>; amount: bigint }
|
|
3401
|
-
fill:
|
|
3957
|
+
fill: getSwaps.Fill
|
|
3402
3958
|
source: { address: z.output<typeof Schema.TokenAddress>; amount: bigint }
|
|
3403
3959
|
}
|
|
3404
3960
|
const oriented: Oriented[] = []
|
|
@@ -3533,8 +4089,8 @@ declare namespace buildSwapRows {
|
|
|
3533
4089
|
destination: z.output<typeof Schema.TokenAddress>
|
|
3534
4090
|
/** Total received on this leg, in base units. */
|
|
3535
4091
|
destinationAmount: bigint
|
|
3536
|
-
/** The leg's fills
|
|
3537
|
-
fills:
|
|
4092
|
+
/** The leg's fills before metadata enrichment. */
|
|
4093
|
+
fills: getSwaps.Fill[]
|
|
3538
4094
|
/** Token the taker sent on this leg. */
|
|
3539
4095
|
source: z.output<typeof Schema.TokenAddress>
|
|
3540
4096
|
/** Total sent on this leg, in base units. */
|
|
@@ -4595,6 +5151,37 @@ function isOrderDoesNotExist(error: unknown): boolean {
|
|
|
4595
5151
|
)
|
|
4596
5152
|
}
|
|
4597
5153
|
|
|
5154
|
+
/** Returns the native DEX 404 used when no executable quote is available. */
|
|
5155
|
+
function quoteNotAvailable(c: Context<App.Environment>) {
|
|
5156
|
+
return Response.error(c, {
|
|
5157
|
+
code: 'quote_not_available',
|
|
5158
|
+
message: 'No executable quote is available on Tempo’s native DEX',
|
|
5159
|
+
status: 404,
|
|
5160
|
+
})
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
/** Distinguishes native DEX route failures from RPC transport failures. */
|
|
5164
|
+
function isQuoteUnavailable(error: unknown): boolean {
|
|
5165
|
+
if (!(error instanceof BaseError)) return false
|
|
5166
|
+
const revert = error.walk((e) => e instanceof ContractFunctionRevertedError)
|
|
5167
|
+
if (!(revert instanceof ContractFunctionRevertedError)) return false
|
|
5168
|
+
return (
|
|
5169
|
+
revert.raw === contractPausedSelector ||
|
|
5170
|
+
['InsufficientLiquidity', 'InvalidBaseToken', 'InvalidToken', 'PairDoesNotExist'].includes(
|
|
5171
|
+
revert.data?.errorName ?? '',
|
|
5172
|
+
)
|
|
5173
|
+
)
|
|
5174
|
+
}
|
|
5175
|
+
|
|
5176
|
+
/** Detects uint128 arithmetic overflow from exact-source DEX quote calls. */
|
|
5177
|
+
function isQuoteAmountOutOfRange(error: unknown): boolean {
|
|
5178
|
+
if (!(error instanceof BaseError)) return false
|
|
5179
|
+
const revert = error.walk((e) => e instanceof ContractFunctionRevertedError)
|
|
5180
|
+
if (!(revert instanceof ContractFunctionRevertedError)) return false
|
|
5181
|
+
const data = revert.data
|
|
5182
|
+
return data?.errorName === 'Panic' && data.args?.[0] === 0x11n
|
|
5183
|
+
}
|
|
5184
|
+
|
|
4598
5185
|
/**
|
|
4599
5186
|
* Resolves a single page of `OrderFilled` events for one order. Pagination is
|
|
4600
5187
|
* keyset on `(block_num, log_idx)`, which is unique and append-only for the
|