tapimo 0.7.2 → 0.9.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/README.md +10 -7
- package/dist/ApiKey.d.ts +3 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +10 -0
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +7 -3
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +9 -2
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +20117 -19183
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +61 -4
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +84 -74
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +14 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +6 -2
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +2 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +11 -11
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +67 -67
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +2 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +3 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/apps/data/App.d.ts +11696 -13937
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +4 -8
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +271 -271
- package/dist/apps/data/routes/addresses.d.ts +398 -167
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +278 -8
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +114 -2
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +123 -123
- package/dist/apps/data/routes/coingecko.d.ts +170 -170
- package/dist/apps/data/routes/earn.d.ts +498 -6
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +876 -4
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +10 -10
- package/dist/apps/data/routes/fee-amm.d.ts +2 -2
- package/dist/apps/data/routes/indexer.d.ts +6 -6
- package/dist/apps/data/routes/indexer.js +4 -1
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +235 -235
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/rpc.js +4 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/tokens.d.ts +449 -449
- package/dist/apps/data/routes/transactions.d.ts +2 -2
- package/dist/apps/data/routes/transfers.d.ts +81 -81
- package/dist/apps/data/routes/valuation.d.ts +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +9 -9
- package/dist/apps/data/routes/zones.d.ts +14 -14
- package/dist/apps/funding/Access.d.ts +16 -0
- package/dist/apps/funding/Access.d.ts.map +1 -0
- package/dist/apps/funding/Access.js +22 -0
- package/dist/apps/funding/Access.js.map +1 -0
- package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1640 -3217
- package/dist/apps/funding/App.d.ts.map +1 -0
- package/dist/apps/funding/App.js +61 -0
- package/dist/apps/funding/App.js.map +1 -0
- package/dist/apps/funding/Inventory.d.ts +5 -0
- package/dist/apps/funding/Inventory.d.ts.map +1 -0
- package/dist/apps/funding/Inventory.js +25 -0
- package/dist/apps/funding/Inventory.js.map +1 -0
- package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
- package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
- package/dist/apps/funding/ProviderAttempts.js +24 -0
- package/dist/apps/funding/ProviderAttempts.js.map +1 -0
- package/dist/apps/funding/index.d.ts +2 -0
- package/dist/apps/funding/index.d.ts.map +1 -0
- package/dist/apps/funding/index.js +2 -0
- package/dist/apps/funding/index.js.map +1 -0
- package/dist/apps/funding/routes/chains.d.ts +169 -0
- package/dist/apps/funding/routes/chains.d.ts.map +1 -0
- package/dist/apps/funding/routes/chains.js +80 -0
- package/dist/apps/funding/routes/chains.js.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts +1997 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.js +778 -0
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
- package/dist/apps/funding/routes/providers.d.ts +91 -0
- package/dist/apps/funding/routes/providers.d.ts.map +1 -0
- package/dist/apps/funding/routes/providers.js +56 -0
- package/dist/apps/funding/routes/providers.js.map +1 -0
- package/dist/apps/funding/routes/quotes.d.ts +426 -0
- package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
- package/dist/apps/funding/routes/quotes.js +188 -0
- package/dist/apps/funding/routes/quotes.js.map +1 -0
- package/dist/apps/funding/routes/transfers.d.ts +1999 -0
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
- package/dist/apps/funding/routes/transfers.js +614 -0
- package/dist/apps/funding/routes/transfers.js.map +1 -0
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +430 -165
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +298 -21
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +75 -1
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +6 -6
- package/dist/apps/management/routes/faucet.d.ts +35 -35
- package/dist/apps/management/routes/invitations.d.ts +122 -122
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/members.d.ts +69 -69
- package/dist/apps/management/routes/orgs.d.ts +99 -99
- package/dist/apps/management/routes/projects.d.ts +25 -25
- package/dist/apps/management/routes/scopes.d.ts +1 -1
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +77 -77
- package/dist/apps/management/routes/webhooks.d.ts +128 -128
- package/dist/apps/mcp.d.ts +2 -3
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +16 -14
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/metadata.d.ts +4 -0
- package/dist/apps/metadata.d.ts.map +1 -1
- package/dist/apps/metadata.js +10 -0
- package/dist/apps/metadata.js.map +1 -1
- package/dist/apps/mpp/App.d.ts +44 -44
- package/dist/cloudflare.d.ts +50 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +178 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +45 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +20 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +12 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
- package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
- package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
- package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
- package/dist/db/tables/fundingDeposits.d.ts +74 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +126 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +15 -11
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +52 -17
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.js +1 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +2 -2
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +10 -4
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/IpAllowlist.d.ts +15 -0
- package/dist/internal/IpAllowlist.d.ts.map +1 -0
- package/dist/internal/IpAllowlist.js +27 -0
- package/dist/internal/IpAllowlist.js.map +1 -0
- package/dist/internal/OpenApi.d.ts +1 -1
- package/dist/internal/OpenApi.js +1 -1
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Tidx.d.ts +6 -0
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +13 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +8 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -5
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/funding/Deposit.d.ts +42 -0
- package/dist/internal/funding/Deposit.d.ts.map +1 -1
- package/dist/internal/funding/Deposit.js +37 -5
- package/dist/internal/funding/Deposit.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts +1 -5
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +4 -3
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +102 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +5 -0
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts +16 -2
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +348 -54
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts +108 -0
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
- package/dist/internal/funding/SourceObservation.js +143 -0
- package/dist/internal/funding/SourceObservation.js.map +1 -0
- package/dist/internal/funding/Subsidy.d.ts +2 -2
- package/dist/internal/funding/Subsidy.d.ts.map +1 -1
- package/dist/internal/funding/Subsidy.js +1 -1
- package/dist/internal/funding/Subsidy.js.map +1 -1
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +43 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +438 -27
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/ApiKey.test.ts +35 -0
- package/src/ApiKey.ts +27 -0
- package/src/ApiKeys.test.ts +29 -0
- package/src/ApiKeys.ts +11 -3
- package/src/App.test.ts +55 -5
- package/src/App.ts +79 -5
- package/src/Client.test-d.ts +203 -28
- package/src/admin/apps/api-keys.test.ts +25 -1
- package/src/admin/apps/api-keys.ts +6 -2
- package/src/admin/ui.generated.ts +67 -67
- package/src/analytics/Analytics.ts +4 -0
- package/src/apps/composition.test.ts +130 -3
- package/src/apps/data/App.test.ts +0 -12
- package/src/apps/data/App.ts +4 -8
- package/src/apps/data/routes/balances.test.ts +42 -0
- package/src/apps/data/routes/balances.ts +218 -85
- package/src/apps/data/routes/earn.test.ts +363 -0
- package/src/apps/data/routes/earn.ts +1141 -3
- package/src/apps/data/routes/indexer.ts +2 -1
- package/src/apps/data/routes/rpc.ts +2 -0
- package/src/apps/data/routes/tokens.test.ts +6 -8
- package/src/apps/funding/Access.ts +26 -0
- package/src/apps/funding/App.test.ts +78 -0
- package/src/apps/funding/App.ts +78 -0
- package/src/apps/funding/Inventory.ts +30 -0
- package/src/apps/funding/ProviderAttempts.ts +26 -0
- package/src/apps/funding/index.ts +1 -0
- package/src/apps/funding/routes/chains.test.ts +78 -0
- package/src/apps/funding/routes/chains.ts +101 -0
- package/src/apps/funding/routes/deposit-addresses.test.ts +1649 -0
- package/src/apps/funding/routes/deposit-addresses.ts +932 -0
- package/src/apps/funding/routes/providers.test.ts +165 -0
- package/src/apps/funding/routes/providers.ts +73 -0
- package/src/apps/funding/routes/quotes.test.ts +511 -0
- package/src/apps/funding/routes/quotes.ts +205 -0
- package/src/apps/funding/routes/transfers.test.ts +1236 -0
- package/src/apps/funding/routes/transfers.ts +704 -0
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +98 -0
- package/src/apps/management/routes/api-keys.ts +96 -1
- package/src/apps/mcp.test.ts +17 -2
- package/src/apps/mcp.ts +18 -13
- package/src/apps/metadata.ts +12 -0
- package/src/apps/mpp/internal/Policy.test.ts +7 -1
- package/src/cloudflare.ts +279 -0
- package/src/db/Db.ts +71 -0
- package/src/db/Schema.ts +24 -0
- package/src/db/tables/fundingDepositAddresses.ts +28 -0
- package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
- package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
- package/src/db/tables/fundingDeposits.test.ts +239 -0
- package/src/db/tables/fundingDeposits.ts +214 -1
- package/src/internal/Auth.test.ts +205 -5
- package/src/internal/Auth.ts +63 -19
- package/src/internal/Cache.test.ts +37 -3
- package/src/internal/Cache.ts +1 -1
- package/src/internal/EdgeCache.test.ts +7 -7
- package/src/internal/EdgeCache.ts +13 -4
- package/src/internal/IpAllowlist.test.ts +52 -0
- package/src/internal/IpAllowlist.ts +39 -0
- package/src/internal/Log.test.ts +7 -1
- package/src/internal/OpenApi.ts +1 -1
- package/src/internal/Tidx.test.ts +28 -0
- package/src/internal/Tidx.ts +19 -3
- package/src/internal/Viem.test.ts +28 -0
- package/src/internal/Viem.ts +21 -5
- package/src/internal/funding/Deposit.ts +74 -5
- package/src/internal/funding/Evidence.test.ts +2 -2
- package/src/internal/funding/Evidence.ts +4 -9
- package/src/internal/funding/Provider.ts +115 -0
- package/src/internal/funding/Reconciliation.local.test.ts +7 -0
- package/src/internal/funding/Reconciliation.test.ts +1364 -109
- package/src/internal/funding/Reconciliation.ts +467 -88
- package/src/internal/funding/SourceObservation.test.ts +301 -0
- package/src/internal/funding/SourceObservation.ts +267 -0
- package/src/internal/funding/Subsidy.ts +2 -2
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/relay.test.ts +94 -4
- package/src/internal/funding/providers/relay.ts +533 -27
- package/dist/apps/data/routes/funding.d.ts.map +0 -1
- package/dist/apps/data/routes/funding.js +0 -1557
- package/dist/apps/data/routes/funding.js.map +0 -1
- package/src/apps/data/routes/funding.test.ts +0 -2994
- package/src/apps/data/routes/funding.ts +0 -1821
|
@@ -1,1821 +0,0 @@
|
|
|
1
|
-
import { type Context, Hono } from 'hono'
|
|
2
|
-
import { etag } from 'hono/etag'
|
|
3
|
-
import * as z from 'zod/mini'
|
|
4
|
-
|
|
5
|
-
import type * as App from '../../../App.js'
|
|
6
|
-
import * as Db from '../../../db/Db.js'
|
|
7
|
-
import * as FundingDepositAddresses from '../../../db/tables/fundingDepositAddresses.js'
|
|
8
|
-
import * as FundingDeposits from '../../../db/tables/fundingDeposits.js'
|
|
9
|
-
import * as FundingIdempotency from '../../../db/tables/fundingIdempotency.js'
|
|
10
|
-
import * as FundingTransfers from '../../../db/tables/fundingTransfers.js'
|
|
11
|
-
import * as Auth from '../../../internal/Auth.js'
|
|
12
|
-
import * as Cache from '../../../internal/Cache.js'
|
|
13
|
-
import * as Cursor from '../../../internal/Cursor.js'
|
|
14
|
-
import * as FundingCatalog from '../../../internal/funding/Catalog.js'
|
|
15
|
-
import * as FundingDeposit from '../../../internal/funding/Deposit.js'
|
|
16
|
-
import * as FundingDepositAddress from '../../../internal/funding/DepositAddress.js'
|
|
17
|
-
import * as FundingProvider from '../../../internal/funding/Provider.js'
|
|
18
|
-
import * as FundingTransfer from '../../../internal/funding/Transfer.js'
|
|
19
|
-
import * as Idempotency from '../../../internal/Idempotency.js'
|
|
20
|
-
import type * as Log from '../../../internal/Log.js'
|
|
21
|
-
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
22
|
-
import * as Response from '../../../internal/Response.js'
|
|
23
|
-
import * as Schema from '../../../internal/Schema.js'
|
|
24
|
-
import * as Value from '../../../internal/Value.js'
|
|
25
|
-
|
|
26
|
-
/** Zod schemas owned by funding quote resources. */
|
|
27
|
-
export namespace schema {
|
|
28
|
-
/** Source or destination chain reference. */
|
|
29
|
-
export const Chain = FundingProvider.schema.ChainRef
|
|
30
|
-
|
|
31
|
-
/** Source or destination token reference. */
|
|
32
|
-
export const Token = FundingProvider.schema.TokenRef
|
|
33
|
-
|
|
34
|
-
/** Self-contained source or destination amount in a funding quote. */
|
|
35
|
-
export const TokenAmount = Schema.describe(
|
|
36
|
-
Schema.TokenAmount,
|
|
37
|
-
'A funding amount in base units and human-readable form.',
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
/** Provider summary embedded in a funding quote. */
|
|
41
|
-
export const ProviderRef = FundingProvider.schema.ProviderRef
|
|
42
|
-
|
|
43
|
-
/** Quote quality signals exposed directly instead of implied by list order. */
|
|
44
|
-
export const Quality = FundingProvider.schema.FundingQuoteQuality
|
|
45
|
-
|
|
46
|
-
/** Provider quote freshness data. */
|
|
47
|
-
export const QuoteDetails = FundingProvider.schema.QuoteDetails
|
|
48
|
-
|
|
49
|
-
/** Successful live funding quote. */
|
|
50
|
-
export const FundingQuote = FundingProvider.schema.FundingQuote
|
|
51
|
-
|
|
52
|
-
/** Funding quote provider catalog entry. */
|
|
53
|
-
export const Provider = Schema.describe(ProviderRef, 'One funding quote provider.')
|
|
54
|
-
|
|
55
|
-
/** Chain inventory entry returned by the funding chains endpoint. */
|
|
56
|
-
export const ChainInventory = Schema.describe(
|
|
57
|
-
z.extend(Chain, {
|
|
58
|
-
tokens: z.array(Token).check(z.describe('Stablecoins known on this source chain.')),
|
|
59
|
-
}),
|
|
60
|
-
'One source chain and its known stablecoin inventory.',
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
/** Schemas for the quoteFunding operation. */
|
|
64
|
-
export namespace quoteFunding {
|
|
65
|
-
/** Query parameters for live funding quotes. */
|
|
66
|
-
export const Query = FundingProvider.schema.QuoteRequest
|
|
67
|
-
|
|
68
|
-
/** Non-paginated list of successful funding quotes. */
|
|
69
|
-
export const Response = Schema.describe(
|
|
70
|
-
z.object({
|
|
71
|
-
data: z.array(FundingQuote).check(z.describe('Successful live funding quotes.')),
|
|
72
|
-
}),
|
|
73
|
-
'A non-paginated list of successful live funding quotes.',
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Schemas for the getFundingChains operation. */
|
|
78
|
-
export namespace getFundingChains {
|
|
79
|
-
/** Non-paginated source-chain inventory. */
|
|
80
|
-
export const Response = Schema.describe(
|
|
81
|
-
z.object({
|
|
82
|
-
data: z
|
|
83
|
-
.array(ChainInventory)
|
|
84
|
-
.check(z.describe('Source chains and stablecoins supported by funding quotes.')),
|
|
85
|
-
}),
|
|
86
|
-
'A non-paginated list of source chains and stablecoin inventory.',
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Schemas for the getFundingProviders operation. */
|
|
91
|
-
export namespace getFundingProviders {
|
|
92
|
-
/** Non-paginated funding quote provider catalog. */
|
|
93
|
-
export const Response = Schema.describe(
|
|
94
|
-
z.object({
|
|
95
|
-
data: z.array(Provider).check(z.describe("Providers in Tempo's funding quote catalog.")),
|
|
96
|
-
}),
|
|
97
|
-
'A non-paginated list of funding quote providers.',
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** A durable funding transfer. Reads never include the executable `action`. */
|
|
102
|
-
export const Transfer = FundingTransfer.schema.FundingTransfer
|
|
103
|
-
|
|
104
|
-
/** One reusable funding deposit address. */
|
|
105
|
-
export const DepositAddress = FundingDepositAddress.schema.FundingDepositAddress
|
|
106
|
-
|
|
107
|
-
/** One detected funding deposit. */
|
|
108
|
-
export const Deposit = FundingDeposit.schema.FundingDeposit
|
|
109
|
-
|
|
110
|
-
/** Schemas for the createFundingDepositAddress operation. */
|
|
111
|
-
export namespace createFundingDepositAddress {
|
|
112
|
-
/** Funding deposit address creation request body. */
|
|
113
|
-
export const Body = z
|
|
114
|
-
.strictObject({
|
|
115
|
-
amount: FundingTransfer.schema.CreateRequest.shape.amount,
|
|
116
|
-
destinationToken: z
|
|
117
|
-
.string()
|
|
118
|
-
.check(
|
|
119
|
-
z.minLength(1),
|
|
120
|
-
z.describe('Tempo destination token symbol, contract address, or token key.'),
|
|
121
|
-
z.meta({ examples: ['usdt0'] }),
|
|
122
|
-
),
|
|
123
|
-
recipient: z
|
|
124
|
-
.string()
|
|
125
|
-
.check(
|
|
126
|
-
z.regex(/^0x[0-9a-fA-F]{40}$/),
|
|
127
|
-
z.describe('Tempo account that receives completed deposits.'),
|
|
128
|
-
z.meta({ examples: [`0x${'11'.repeat(20)}`] }),
|
|
129
|
-
),
|
|
130
|
-
refundAddress: z
|
|
131
|
-
.string()
|
|
132
|
-
.check(
|
|
133
|
-
z.minLength(1),
|
|
134
|
-
z.describe('Source-chain account that receives refunds.'),
|
|
135
|
-
z.meta({ examples: ['TJRabPrwbZy45sbavfcjinPJC18kjpRTv8'] }),
|
|
136
|
-
),
|
|
137
|
-
sourceChain: z
|
|
138
|
-
.string()
|
|
139
|
-
.check(
|
|
140
|
-
z.minLength(1),
|
|
141
|
-
z.describe('Source chain CAIP-2 id, slug, or alias.'),
|
|
142
|
-
z.meta({ examples: ['tron', 'tron:0x2b6653dc'] }),
|
|
143
|
-
),
|
|
144
|
-
sourceToken: z
|
|
145
|
-
.string()
|
|
146
|
-
.check(
|
|
147
|
-
z.minLength(1),
|
|
148
|
-
z.describe('Source token symbol, contract address, or token key.'),
|
|
149
|
-
z.meta({ examples: ['usdt'] }),
|
|
150
|
-
),
|
|
151
|
-
subsidize: z
|
|
152
|
-
.optional(z.boolean())
|
|
153
|
-
.check(
|
|
154
|
-
z.describe('Guarantees normalized 1:1 delivery through a Tempo-funded subsidy.'),
|
|
155
|
-
z.meta({ examples: [false] }),
|
|
156
|
-
),
|
|
157
|
-
})
|
|
158
|
-
.check(z.describe('Reusable funding deposit address creation request.'))
|
|
159
|
-
|
|
160
|
-
/** Required headers for funding deposit address creation. */
|
|
161
|
-
export const Headers = z.object({
|
|
162
|
-
'idempotency-key': z
|
|
163
|
-
.string()
|
|
164
|
-
.check(
|
|
165
|
-
z.minLength(1),
|
|
166
|
-
z.describe(
|
|
167
|
-
'Opaque retry key scoped to the API key. Matching requests replay successful responses for 24 hours; changed input conflicts, in-flight requests block, and failed attempts release the key.',
|
|
168
|
-
),
|
|
169
|
-
z.meta({ examples: ['funding_01k1c5j8q8p0be6j5v9m6d1e4r'] }),
|
|
170
|
-
),
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
/** The reusable address, with quoted terms only when the provider address was newly stored. */
|
|
174
|
-
export const Response = z
|
|
175
|
-
.union([
|
|
176
|
-
FundingDepositAddress.schema.CreatedFundingDepositAddress,
|
|
177
|
-
FundingDepositAddress.schema.FundingDepositAddress,
|
|
178
|
-
])
|
|
179
|
-
.check(
|
|
180
|
-
z.describe(
|
|
181
|
-
'A reusable funding deposit address, including terms sampled for a newly stored provider address.',
|
|
182
|
-
),
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Schemas for the getFundingDepositAddress operation. */
|
|
187
|
-
export namespace getFundingDepositAddress {
|
|
188
|
-
/** Path parameters for reading a funding deposit address. */
|
|
189
|
-
export const Params = z
|
|
190
|
-
.object({
|
|
191
|
-
id: z
|
|
192
|
-
.string()
|
|
193
|
-
.check(
|
|
194
|
-
z.regex(/^fda_[A-Za-z0-9_-]+$/),
|
|
195
|
-
z.describe('Funding deposit address ID (`fda_…`).'),
|
|
196
|
-
z.meta({ examples: ['fda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
|
|
197
|
-
),
|
|
198
|
-
})
|
|
199
|
-
.check(z.describe('Path parameters for reading a funding deposit address.'))
|
|
200
|
-
|
|
201
|
-
/** One reusable funding deposit address. */
|
|
202
|
-
export const Response = DepositAddress
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/** Schemas for the listFundingDepositAddressDeposits operation. */
|
|
206
|
-
export namespace listFundingDepositAddressDeposits {
|
|
207
|
-
/** Path parameters for listing one address's deposits. */
|
|
208
|
-
export const Params = getFundingDepositAddress.Params
|
|
209
|
-
|
|
210
|
-
/** Query parameters for listing one address's deposits. */
|
|
211
|
-
export const Query = z
|
|
212
|
-
.object({ cursor: Schema.Cursor, limit: Schema.Limit })
|
|
213
|
-
.check(z.describe('Paging parameters for one funding deposit address.'))
|
|
214
|
-
|
|
215
|
-
/** One page of detected deposits. */
|
|
216
|
-
export const Response = z
|
|
217
|
-
.object({
|
|
218
|
-
data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
|
|
219
|
-
nextCursor: Schema.NextCursor,
|
|
220
|
-
})
|
|
221
|
-
.check(z.describe('A page of deposits detected for one funding deposit address.'))
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** Schemas for the getFundingDeposit operation. */
|
|
225
|
-
export namespace getFundingDeposit {
|
|
226
|
-
/** Path parameters for reading a funding deposit. */
|
|
227
|
-
export const Params = z
|
|
228
|
-
.object({
|
|
229
|
-
id: z
|
|
230
|
-
.string()
|
|
231
|
-
.check(
|
|
232
|
-
z.regex(/^fdp_[A-Za-z0-9_-]+$/),
|
|
233
|
-
z.describe('Funding deposit id (`fdp_…`).'),
|
|
234
|
-
z.meta({ examples: ['fdp_001785792060000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
|
|
235
|
-
),
|
|
236
|
-
})
|
|
237
|
-
.check(z.describe('Path parameters for reading a funding deposit.'))
|
|
238
|
-
|
|
239
|
-
/** One detected funding deposit. */
|
|
240
|
-
export const Response = Deposit
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Schemas for the listFundingTransfers operation. */
|
|
244
|
-
export namespace listFundingTransfers {
|
|
245
|
-
/** Query parameters for listing funding transfers. */
|
|
246
|
-
export const Query = z
|
|
247
|
-
.object({
|
|
248
|
-
cursor: Schema.Cursor,
|
|
249
|
-
include: Schema.totalCountInclude,
|
|
250
|
-
limit: Schema.Limit,
|
|
251
|
-
status: z.optional(FundingTransfer.schema.Status),
|
|
252
|
-
})
|
|
253
|
-
.check(z.describe('Query parameters for listing funding transfers.'))
|
|
254
|
-
|
|
255
|
-
/** Paginated funding transfer list. */
|
|
256
|
-
export const Response = z
|
|
257
|
-
.object({
|
|
258
|
-
data: z.array(Transfer).check(z.describe('Funding transfers on this page, newest first.')),
|
|
259
|
-
meta: z
|
|
260
|
-
.optional(Schema.CountMeta)
|
|
261
|
-
.check(z.describe('Extra response metadata requested with `include`.')),
|
|
262
|
-
nextCursor: Schema.NextCursor,
|
|
263
|
-
})
|
|
264
|
-
.check(z.describe('A page of funding transfers.'))
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Schemas for the createFundingTransfer operation. */
|
|
268
|
-
export namespace createFundingTransfer {
|
|
269
|
-
/** Funding transfer creation request body. */
|
|
270
|
-
export const Body = FundingTransfer.schema.CreateRequest
|
|
271
|
-
|
|
272
|
-
/** Required headers for funding transfer creation. */
|
|
273
|
-
export const Headers = z.object({
|
|
274
|
-
'idempotency-key': z
|
|
275
|
-
.string()
|
|
276
|
-
.check(
|
|
277
|
-
z.minLength(1),
|
|
278
|
-
z.describe(
|
|
279
|
-
'Opaque retry key scoped to the API key. Matching requests replay successful responses for 24 hours; changed input conflicts, in-flight requests block, and failed attempts release the key.',
|
|
280
|
-
),
|
|
281
|
-
z.meta({ examples: ['funding_01k1c5j8q8p0be6j5v9m6d1e4r'] }),
|
|
282
|
-
),
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
/** The created transfer with its one-time executable action. */
|
|
286
|
-
export const Response = FundingTransfer.schema.CreatedFundingTransfer
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const destinationCreateFields = {
|
|
290
|
-
amount: FundingTransfer.schema.CreateRequest.shape.amount,
|
|
291
|
-
mode: FundingTransfer.schema.CreateRequest.shape.mode,
|
|
292
|
-
recipient: FundingTransfer.schema.CreateRequest.shape.recipient,
|
|
293
|
-
sender: FundingTransfer.schema.CreateRequest.shape.sender,
|
|
294
|
-
slippageBps: FundingTransfer.schema.CreateRequest.shape.slippageBps,
|
|
295
|
-
sourceChain: FundingTransfer.schema.CreateRequest.shape.sourceChain,
|
|
296
|
-
sourceToken: FundingTransfer.schema.CreateRequest.shape.sourceToken,
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** Schemas for the createFundingTransferVault operation. */
|
|
300
|
-
export namespace createFundingTransferVault {
|
|
301
|
-
/** Funding transfer creation request for an Earn vault destination. */
|
|
302
|
-
export const Body = z
|
|
303
|
-
.strictObject({
|
|
304
|
-
...destinationCreateFields,
|
|
305
|
-
vaultAddress: z
|
|
306
|
-
.string()
|
|
307
|
-
.check(
|
|
308
|
-
z.regex(/^0x[0-9a-fA-F]{40}$/),
|
|
309
|
-
z.describe('Tempo Earn vault contract address.'),
|
|
310
|
-
z.meta({ examples: [`0x${'f4'.repeat(20)}`] }),
|
|
311
|
-
),
|
|
312
|
-
})
|
|
313
|
-
.check(z.describe('Vault funding transfer creation request.'))
|
|
314
|
-
|
|
315
|
-
/** Required headers for vault funding transfer creation. */
|
|
316
|
-
export const Headers = createFundingTransfer.Headers
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/** Schemas for the createFundingTransferZone operation. */
|
|
320
|
-
export namespace createFundingTransferZone {
|
|
321
|
-
/** Funding transfer creation request for a Zone destination. */
|
|
322
|
-
export const Body = z
|
|
323
|
-
.strictObject({
|
|
324
|
-
amount: destinationCreateFields.amount,
|
|
325
|
-
destinationChain: z
|
|
326
|
-
.string()
|
|
327
|
-
.check(
|
|
328
|
-
z.regex(/^eip155:\d+$/),
|
|
329
|
-
z.describe('Configured Zone CAIP-2 id.'),
|
|
330
|
-
z.meta({ examples: ['eip155:421700001'] }),
|
|
331
|
-
),
|
|
332
|
-
destinationToken: FundingTransfer.schema.CreateRequest.shape.destinationToken,
|
|
333
|
-
mode: destinationCreateFields.mode,
|
|
334
|
-
recipient: destinationCreateFields.recipient,
|
|
335
|
-
recipientFallback: z
|
|
336
|
-
.string()
|
|
337
|
-
.check(
|
|
338
|
-
z.regex(/^0x[0-9a-fA-F]{40}$/),
|
|
339
|
-
z.describe('Public Tempo recipient used if the Zone credit cannot complete.'),
|
|
340
|
-
z.meta({ examples: [`0x${'22'.repeat(20)}`] }),
|
|
341
|
-
),
|
|
342
|
-
sender: destinationCreateFields.sender,
|
|
343
|
-
slippageBps: destinationCreateFields.slippageBps,
|
|
344
|
-
sourceChain: destinationCreateFields.sourceChain,
|
|
345
|
-
sourceToken: destinationCreateFields.sourceToken,
|
|
346
|
-
})
|
|
347
|
-
.check(z.describe('Zone funding transfer creation request.'))
|
|
348
|
-
|
|
349
|
-
/** Required headers for Zone funding transfer creation. */
|
|
350
|
-
export const Headers = createFundingTransfer.Headers
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/** Schemas for the getFundingTransfer operation. */
|
|
354
|
-
export namespace getFundingTransfer {
|
|
355
|
-
/** Path parameters for a funding transfer read. */
|
|
356
|
-
export const Params = z
|
|
357
|
-
.object({
|
|
358
|
-
id: z
|
|
359
|
-
.string()
|
|
360
|
-
.check(
|
|
361
|
-
z.describe('Funding transfer ID (`ftr_…`).'),
|
|
362
|
-
z.meta({ examples: ['ftr_001785729600000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
|
|
363
|
-
),
|
|
364
|
-
})
|
|
365
|
-
.check(z.describe('Path parameters for a funding transfer request.'))
|
|
366
|
-
|
|
367
|
-
/** The funding transfer resource, returned directly. */
|
|
368
|
-
export const Response = Transfer
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const baseChainExample = {
|
|
373
|
-
addressFormat: 'hex',
|
|
374
|
-
id: 'eip155:8453',
|
|
375
|
-
kind: 'evm',
|
|
376
|
-
name: 'Base',
|
|
377
|
-
} as const
|
|
378
|
-
|
|
379
|
-
const baseUsdcExample = {
|
|
380
|
-
address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
381
|
-
currency: 'USD',
|
|
382
|
-
decimals: 6,
|
|
383
|
-
name: 'USD Coin',
|
|
384
|
-
standard: 'ERC-20',
|
|
385
|
-
symbol: 'USDC',
|
|
386
|
-
tokenKey: 'eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
387
|
-
verified: true,
|
|
388
|
-
} as const
|
|
389
|
-
|
|
390
|
-
const fundingQuoteResponseExample = {
|
|
391
|
-
data: [
|
|
392
|
-
{
|
|
393
|
-
destinationAmount: Value.tokenAmount({
|
|
394
|
-
baseUnits: '979295',
|
|
395
|
-
currency: 'USD',
|
|
396
|
-
decimals: 6,
|
|
397
|
-
}),
|
|
398
|
-
destinationAmountMin: Value.tokenAmount({
|
|
399
|
-
baseUnits: '939242',
|
|
400
|
-
currency: 'USD',
|
|
401
|
-
decimals: 6,
|
|
402
|
-
}),
|
|
403
|
-
destinationChain: {
|
|
404
|
-
addressFormat: 'hex',
|
|
405
|
-
id: 'eip155:4217',
|
|
406
|
-
kind: 'evm',
|
|
407
|
-
name: 'Tempo',
|
|
408
|
-
},
|
|
409
|
-
destinationToken: {
|
|
410
|
-
address: '0x20c000000000000000000000b9537d11c60e8b50',
|
|
411
|
-
currency: 'USD',
|
|
412
|
-
decimals: 6,
|
|
413
|
-
name: 'Bridged USDC (Stargate)',
|
|
414
|
-
standard: 'TIP-20',
|
|
415
|
-
symbol: 'USDC.e',
|
|
416
|
-
tokenKey: 'eip155:4217/erc20:0x20c000000000000000000000b9537d11c60e8b50',
|
|
417
|
-
verified: true,
|
|
418
|
-
},
|
|
419
|
-
id: 'base-usdc-tempo-usdce-relay',
|
|
420
|
-
provider: { id: 'relay', name: 'Relay' },
|
|
421
|
-
quality: {
|
|
422
|
-
estimatedSeconds: 1,
|
|
423
|
-
liquiditySource: 'providerQuote',
|
|
424
|
-
sourceDetail: 'relay:quote-v2',
|
|
425
|
-
tier: 'liquid',
|
|
426
|
-
},
|
|
427
|
-
quote: {
|
|
428
|
-
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
429
|
-
},
|
|
430
|
-
sourceAmount: Value.tokenAmount({
|
|
431
|
-
baseUnits: '1000000',
|
|
432
|
-
currency: 'USD',
|
|
433
|
-
decimals: 6,
|
|
434
|
-
}),
|
|
435
|
-
sourceChain: baseChainExample,
|
|
436
|
-
sourceToken: baseUsdcExample,
|
|
437
|
-
},
|
|
438
|
-
],
|
|
439
|
-
} satisfies z.output<typeof schema.quoteFunding.Response>
|
|
440
|
-
|
|
441
|
-
const fundingChainsResponseExample = {
|
|
442
|
-
data: [{ ...baseChainExample, tokens: [baseUsdcExample] }],
|
|
443
|
-
} satisfies z.output<typeof schema.getFundingChains.Response>
|
|
444
|
-
|
|
445
|
-
const providersResponseExample = {
|
|
446
|
-
data: [{ id: 'relay', name: 'Relay' }],
|
|
447
|
-
} satisfies z.output<typeof schema.getFundingProviders.Response>
|
|
448
|
-
|
|
449
|
-
const tronChainExample = {
|
|
450
|
-
addressFormat: 'base58check',
|
|
451
|
-
id: 'tron:0x2b6653dc',
|
|
452
|
-
kind: 'tron',
|
|
453
|
-
name: 'Tron',
|
|
454
|
-
} as const
|
|
455
|
-
|
|
456
|
-
const tronUsdtExample = {
|
|
457
|
-
address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
|
|
458
|
-
currency: 'USD',
|
|
459
|
-
decimals: 6,
|
|
460
|
-
name: 'Tether USD',
|
|
461
|
-
standard: 'TRC-20',
|
|
462
|
-
symbol: 'USDT',
|
|
463
|
-
tokenKey: 'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
|
|
464
|
-
verified: true,
|
|
465
|
-
} as const
|
|
466
|
-
|
|
467
|
-
const tempoChainExample = {
|
|
468
|
-
addressFormat: 'hex',
|
|
469
|
-
id: 'eip155:4217',
|
|
470
|
-
kind: 'evm',
|
|
471
|
-
name: 'Tempo',
|
|
472
|
-
} as const
|
|
473
|
-
|
|
474
|
-
const tempoUsdt0Example = {
|
|
475
|
-
address: '0x20c00000000000000000000014f22ca97301eb73',
|
|
476
|
-
currency: 'USD',
|
|
477
|
-
decimals: 6,
|
|
478
|
-
name: 'USDT0',
|
|
479
|
-
standard: 'TIP-20',
|
|
480
|
-
symbol: 'USDT0',
|
|
481
|
-
tokenKey: 'eip155:4217/erc20:0x20c00000000000000000000014f22ca97301eb73',
|
|
482
|
-
verified: true,
|
|
483
|
-
} as const
|
|
484
|
-
|
|
485
|
-
const fundingDepositAddressExample = {
|
|
486
|
-
address: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
|
|
487
|
-
createdAt: '2026-08-04T00:00:00.000Z',
|
|
488
|
-
destinationChain: tempoChainExample,
|
|
489
|
-
destinationToken: tempoUsdt0Example,
|
|
490
|
-
id: 'fda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx',
|
|
491
|
-
provider: { id: 'relay', name: 'Relay' },
|
|
492
|
-
recipient: `0x${'11'.repeat(20)}`,
|
|
493
|
-
refundAddress: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
|
|
494
|
-
sourceChain: tronChainExample,
|
|
495
|
-
sourceToken: tronUsdtExample,
|
|
496
|
-
status: 'active',
|
|
497
|
-
subsidize: false,
|
|
498
|
-
updatedAt: '2026-08-04T00:00:00.000Z',
|
|
499
|
-
} satisfies z.output<typeof schema.DepositAddress>
|
|
500
|
-
|
|
501
|
-
const createdFundingDepositAddressExample = {
|
|
502
|
-
...fundingDepositAddressExample,
|
|
503
|
-
destinationAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
|
|
504
|
-
destinationAmountMin: Value.tokenAmount({
|
|
505
|
-
baseUnits: '1000000',
|
|
506
|
-
currency: 'USD',
|
|
507
|
-
decimals: 6,
|
|
508
|
-
}),
|
|
509
|
-
fees: [],
|
|
510
|
-
quote: { sampledAt: '2026-08-04T00:00:00.000Z' },
|
|
511
|
-
sourceAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
|
|
512
|
-
} satisfies z.output<typeof schema.createFundingDepositAddress.Response>
|
|
513
|
-
|
|
514
|
-
const fundingDepositExample = {
|
|
515
|
-
createdAt: '2026-08-04T00:01:00.000Z',
|
|
516
|
-
depositAddressId: fundingDepositAddressExample.id,
|
|
517
|
-
destinationChain: tempoChainExample,
|
|
518
|
-
destinationToken: tempoUsdt0Example,
|
|
519
|
-
id: 'fdp_001785792060000_2ZPE2gvateYEQ0dQslgvkhjx',
|
|
520
|
-
provider: { id: 'relay', name: 'Relay' },
|
|
521
|
-
recipient: fundingDepositAddressExample.recipient,
|
|
522
|
-
refundAddress: fundingDepositAddressExample.refundAddress,
|
|
523
|
-
sourceChain: tronChainExample,
|
|
524
|
-
sourceToken: tronUsdtExample,
|
|
525
|
-
sourceTransactionHashes: ['9f2c4c80dbb468878baa8c07ff523e0464bf6437f2a486268508485d83f14d12'],
|
|
526
|
-
status: 'detected',
|
|
527
|
-
updatedAt: '2026-08-04T00:01:00.000Z',
|
|
528
|
-
} satisfies z.output<typeof schema.Deposit>
|
|
529
|
-
|
|
530
|
-
const fundingDepositAddressDepositsExample = {
|
|
531
|
-
data: [fundingDepositExample],
|
|
532
|
-
nextCursor: null,
|
|
533
|
-
} satisfies z.output<typeof schema.listFundingDepositAddressDeposits.Response>
|
|
534
|
-
|
|
535
|
-
const fundingTransferExample = {
|
|
536
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
537
|
-
destinationAmount: Value.tokenAmount({ baseUnits: '979295', currency: 'USD', decimals: 6 }),
|
|
538
|
-
destinationAmountMin: Value.tokenAmount({ baseUnits: '939242', currency: 'USD', decimals: 6 }),
|
|
539
|
-
destinationChain: {
|
|
540
|
-
addressFormat: 'hex',
|
|
541
|
-
id: 'eip155:4217',
|
|
542
|
-
kind: 'evm',
|
|
543
|
-
name: 'Tempo',
|
|
544
|
-
},
|
|
545
|
-
destinationToken: {
|
|
546
|
-
address: '0x20c0000000000000000000000000000000000000',
|
|
547
|
-
currency: 'USD',
|
|
548
|
-
decimals: 6,
|
|
549
|
-
name: 'PathUSD',
|
|
550
|
-
standard: 'TIP-20',
|
|
551
|
-
symbol: 'pathUSD',
|
|
552
|
-
tokenKey: 'eip155:4217/erc20:0x20c0000000000000000000000000000000000000',
|
|
553
|
-
verified: true,
|
|
554
|
-
},
|
|
555
|
-
fees: [
|
|
556
|
-
{
|
|
557
|
-
amount: Value.tokenAmount({ baseUnits: '20705', currency: 'USD', decimals: 6 }),
|
|
558
|
-
side: 'source',
|
|
559
|
-
token: baseUsdcExample,
|
|
560
|
-
type: 'provider',
|
|
561
|
-
},
|
|
562
|
-
],
|
|
563
|
-
id: 'ftr_001767225600000_2ZPE2gvateYEQ0dQslgvkhjx',
|
|
564
|
-
method: 'transaction',
|
|
565
|
-
mode: 'exactSource',
|
|
566
|
-
provider: { id: 'relay', name: 'Relay' },
|
|
567
|
-
quote: {
|
|
568
|
-
expiresAt: '2026-01-01T00:01:00.000Z',
|
|
569
|
-
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
570
|
-
},
|
|
571
|
-
recipient: `0x${'11'.repeat(20)}`,
|
|
572
|
-
sender: `0x${'22'.repeat(20)}`,
|
|
573
|
-
sourceAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
|
|
574
|
-
sourceChain: baseChainExample,
|
|
575
|
-
sourceToken: baseUsdcExample,
|
|
576
|
-
status: 'awaiting-source',
|
|
577
|
-
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
578
|
-
version: 1,
|
|
579
|
-
} satisfies z.output<typeof schema.Transfer>
|
|
580
|
-
|
|
581
|
-
const listFundingTransfersResponseExample = {
|
|
582
|
-
data: [fundingTransferExample],
|
|
583
|
-
nextCursor: null,
|
|
584
|
-
} satisfies z.output<typeof schema.listFundingTransfers.Response>
|
|
585
|
-
|
|
586
|
-
const createdFundingTransferExample = {
|
|
587
|
-
action: {
|
|
588
|
-
calls: [
|
|
589
|
-
{
|
|
590
|
-
data: `0x095ea7b3000000000000000000000000${'44'.repeat(20)}00000000000000000000000000000000000000000000000000000000000f4240`,
|
|
591
|
-
to: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
592
|
-
value: '0x0',
|
|
593
|
-
},
|
|
594
|
-
{
|
|
595
|
-
data: '0xe8017952',
|
|
596
|
-
to: `0x${'44'.repeat(20)}`,
|
|
597
|
-
value: '0x0',
|
|
598
|
-
},
|
|
599
|
-
],
|
|
600
|
-
type: 'evm:calls',
|
|
601
|
-
},
|
|
602
|
-
...fundingTransferExample,
|
|
603
|
-
} satisfies z.output<typeof schema.createFundingTransfer.Response>
|
|
604
|
-
|
|
605
|
-
const unimplementedFundingTransferResponses = (() => {
|
|
606
|
-
const responses = OpenApi.responses({
|
|
607
|
-
errors: {
|
|
608
|
-
400: { codes: ['body_invalid', 'idempotency_key_required'] },
|
|
609
|
-
501: {
|
|
610
|
-
codes: ['not_implemented'],
|
|
611
|
-
description: 'This funding transfer destination is not implemented.',
|
|
612
|
-
},
|
|
613
|
-
},
|
|
614
|
-
success: {
|
|
615
|
-
description: 'Unused until this destination is implemented.',
|
|
616
|
-
schema: z.never(),
|
|
617
|
-
},
|
|
618
|
-
})
|
|
619
|
-
const { 200: success, 502: upstream, ...errors } = responses
|
|
620
|
-
void success
|
|
621
|
-
void upstream
|
|
622
|
-
return errors
|
|
623
|
-
})()
|
|
624
|
-
|
|
625
|
-
// Replay the exact successful creation response for 24 hours.
|
|
626
|
-
const idempotencyReplayTtlMs = 86_400_000
|
|
627
|
-
// Pending claims only cover the bounded provider preparation window.
|
|
628
|
-
const idempotencyPendingTtlMs = 60_000
|
|
629
|
-
|
|
630
|
-
const depositAddressCheckpoint = Schema.describe(
|
|
631
|
-
z.object({
|
|
632
|
-
candidate: Schema.describe(
|
|
633
|
-
z.object({
|
|
634
|
-
destinationChain: FundingProvider.schema.ChainRef,
|
|
635
|
-
destinationToken: FundingProvider.schema.TokenRef,
|
|
636
|
-
sourceChain: FundingProvider.schema.ChainRef,
|
|
637
|
-
sourceToken: FundingProvider.schema.TokenRef,
|
|
638
|
-
}),
|
|
639
|
-
'Normalized route retained after provider provisioning.',
|
|
640
|
-
),
|
|
641
|
-
provider: FundingProvider.schema.ProviderRef,
|
|
642
|
-
recipient: z.string().check(z.describe('Normalized Tempo recipient.')),
|
|
643
|
-
refundAddress: z.string().check(z.describe('Normalized source-chain refund account.')),
|
|
644
|
-
result: FundingProvider.schema.DepositAddressResult,
|
|
645
|
-
}),
|
|
646
|
-
'Provider result retained until deposit address creation completes.',
|
|
647
|
-
)
|
|
648
|
-
|
|
649
|
-
const inventorySourceScopes = new WeakMap<Db.Source, number>()
|
|
650
|
-
let nextInventorySourceScope = 0
|
|
651
|
-
const fundingEtag = etag()
|
|
652
|
-
|
|
653
|
-
function inventorySourceScope(source: Db.Source) {
|
|
654
|
-
const current = inventorySourceScopes.get(source)
|
|
655
|
-
if (current !== undefined) return current
|
|
656
|
-
// A production isolate's source is consistently zero, while multi-app runtimes receive distinct scopes.
|
|
657
|
-
const scope = nextInventorySourceScope++
|
|
658
|
-
inventorySourceScopes.set(source, scope)
|
|
659
|
-
return scope
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/** Mounts live funding quotes and their supporting inventory endpoints. */
|
|
663
|
-
export function funding() {
|
|
664
|
-
const inventoryKey = (c: Context<App.Environment>) => {
|
|
665
|
-
const url = new URL(c.req.url)
|
|
666
|
-
const providers = c
|
|
667
|
-
.get('providers')
|
|
668
|
-
.filter(FundingProvider.is)
|
|
669
|
-
.map((provider) => provider.id)
|
|
670
|
-
.sort()
|
|
671
|
-
url.searchParams.set('__tempo_database', String(inventorySourceScope(c.get('dbCached'))))
|
|
672
|
-
url.searchParams.set('__tempo_providers', providers.join(','))
|
|
673
|
-
return url.toString()
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
return new Hono<App.Environment>()
|
|
677
|
-
.post('/webhooks/funding/:providerId', async (c) => {
|
|
678
|
-
const provider = c
|
|
679
|
-
.get('providers')
|
|
680
|
-
.filter(FundingProvider.is)
|
|
681
|
-
.find((candidate) => candidate.id === c.req.param('providerId'))
|
|
682
|
-
if (!provider || !FundingProvider.canReceiveWebhook(provider)) return c.body(null, 404)
|
|
683
|
-
try {
|
|
684
|
-
await provider.webhook.receive({
|
|
685
|
-
body: await c.req.text(),
|
|
686
|
-
headers: c.req.raw.headers,
|
|
687
|
-
})
|
|
688
|
-
return c.body(null, 204)
|
|
689
|
-
} catch (cause) {
|
|
690
|
-
if (cause instanceof FundingProvider.WebhookAuthenticationError) return c.body(null, 401)
|
|
691
|
-
if (cause instanceof FundingProvider.WebhookPayloadError) return c.body(null, 400)
|
|
692
|
-
throw cause
|
|
693
|
-
}
|
|
694
|
-
})
|
|
695
|
-
.get(
|
|
696
|
-
'/v1/funding/quotes',
|
|
697
|
-
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
698
|
-
OpenApi.validate('query', schema.quoteFunding.Query, {
|
|
699
|
-
code: 'query_invalid',
|
|
700
|
-
message: 'Invalid query parameters',
|
|
701
|
-
}),
|
|
702
|
-
OpenApi.describeRoute({
|
|
703
|
-
description: 'Returns live quotes for transferring stablecoins to Tempo.',
|
|
704
|
-
operationId: 'quoteFunding',
|
|
705
|
-
responses: OpenApi.responses({
|
|
706
|
-
errors: {
|
|
707
|
-
400: { codes: ['query_invalid'] },
|
|
708
|
-
502: {
|
|
709
|
-
codes: ['upstream_error'],
|
|
710
|
-
description:
|
|
711
|
-
'No quote succeeded and at least one matching provider failed unexpectedly.',
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
success: {
|
|
715
|
-
description: 'Live funding quotes. An empty list means no provider returned a quote.',
|
|
716
|
-
example: fundingQuoteResponseExample,
|
|
717
|
-
schema: schema.quoteFunding.Response,
|
|
718
|
-
},
|
|
719
|
-
}),
|
|
720
|
-
summary: 'Get quotes',
|
|
721
|
-
tags: ['Funding'],
|
|
722
|
-
}),
|
|
723
|
-
async (c) => {
|
|
724
|
-
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
725
|
-
if (OpenApi.narrowValidation)
|
|
726
|
-
return OpenApi.validationError(c, {
|
|
727
|
-
code: 'query_invalid',
|
|
728
|
-
message: 'Invalid query parameters',
|
|
729
|
-
})
|
|
730
|
-
|
|
731
|
-
const query = c.req.valid('query')
|
|
732
|
-
const attempts: FundingProvider.ProviderAttempt[] = []
|
|
733
|
-
const catalog = await FundingCatalog.snapshot(c)
|
|
734
|
-
const providers = c.get('providers').filter(FundingProvider.is)
|
|
735
|
-
try {
|
|
736
|
-
c.header('Cache-Control', 'no-store')
|
|
737
|
-
const data = await FundingProvider.getQuotes({
|
|
738
|
-
cache: { bypass: cacheBypass(c.req.raw) },
|
|
739
|
-
catalog,
|
|
740
|
-
destinationToken: query.destinationToken,
|
|
741
|
-
onAttempt: (attempt) => attempts.push(attempt),
|
|
742
|
-
provider: query.provider,
|
|
743
|
-
providers,
|
|
744
|
-
sourceAmount: query.sourceAmount,
|
|
745
|
-
sourceAmountUnits: query.sourceAmountUnits,
|
|
746
|
-
sourceChain: query.sourceChain,
|
|
747
|
-
sourceToken: query.sourceToken,
|
|
748
|
-
})
|
|
749
|
-
if (attempts.length > 0) c.set('fundingProviderAttempts', attempts.map(formatAttempt))
|
|
750
|
-
return c.json(
|
|
751
|
-
Response.validated(schema.quoteFunding.Response, {
|
|
752
|
-
data,
|
|
753
|
-
}),
|
|
754
|
-
200,
|
|
755
|
-
)
|
|
756
|
-
} catch (cause) {
|
|
757
|
-
const message = requestErrorMessage(cause)
|
|
758
|
-
if (message) return Response.error(c, { code: 'query_invalid', message, status: 400 })
|
|
759
|
-
if (cause instanceof FundingProvider.ProviderQuoteError) {
|
|
760
|
-
if (cause.fundingProviderAttempts.length > 0)
|
|
761
|
-
c.set('fundingProviderAttempts', cause.fundingProviderAttempts)
|
|
762
|
-
if (cause.providerFailure) c.set('providerFailure', cause.providerFailure)
|
|
763
|
-
if (cause.providerFailures) c.set('providerFailures', cause.providerFailures)
|
|
764
|
-
return Response.error(c, {
|
|
765
|
-
code: 'upstream_error',
|
|
766
|
-
message: 'Quote providers could not complete the request.',
|
|
767
|
-
status: 502,
|
|
768
|
-
})
|
|
769
|
-
}
|
|
770
|
-
throw cause
|
|
771
|
-
}
|
|
772
|
-
},
|
|
773
|
-
)
|
|
774
|
-
.get(
|
|
775
|
-
'/v1/funding/chains',
|
|
776
|
-
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
777
|
-
OpenApi.describeRoute({
|
|
778
|
-
description: 'Lists supported source chains and tokens.',
|
|
779
|
-
operationId: 'getFundingChains',
|
|
780
|
-
responses: OpenApi.responses({
|
|
781
|
-
success: {
|
|
782
|
-
description: 'Supported source chains and stablecoins.',
|
|
783
|
-
example: fundingChainsResponseExample,
|
|
784
|
-
schema: schema.getFundingChains.Response,
|
|
785
|
-
},
|
|
786
|
-
}),
|
|
787
|
-
summary: 'Get chains',
|
|
788
|
-
tags: ['Funding'],
|
|
789
|
-
}),
|
|
790
|
-
Cache.response<App.Environment>({
|
|
791
|
-
cacheControl: Cache.policies.stable,
|
|
792
|
-
key: inventoryKey,
|
|
793
|
-
name: 'tempo-api:funding-chains:v5',
|
|
794
|
-
}),
|
|
795
|
-
async (c) => {
|
|
796
|
-
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
797
|
-
const catalog = await FundingCatalog.snapshot(c)
|
|
798
|
-
const providers = c.get('providers').filter(FundingProvider.is)
|
|
799
|
-
return c.json(
|
|
800
|
-
Response.validated(schema.getFundingChains.Response, {
|
|
801
|
-
data: FundingProvider.getChains({ catalog, providers }),
|
|
802
|
-
}),
|
|
803
|
-
200,
|
|
804
|
-
)
|
|
805
|
-
},
|
|
806
|
-
)
|
|
807
|
-
.get(
|
|
808
|
-
'/v1/funding/providers',
|
|
809
|
-
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
810
|
-
OpenApi.describeRoute({
|
|
811
|
-
description: 'Lists available transfer providers.',
|
|
812
|
-
operationId: 'getFundingProviders',
|
|
813
|
-
responses: OpenApi.responses({
|
|
814
|
-
success: {
|
|
815
|
-
description: 'Funding quote providers supported by Tempo.',
|
|
816
|
-
example: providersResponseExample,
|
|
817
|
-
schema: schema.getFundingProviders.Response,
|
|
818
|
-
},
|
|
819
|
-
}),
|
|
820
|
-
summary: 'Get providers',
|
|
821
|
-
tags: ['Funding'],
|
|
822
|
-
}),
|
|
823
|
-
Cache.response<App.Environment>({
|
|
824
|
-
cacheControl: Cache.policies.stable,
|
|
825
|
-
key: inventoryKey,
|
|
826
|
-
name: 'tempo-api:funding-providers:v4',
|
|
827
|
-
}),
|
|
828
|
-
async (c) => {
|
|
829
|
-
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
830
|
-
const catalog = await FundingCatalog.snapshot(c)
|
|
831
|
-
const providers = c.get('providers').filter(FundingProvider.is)
|
|
832
|
-
return c.json(
|
|
833
|
-
Response.validated(schema.getFundingProviders.Response, {
|
|
834
|
-
data: FundingProvider.getProviders({ catalog, providers }),
|
|
835
|
-
}),
|
|
836
|
-
200,
|
|
837
|
-
)
|
|
838
|
-
},
|
|
839
|
-
)
|
|
840
|
-
.post(
|
|
841
|
-
'/v1/funding/deposit-addresses',
|
|
842
|
-
Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
|
|
843
|
-
OpenApi.validate('header', schema.createFundingDepositAddress.Headers, {
|
|
844
|
-
code: 'idempotency_key_required',
|
|
845
|
-
message: 'Funding deposit address creation requires an `Idempotency-Key` header.',
|
|
846
|
-
}),
|
|
847
|
-
OpenApi.validate('json', schema.createFundingDepositAddress.Body, {
|
|
848
|
-
code: 'body_invalid',
|
|
849
|
-
message: 'Check the request body and try again.',
|
|
850
|
-
}),
|
|
851
|
-
OpenApi.describeRoute({
|
|
852
|
-
description: 'Creates or returns a reusable deposit address for funding a Tempo account.',
|
|
853
|
-
operationId: 'createFundingDepositAddress',
|
|
854
|
-
responses: OpenApi.responses({
|
|
855
|
-
errors: {
|
|
856
|
-
400: { codes: ['body_invalid', 'idempotency_key_required'] },
|
|
857
|
-
404: {
|
|
858
|
-
codes: ['deposit_address_not_available'],
|
|
859
|
-
description: 'No provider can provision the requested reusable address route.',
|
|
860
|
-
},
|
|
861
|
-
409: {
|
|
862
|
-
codes: ['idempotency_conflict', 'idempotency_in_progress'],
|
|
863
|
-
description: 'The Idempotency-Key conflicts with an existing or in-flight request.',
|
|
864
|
-
},
|
|
865
|
-
502: {
|
|
866
|
-
codes: ['upstream_error'],
|
|
867
|
-
description: 'The selected provider could not provision the address.',
|
|
868
|
-
},
|
|
869
|
-
},
|
|
870
|
-
success: {
|
|
871
|
-
description: 'The created or matching reusable funding deposit address and quote.',
|
|
872
|
-
example: createdFundingDepositAddressExample,
|
|
873
|
-
schema: schema.createFundingDepositAddress.Response,
|
|
874
|
-
},
|
|
875
|
-
}),
|
|
876
|
-
summary: 'Create deposit address',
|
|
877
|
-
tags: ['Funding'],
|
|
878
|
-
}),
|
|
879
|
-
async (c) => {
|
|
880
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
881
|
-
if (OpenApi.narrowValidation)
|
|
882
|
-
return OpenApi.validationError(c, {
|
|
883
|
-
code: 'body_invalid',
|
|
884
|
-
message: 'Check the request body and try again.',
|
|
885
|
-
})
|
|
886
|
-
|
|
887
|
-
const principal = Auth.getPrincipal(c)
|
|
888
|
-
const owner = fundingOwner(c)
|
|
889
|
-
if (!owner || principal?.type !== 'api_key') return fundingUnauthorized(c)
|
|
890
|
-
c.header('Cache-Control', 'no-store')
|
|
891
|
-
|
|
892
|
-
const body = c.req.valid('json')
|
|
893
|
-
const subsidize = body.subsidize ?? false
|
|
894
|
-
const db = Db.get(c.get('db'))
|
|
895
|
-
const identity = {
|
|
896
|
-
apiKeyId: principal.id,
|
|
897
|
-
keyHash: Idempotency.inputHash(c.req.valid('header')['idempotency-key']),
|
|
898
|
-
requestHash: Idempotency.inputHash({
|
|
899
|
-
body,
|
|
900
|
-
endpoint: 'POST /v1/funding/deposit-addresses',
|
|
901
|
-
}),
|
|
902
|
-
}
|
|
903
|
-
const claim = await FundingIdempotency.claim(db, {
|
|
904
|
-
...identity,
|
|
905
|
-
ttlMs: idempotencyPendingTtlMs,
|
|
906
|
-
})
|
|
907
|
-
if (claim.type === 'mismatch')
|
|
908
|
-
return Response.error(c, {
|
|
909
|
-
code: 'idempotency_conflict',
|
|
910
|
-
message: 'This Idempotency-Key was already used with different input.',
|
|
911
|
-
status: 409,
|
|
912
|
-
})
|
|
913
|
-
if (claim.type === 'pending')
|
|
914
|
-
return Response.error(c, {
|
|
915
|
-
code: 'idempotency_in_progress',
|
|
916
|
-
message: 'An equivalent funding deposit address creation is still running.',
|
|
917
|
-
status: 409,
|
|
918
|
-
})
|
|
919
|
-
if (claim.type === 'replay') {
|
|
920
|
-
const response = JSON.parse(claim.response) as Record<string, unknown>
|
|
921
|
-
return c.json(
|
|
922
|
-
Response.validated(schema.createFundingDepositAddress.Response, {
|
|
923
|
-
...response,
|
|
924
|
-
subsidize: response['subsidize'] ?? false,
|
|
925
|
-
}),
|
|
926
|
-
200,
|
|
927
|
-
)
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
const release = async () => {
|
|
931
|
-
try {
|
|
932
|
-
await FundingIdempotency.release(db, {
|
|
933
|
-
apiKeyId: identity.apiKeyId,
|
|
934
|
-
createdAt: claim.createdAt,
|
|
935
|
-
keyHash: identity.keyHash,
|
|
936
|
-
})
|
|
937
|
-
} catch {
|
|
938
|
-
// The pending claim expires on its own; the response stays authoritative.
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
const complete = async (
|
|
942
|
-
database: Db.Db,
|
|
943
|
-
response: z.output<typeof schema.createFundingDepositAddress.Response>,
|
|
944
|
-
) => {
|
|
945
|
-
const validated = Response.validated(
|
|
946
|
-
schema.createFundingDepositAddress.Response,
|
|
947
|
-
response,
|
|
948
|
-
)
|
|
949
|
-
const completion = await FundingIdempotency.complete(database, {
|
|
950
|
-
apiKeyId: identity.apiKeyId,
|
|
951
|
-
createdAt: claim.createdAt,
|
|
952
|
-
keyHash: identity.keyHash,
|
|
953
|
-
replayTtlMs: idempotencyReplayTtlMs,
|
|
954
|
-
response: JSON.stringify(validated),
|
|
955
|
-
})
|
|
956
|
-
if (!completion)
|
|
957
|
-
throw new IdempotencyLeaseLostError('Funding deposit address creation lease was lost.')
|
|
958
|
-
return validated
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
const attempts: FundingProvider.ProviderAttempt[] = []
|
|
962
|
-
const provisioning = { succeeded: claim.type === 'resume' }
|
|
963
|
-
try {
|
|
964
|
-
const selected = await (async () => {
|
|
965
|
-
if (claim.type === 'resume')
|
|
966
|
-
return depositAddressCheckpoint.parse(JSON.parse(claim.response))
|
|
967
|
-
const catalog = await FundingCatalog.snapshot(c)
|
|
968
|
-
const providers = c.get('providers').filter(FundingProvider.is)
|
|
969
|
-
const provisioned = await FundingProvider.createDepositAddress({
|
|
970
|
-
amount: body.amount,
|
|
971
|
-
catalog,
|
|
972
|
-
destinationChainId: c.get('chainId'),
|
|
973
|
-
destinationToken: body.destinationToken,
|
|
974
|
-
onAttempt: (attempt) => attempts.push(attempt),
|
|
975
|
-
providers,
|
|
976
|
-
recipient: body.recipient,
|
|
977
|
-
refundAddress: body.refundAddress,
|
|
978
|
-
sourceChain: body.sourceChain,
|
|
979
|
-
sourceToken: body.sourceToken,
|
|
980
|
-
subsidize,
|
|
981
|
-
})
|
|
982
|
-
if (!provisioned) return undefined
|
|
983
|
-
provisioning.succeeded = true
|
|
984
|
-
const checkpoint = depositAddressCheckpoint.parse({
|
|
985
|
-
candidate: provisioned.candidate,
|
|
986
|
-
provider: provisioned.provider,
|
|
987
|
-
recipient: provisioned.recipient,
|
|
988
|
-
refundAddress: provisioned.refundAddress,
|
|
989
|
-
result: provisioned.result,
|
|
990
|
-
})
|
|
991
|
-
const stored = await FundingIdempotency.checkpoint(db, {
|
|
992
|
-
apiKeyId: identity.apiKeyId,
|
|
993
|
-
createdAt: claim.createdAt,
|
|
994
|
-
keyHash: identity.keyHash,
|
|
995
|
-
replayTtlMs: idempotencyReplayTtlMs,
|
|
996
|
-
response: JSON.stringify(checkpoint),
|
|
997
|
-
})
|
|
998
|
-
if (!stored)
|
|
999
|
-
throw new IdempotencyLeaseLostError(
|
|
1000
|
-
'Funding deposit address creation lease was lost.',
|
|
1001
|
-
)
|
|
1002
|
-
return checkpoint
|
|
1003
|
-
})()
|
|
1004
|
-
if (attempts.length > 0) c.set('fundingProviderAttempts', attempts.map(formatAttempt))
|
|
1005
|
-
if (!selected) {
|
|
1006
|
-
await release()
|
|
1007
|
-
return depositAddressNotAvailable(c)
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
const {
|
|
1011
|
-
candidate: selectedCandidate,
|
|
1012
|
-
provider,
|
|
1013
|
-
recipient,
|
|
1014
|
-
refundAddress,
|
|
1015
|
-
result,
|
|
1016
|
-
} = selected
|
|
1017
|
-
const feeToken = (side: 'destination' | 'source') =>
|
|
1018
|
-
side === 'source' ? selectedCandidate.sourceToken : selectedCandidate.destinationToken
|
|
1019
|
-
const created = await db.transaction(async (tx) => {
|
|
1020
|
-
const stored = await FundingDepositAddress.createOrGet(tx, {
|
|
1021
|
-
apiKeyId: principal.id,
|
|
1022
|
-
deliveryStrategy: 'provider',
|
|
1023
|
-
environment: owner.environment,
|
|
1024
|
-
orgId: owner.orgId,
|
|
1025
|
-
...(owner.projectId === undefined ? {} : { projectId: owner.projectId }),
|
|
1026
|
-
providerOutputToken: selectedCandidate.destinationToken,
|
|
1027
|
-
providerRequestIds: [result.correlation.requestId],
|
|
1028
|
-
providerState:
|
|
1029
|
-
result.correlation.checkEndpoint === undefined
|
|
1030
|
-
? undefined
|
|
1031
|
-
: { checkEndpoint: result.correlation.checkEndpoint },
|
|
1032
|
-
snapshot: {
|
|
1033
|
-
address: result.address,
|
|
1034
|
-
destinationChain: selectedCandidate.destinationChain,
|
|
1035
|
-
destinationToken: selectedCandidate.destinationToken,
|
|
1036
|
-
provider,
|
|
1037
|
-
recipient,
|
|
1038
|
-
refundAddress,
|
|
1039
|
-
sourceChain: selectedCandidate.sourceChain,
|
|
1040
|
-
sourceToken: selectedCandidate.sourceToken,
|
|
1041
|
-
subsidize,
|
|
1042
|
-
},
|
|
1043
|
-
})
|
|
1044
|
-
const record = FundingDepositAddress.toPublic(stored.record)
|
|
1045
|
-
if (stored.type === 'existing') return complete(tx, record)
|
|
1046
|
-
return complete(tx, {
|
|
1047
|
-
...record,
|
|
1048
|
-
destinationAmount: Value.tokenAmount({
|
|
1049
|
-
baseUnits: result.destinationAmount,
|
|
1050
|
-
currency: selectedCandidate.destinationToken.currency,
|
|
1051
|
-
decimals: selectedCandidate.destinationToken.decimals,
|
|
1052
|
-
}),
|
|
1053
|
-
destinationAmountMin: Value.tokenAmount({
|
|
1054
|
-
baseUnits: result.destinationAmountMin,
|
|
1055
|
-
currency: selectedCandidate.destinationToken.currency,
|
|
1056
|
-
decimals: selectedCandidate.destinationToken.decimals,
|
|
1057
|
-
}),
|
|
1058
|
-
fees: result.fees.map((fee) => ({
|
|
1059
|
-
amount: Value.tokenAmount({
|
|
1060
|
-
baseUnits: fee.amount,
|
|
1061
|
-
currency: feeToken(fee.side).currency,
|
|
1062
|
-
decimals: feeToken(fee.side).decimals,
|
|
1063
|
-
}),
|
|
1064
|
-
side: fee.side,
|
|
1065
|
-
token: feeToken(fee.side),
|
|
1066
|
-
type: 'provider',
|
|
1067
|
-
})),
|
|
1068
|
-
quote: {
|
|
1069
|
-
...(result.expiresAt === undefined ? {} : { expiresAt: result.expiresAt }),
|
|
1070
|
-
sampledAt: result.sampledAt,
|
|
1071
|
-
},
|
|
1072
|
-
sourceAmount: Value.tokenAmount({
|
|
1073
|
-
baseUnits: body.amount,
|
|
1074
|
-
currency: selectedCandidate.sourceToken.currency,
|
|
1075
|
-
decimals: selectedCandidate.sourceToken.decimals,
|
|
1076
|
-
}),
|
|
1077
|
-
})
|
|
1078
|
-
})
|
|
1079
|
-
return c.json(created, 200)
|
|
1080
|
-
} catch (cause) {
|
|
1081
|
-
if (!provisioning.succeeded) await release()
|
|
1082
|
-
if (cause instanceof FundingProvider.InvalidRefundAddressError)
|
|
1083
|
-
return Response.error(c, {
|
|
1084
|
-
code: 'body_invalid',
|
|
1085
|
-
message: 'Invalid `refundAddress`.',
|
|
1086
|
-
status: 400,
|
|
1087
|
-
})
|
|
1088
|
-
if (cause instanceof FundingProvider.UnsupportedSourceChainError)
|
|
1089
|
-
return depositAddressNotAvailable(c)
|
|
1090
|
-
if (
|
|
1091
|
-
cause instanceof FundingDepositAddress.ConflictError ||
|
|
1092
|
-
cause instanceof FundingProvider.ProviderQuoteError
|
|
1093
|
-
) {
|
|
1094
|
-
if (cause instanceof FundingDepositAddress.ConflictError)
|
|
1095
|
-
return Response.error(c, {
|
|
1096
|
-
code: 'upstream_error',
|
|
1097
|
-
message: 'The funding provider returned a conflicting deposit address.',
|
|
1098
|
-
status: 502,
|
|
1099
|
-
})
|
|
1100
|
-
if (cause.fundingProviderAttempts.length > 0)
|
|
1101
|
-
c.set('fundingProviderAttempts', cause.fundingProviderAttempts)
|
|
1102
|
-
if (cause.providerFailure) c.set('providerFailure', cause.providerFailure)
|
|
1103
|
-
if (cause.providerFailures) c.set('providerFailures', cause.providerFailures)
|
|
1104
|
-
return Response.error(c, {
|
|
1105
|
-
code: 'upstream_error',
|
|
1106
|
-
message: 'The funding provider could not provision the deposit address.',
|
|
1107
|
-
status: 502,
|
|
1108
|
-
})
|
|
1109
|
-
}
|
|
1110
|
-
throw cause
|
|
1111
|
-
}
|
|
1112
|
-
},
|
|
1113
|
-
)
|
|
1114
|
-
.get(
|
|
1115
|
-
'/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}/deposits',
|
|
1116
|
-
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
1117
|
-
OpenApi.validate('param', schema.listFundingDepositAddressDeposits.Params, {
|
|
1118
|
-
code: 'param_invalid',
|
|
1119
|
-
message: 'Check the path parameters and try again.',
|
|
1120
|
-
}),
|
|
1121
|
-
OpenApi.validate('query', schema.listFundingDepositAddressDeposits.Query, {
|
|
1122
|
-
code: 'query_invalid',
|
|
1123
|
-
message: 'Check the query parameters and try again.',
|
|
1124
|
-
}),
|
|
1125
|
-
OpenApi.describeRoute({
|
|
1126
|
-
description: 'Lists deposits detected for one funding deposit address, newest first.',
|
|
1127
|
-
operationId: 'listFundingDepositAddressDeposits',
|
|
1128
|
-
responses: OpenApi.responses({
|
|
1129
|
-
errors: {
|
|
1130
|
-
400: { codes: ['param_invalid', 'query_invalid'] },
|
|
1131
|
-
404: {
|
|
1132
|
-
codes: ['funding_deposit_address_not_found'],
|
|
1133
|
-
description: 'No visible funding deposit address exists for the identifier.',
|
|
1134
|
-
},
|
|
1135
|
-
},
|
|
1136
|
-
success: {
|
|
1137
|
-
description: 'A page of deposits detected for the address.',
|
|
1138
|
-
example: fundingDepositAddressDepositsExample,
|
|
1139
|
-
schema: schema.listFundingDepositAddressDeposits.Response,
|
|
1140
|
-
},
|
|
1141
|
-
}),
|
|
1142
|
-
summary: 'List deposits',
|
|
1143
|
-
tags: ['Funding'],
|
|
1144
|
-
}),
|
|
1145
|
-
async (c) => {
|
|
1146
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1147
|
-
if (OpenApi.narrowValidation)
|
|
1148
|
-
return OpenApi.validationError(c, {
|
|
1149
|
-
code: 'query_invalid',
|
|
1150
|
-
message: 'Check the request parameters and try again.',
|
|
1151
|
-
})
|
|
1152
|
-
|
|
1153
|
-
const owner = fundingOwner(c)
|
|
1154
|
-
if (!owner) return fundingUnauthorized(c)
|
|
1155
|
-
|
|
1156
|
-
const db = Db.get(c.get('db'))
|
|
1157
|
-
const { id } = c.req.valid('param')
|
|
1158
|
-
const { cursor, limit } = c.req.valid('query')
|
|
1159
|
-
const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
|
|
1160
|
-
try {
|
|
1161
|
-
const address = await FundingDepositAddresses.getOwned(db, owner, id)
|
|
1162
|
-
if (!address)
|
|
1163
|
-
return Response.error(c, {
|
|
1164
|
-
code: 'funding_deposit_address_not_found',
|
|
1165
|
-
message: 'Funding deposit address not found',
|
|
1166
|
-
status: 404,
|
|
1167
|
-
})
|
|
1168
|
-
|
|
1169
|
-
const rows = await FundingDeposits.listByAddress(db, owner, id, {
|
|
1170
|
-
...(decoded
|
|
1171
|
-
? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
|
|
1172
|
-
: {}),
|
|
1173
|
-
limit: limit + 1,
|
|
1174
|
-
})
|
|
1175
|
-
const page = Cursor.paginate({
|
|
1176
|
-
key: (record) => [record.createdAt, record.id],
|
|
1177
|
-
limit,
|
|
1178
|
-
rows,
|
|
1179
|
-
})
|
|
1180
|
-
const data = page.rows.map(FundingDeposit.toPublic)
|
|
1181
|
-
|
|
1182
|
-
c.header('Cache-Control', Cache.policies.noStore)
|
|
1183
|
-
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
|
|
1184
|
-
return c.json(
|
|
1185
|
-
Response.validated(schema.listFundingDepositAddressDeposits.Response, {
|
|
1186
|
-
data,
|
|
1187
|
-
nextCursor: page.nextCursor,
|
|
1188
|
-
}),
|
|
1189
|
-
200,
|
|
1190
|
-
)
|
|
1191
|
-
} catch (cause) {
|
|
1192
|
-
return Response.upstream(c, cause)
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
)
|
|
1196
|
-
.get(
|
|
1197
|
-
'/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}',
|
|
1198
|
-
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
1199
|
-
OpenApi.validate('param', schema.getFundingDepositAddress.Params, {
|
|
1200
|
-
code: 'param_invalid',
|
|
1201
|
-
message: 'Check the path parameters and try again.',
|
|
1202
|
-
}),
|
|
1203
|
-
OpenApi.describeRoute({
|
|
1204
|
-
description: 'Returns one reusable funding deposit address.',
|
|
1205
|
-
operationId: 'getFundingDepositAddress',
|
|
1206
|
-
responses: OpenApi.responses({
|
|
1207
|
-
errors: {
|
|
1208
|
-
400: { codes: ['param_invalid'] },
|
|
1209
|
-
404: {
|
|
1210
|
-
codes: ['funding_deposit_address_not_found'],
|
|
1211
|
-
description: 'No visible funding deposit address exists for the identifier.',
|
|
1212
|
-
},
|
|
1213
|
-
},
|
|
1214
|
-
success: {
|
|
1215
|
-
description: 'The requested funding deposit address.',
|
|
1216
|
-
example: fundingDepositAddressExample,
|
|
1217
|
-
schema: schema.getFundingDepositAddress.Response,
|
|
1218
|
-
},
|
|
1219
|
-
}),
|
|
1220
|
-
summary: 'Get deposit address',
|
|
1221
|
-
tags: ['Funding'],
|
|
1222
|
-
}),
|
|
1223
|
-
async (c) => {
|
|
1224
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1225
|
-
if (OpenApi.narrowValidation)
|
|
1226
|
-
return OpenApi.validationError(c, {
|
|
1227
|
-
code: 'param_invalid',
|
|
1228
|
-
message: 'Check the path parameters and try again.',
|
|
1229
|
-
})
|
|
1230
|
-
|
|
1231
|
-
const owner = fundingOwner(c)
|
|
1232
|
-
if (!owner) return fundingUnauthorized(c)
|
|
1233
|
-
|
|
1234
|
-
const db = Db.get(c.get('db'))
|
|
1235
|
-
const { id } = c.req.valid('param')
|
|
1236
|
-
try {
|
|
1237
|
-
const record = await FundingDepositAddresses.getOwned(db, owner, id)
|
|
1238
|
-
if (!record)
|
|
1239
|
-
return Response.error(c, {
|
|
1240
|
-
code: 'funding_deposit_address_not_found',
|
|
1241
|
-
message: 'Funding deposit address not found',
|
|
1242
|
-
status: 404,
|
|
1243
|
-
})
|
|
1244
|
-
|
|
1245
|
-
c.header('Cache-Control', Cache.policies.noStore)
|
|
1246
|
-
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
|
|
1247
|
-
return c.json(
|
|
1248
|
-
Response.validated(
|
|
1249
|
-
schema.getFundingDepositAddress.Response,
|
|
1250
|
-
FundingDepositAddress.toPublic(record),
|
|
1251
|
-
),
|
|
1252
|
-
200,
|
|
1253
|
-
)
|
|
1254
|
-
} catch (cause) {
|
|
1255
|
-
return Response.upstream(c, cause)
|
|
1256
|
-
}
|
|
1257
|
-
},
|
|
1258
|
-
)
|
|
1259
|
-
.get(
|
|
1260
|
-
'/v1/funding/deposits/:id{fdp_[A-Za-z0-9_-]+}',
|
|
1261
|
-
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
1262
|
-
OpenApi.validate('param', schema.getFundingDeposit.Params, {
|
|
1263
|
-
code: 'param_invalid',
|
|
1264
|
-
message: 'Check the path parameters and try again.',
|
|
1265
|
-
}),
|
|
1266
|
-
OpenApi.describeRoute({
|
|
1267
|
-
description: 'Returns one detected deposit and its delivery status.',
|
|
1268
|
-
operationId: 'getFundingDeposit',
|
|
1269
|
-
responses: OpenApi.responses({
|
|
1270
|
-
errors: {
|
|
1271
|
-
400: { codes: ['param_invalid'] },
|
|
1272
|
-
404: {
|
|
1273
|
-
codes: ['funding_deposit_not_found'],
|
|
1274
|
-
description: 'No visible funding deposit exists for the identifier.',
|
|
1275
|
-
},
|
|
1276
|
-
},
|
|
1277
|
-
success: {
|
|
1278
|
-
description: 'The requested funding deposit.',
|
|
1279
|
-
example: fundingDepositExample,
|
|
1280
|
-
schema: schema.getFundingDeposit.Response,
|
|
1281
|
-
},
|
|
1282
|
-
}),
|
|
1283
|
-
summary: 'Get deposit',
|
|
1284
|
-
tags: ['Funding'],
|
|
1285
|
-
}),
|
|
1286
|
-
fundingEtag,
|
|
1287
|
-
async (c) => {
|
|
1288
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1289
|
-
if (OpenApi.narrowValidation)
|
|
1290
|
-
return OpenApi.validationError(c, {
|
|
1291
|
-
code: 'param_invalid',
|
|
1292
|
-
message: 'Check the path parameters and try again.',
|
|
1293
|
-
})
|
|
1294
|
-
|
|
1295
|
-
const owner = fundingOwner(c)
|
|
1296
|
-
if (!owner) return fundingUnauthorized(c)
|
|
1297
|
-
|
|
1298
|
-
const db = Db.get(c.get('db'))
|
|
1299
|
-
const { id } = c.req.valid('param')
|
|
1300
|
-
try {
|
|
1301
|
-
const record = await FundingDeposits.getOwned(db, owner, id)
|
|
1302
|
-
if (!record)
|
|
1303
|
-
return Response.error(c, {
|
|
1304
|
-
code: 'funding_deposit_not_found',
|
|
1305
|
-
message: 'Funding deposit not found',
|
|
1306
|
-
status: 404,
|
|
1307
|
-
})
|
|
1308
|
-
|
|
1309
|
-
c.header('Cache-Control', Cache.policies.state)
|
|
1310
|
-
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
|
|
1311
|
-
return c.json(
|
|
1312
|
-
Response.validated(schema.getFundingDeposit.Response, FundingDeposit.toPublic(record)),
|
|
1313
|
-
200,
|
|
1314
|
-
)
|
|
1315
|
-
} catch (cause) {
|
|
1316
|
-
return Response.upstream(c, cause)
|
|
1317
|
-
}
|
|
1318
|
-
},
|
|
1319
|
-
)
|
|
1320
|
-
.get(
|
|
1321
|
-
'/v1/funding/transfers',
|
|
1322
|
-
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
1323
|
-
OpenApi.validate('query', schema.listFundingTransfers.Query, {
|
|
1324
|
-
code: 'query_invalid',
|
|
1325
|
-
message: 'Check the query parameters and try again.',
|
|
1326
|
-
}),
|
|
1327
|
-
OpenApi.describeRoute({
|
|
1328
|
-
description: 'Lists transfers from newest to oldest.',
|
|
1329
|
-
operationId: 'listFundingTransfers',
|
|
1330
|
-
responses: OpenApi.responses({
|
|
1331
|
-
errors: {
|
|
1332
|
-
400: { codes: ['query_invalid'] },
|
|
1333
|
-
},
|
|
1334
|
-
success: {
|
|
1335
|
-
description: 'A page of funding transfers.',
|
|
1336
|
-
example: listFundingTransfersResponseExample,
|
|
1337
|
-
schema: schema.listFundingTransfers.Response,
|
|
1338
|
-
},
|
|
1339
|
-
}),
|
|
1340
|
-
summary: 'List transfers',
|
|
1341
|
-
tags: ['Funding'],
|
|
1342
|
-
}),
|
|
1343
|
-
fundingEtag,
|
|
1344
|
-
async (c) => {
|
|
1345
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1346
|
-
if (OpenApi.narrowValidation)
|
|
1347
|
-
return OpenApi.validationError(c, {
|
|
1348
|
-
code: 'query_invalid',
|
|
1349
|
-
message: 'Check the query parameters and try again.',
|
|
1350
|
-
})
|
|
1351
|
-
|
|
1352
|
-
const owner = fundingOwner(c)
|
|
1353
|
-
if (!owner) return fundingUnauthorized(c)
|
|
1354
|
-
|
|
1355
|
-
const db = Db.get(c.get('db'))
|
|
1356
|
-
const { cursor, include, limit, status } = c.req.valid('query')
|
|
1357
|
-
try {
|
|
1358
|
-
// `totalCount` is opt-in and capped (a capped subquery shares the
|
|
1359
|
-
// list filter); run it concurrently with the page list. Best-effort:
|
|
1360
|
-
// a failure omits `meta` rather than failing the page.
|
|
1361
|
-
const countPromise = include.includes('totalCount')
|
|
1362
|
-
? FundingTransfers.count(db, owner, { cap: Schema.countCap, status }).catch(() => {
|
|
1363
|
-
c.set('fundingTransferCountFailed', true)
|
|
1364
|
-
return undefined
|
|
1365
|
-
})
|
|
1366
|
-
: undefined
|
|
1367
|
-
|
|
1368
|
-
// Fetch one extra to detect a further page without a second round-trip.
|
|
1369
|
-
const rows = await FundingTransfers.list(db, owner, { cursor, limit: limit + 1, status })
|
|
1370
|
-
const hasMore = rows.length > limit
|
|
1371
|
-
const data = (hasMore ? rows.slice(0, limit) : rows).map(FundingTransfer.toPublic)
|
|
1372
|
-
const nextCursor = hasMore ? (data[data.length - 1]?.id ?? null) : null
|
|
1373
|
-
|
|
1374
|
-
const count = countPromise ? await countPromise : undefined
|
|
1375
|
-
|
|
1376
|
-
c.header('Cache-Control', Cache.policies.feed)
|
|
1377
|
-
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
|
|
1378
|
-
|
|
1379
|
-
return c.json(
|
|
1380
|
-
Response.validated(schema.listFundingTransfers.Response, {
|
|
1381
|
-
data,
|
|
1382
|
-
...(count !== undefined
|
|
1383
|
-
? {
|
|
1384
|
-
meta: {
|
|
1385
|
-
totalCountCapped: count > Schema.countCap,
|
|
1386
|
-
totalCount: Math.min(count, Schema.countCap),
|
|
1387
|
-
},
|
|
1388
|
-
}
|
|
1389
|
-
: {}),
|
|
1390
|
-
nextCursor,
|
|
1391
|
-
}),
|
|
1392
|
-
200,
|
|
1393
|
-
)
|
|
1394
|
-
} catch (cause) {
|
|
1395
|
-
return Response.upstream(c, cause)
|
|
1396
|
-
}
|
|
1397
|
-
},
|
|
1398
|
-
)
|
|
1399
|
-
.get(
|
|
1400
|
-
'/v1/funding/transfers/:id{ftr_[A-Za-z0-9_-]+}',
|
|
1401
|
-
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
1402
|
-
OpenApi.validate('param', schema.getFundingTransfer.Params, {
|
|
1403
|
-
code: 'param_invalid',
|
|
1404
|
-
message: 'Check the path parameters and try again.',
|
|
1405
|
-
}),
|
|
1406
|
-
OpenApi.describeRoute({
|
|
1407
|
-
description: 'Returns a transfer by ID.',
|
|
1408
|
-
operationId: 'getFundingTransfer',
|
|
1409
|
-
responses: OpenApi.responses({
|
|
1410
|
-
errors: {
|
|
1411
|
-
400: { codes: ['param_invalid'] },
|
|
1412
|
-
404: {
|
|
1413
|
-
codes: ['funding_transfer_not_found'],
|
|
1414
|
-
description: 'No visible funding transfer exists for the identifier.',
|
|
1415
|
-
},
|
|
1416
|
-
},
|
|
1417
|
-
success: {
|
|
1418
|
-
description: 'The requested funding transfer.',
|
|
1419
|
-
example: fundingTransferExample,
|
|
1420
|
-
schema: schema.getFundingTransfer.Response,
|
|
1421
|
-
},
|
|
1422
|
-
}),
|
|
1423
|
-
summary: 'Get transfer',
|
|
1424
|
-
tags: ['Funding'],
|
|
1425
|
-
}),
|
|
1426
|
-
async (c) => {
|
|
1427
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1428
|
-
if (OpenApi.narrowValidation)
|
|
1429
|
-
return OpenApi.validationError(c, {
|
|
1430
|
-
code: 'param_invalid',
|
|
1431
|
-
message: 'Check the path parameters and try again.',
|
|
1432
|
-
})
|
|
1433
|
-
|
|
1434
|
-
const owner = fundingOwner(c)
|
|
1435
|
-
if (!owner) return fundingUnauthorized(c)
|
|
1436
|
-
|
|
1437
|
-
const db = Db.get(c.get('db'))
|
|
1438
|
-
const { id } = c.req.valid('param')
|
|
1439
|
-
try {
|
|
1440
|
-
// Ownership filters in SQL, so absent and foreign rows are the same 404.
|
|
1441
|
-
const record = await FundingTransfers.getOwned(db, owner, id)
|
|
1442
|
-
if (!record)
|
|
1443
|
-
return Response.error(c, {
|
|
1444
|
-
code: 'funding_transfer_not_found',
|
|
1445
|
-
message: 'Funding transfer not found',
|
|
1446
|
-
status: 404,
|
|
1447
|
-
})
|
|
1448
|
-
c.header('Cache-Control', Cache.policies.noStore)
|
|
1449
|
-
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
|
|
1450
|
-
return c.json(
|
|
1451
|
-
Response.validated(
|
|
1452
|
-
schema.getFundingTransfer.Response,
|
|
1453
|
-
FundingTransfer.toPublic(record),
|
|
1454
|
-
),
|
|
1455
|
-
200,
|
|
1456
|
-
)
|
|
1457
|
-
} catch (cause) {
|
|
1458
|
-
return Response.upstream(c, cause)
|
|
1459
|
-
}
|
|
1460
|
-
},
|
|
1461
|
-
)
|
|
1462
|
-
.post(
|
|
1463
|
-
'/v1/funding/transfers',
|
|
1464
|
-
Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
|
|
1465
|
-
OpenApi.validate('header', schema.createFundingTransfer.Headers, {
|
|
1466
|
-
code: 'idempotency_key_required',
|
|
1467
|
-
message: 'Funding transfer creation requires an `Idempotency-Key` header.',
|
|
1468
|
-
}),
|
|
1469
|
-
OpenApi.validate('json', schema.createFundingTransfer.Body, {
|
|
1470
|
-
code: 'body_invalid',
|
|
1471
|
-
message: 'Check the request body and try again.',
|
|
1472
|
-
}),
|
|
1473
|
-
OpenApi.describeRoute({
|
|
1474
|
-
description: 'Creates a transfer to a Tempo account.',
|
|
1475
|
-
operationId: 'createFundingTransfer',
|
|
1476
|
-
responses: OpenApi.responses({
|
|
1477
|
-
errors: {
|
|
1478
|
-
400: { codes: ['body_invalid', 'idempotency_key_required'] },
|
|
1479
|
-
404: {
|
|
1480
|
-
codes: ['quote_not_available'],
|
|
1481
|
-
description: 'No provider can prepare the requested route.',
|
|
1482
|
-
},
|
|
1483
|
-
409: {
|
|
1484
|
-
codes: ['idempotency_conflict', 'idempotency_in_progress'],
|
|
1485
|
-
description: 'The Idempotency-Key conflicts with an existing or in-flight request.',
|
|
1486
|
-
},
|
|
1487
|
-
502: {
|
|
1488
|
-
codes: ['upstream_error'],
|
|
1489
|
-
description: 'No provider succeeded and at least one failed unexpectedly.',
|
|
1490
|
-
},
|
|
1491
|
-
},
|
|
1492
|
-
success: {
|
|
1493
|
-
description: 'The created funding transfer with its one-time executable action.',
|
|
1494
|
-
example: createdFundingTransferExample,
|
|
1495
|
-
schema: schema.createFundingTransfer.Response,
|
|
1496
|
-
},
|
|
1497
|
-
}),
|
|
1498
|
-
summary: 'Create transfer',
|
|
1499
|
-
tags: ['Funding'],
|
|
1500
|
-
}),
|
|
1501
|
-
async (c) => {
|
|
1502
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1503
|
-
if (OpenApi.narrowValidation)
|
|
1504
|
-
return OpenApi.validationError(c, {
|
|
1505
|
-
code: 'body_invalid',
|
|
1506
|
-
message: 'Check the request body and try again.',
|
|
1507
|
-
})
|
|
1508
|
-
|
|
1509
|
-
const principal = Auth.getPrincipal(c)
|
|
1510
|
-
const owner = fundingOwner(c)
|
|
1511
|
-
if (!owner || principal?.type !== 'api_key') return fundingUnauthorized(c)
|
|
1512
|
-
c.header('Cache-Control', 'no-store')
|
|
1513
|
-
|
|
1514
|
-
const idempotencyKey = c.req.valid('header')['idempotency-key']
|
|
1515
|
-
|
|
1516
|
-
const body = c.req.valid('json')
|
|
1517
|
-
const db = Db.get(c.get('db'))
|
|
1518
|
-
const identity = {
|
|
1519
|
-
apiKeyId: principal.id,
|
|
1520
|
-
keyHash: Idempotency.inputHash(idempotencyKey),
|
|
1521
|
-
requestHash: Idempotency.inputHash(body),
|
|
1522
|
-
}
|
|
1523
|
-
const claim = await FundingIdempotency.claim(db, {
|
|
1524
|
-
...identity,
|
|
1525
|
-
ttlMs: idempotencyPendingTtlMs,
|
|
1526
|
-
})
|
|
1527
|
-
if (claim.type === 'mismatch')
|
|
1528
|
-
return Response.error(c, {
|
|
1529
|
-
code: 'idempotency_conflict',
|
|
1530
|
-
message: 'This Idempotency-Key was already used with different input.',
|
|
1531
|
-
status: 409,
|
|
1532
|
-
})
|
|
1533
|
-
if (claim.type === 'pending')
|
|
1534
|
-
return Response.error(c, {
|
|
1535
|
-
code: 'idempotency_in_progress',
|
|
1536
|
-
message: 'An equivalent funding transfer creation is still running.',
|
|
1537
|
-
status: 409,
|
|
1538
|
-
})
|
|
1539
|
-
if (claim.type === 'replay')
|
|
1540
|
-
return c.json(
|
|
1541
|
-
JSON.parse(claim.response) as z.output<typeof schema.createFundingTransfer.Response>,
|
|
1542
|
-
200,
|
|
1543
|
-
)
|
|
1544
|
-
|
|
1545
|
-
const release = async () => {
|
|
1546
|
-
try {
|
|
1547
|
-
await FundingIdempotency.release(db, {
|
|
1548
|
-
apiKeyId: identity.apiKeyId,
|
|
1549
|
-
createdAt: claim.createdAt,
|
|
1550
|
-
keyHash: identity.keyHash,
|
|
1551
|
-
})
|
|
1552
|
-
} catch {
|
|
1553
|
-
// The pending claim expires on its own; the response stays authoritative.
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
try {
|
|
1557
|
-
const attempts: FundingProvider.ProviderAttempt[] = []
|
|
1558
|
-
const catalog = await FundingCatalog.snapshot(c)
|
|
1559
|
-
const providers = c.get('providers').filter(FundingProvider.is)
|
|
1560
|
-
const selected = await FundingProvider.prepareTransfer({
|
|
1561
|
-
catalog,
|
|
1562
|
-
destinationChainId: c.get('chainId'),
|
|
1563
|
-
destinationToken: body.destinationToken,
|
|
1564
|
-
method: 'transaction',
|
|
1565
|
-
mode: body.mode,
|
|
1566
|
-
onAttempt: (attempt) => attempts.push(attempt),
|
|
1567
|
-
providers,
|
|
1568
|
-
recipient: body.recipient,
|
|
1569
|
-
sender: body.sender,
|
|
1570
|
-
slippageBps: body.slippageBps,
|
|
1571
|
-
sourceAmount: body.amount,
|
|
1572
|
-
sourceChain: body.sourceChain,
|
|
1573
|
-
sourceToken: body.sourceToken,
|
|
1574
|
-
})
|
|
1575
|
-
if (attempts.length > 0) c.set('fundingProviderAttempts', attempts.map(formatAttempt))
|
|
1576
|
-
if (!selected) {
|
|
1577
|
-
await release()
|
|
1578
|
-
return quoteNotAvailable(c)
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
const { candidate, result } = selected
|
|
1582
|
-
const feeToken = (side: 'destination' | 'source') =>
|
|
1583
|
-
side === 'source' ? candidate.sourceToken : candidate.destinationToken
|
|
1584
|
-
const snapshot: FundingTransfer.Snapshot = {
|
|
1585
|
-
destinationAmount: Value.tokenAmount({
|
|
1586
|
-
baseUnits: result.destinationAmount,
|
|
1587
|
-
currency: candidate.destinationToken.currency,
|
|
1588
|
-
decimals: candidate.destinationToken.decimals,
|
|
1589
|
-
}),
|
|
1590
|
-
destinationAmountMin: Value.tokenAmount({
|
|
1591
|
-
baseUnits: result.destinationAmountMin,
|
|
1592
|
-
currency: candidate.destinationToken.currency,
|
|
1593
|
-
decimals: candidate.destinationToken.decimals,
|
|
1594
|
-
}),
|
|
1595
|
-
destinationChain: candidate.destinationChain,
|
|
1596
|
-
destinationToken: candidate.destinationToken,
|
|
1597
|
-
fees: result.fees.map((fee) => ({
|
|
1598
|
-
amount: Value.tokenAmount({
|
|
1599
|
-
baseUnits: fee.amount,
|
|
1600
|
-
currency: feeToken(fee.side).currency,
|
|
1601
|
-
decimals: feeToken(fee.side).decimals,
|
|
1602
|
-
}),
|
|
1603
|
-
side: fee.side,
|
|
1604
|
-
token: feeToken(fee.side),
|
|
1605
|
-
type: 'provider',
|
|
1606
|
-
})),
|
|
1607
|
-
method: 'transaction',
|
|
1608
|
-
mode: body.mode,
|
|
1609
|
-
provider: candidate.provider,
|
|
1610
|
-
quote: { expiresAt: result.expiresAt, sampledAt: result.sampledAt },
|
|
1611
|
-
recipient: body.recipient,
|
|
1612
|
-
sender: body.sender,
|
|
1613
|
-
sourceAmount: Value.tokenAmount({
|
|
1614
|
-
baseUnits: candidate.sourceAmount.amount,
|
|
1615
|
-
currency: candidate.sourceToken.currency,
|
|
1616
|
-
decimals: candidate.sourceToken.decimals,
|
|
1617
|
-
}),
|
|
1618
|
-
sourceChain: candidate.sourceChain,
|
|
1619
|
-
sourceToken: candidate.sourceToken,
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
// The transfer row, its version-1 event, and the replay body commit
|
|
1623
|
-
// atomically so a crash cannot leave a claimed key without a replay.
|
|
1624
|
-
const created = await db.transaction(async (tx) => {
|
|
1625
|
-
const record = await FundingTransfer.createIn(tx, {
|
|
1626
|
-
apiKeyId: principal.id,
|
|
1627
|
-
environment: owner.environment,
|
|
1628
|
-
orgId: owner.orgId,
|
|
1629
|
-
projectId: owner.projectId,
|
|
1630
|
-
providerState: result.correlation,
|
|
1631
|
-
snapshot,
|
|
1632
|
-
})
|
|
1633
|
-
const validated = Response.validated(
|
|
1634
|
-
schema.createFundingTransfer.Response,
|
|
1635
|
-
FundingTransfer.toCreated(record, result.action),
|
|
1636
|
-
)
|
|
1637
|
-
const completion = await FundingIdempotency.complete(tx, {
|
|
1638
|
-
apiKeyId: identity.apiKeyId,
|
|
1639
|
-
createdAt: claim.createdAt,
|
|
1640
|
-
keyHash: identity.keyHash,
|
|
1641
|
-
replayTtlMs: idempotencyReplayTtlMs,
|
|
1642
|
-
response: JSON.stringify(validated),
|
|
1643
|
-
transferId: record.id,
|
|
1644
|
-
})
|
|
1645
|
-
if (!completion)
|
|
1646
|
-
throw new IdempotencyLeaseLostError('Funding transfer creation lease was lost.')
|
|
1647
|
-
return validated
|
|
1648
|
-
})
|
|
1649
|
-
return c.json(created, 200)
|
|
1650
|
-
} catch (cause) {
|
|
1651
|
-
await release()
|
|
1652
|
-
if (cause instanceof FundingProvider.InvalidAmountError)
|
|
1653
|
-
return Response.error(c, { code: 'body_invalid', message: 'Invalid `amount`.', status: 400 }) // prettier-ignore
|
|
1654
|
-
if (cause instanceof FundingProvider.UnsupportedSourceChainError)
|
|
1655
|
-
return quoteNotAvailable(c)
|
|
1656
|
-
if (cause instanceof FundingProvider.ProviderQuoteError) {
|
|
1657
|
-
if (cause.fundingProviderAttempts.length > 0)
|
|
1658
|
-
c.set('fundingProviderAttempts', cause.fundingProviderAttempts)
|
|
1659
|
-
if (cause.providerFailure) c.set('providerFailure', cause.providerFailure)
|
|
1660
|
-
if (cause.providerFailures) c.set('providerFailures', cause.providerFailures)
|
|
1661
|
-
return Response.error(c, {
|
|
1662
|
-
code: 'upstream_error',
|
|
1663
|
-
message: 'No funding provider could prepare the transfer.',
|
|
1664
|
-
status: 502,
|
|
1665
|
-
})
|
|
1666
|
-
}
|
|
1667
|
-
throw cause
|
|
1668
|
-
}
|
|
1669
|
-
},
|
|
1670
|
-
)
|
|
1671
|
-
.post(
|
|
1672
|
-
'/v1/funding/transfers/vault',
|
|
1673
|
-
Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
|
|
1674
|
-
OpenApi.validate('header', schema.createFundingTransferVault.Headers, {
|
|
1675
|
-
code: 'idempotency_key_required',
|
|
1676
|
-
message: 'Funding transfer creation requires an `Idempotency-Key` header.',
|
|
1677
|
-
}),
|
|
1678
|
-
OpenApi.validate('json', schema.createFundingTransferVault.Body, {
|
|
1679
|
-
code: 'body_invalid',
|
|
1680
|
-
message: 'Check the request body and try again.',
|
|
1681
|
-
}),
|
|
1682
|
-
OpenApi.describeRoute({
|
|
1683
|
-
description: 'Creates a transfer into a Tempo Earn vault.',
|
|
1684
|
-
operationId: 'createFundingTransferVault',
|
|
1685
|
-
responses: unimplementedFundingTransferResponses,
|
|
1686
|
-
summary: 'Create transfer into vault',
|
|
1687
|
-
tags: ['Funding'],
|
|
1688
|
-
}),
|
|
1689
|
-
(c) => {
|
|
1690
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1691
|
-
if (OpenApi.narrowValidation)
|
|
1692
|
-
return OpenApi.validationError(c, {
|
|
1693
|
-
code: 'body_invalid',
|
|
1694
|
-
message: 'Check the request body and try again.',
|
|
1695
|
-
})
|
|
1696
|
-
|
|
1697
|
-
c.header('Cache-Control', 'no-store')
|
|
1698
|
-
return Response.error(c, {
|
|
1699
|
-
code: 'not_implemented',
|
|
1700
|
-
message: 'Vault funding transfers are not implemented.',
|
|
1701
|
-
status: 501,
|
|
1702
|
-
})
|
|
1703
|
-
},
|
|
1704
|
-
)
|
|
1705
|
-
.post(
|
|
1706
|
-
'/v1/funding/transfers/zone',
|
|
1707
|
-
Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
|
|
1708
|
-
OpenApi.validate('header', schema.createFundingTransferZone.Headers, {
|
|
1709
|
-
code: 'idempotency_key_required',
|
|
1710
|
-
message: 'Funding transfer creation requires an `Idempotency-Key` header.',
|
|
1711
|
-
}),
|
|
1712
|
-
OpenApi.validate('json', schema.createFundingTransferZone.Body, {
|
|
1713
|
-
code: 'body_invalid',
|
|
1714
|
-
message: 'Check the request body and try again.',
|
|
1715
|
-
}),
|
|
1716
|
-
OpenApi.describeRoute({
|
|
1717
|
-
description: 'Creates a transfer into a Tempo Zone.',
|
|
1718
|
-
operationId: 'createFundingTransferZone',
|
|
1719
|
-
responses: unimplementedFundingTransferResponses,
|
|
1720
|
-
summary: 'Create transfer into zone',
|
|
1721
|
-
tags: ['Funding'],
|
|
1722
|
-
}),
|
|
1723
|
-
(c) => {
|
|
1724
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
1725
|
-
if (OpenApi.narrowValidation)
|
|
1726
|
-
return OpenApi.validationError(c, {
|
|
1727
|
-
code: 'body_invalid',
|
|
1728
|
-
message: 'Check the request body and try again.',
|
|
1729
|
-
})
|
|
1730
|
-
|
|
1731
|
-
c.header('Cache-Control', 'no-store')
|
|
1732
|
-
return Response.error(c, {
|
|
1733
|
-
code: 'not_implemented',
|
|
1734
|
-
message: 'Zone funding transfers are not implemented.',
|
|
1735
|
-
status: 501,
|
|
1736
|
-
})
|
|
1737
|
-
},
|
|
1738
|
-
)
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
function quoteNotAvailable(c: Context<App.Environment>) {
|
|
1742
|
-
return Response.error(c, {
|
|
1743
|
-
code: 'quote_not_available',
|
|
1744
|
-
message: 'No provider can prepare the requested funding route.',
|
|
1745
|
-
status: 404,
|
|
1746
|
-
})
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
function depositAddressNotAvailable(c: Context<App.Environment>) {
|
|
1750
|
-
return Response.error(c, {
|
|
1751
|
-
code: 'deposit_address_not_available',
|
|
1752
|
-
message: 'No provider can provision the requested reusable deposit address.',
|
|
1753
|
-
status: 404,
|
|
1754
|
-
})
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
/** Ownership filter from the API-key principal; other principals get no funding access. */
|
|
1758
|
-
function fundingOwner(c: Context<App.Environment>): FundingTransfers.Owner | null {
|
|
1759
|
-
const principal = Auth.getPrincipal(c)
|
|
1760
|
-
if (principal?.type !== 'api_key') return null
|
|
1761
|
-
return {
|
|
1762
|
-
environment: principal.environment,
|
|
1763
|
-
orgId: principal.orgId,
|
|
1764
|
-
projectId: principal.projectId,
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
function fundingUnauthorized(c: Context<App.Environment>) {
|
|
1769
|
-
return Response.error(c, {
|
|
1770
|
-
code: 'unauthorized',
|
|
1771
|
-
message: 'Tempo could not determine which organization owns this funding resource.',
|
|
1772
|
-
status: 401,
|
|
1773
|
-
})
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
function formatAttempt(attempt: FundingProvider.ProviderAttempt): Log.FundingProviderAttempt {
|
|
1777
|
-
const { id } = attempt.provider
|
|
1778
|
-
if (attempt.outcome !== 'failed')
|
|
1779
|
-
return {
|
|
1780
|
-
durationMs: attempt.durationMs,
|
|
1781
|
-
id,
|
|
1782
|
-
operation: attempt.operation,
|
|
1783
|
-
outcome: attempt.outcome,
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
return {
|
|
1787
|
-
durationMs: attempt.durationMs,
|
|
1788
|
-
failure: FundingProvider.failure({
|
|
1789
|
-
cause: attempt.cause,
|
|
1790
|
-
operation: attempt.operation,
|
|
1791
|
-
provider: attempt.provider,
|
|
1792
|
-
}).failure,
|
|
1793
|
-
id,
|
|
1794
|
-
operation: attempt.operation,
|
|
1795
|
-
outcome: attempt.outcome,
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
// Maps caller-input errors to a specific 400 message; anything else rethrows.
|
|
1800
|
-
function requestErrorMessage(cause: unknown) {
|
|
1801
|
-
if (cause instanceof FundingProvider.InvalidAmountError) return 'Invalid `sourceAmount`.'
|
|
1802
|
-
if (cause instanceof FundingProvider.UnsupportedDestinationTokenError)
|
|
1803
|
-
return 'Unsupported `destinationToken`.'
|
|
1804
|
-
if (cause instanceof FundingProvider.UnsupportedProviderError) return 'Unsupported `provider`.'
|
|
1805
|
-
if (cause instanceof FundingProvider.UnsupportedSourceChainError)
|
|
1806
|
-
return 'Unsupported `sourceChain`.'
|
|
1807
|
-
return undefined
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
function cacheBypass(request: Request) {
|
|
1811
|
-
const directives = (
|
|
1812
|
-
request.headers.get('Cache-Control') ??
|
|
1813
|
-
request.headers.get('Pragma') ??
|
|
1814
|
-
''
|
|
1815
|
-
).toLowerCase()
|
|
1816
|
-
return directives.includes('no-cache') || directives.includes('no-store')
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
class IdempotencyLeaseLostError extends Error {
|
|
1820
|
-
override name = 'Funding.IdempotencyLeaseLostError'
|
|
1821
|
-
}
|