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
|
@@ -359,9 +359,13 @@ export class ConfigError extends Error {
|
|
|
359
359
|
|
|
360
360
|
/** Failed ClickHouse insert (non-2xx HTTP response). */
|
|
361
361
|
export class InsertError extends Error {
|
|
362
|
+
/** HTTP response status returned by ClickHouse. */
|
|
363
|
+
status: number
|
|
364
|
+
|
|
362
365
|
constructor(status: number, body: string) {
|
|
363
366
|
super(`ClickHouse insert failed with status ${status}: ${body.slice(0, 500)}`)
|
|
364
367
|
this.name = 'Analytics.InsertError'
|
|
368
|
+
this.status = status
|
|
365
369
|
}
|
|
366
370
|
}
|
|
367
371
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { App, Store } from 'tapimo'
|
|
2
|
-
import { data, management, mpp, relay } from 'tapimo/apps'
|
|
2
|
+
import { data, funding, management, mpp, relay } from 'tapimo/apps'
|
|
3
3
|
import { Hono } from 'hono'
|
|
4
4
|
import { describeRoute } from 'hono-openapi'
|
|
5
5
|
import * as TestApp from '../../test/App.js'
|
|
@@ -14,8 +14,16 @@ async function document(app: { request: (path: string) => Promise<Response> | Re
|
|
|
14
14
|
const res = await app.request('/openapi.json')
|
|
15
15
|
return (await res.json()) as {
|
|
16
16
|
components: { securitySchemes: Record<string, unknown> }
|
|
17
|
-
paths: Record<
|
|
18
|
-
|
|
17
|
+
paths: Record<
|
|
18
|
+
string,
|
|
19
|
+
Partial<Record<'get' | 'post', { operationId: string; tags?: readonly string[] | undefined }>>
|
|
20
|
+
>
|
|
21
|
+
tags: readonly {
|
|
22
|
+
description: string
|
|
23
|
+
name: string
|
|
24
|
+
'x-displayName'?: string | undefined
|
|
25
|
+
'x-pagePath'?: string | undefined
|
|
26
|
+
}[]
|
|
19
27
|
webhooks?: Record<string, unknown> | undefined
|
|
20
28
|
'x-tagGroups'?: readonly { name: string; tags: readonly string[] }[] | undefined
|
|
21
29
|
}
|
|
@@ -146,10 +154,12 @@ describe('data()', () => {
|
|
|
146
154
|
expect(keys.some((path) => path.startsWith('/v1/blocks'))).toBe(true)
|
|
147
155
|
expect(keys.some((path) => path.startsWith('/v1/orgs'))).toBe(false)
|
|
148
156
|
expect(keys.some((path) => path.startsWith('/v1/me'))).toBe(false)
|
|
157
|
+
expect(keys.some((path) => path.startsWith('/v1/funding'))).toBe(false)
|
|
149
158
|
|
|
150
159
|
// The omitted management route resolves to a 404 (never mounted), not a 401.
|
|
151
160
|
const orgs = await app.request('/v1/orgs')
|
|
152
161
|
expect(orgs.status).toBe(404)
|
|
162
|
+
expect((await app.request('/v1/funding/chains')).status).toBe(404)
|
|
153
163
|
})
|
|
154
164
|
|
|
155
165
|
test('inherits shared chain context', async () => {
|
|
@@ -272,6 +282,123 @@ describe('data()', () => {
|
|
|
272
282
|
})
|
|
273
283
|
})
|
|
274
284
|
|
|
285
|
+
describe('funding()', () => {
|
|
286
|
+
test('mounts the Funding & Bridge API independently', async () => {
|
|
287
|
+
const group = funding()
|
|
288
|
+
expectTypeOf(App.funding).toEqualTypeOf<typeof funding>()
|
|
289
|
+
expectTypeOf(group).toEqualTypeOf<App.FundingApp>()
|
|
290
|
+
const app = App.create({ auth: false, db: TestApp.database() }).route('/', group)
|
|
291
|
+
const spec = await document(app)
|
|
292
|
+
|
|
293
|
+
expect(
|
|
294
|
+
Object.keys(spec.paths)
|
|
295
|
+
.filter((path) => path.startsWith('/v1/funding'))
|
|
296
|
+
.sort(),
|
|
297
|
+
).toEqual(
|
|
298
|
+
[
|
|
299
|
+
'/v1/funding/chains',
|
|
300
|
+
'/v1/funding/deposit-addresses',
|
|
301
|
+
'/v1/funding/deposit-addresses/{id}',
|
|
302
|
+
'/v1/funding/deposit-addresses/{id}/reconcile',
|
|
303
|
+
'/v1/funding/deposits',
|
|
304
|
+
'/v1/funding/deposits/{id}',
|
|
305
|
+
'/v1/funding/providers',
|
|
306
|
+
'/v1/funding/quotes',
|
|
307
|
+
'/v1/funding/transfers',
|
|
308
|
+
'/v1/funding/transfers/vault',
|
|
309
|
+
'/v1/funding/transfers/zone',
|
|
310
|
+
'/v1/funding/transfers/{id}',
|
|
311
|
+
].sort(),
|
|
312
|
+
)
|
|
313
|
+
expect(Object.keys(spec.paths).some((path) => path.startsWith('/v1/tokens'))).toBe(false)
|
|
314
|
+
expect(spec.paths['/webhooks/funding/{providerId}']).toBeUndefined()
|
|
315
|
+
expect(spec['x-tagGroups']).toEqual([
|
|
316
|
+
{
|
|
317
|
+
name: 'Funding & Bridge API',
|
|
318
|
+
tags: ['Chains', 'Deposit Addresses', 'Providers', 'Quotes', 'Funding Transfers'],
|
|
319
|
+
},
|
|
320
|
+
])
|
|
321
|
+
expect(
|
|
322
|
+
Object.values(spec.paths).flatMap((path) =>
|
|
323
|
+
[path.get, path.post].flatMap((operation) =>
|
|
324
|
+
operation?.tags?.includes('Deposit Addresses') ? [operation.operationId] : [],
|
|
325
|
+
),
|
|
326
|
+
),
|
|
327
|
+
).toEqual([
|
|
328
|
+
'createFundingDepositAddress',
|
|
329
|
+
'reconcileFundingDepositAddress',
|
|
330
|
+
'getFundingDepositAddress',
|
|
331
|
+
'getFundingDeposit',
|
|
332
|
+
'listFundingDeposits',
|
|
333
|
+
])
|
|
334
|
+
expect((await app.request('/v1/tokens')).status).toBe(404)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test('keeps data and funding transfers separate with the same display name', async () => {
|
|
338
|
+
const app = App.create({ auth: false, db: TestApp.database() })
|
|
339
|
+
.route('/', data())
|
|
340
|
+
.route('/', funding())
|
|
341
|
+
const spec = await document(app)
|
|
342
|
+
const operationTag = (path: string, method: 'get' | 'post') =>
|
|
343
|
+
(spec.paths[path] as Record<string, { tags?: readonly string[] | undefined } | undefined>)[
|
|
344
|
+
method
|
|
345
|
+
]?.tags
|
|
346
|
+
|
|
347
|
+
expect({
|
|
348
|
+
chains: operationTag('/v1/funding/chains', 'get'),
|
|
349
|
+
depositAddress: operationTag('/v1/funding/deposit-addresses', 'post'),
|
|
350
|
+
deposits: operationTag('/v1/funding/deposits', 'get'),
|
|
351
|
+
providers: operationTag('/v1/funding/providers', 'get'),
|
|
352
|
+
quotes: operationTag('/v1/funding/quotes', 'get'),
|
|
353
|
+
tokenTransfers: operationTag('/v1/transfers', 'get'),
|
|
354
|
+
transfers: operationTag('/v1/funding/transfers', 'get'),
|
|
355
|
+
vault: operationTag('/v1/funding/transfers/vault', 'post'),
|
|
356
|
+
zone: operationTag('/v1/funding/transfers/zone', 'post'),
|
|
357
|
+
}).toEqual({
|
|
358
|
+
chains: ['Chains'],
|
|
359
|
+
depositAddress: ['Deposit Addresses'],
|
|
360
|
+
deposits: ['Deposit Addresses'],
|
|
361
|
+
providers: ['Providers'],
|
|
362
|
+
quotes: ['Quotes'],
|
|
363
|
+
tokenTransfers: ['Transfers'],
|
|
364
|
+
transfers: ['Funding Transfers'],
|
|
365
|
+
vault: ['Funding Transfers'],
|
|
366
|
+
zone: ['Funding Transfers'],
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
const groups = Object.fromEntries(
|
|
370
|
+
(spec['x-tagGroups'] ?? []).map((group) => [group.name, group.tags]),
|
|
371
|
+
)
|
|
372
|
+
expect(groups['Data API']).toContain('Transfers')
|
|
373
|
+
expect(groups['Data API']).not.toContain('Funding Transfers')
|
|
374
|
+
expect(groups['Funding & Bridge API']).toContain('Funding Transfers')
|
|
375
|
+
expect(groups['Funding & Bridge API']).not.toContain('Transfers')
|
|
376
|
+
expect(spec.tags.filter((tag) => tag.name === 'Transfers')).toHaveLength(1)
|
|
377
|
+
expect(spec.tags.filter((tag) => tag.name === 'Funding Transfers')).toEqual([
|
|
378
|
+
{
|
|
379
|
+
description: 'Inbound funding transfers.',
|
|
380
|
+
name: 'Funding Transfers',
|
|
381
|
+
'x-displayName': 'Transfers',
|
|
382
|
+
'x-pagePath': 'funding/transfers',
|
|
383
|
+
},
|
|
384
|
+
])
|
|
385
|
+
expect(
|
|
386
|
+
Object.fromEntries(
|
|
387
|
+
(groups['Funding & Bridge API'] ?? []).map((name) => {
|
|
388
|
+
const tag = spec.tags.find((tag) => tag.name === name)
|
|
389
|
+
return [tag?.['x-displayName'] ?? tag?.name, tag?.['x-pagePath']]
|
|
390
|
+
}),
|
|
391
|
+
),
|
|
392
|
+
).toEqual({
|
|
393
|
+
Chains: 'funding/chains',
|
|
394
|
+
'Deposit Addresses': 'funding/deposit-addresses',
|
|
395
|
+
Providers: 'funding/providers',
|
|
396
|
+
Quotes: 'funding/quotes',
|
|
397
|
+
Transfers: 'funding/transfers',
|
|
398
|
+
})
|
|
399
|
+
})
|
|
400
|
+
})
|
|
401
|
+
|
|
275
402
|
describe('relay()', () => {
|
|
276
403
|
test('mounts the relay after data() without documenting RPC routes', async () => {
|
|
277
404
|
const composed = App.create({ db: TestApp.database() }).route('/', data()).route('/', relay())
|
|
@@ -386,18 +386,6 @@ describe('zone chain gate', () => {
|
|
|
386
386
|
expect((await privateApp.request(path)).status).toBe(403)
|
|
387
387
|
})
|
|
388
388
|
|
|
389
|
-
test('public chains remain edge-cacheable on a Zone deployment', async () => {
|
|
390
|
-
const store = Store.memory()
|
|
391
|
-
const app = TestApp.create({ auth: false, cache: { store }, zones: [zoneOptions(zone)] })
|
|
392
|
-
const path = '/v1/funding/chains'
|
|
393
|
-
|
|
394
|
-
const first = await app.request(path)
|
|
395
|
-
const second = await app.request(path)
|
|
396
|
-
expect(first.status).toBe(200)
|
|
397
|
-
expect(second.status).toBe(200)
|
|
398
|
-
expect(second.headers.get('cache-control')).toContain('public')
|
|
399
|
-
})
|
|
400
|
-
|
|
401
389
|
test('serves anonymous token lists from the edge cache on public chains', async () => {
|
|
402
390
|
const store = Store.memory()
|
|
403
391
|
const app = TestApp.create({ auth: false, cache: { store }, zones: [zoneOptions(zone)] })
|
package/src/apps/data/App.ts
CHANGED
|
@@ -25,7 +25,6 @@ import { coingecko } from './routes/coingecko.js'
|
|
|
25
25
|
import { earn } from './routes/earn.js'
|
|
26
26
|
import { exchanges } from './routes/exchanges.js'
|
|
27
27
|
import { feeAmm } from './routes/fee-amm.js'
|
|
28
|
-
import { funding } from './routes/funding.js'
|
|
29
28
|
import { indexer } from './routes/indexer.js'
|
|
30
29
|
import { receipts } from './routes/receipts.js'
|
|
31
30
|
import { rpc } from './routes/rpc.js'
|
|
@@ -217,7 +216,6 @@ export function data(options: data.Options = {}) {
|
|
|
217
216
|
.route('/', earn_routes)
|
|
218
217
|
.route('/', exchanges({ fx: options.fx }))
|
|
219
218
|
.route('/', feeAmm())
|
|
220
|
-
.route('/', funding())
|
|
221
219
|
.route('/', indexer_routes)
|
|
222
220
|
.route('/', receipts({ fx: options.fx }))
|
|
223
221
|
.route('/', tokenlist())
|
|
@@ -375,13 +373,12 @@ const openapi = {
|
|
|
375
373
|
{ name: 'Earn', description: 'Vaults that earn yield from assets deposited on Tempo.' },
|
|
376
374
|
{ name: 'Exchange', description: "Tempo's built-in stablecoin exchange: pairs, swaps, orders, prices." }, // prettier-ignore
|
|
377
375
|
{ name: 'Fee AMM', description: 'Pools that convert stablecoins to pay fees.' },
|
|
378
|
-
{
|
|
379
|
-
name: 'Funding',
|
|
380
|
-
description: 'Quote, transfer, and deposit management for funding Tempo from other chains.',
|
|
381
|
-
},
|
|
382
376
|
{ name: 'Tokens', description: 'TIP-20 token details, supply, and holders.' },
|
|
383
377
|
{ name: 'Transactions', description: 'Transactions submitted to Tempo, and their receipts.' },
|
|
384
|
-
{
|
|
378
|
+
{
|
|
379
|
+
name: 'Transfers',
|
|
380
|
+
description: 'Token movements from executing transactions.',
|
|
381
|
+
},
|
|
385
382
|
{ name: 'Verified Tokens', description: 'A curated, trusted list of TIP-20 tokens.' },
|
|
386
383
|
{ name: 'Webhooks', description: 'Get signed callbacks when onchain events happen.' },
|
|
387
384
|
{ name: 'Zones', description: 'Private chain operations anchored to Tempo.' },
|
|
@@ -399,7 +396,6 @@ const openapi = {
|
|
|
399
396
|
'Earn',
|
|
400
397
|
'Exchange',
|
|
401
398
|
'Fee AMM',
|
|
402
|
-
'Funding',
|
|
403
399
|
'Tokens',
|
|
404
400
|
'Transactions',
|
|
405
401
|
'Transfers',
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Schema } from 'tapimo'
|
|
4
4
|
import { FxOracle } from 'tapimo/apps'
|
|
5
5
|
import { Value as core_Value } from 'ox'
|
|
6
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
6
7
|
import { Actions } from 'viem/tempo'
|
|
7
8
|
|
|
8
9
|
import * as TestApp from '../../../../test/App.js'
|
|
@@ -469,3 +470,44 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
469
470
|
expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
|
|
470
471
|
})
|
|
471
472
|
})
|
|
473
|
+
|
|
474
|
+
describe('GET /v1/addresses/:address/balances/:token', () => {
|
|
475
|
+
test('returns one live balance without indexed holder discovery', async () => {
|
|
476
|
+
const client = app()
|
|
477
|
+
const holder = await holderWithBalance
|
|
478
|
+
const response = await client.v1.addresses[':address'].balances[':token'].$get(
|
|
479
|
+
{
|
|
480
|
+
param: { address: holder, token: TestApp.tokenWithHolders },
|
|
481
|
+
query: {},
|
|
482
|
+
},
|
|
483
|
+
TestApp.auth,
|
|
484
|
+
)
|
|
485
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalance.Response)
|
|
486
|
+
|
|
487
|
+
expect(response.status).toBe(200)
|
|
488
|
+
expect(body.id).toBe(TestApp.tokenWithHolders)
|
|
489
|
+
expect(body.token.address).toBe(TestApp.tokenWithHolders)
|
|
490
|
+
expect(body.amount).toMatch(/^\d+$/)
|
|
491
|
+
expect(core_Value.from(body.formatted, body.decimals)).toBe(BigInt(body.amount))
|
|
492
|
+
expect(body.valuation).toEqual({ amount: body.formatted, currency: 'USD' })
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
test('returns zero for a token the account does not hold', async () => {
|
|
496
|
+
const client = app()
|
|
497
|
+
const response = await client.v1.addresses[':address'].balances[':token'].$get(
|
|
498
|
+
{
|
|
499
|
+
param: {
|
|
500
|
+
address: privateKeyToAccount(generatePrivateKey()).address,
|
|
501
|
+
token: TestApp.tokenWithHolders,
|
|
502
|
+
},
|
|
503
|
+
query: {},
|
|
504
|
+
},
|
|
505
|
+
TestApp.auth,
|
|
506
|
+
)
|
|
507
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalance.Response)
|
|
508
|
+
|
|
509
|
+
expect(response.status).toBe(200)
|
|
510
|
+
expect(body.amount).toBe('0')
|
|
511
|
+
expect(body.formatted).toBe('0')
|
|
512
|
+
})
|
|
513
|
+
})
|
|
@@ -181,102 +181,235 @@ export namespace schema {
|
|
|
181
181
|
})
|
|
182
182
|
.check(z.describe('A page of TIP-20 token balances held by this account.'))
|
|
183
183
|
}
|
|
184
|
+
|
|
185
|
+
/** Schemas for the getAddressBalance operation. */
|
|
186
|
+
export namespace getAddressBalance {
|
|
187
|
+
/** Path parameters for a single address balance request. */
|
|
188
|
+
export const Params = z
|
|
189
|
+
.object({
|
|
190
|
+
address: Schema.Address.check(
|
|
191
|
+
z.describe('The account address whose token balance you want to read.'),
|
|
192
|
+
),
|
|
193
|
+
token: Schema.TokenAddress.check(z.describe('The TIP-20 token contract address.')),
|
|
194
|
+
})
|
|
195
|
+
.check(z.describe('Path parameters for reading one account token balance.'))
|
|
196
|
+
|
|
197
|
+
/** Query parameters for a single address balance request. */
|
|
198
|
+
export const Query = z
|
|
199
|
+
.strictObject({
|
|
200
|
+
chainId: Schema.ChainIdQuery,
|
|
201
|
+
'valuation.currency': Schema.Denomination,
|
|
202
|
+
})
|
|
203
|
+
.check(z.describe('Query parameters for reading one account token balance.'))
|
|
204
|
+
|
|
205
|
+
/** A single TIP-20 token balance held by an address. */
|
|
206
|
+
export const Response = getAddressBalances.Balance.check(
|
|
207
|
+
z.describe('One TIP-20 token balance held by this account.'),
|
|
208
|
+
)
|
|
209
|
+
}
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
/**
|
|
187
213
|
* Creates address-scoped balance handlers, mounted under the `/addresses`
|
|
188
|
-
* composer. Exposes
|
|
214
|
+
* composer. Exposes the held-balance page and individual token balances.
|
|
189
215
|
*/
|
|
190
216
|
export function addresses(options: addresses.Options = {}) {
|
|
191
217
|
const oracle = options.fx?.oracle ?? FxOracle.ecb()
|
|
192
|
-
return new Hono<App.Environment>()
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
OpenApi.validate('query', schema.getAddressBalances.Query, {
|
|
200
|
-
code: 'query_invalid',
|
|
201
|
-
message: 'Invalid query parameters',
|
|
202
|
-
}),
|
|
203
|
-
OpenApi.describeRoute({
|
|
204
|
-
description:
|
|
205
|
-
'Lists how much of each TIP-20 token an account holds, ordered from largest to smallest balance. Amounts are returned in raw base units and human-readable form, and each verified holding carries its nominal value in the requested denomination.',
|
|
206
|
-
operationId: 'getAddressBalances',
|
|
207
|
-
responses: OpenApi.responses({
|
|
208
|
-
errors: { 502: 'Could not read balance or token data from an upstream service.' },
|
|
209
|
-
success: {
|
|
210
|
-
description: 'A page of token balances for this account.',
|
|
211
|
-
schema: schema.getAddressBalances.Response,
|
|
212
|
-
},
|
|
218
|
+
return new Hono<App.Environment>()
|
|
219
|
+
.get(
|
|
220
|
+
'/v1/addresses/:address/balances',
|
|
221
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
222
|
+
OpenApi.validate('param', schema.getAddressBalances.Params, {
|
|
223
|
+
code: 'address_invalid',
|
|
224
|
+
message: 'Invalid account address',
|
|
213
225
|
}),
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
226
|
+
OpenApi.validate('query', schema.getAddressBalances.Query, {
|
|
227
|
+
code: 'query_invalid',
|
|
228
|
+
message: 'Invalid query parameters',
|
|
229
|
+
}),
|
|
230
|
+
OpenApi.describeRoute({
|
|
231
|
+
description:
|
|
232
|
+
'Lists how much of each TIP-20 token an account holds, ordered from largest to smallest balance. Amounts are returned in raw base units and human-readable form, and each verified holding carries its nominal value in the requested denomination.',
|
|
233
|
+
operationId: 'getAddressBalances',
|
|
234
|
+
responses: OpenApi.responses({
|
|
235
|
+
errors: { 502: 'Could not read balance or token data from an upstream service.' },
|
|
236
|
+
success: {
|
|
237
|
+
description: 'A page of token balances for this account.',
|
|
238
|
+
schema: schema.getAddressBalances.Response,
|
|
239
|
+
},
|
|
240
|
+
}),
|
|
241
|
+
summary: 'List address balances',
|
|
242
|
+
tags: ['Balances'],
|
|
243
|
+
}),
|
|
244
|
+
Cache.response<App.Environment>({
|
|
245
|
+
cacheControl: Cache.policies.noStore,
|
|
246
|
+
name: 'tempo-api:addresses:v1',
|
|
247
|
+
key: (c) => Cache.urlKey(c, schema.getAddressBalances.Query),
|
|
248
|
+
}),
|
|
249
|
+
async (c) => {
|
|
250
|
+
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
251
|
+
if (OpenApi.narrowValidation)
|
|
252
|
+
return OpenApi.validationError(c, {
|
|
253
|
+
code: 'address_invalid',
|
|
254
|
+
message: 'Invalid request parameters',
|
|
255
|
+
})
|
|
256
|
+
if (OpenApi.narrowValidation)
|
|
257
|
+
return OpenApi.validationError(c, {
|
|
258
|
+
code: 'query_invalid',
|
|
259
|
+
message: 'Invalid request parameters',
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
const { address } = c.req.valid('param')
|
|
263
|
+
const query = c.req.valid('query')
|
|
237
264
|
|
|
238
|
-
|
|
265
|
+
try {
|
|
266
|
+
const chainId = query.chainId ?? c.get('chainId')
|
|
267
|
+
// `currency` only makes sense against verified tokens, so it implies
|
|
268
|
+
// `verified=true` from the caller's perspective.
|
|
269
|
+
const verified = query.verified || query.currency !== undefined
|
|
270
|
+
const feeEligible = query.feeEligible ?? false
|
|
271
|
+
|
|
272
|
+
// The fee-token set powers both the per-row `feeEligible` flag (always)
|
|
273
|
+
// and the optional `feeEligible=true` page restriction. It is memoized
|
|
274
|
+
// per chain (a near-constant), so one read feeds the page, the count,
|
|
275
|
+
// and the row enrichment. It is a best-effort enrichment: a lookup
|
|
276
|
+
// failure omits `feeEligible` rather than failing the page — unless the
|
|
277
|
+
// caller explicitly asked to filter by `feeEligible`, where the filter
|
|
278
|
+
// can't be honored, so the failure surfaces as the declared `502`.
|
|
279
|
+
const feeTokens = await FeeAmm.feeTokenSet(c, chainId).catch((cause) => {
|
|
280
|
+
if (feeEligible) throw cause
|
|
281
|
+
return undefined
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const balances = await getAddressBalances(c, {
|
|
285
|
+
address,
|
|
286
|
+
chainId,
|
|
287
|
+
currency: query.currency,
|
|
288
|
+
cursor: query.cursor,
|
|
289
|
+
denomination: query['valuation.currency'] ?? 'USD',
|
|
290
|
+
feeEligible,
|
|
291
|
+
feeTokens,
|
|
292
|
+
includeTotalCount: query.include.includes('totalCount'),
|
|
293
|
+
limit: query.limit,
|
|
294
|
+
oracle,
|
|
295
|
+
page: query.page,
|
|
296
|
+
verified,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
return c.json(Response.validated(schema.getAddressBalances.Response, balances), 200)
|
|
300
|
+
} catch (cause) {
|
|
301
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
302
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
303
|
+
return Response.upstream(c, cause)
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
.get(
|
|
308
|
+
'/v1/addresses/:address/balances/:token',
|
|
309
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
310
|
+
OpenApi.validate('param', schema.getAddressBalance.Params, {
|
|
311
|
+
code: 'address_invalid',
|
|
312
|
+
message: 'Invalid address or token address',
|
|
313
|
+
}),
|
|
314
|
+
OpenApi.validate('query', schema.getAddressBalance.Query, {
|
|
315
|
+
code: 'query_invalid',
|
|
316
|
+
message: 'Invalid query parameters',
|
|
317
|
+
}),
|
|
318
|
+
OpenApi.describeRoute({
|
|
319
|
+
description:
|
|
320
|
+
'Returns the live balance of one TIP-20 token held by an account, including token metadata and a human-readable amount.',
|
|
321
|
+
operationId: 'getAddressBalance',
|
|
322
|
+
responses: OpenApi.responses({
|
|
323
|
+
errors: {
|
|
324
|
+
404: {
|
|
325
|
+
description: 'The token is not a registered TIP-20 token.',
|
|
326
|
+
codes: ['token_not_found'],
|
|
327
|
+
},
|
|
328
|
+
502: 'Could not read balance or token data from an upstream service.',
|
|
329
|
+
},
|
|
330
|
+
success: {
|
|
331
|
+
description: 'The requested token balance for this account.',
|
|
332
|
+
schema: schema.getAddressBalance.Response,
|
|
333
|
+
},
|
|
334
|
+
}),
|
|
335
|
+
summary: 'Get address balance',
|
|
336
|
+
tags: ['Balances'],
|
|
337
|
+
}),
|
|
338
|
+
Cache.response<App.Environment>({
|
|
339
|
+
cacheControl: Cache.policies.noStore,
|
|
340
|
+
name: 'tempo-api:addresses:v1',
|
|
341
|
+
key: (c) => Cache.urlKey(c, schema.getAddressBalance.Query),
|
|
342
|
+
}),
|
|
343
|
+
async (c) => {
|
|
344
|
+
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
345
|
+
if (OpenApi.narrowValidation)
|
|
346
|
+
return OpenApi.validationError(c, {
|
|
347
|
+
code: 'address_invalid',
|
|
348
|
+
message: 'Invalid request parameters',
|
|
349
|
+
})
|
|
350
|
+
if (OpenApi.narrowValidation)
|
|
351
|
+
return OpenApi.validationError(c, {
|
|
352
|
+
code: 'query_invalid',
|
|
353
|
+
message: 'Invalid request parameters',
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
const { address, token } = c.req.valid('param')
|
|
357
|
+
const query = c.req.valid('query')
|
|
239
358
|
const chainId = query.chainId ?? c.get('chainId')
|
|
240
|
-
// `currency` only makes sense against verified tokens, so it implies
|
|
241
|
-
// `verified=true` from the caller's perspective.
|
|
242
|
-
const verified = query.verified || query.currency !== undefined
|
|
243
|
-
const feeEligible = query.feeEligible ?? false
|
|
244
|
-
|
|
245
|
-
// The fee-token set powers both the per-row `feeEligible` flag (always)
|
|
246
|
-
// and the optional `feeEligible=true` page restriction. It is memoized
|
|
247
|
-
// per chain (a near-constant), so one read feeds the page, the count,
|
|
248
|
-
// and the row enrichment. It is a best-effort enrichment: a lookup
|
|
249
|
-
// failure omits `feeEligible` rather than failing the page — unless the
|
|
250
|
-
// caller explicitly asked to filter by `feeEligible`, where the filter
|
|
251
|
-
// can't be honored, so the failure surfaces as the declared `502`.
|
|
252
|
-
const feeTokens = await FeeAmm.feeTokenSet(c, chainId).catch((cause) => {
|
|
253
|
-
if (feeEligible) throw cause
|
|
254
|
-
return undefined
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
const balances = await getAddressBalances(c, {
|
|
258
|
-
address,
|
|
259
|
-
chainId,
|
|
260
|
-
currency: query.currency,
|
|
261
|
-
cursor: query.cursor,
|
|
262
|
-
denomination: query['valuation.currency'] ?? 'USD',
|
|
263
|
-
feeEligible,
|
|
264
|
-
feeTokens,
|
|
265
|
-
includeTotalCount: query.include.includes('totalCount'),
|
|
266
|
-
limit: query.limit,
|
|
267
|
-
oracle,
|
|
268
|
-
page: query.page,
|
|
269
|
-
verified,
|
|
270
|
-
})
|
|
271
359
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
360
|
+
try {
|
|
361
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId)
|
|
362
|
+
const [balance, resolvedToken] = await Promise.all([
|
|
363
|
+
Timing.time(c, 'balance_rpc', async () => {
|
|
364
|
+
const result = await Actions.token.getBalance(c.get('getClient')(chainId), {
|
|
365
|
+
account: address,
|
|
366
|
+
token,
|
|
367
|
+
})
|
|
368
|
+
return result.amount
|
|
369
|
+
}),
|
|
370
|
+
Timing.time(c, 'token', () =>
|
|
371
|
+
Tokens.resolveToken(c, { address: token, chainId, include: [], snapshot }),
|
|
372
|
+
),
|
|
373
|
+
])
|
|
374
|
+
const denomination = query['valuation.currency'] ?? 'USD'
|
|
375
|
+
const rates = await Valuation.ratesFor(c, {
|
|
376
|
+
currencies: snapshot.byAddress.has(token) ? [resolvedToken.currency] : [],
|
|
377
|
+
denomination,
|
|
378
|
+
oracle,
|
|
379
|
+
})
|
|
380
|
+
const feeTokens = await FeeAmm.feeTokenSet(c, chainId).catch(() => undefined)
|
|
381
|
+
|
|
382
|
+
return c.json(
|
|
383
|
+
Response.validated(schema.getAddressBalance.Response, {
|
|
384
|
+
amount: balance.toString(),
|
|
385
|
+
currency: resolvedToken.currency,
|
|
386
|
+
decimals: resolvedToken.decimals,
|
|
387
|
+
...(feeTokens ? { feeEligible: feeTokens.has(token) } : {}),
|
|
388
|
+
formatted: core_Value.format(balance, resolvedToken.decimals),
|
|
389
|
+
id: token,
|
|
390
|
+
token: resolvedToken,
|
|
391
|
+
valuation: Valuation.valuationFor({
|
|
392
|
+
amount: balance,
|
|
393
|
+
denomination,
|
|
394
|
+
rates,
|
|
395
|
+
token: snapshot.byAddress.get(token),
|
|
396
|
+
}),
|
|
397
|
+
}),
|
|
398
|
+
200,
|
|
399
|
+
)
|
|
400
|
+
} catch (cause) {
|
|
401
|
+
if (Tokens.isTokenNotFound(cause))
|
|
402
|
+
return Response.error(c, {
|
|
403
|
+
code: 'token_not_found',
|
|
404
|
+
message: 'Token not found',
|
|
405
|
+
status: 404,
|
|
406
|
+
})
|
|
407
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
408
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
409
|
+
return Response.upstream(c, cause)
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
)
|
|
280
413
|
}
|
|
281
414
|
|
|
282
415
|
export declare namespace addresses {
|