tapimo 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +4887 -5074
- package/dist/App.d.ts.map +1 -1
- package/dist/Webhooks.d.ts +21 -20
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +66 -23
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +168 -168
- package/dist/apps/Zones.d.ts +34 -22
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +9 -8
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +5198 -5099
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +4 -5
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +19 -4
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +270 -270
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +32 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +52 -52
- package/dist/apps/data/routes/balances.d.ts +36 -36
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +135 -81
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +125 -125
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/earn.d.ts +512 -58
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +368 -44
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +120 -120
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +114 -118
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +68 -47
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +461 -459
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +59 -51
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +25 -23
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +108 -86
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +618 -105
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +21 -21
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +2 -2
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
- package/dist/apps/data/routes/webhooks.d.ts +102 -505
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +484 -342
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +12 -12
- package/dist/apps/data/routes/zones.d.ts.map +1 -1
- package/dist/apps/data/routes/zones.js +3 -1
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/App.d.ts +96 -96
- package/dist/apps/funding/routes/deposit-addresses.d.ts +52 -52
- package/dist/apps/funding/routes/quotes.d.ts +89 -89
- package/dist/apps/funding/routes/transfers.d.ts +4 -4
- package/dist/apps/management/App.d.ts +429 -727
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +24 -24
- package/dist/apps/management/routes/billing.d.ts +72 -72
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invitations.d.ts +134 -134
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +30 -30
- package/dist/apps/management/routes/orgs.d.ts +98 -98
- package/dist/apps/management/routes/projects.d.ts +20 -20
- package/dist/apps/management/routes/usage.d.ts +24 -24
- package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
- package/dist/apps/management/routes/webhooks.d.ts +158 -573
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/mpp/App.d.ts +56 -44
- package/dist/apps/mpp/App.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.d.ts +3 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.js +3 -0
- package/dist/apps/mpp/Mpp.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +9 -2
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +2 -2
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +17 -15
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +4 -1
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +56 -3
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +76 -3
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +112 -3
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +116 -3
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.js +1 -1
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Tidx.d.ts +1 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +1 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +2 -1
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts +2 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +11 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +55 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -0
- package/dist/internal/WebhookTransfer.js +296 -0
- package/dist/internal/WebhookTransfer.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +37 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +87 -13
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +4 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +11 -7
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +128 -0
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +3 -0
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +12 -1
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/Client.test-d.ts +60 -5
- package/src/Webhooks.bench.ts +714 -0
- package/src/Webhooks.memory.bench.ts +250 -0
- package/src/Webhooks.test.ts +77 -6
- package/src/Webhooks.ts +83 -29
- package/src/apps/Zones.test.ts +5 -4
- package/src/apps/Zones.ts +9 -8
- package/src/apps/data/FxOracle.test.ts +18 -0
- package/src/apps/data/FxOracle.ts +28 -5
- package/src/apps/data/routes/activities.test.ts +19 -0
- package/src/apps/data/routes/activities.ts +34 -30
- package/src/apps/data/routes/balances.test.ts +119 -10
- package/src/apps/data/routes/balances.ts +154 -95
- package/src/apps/data/routes/blocks.test.ts +12 -6
- package/src/apps/data/routes/earn.test.ts +197 -0
- package/src/apps/data/routes/earn.ts +486 -44
- package/src/apps/data/routes/exchanges.test.ts +7 -6
- package/src/apps/data/routes/exchanges.ts +124 -117
- package/src/apps/data/routes/receipts.test.ts +24 -7
- package/src/apps/data/routes/receipts.ts +67 -50
- package/src/apps/data/routes/tokens.test.ts +51 -0
- package/src/apps/data/routes/transactions.test.ts +42 -0
- package/src/apps/data/routes/transactions.ts +44 -39
- package/src/apps/data/routes/transfers.test.ts +402 -24
- package/src/apps/data/routes/transfers.ts +708 -101
- package/src/apps/data/routes/valuation.test.ts +26 -0
- package/src/apps/data/routes/valuation.ts +6 -2
- package/src/apps/data/routes/webhooks.test.ts +414 -130
- package/src/apps/data/routes/webhooks.ts +603 -419
- package/src/apps/data/routes/zones.ts +6 -1
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/mpp/Mpp.test.ts +13 -1
- package/src/apps/mpp/Mpp.ts +8 -0
- package/src/apps/mpp/internal/Adapter.ts +10 -2
- package/src/db/Db.test.ts +40 -6
- package/src/db/Db.ts +33 -19
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookSubscriptions.ts +116 -4
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/MetricSink.test.ts +518 -0
- package/src/internal/MetricSink.ts +266 -9
- package/src/internal/Schema.ts +1 -1
- package/src/internal/Tidx.test.ts +4 -4
- package/src/internal/Tidx.ts +1 -3
- package/src/internal/Viem.test.ts +8 -0
- package/src/internal/Viem.ts +2 -1
- package/src/internal/WebhookDestination.test.ts +20 -0
- package/src/internal/WebhookDestination.ts +10 -0
- package/src/internal/WebhookTransfer.ts +426 -0
- package/src/internal/Webhooks.test.ts +137 -0
- package/src/internal/Webhooks.ts +131 -17
- package/src/internal/funding/Provider.test.ts +47 -7
- package/src/internal/funding/Provider.ts +12 -7
- package/src/internal/funding/Reconciliation.local.test.ts +3 -1
- package/src/internal/funding/Reconciliation.test.ts +1240 -0
- package/src/internal/funding/Reconciliation.ts +136 -0
- package/src/internal/funding/SourceObservation.test.ts +1 -0
- package/src/internal/funding/providers/relay.test.ts +1 -4
- package/src/internal/funding/providers/relay.ts +16 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Context, Hono } from 'hono'
|
|
2
2
|
import { Value as core_Value } from 'ox'
|
|
3
|
+
import type { Actions } from 'viem/tempo'
|
|
3
4
|
import {
|
|
4
5
|
type Chain,
|
|
5
6
|
ContractFunctionExecutionError,
|
|
@@ -56,6 +57,10 @@ const venueSharesDepositedSignature =
|
|
|
56
57
|
|
|
57
58
|
const maxScannedDeployments = 250
|
|
58
59
|
const scanLimit = 50
|
|
60
|
+
|
|
61
|
+
// Concurrent metadata reads in one macrotask collapse into one deployless
|
|
62
|
+
// multicall; an await between small chunks keeps each call under the RPC limit.
|
|
63
|
+
const tokenReadChunkSize = 20
|
|
59
64
|
/** Block boundary that includes all indexed history. */
|
|
60
65
|
const beforeFirstBlockNumber = -1
|
|
61
66
|
|
|
@@ -157,7 +162,9 @@ export namespace schema {
|
|
|
157
162
|
z.describe('Tokens accepted by private deposits.'),
|
|
158
163
|
z.meta({ examples: [['0x20c000000000000000000000ff04042ee92fd449']] }),
|
|
159
164
|
),
|
|
160
|
-
name: z
|
|
165
|
+
name: z
|
|
166
|
+
.string()
|
|
167
|
+
.check(z.describe('Private Zone name.'), z.meta({ examples: ['zone-moderato'] })),
|
|
161
168
|
outputTokens: z
|
|
162
169
|
.array(Schema.Address)
|
|
163
170
|
.check(
|
|
@@ -230,7 +237,7 @@ export namespace schema {
|
|
|
230
237
|
.nullable(value)
|
|
231
238
|
.check(
|
|
232
239
|
z.describe(
|
|
233
|
-
'Assets returned for one whole
|
|
240
|
+
'Assets returned for one whole earn share, in the asset display currency, or null when the engine cannot quote an exit.',
|
|
234
241
|
),
|
|
235
242
|
),
|
|
236
243
|
shareToken: Tokens.schema.Token.check(z.describe('Token representing vault shares.')),
|
|
@@ -255,7 +262,7 @@ export namespace schema {
|
|
|
255
262
|
verified: z
|
|
256
263
|
.boolean()
|
|
257
264
|
.check(
|
|
258
|
-
z.describe('Whether this vault is in the curated
|
|
265
|
+
z.describe('Whether this vault is in the curated earn registry.'),
|
|
259
266
|
z.meta({ examples: [true] }),
|
|
260
267
|
),
|
|
261
268
|
zone: z
|
|
@@ -264,7 +271,7 @@ export namespace schema {
|
|
|
264
271
|
})
|
|
265
272
|
|
|
266
273
|
/** One compatible Earn vault resolved from current chain state. */
|
|
267
|
-
export const Vault = Schema.describe(vault, 'A compatible
|
|
274
|
+
export const Vault = Schema.describe(vault, 'A compatible earn vault resolved from chain state.')
|
|
268
275
|
|
|
269
276
|
/** One registry-curated Earn vault. */
|
|
270
277
|
export const VerifiedVault = Schema.describe(
|
|
@@ -279,16 +286,16 @@ export namespace schema {
|
|
|
279
286
|
verified: z
|
|
280
287
|
.literal(true)
|
|
281
288
|
.check(
|
|
282
|
-
z.describe('This vault is in the curated
|
|
289
|
+
z.describe('This vault is in the curated earn registry.'),
|
|
283
290
|
z.meta({ examples: [true] }),
|
|
284
291
|
),
|
|
285
292
|
}),
|
|
286
|
-
'A registry-curated
|
|
293
|
+
'A registry-curated earn vault resolved from chain state.',
|
|
287
294
|
)
|
|
288
295
|
|
|
289
296
|
const position = z.strictObject({
|
|
290
297
|
account: Schema.Address.check(
|
|
291
|
-
z.describe('Account whose
|
|
298
|
+
z.describe('Account whose earn position was read.'),
|
|
292
299
|
z.meta({ examples: ['0xbe058e1c4df8a4366a387bf595b284246a93039e'] }),
|
|
293
300
|
),
|
|
294
301
|
assetAllowance: Schema.DecimalString.check(
|
|
@@ -315,16 +322,49 @@ export namespace schema {
|
|
|
315
322
|
z.meta({ examples: ['99500000'] }),
|
|
316
323
|
),
|
|
317
324
|
shareToken: Schema.tokenAddress('0x20c000000000000000000000300e14ab91a10769').check(
|
|
318
|
-
z.describe('TIP-20 token representing
|
|
325
|
+
z.describe('TIP-20 token representing earn shares.'),
|
|
319
326
|
),
|
|
320
327
|
value: Schema.DecimalString.check(
|
|
321
328
|
z.describe(
|
|
322
|
-
'Current asset value of this account’s
|
|
329
|
+
'Current asset value of this account’s earn shares, including fees, in asset base units.',
|
|
323
330
|
),
|
|
324
331
|
z.meta({ examples: ['100000000'] }),
|
|
325
332
|
),
|
|
326
333
|
})
|
|
327
334
|
|
|
335
|
+
const addressPosition = z.strictObject({
|
|
336
|
+
assetAmount: value.check(
|
|
337
|
+
z.describe('Current asset value of the held shares, including fees, in `assetToken` units.'),
|
|
338
|
+
),
|
|
339
|
+
assetToken: Tokens.schema.Token.check(z.describe('Asset accepted by the vault.')),
|
|
340
|
+
id: Schema.Address.check(
|
|
341
|
+
z.describe('Stable resource ID for this position, equal to the vault address.'),
|
|
342
|
+
z.meta({ examples: ['0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a'] }),
|
|
343
|
+
),
|
|
344
|
+
shareAmount: value.check(
|
|
345
|
+
z.describe('Earn share balance this account holds, in `shareToken` units.'),
|
|
346
|
+
),
|
|
347
|
+
shareToken: Tokens.schema.Token.check(z.describe('Token representing vault shares.')),
|
|
348
|
+
valuation: z
|
|
349
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
350
|
+
.check(
|
|
351
|
+
z.describe(
|
|
352
|
+
'The position’s nominal value when `valuation.currency` is requested, or `null` when ' +
|
|
353
|
+
'the asset token is unverified, its display currency has no rate, or rates were unavailable.',
|
|
354
|
+
),
|
|
355
|
+
),
|
|
356
|
+
vaultAddress: Schema.Address.check(
|
|
357
|
+
z.describe('Earn vault contract address holding this position.'),
|
|
358
|
+
z.meta({ examples: ['0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a'] }),
|
|
359
|
+
),
|
|
360
|
+
verified: z
|
|
361
|
+
.boolean()
|
|
362
|
+
.check(
|
|
363
|
+
z.describe('Whether the vault is in the curated earn registry.'),
|
|
364
|
+
z.meta({ examples: [true] }),
|
|
365
|
+
),
|
|
366
|
+
})
|
|
367
|
+
|
|
328
368
|
const earningsBase = z.strictObject({
|
|
329
369
|
account: Schema.Address.check(
|
|
330
370
|
z.describe('Account whose vault earnings were calculated.'),
|
|
@@ -336,7 +376,7 @@ export namespace schema {
|
|
|
336
376
|
),
|
|
337
377
|
currentValue: Schema.DecimalString.check(
|
|
338
378
|
z.describe(
|
|
339
|
-
'Asset value of the account’s active
|
|
379
|
+
'Asset value of the account’s active earn shares at the latest indexed block, in asset base units.',
|
|
340
380
|
),
|
|
341
381
|
z.meta({ examples: ['600000'] }),
|
|
342
382
|
),
|
|
@@ -483,7 +523,7 @@ export namespace schema {
|
|
|
483
523
|
|
|
484
524
|
const collectionQuery = z
|
|
485
525
|
.extend(collectionFilters, { 'apy.window': EarnRates.schema.Window })
|
|
486
|
-
.check(z.describe('Query parameters for listing
|
|
526
|
+
.check(z.describe('Query parameters for listing earn vaults.'))
|
|
487
527
|
|
|
488
528
|
const vaultSelector = z.strictObject({
|
|
489
529
|
chainId: Schema.ChainIdQuery,
|
|
@@ -500,11 +540,11 @@ export namespace schema {
|
|
|
500
540
|
.strictObject({
|
|
501
541
|
data: z
|
|
502
542
|
.array(Vault)
|
|
503
|
-
.check(z.describe('Compatible deployed
|
|
543
|
+
.check(z.describe('Compatible deployed earn vaults in deployment order.')),
|
|
504
544
|
meta: z.optional(meta),
|
|
505
545
|
nextCursor: Schema.NextCursor,
|
|
506
546
|
})
|
|
507
|
-
.check(z.describe('A page of compatible deployed
|
|
547
|
+
.check(z.describe('A page of compatible deployed earn vaults.'))
|
|
508
548
|
}
|
|
509
549
|
|
|
510
550
|
/** Schemas for the getVerifiedEarnVaults operation. */
|
|
@@ -512,7 +552,7 @@ export namespace schema {
|
|
|
512
552
|
/** Collection query without rates or valuation, for surfaces that cannot measure them. */
|
|
513
553
|
export const BaseQuery = Schema.describe(
|
|
514
554
|
z.extend(collectionFilters, { include: baseIncludeQuery }),
|
|
515
|
-
'Query parameters for listing
|
|
555
|
+
'Query parameters for listing earn vaults.',
|
|
516
556
|
)
|
|
517
557
|
|
|
518
558
|
/** Query parameters for registry-curated Earn vaults. */
|
|
@@ -523,37 +563,95 @@ export namespace schema {
|
|
|
523
563
|
.strictObject({
|
|
524
564
|
data: z
|
|
525
565
|
.array(VerifiedVault)
|
|
526
|
-
.check(z.describe('Registry-curated
|
|
566
|
+
.check(z.describe('Registry-curated earn vaults ordered by vault address.')),
|
|
527
567
|
meta: z.optional(meta),
|
|
528
568
|
nextCursor: Schema.NextCursor,
|
|
529
569
|
})
|
|
530
|
-
.check(z.describe('A page of registry-curated
|
|
570
|
+
.check(z.describe('A page of registry-curated earn vaults.'))
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Schemas for the getEarnAddressPositions operation. */
|
|
574
|
+
export namespace getEarnAddressPositions {
|
|
575
|
+
/** Path parameters identifying the account. */
|
|
576
|
+
export const Params = z
|
|
577
|
+
.strictObject({
|
|
578
|
+
address: Schema.Address.check(z.describe('Account whose earn positions to list.')),
|
|
579
|
+
})
|
|
580
|
+
.check(z.describe('Path parameters identifying an account with earn positions.'))
|
|
581
|
+
|
|
582
|
+
/** Query parameters for listing an account's earn positions. */
|
|
583
|
+
export const Query = z
|
|
584
|
+
.strictObject({
|
|
585
|
+
chainId: Schema.ChainIdQuery,
|
|
586
|
+
cursor: Schema.Cursor,
|
|
587
|
+
limit: Schema.Limit,
|
|
588
|
+
'valuation.currency': Schema.Denomination.check(
|
|
589
|
+
z.describe(
|
|
590
|
+
'When present, include each position’s nominal value in this denomination. ' +
|
|
591
|
+
'Case-insensitive and must be priced by the configured FX oracle.',
|
|
592
|
+
),
|
|
593
|
+
),
|
|
594
|
+
verified: z
|
|
595
|
+
.optional(Schema.booleanQuery())
|
|
596
|
+
.check(
|
|
597
|
+
z.describe(
|
|
598
|
+
'When `true`, include only positions in registry-curated (verified) earn vaults.',
|
|
599
|
+
),
|
|
600
|
+
z.meta({ examples: [true] }),
|
|
601
|
+
),
|
|
602
|
+
})
|
|
603
|
+
.check(z.describe('Query parameters for listing an account’s earn positions.'))
|
|
604
|
+
|
|
605
|
+
/** Page-level valuation rate provenance. */
|
|
606
|
+
export const Meta = z
|
|
607
|
+
.object({
|
|
608
|
+
valuation: z
|
|
609
|
+
.optional(Valuation.schema.Pricing)
|
|
610
|
+
.check(
|
|
611
|
+
z.describe(
|
|
612
|
+
'Rate provenance for row valuations. Present when conversion rates were ' +
|
|
613
|
+
'consulted; absent when every value was identity-valued or rates were unavailable.',
|
|
614
|
+
),
|
|
615
|
+
),
|
|
616
|
+
})
|
|
617
|
+
.check(z.describe('Page-level valuation rate provenance.'))
|
|
618
|
+
|
|
619
|
+
/** Paginated list of the account's earn vault positions. */
|
|
620
|
+
export const Response = z
|
|
621
|
+
.strictObject({
|
|
622
|
+
data: z
|
|
623
|
+
.array(addressPosition)
|
|
624
|
+
.check(z.describe('Vaults where the account holds earn shares.')),
|
|
625
|
+
meta: z.optional(Meta),
|
|
626
|
+
nextCursor: Schema.NextCursor,
|
|
627
|
+
})
|
|
628
|
+
.check(z.describe('A page of the account’s earn vault positions.'))
|
|
531
629
|
}
|
|
532
630
|
|
|
533
631
|
/** Schemas for the getEarnVault operation. */
|
|
534
632
|
export namespace getEarnVault {
|
|
535
|
-
/** Path parameters identifying one
|
|
633
|
+
/** Path parameters identifying one earn vault. */
|
|
536
634
|
export const Params = z
|
|
537
635
|
.strictObject({
|
|
538
636
|
vaultId: Schema.Address.check(z.describe('Earn vault contract address.')),
|
|
539
637
|
})
|
|
540
|
-
.check(z.describe('Path parameters identifying one
|
|
638
|
+
.check(z.describe('Path parameters identifying one earn vault.'))
|
|
541
639
|
|
|
542
640
|
/** Detail query without rates or valuation, for surfaces that cannot measure them. */
|
|
543
641
|
export const BaseQuery = Schema.describe(
|
|
544
642
|
z.extend(vaultSelector, { include: baseIncludeQuery }),
|
|
545
|
-
'Query parameters selecting one
|
|
643
|
+
'Query parameters selecting one earn vault.',
|
|
546
644
|
)
|
|
547
645
|
|
|
548
646
|
/** Query parameters selecting the chain containing the vault. */
|
|
549
647
|
export const Query = z
|
|
550
648
|
.extend(vaultSelector, { 'apy.window': EarnRates.schema.Window })
|
|
551
|
-
.check(z.describe('Query parameters selecting one
|
|
649
|
+
.check(z.describe('Query parameters selecting one earn vault.'))
|
|
552
650
|
|
|
553
651
|
/** One compatible Earn vault resolved directly from current chain state. */
|
|
554
652
|
export const Response = Schema.describe(
|
|
555
653
|
z.extend(vault, { meta: z.optional(meta) }),
|
|
556
|
-
'A compatible
|
|
654
|
+
'A compatible earn vault resolved from chain state, with rate provenance for its valued fields.',
|
|
557
655
|
)
|
|
558
656
|
}
|
|
559
657
|
|
|
@@ -562,22 +660,22 @@ export namespace schema {
|
|
|
562
660
|
/** Path parameters identifying the Earn vault and account. */
|
|
563
661
|
export const Params = z
|
|
564
662
|
.strictObject({
|
|
565
|
-
address: Schema.Address.check(z.describe('Account whose
|
|
663
|
+
address: Schema.Address.check(z.describe('Account whose earn position to read.')),
|
|
566
664
|
vaultId: Schema.address('0xdb48b00fb28d9a8db881fcd671c3fb1bf8c486b6').check(
|
|
567
665
|
z.describe('Earn vault contract address.'),
|
|
568
666
|
),
|
|
569
667
|
})
|
|
570
|
-
.check(z.describe('Path parameters identifying an
|
|
668
|
+
.check(z.describe('Path parameters identifying an earn vault position.'))
|
|
571
669
|
|
|
572
670
|
/** Query parameters selecting the chain containing the vault. */
|
|
573
671
|
export const Query = z
|
|
574
672
|
.strictObject({ chainId: Schema.ChainIdQuery })
|
|
575
|
-
.check(z.describe('Query parameters selecting an
|
|
673
|
+
.check(z.describe('Query parameters selecting an earn vault position.'))
|
|
576
674
|
|
|
577
675
|
/** Current asset and Earn-share state for one account in one vault. */
|
|
578
676
|
export const Response = Schema.describe(
|
|
579
677
|
position,
|
|
580
|
-
'Current asset and
|
|
678
|
+
'Current asset and earn-share balances, allowances, and asset value for an account in one earn vault.',
|
|
581
679
|
)
|
|
582
680
|
}
|
|
583
681
|
|
|
@@ -591,7 +689,7 @@ export namespace schema {
|
|
|
591
689
|
z.describe('Earn vault contract address.'),
|
|
592
690
|
),
|
|
593
691
|
})
|
|
594
|
-
.check(z.describe('Path parameters identifying an
|
|
692
|
+
.check(z.describe('Path parameters identifying an earn vault account.'))
|
|
595
693
|
|
|
596
694
|
/** Query parameters selecting the chain containing the vault. */
|
|
597
695
|
export const Query = z
|
|
@@ -599,12 +697,12 @@ export namespace schema {
|
|
|
599
697
|
chainId: Schema.ChainIdQuery,
|
|
600
698
|
period: z._default(earningsPeriod, 'lifetime'),
|
|
601
699
|
})
|
|
602
|
-
.check(z.describe('Query parameters selecting an
|
|
700
|
+
.check(z.describe('Query parameters selecting an earn vault account.'))
|
|
603
701
|
|
|
604
702
|
/** Earnings for one account and period in one Earn vault. */
|
|
605
703
|
export const Response = Schema.describe(
|
|
606
704
|
earnings,
|
|
607
|
-
'Indexed asset value and cost-basis-aware earnings for an account in one
|
|
705
|
+
'Indexed asset value and cost-basis-aware earnings for an account in one earn vault.',
|
|
608
706
|
)
|
|
609
707
|
}
|
|
610
708
|
}
|
|
@@ -622,12 +720,12 @@ export function earn(options: earn.Options = {}) {
|
|
|
622
720
|
}),
|
|
623
721
|
OpenApi.describeRoute({
|
|
624
722
|
description:
|
|
625
|
-
'Lists compatible
|
|
723
|
+
'Lists compatible earn vaults discovered from deployment events and resolved from current chain state.',
|
|
626
724
|
operationId: 'getEarnVaults',
|
|
627
725
|
responses: OpenApi.responses({
|
|
628
|
-
errors: { 502: 'Could not read
|
|
726
|
+
errors: { 502: 'Could not read earn deployment or vault data.' },
|
|
629
727
|
success: {
|
|
630
|
-
description: 'A page of compatible deployed
|
|
728
|
+
description: 'A page of compatible deployed earn vaults.',
|
|
631
729
|
schema: schema.getEarnVaults.Response,
|
|
632
730
|
},
|
|
633
731
|
}),
|
|
@@ -668,12 +766,12 @@ export function earn(options: earn.Options = {}) {
|
|
|
668
766
|
}),
|
|
669
767
|
OpenApi.describeRoute({
|
|
670
768
|
description:
|
|
671
|
-
'Lists registry-curated
|
|
769
|
+
'Lists registry-curated earn vaults after resolving their current onchain configuration.',
|
|
672
770
|
operationId: 'getVerifiedEarnVaults',
|
|
673
771
|
responses: OpenApi.responses({
|
|
674
|
-
errors: { 502: 'Could not read curated
|
|
772
|
+
errors: { 502: 'Could not read curated earn registry or vault data.' },
|
|
675
773
|
success: {
|
|
676
|
-
description: 'A page of registry-curated
|
|
774
|
+
description: 'A page of registry-curated earn vaults.',
|
|
677
775
|
schema: schema.getVerifiedEarnVaults.Response,
|
|
678
776
|
},
|
|
679
777
|
}),
|
|
@@ -703,6 +801,86 @@ export function earn(options: earn.Options = {}) {
|
|
|
703
801
|
}
|
|
704
802
|
},
|
|
705
803
|
)
|
|
804
|
+
.get(
|
|
805
|
+
'/v1/earn/addresses/:address/positions',
|
|
806
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
807
|
+
OpenApi.validate('param', schema.getEarnAddressPositions.Params, {
|
|
808
|
+
code: 'positions_invalid',
|
|
809
|
+
message: 'Invalid address positions',
|
|
810
|
+
}),
|
|
811
|
+
OpenApi.validate('query', schema.getEarnAddressPositions.Query, {
|
|
812
|
+
code: 'query_invalid',
|
|
813
|
+
message: 'Invalid query parameters',
|
|
814
|
+
}),
|
|
815
|
+
OpenApi.describeRoute({
|
|
816
|
+
description:
|
|
817
|
+
'Lists every earn vault where an account currently holds shares, with the current balance and value of each position.',
|
|
818
|
+
operationId: 'getEarnAddressPositions',
|
|
819
|
+
responses: OpenApi.responses({
|
|
820
|
+
errors: {
|
|
821
|
+
400: {
|
|
822
|
+
codes: [
|
|
823
|
+
'chain_id_invalid',
|
|
824
|
+
'chain_id_unsupported',
|
|
825
|
+
'positions_invalid',
|
|
826
|
+
'query_invalid',
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
502: 'Could not read earn deployment or position data.',
|
|
830
|
+
},
|
|
831
|
+
success: {
|
|
832
|
+
description: 'A page of the account’s earn vault positions.',
|
|
833
|
+
schema: schema.getEarnAddressPositions.Response,
|
|
834
|
+
},
|
|
835
|
+
}),
|
|
836
|
+
summary: 'List account positions',
|
|
837
|
+
tags: ['Earn'],
|
|
838
|
+
}),
|
|
839
|
+
// `noStore` matches the address balances listing: account holdings must
|
|
840
|
+
// read fresh immediately after a deposit or redemption.
|
|
841
|
+
Cache.response<App.Environment>({
|
|
842
|
+
cacheControl: Cache.policies.noStore,
|
|
843
|
+
key: (c) => Cache.urlKey(c, schema.getEarnAddressPositions.Query),
|
|
844
|
+
name: 'tempo-api:earn-address-positions:v1',
|
|
845
|
+
}),
|
|
846
|
+
async (c) => {
|
|
847
|
+
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
848
|
+
if (OpenApi.narrowValidation)
|
|
849
|
+
return Response.error(c, {
|
|
850
|
+
code: 'chain_id_invalid',
|
|
851
|
+
message: 'Invalid chain id',
|
|
852
|
+
status: 400,
|
|
853
|
+
})
|
|
854
|
+
if (OpenApi.narrowValidation)
|
|
855
|
+
return Response.error(c, {
|
|
856
|
+
code: 'chain_id_unsupported',
|
|
857
|
+
message: 'Unsupported chain id',
|
|
858
|
+
status: 400,
|
|
859
|
+
})
|
|
860
|
+
if (OpenApi.narrowValidation)
|
|
861
|
+
return OpenApi.validationError(c, {
|
|
862
|
+
code: 'positions_invalid',
|
|
863
|
+
message: 'Invalid request parameters',
|
|
864
|
+
})
|
|
865
|
+
if (OpenApi.narrowValidation)
|
|
866
|
+
return OpenApi.validationError(c, {
|
|
867
|
+
code: 'query_invalid',
|
|
868
|
+
message: 'Invalid query parameters',
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
const { address } = c.req.valid('param')
|
|
872
|
+
const query = c.req.valid('query')
|
|
873
|
+
const chainId = query.chainId ?? c.get('chainId')
|
|
874
|
+
try {
|
|
875
|
+
const data = await listPositions(c, { ...query, address, chainId, oracle })
|
|
876
|
+
return c.json(Response.validated(schema.getEarnAddressPositions.Response, data), 200)
|
|
877
|
+
} catch (cause) {
|
|
878
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
879
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
880
|
+
return Response.upstream(c, cause)
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
)
|
|
706
884
|
.get(
|
|
707
885
|
'/v1/earn/vaults/:vaultId/positions/:address',
|
|
708
886
|
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
@@ -716,7 +894,7 @@ export function earn(options: earn.Options = {}) {
|
|
|
716
894
|
}),
|
|
717
895
|
OpenApi.describeRoute({
|
|
718
896
|
description:
|
|
719
|
-
'Gets an account’s asset and
|
|
897
|
+
'Gets an account’s asset and earn-share balances, allowances, and current asset value for one earn vault.',
|
|
720
898
|
operationId: 'getEarnVaultPosition',
|
|
721
899
|
responses: OpenApi.responses({
|
|
722
900
|
errors: {
|
|
@@ -730,12 +908,12 @@ export function earn(options: earn.Options = {}) {
|
|
|
730
908
|
},
|
|
731
909
|
404: {
|
|
732
910
|
codes: ['earn_vault_not_found'],
|
|
733
|
-
description: 'No compatible
|
|
911
|
+
description: 'No compatible earn vault was found at the supplied vault address.',
|
|
734
912
|
},
|
|
735
|
-
502: 'Could not read the
|
|
913
|
+
502: 'Could not read the earn vault position.',
|
|
736
914
|
},
|
|
737
915
|
success: {
|
|
738
|
-
description: 'Current
|
|
916
|
+
description: 'Current earn position for one account in one vault.',
|
|
739
917
|
schema: schema.getEarnVaultPosition.Response,
|
|
740
918
|
},
|
|
741
919
|
}),
|
|
@@ -835,9 +1013,9 @@ export function earn(options: earn.Options = {}) {
|
|
|
835
1013
|
},
|
|
836
1014
|
404: {
|
|
837
1015
|
codes: ['earn_vault_not_found'],
|
|
838
|
-
description: 'No compatible
|
|
1016
|
+
description: 'No compatible earn vault was found at this address.',
|
|
839
1017
|
},
|
|
840
|
-
502: 'Could not read the
|
|
1018
|
+
502: 'Could not read the earn vault earnings.',
|
|
841
1019
|
},
|
|
842
1020
|
success: {
|
|
843
1021
|
description: 'Cost-basis-aware earnings for one account in one vault.',
|
|
@@ -916,18 +1094,18 @@ export function earn(options: earn.Options = {}) {
|
|
|
916
1094
|
}),
|
|
917
1095
|
OpenApi.describeRoute({
|
|
918
1096
|
description:
|
|
919
|
-
'Resolves any compatible
|
|
1097
|
+
'Resolves any compatible earn vault directly from current chain state and enriches it when curated.',
|
|
920
1098
|
operationId: 'getEarnVault',
|
|
921
1099
|
responses: OpenApi.responses({
|
|
922
1100
|
errors: {
|
|
923
1101
|
404: {
|
|
924
1102
|
codes: ['earn_vault_not_found'],
|
|
925
|
-
description: 'No compatible
|
|
1103
|
+
description: 'No compatible earn vault was found at this address.',
|
|
926
1104
|
},
|
|
927
|
-
502: 'Could not read
|
|
1105
|
+
502: 'Could not read earn vault data.',
|
|
928
1106
|
},
|
|
929
1107
|
success: {
|
|
930
|
-
description: 'One compatible
|
|
1108
|
+
description: 'One compatible earn vault.',
|
|
931
1109
|
schema: schema.getEarnVault.Response,
|
|
932
1110
|
},
|
|
933
1111
|
}),
|
|
@@ -2040,6 +2218,270 @@ export declare namespace listVerifiedVaults {
|
|
|
2040
2218
|
}
|
|
2041
2219
|
}
|
|
2042
2220
|
|
|
2221
|
+
/** Lists every vault where an account holds earn shares, ordered by vault address. */
|
|
2222
|
+
export async function listPositions(c: Context<App.Environment>, options: listPositions.Options) {
|
|
2223
|
+
const denomination = options['valuation.currency']
|
|
2224
|
+
const db = Db.get(c.get('dbCached'))
|
|
2225
|
+
const recordsPromise = core_EarnVaults.list(db, { chainId: options.chainId })
|
|
2226
|
+
// The verified page needs no index: the registry is the candidate set.
|
|
2227
|
+
const heldPromise = options.verified
|
|
2228
|
+
? undefined
|
|
2229
|
+
: queryHeldVaults(c, { address: options.address, chainId: options.chainId })
|
|
2230
|
+
// Observing these branches prevents unhandled rejections while the
|
|
2231
|
+
// denomination validates; the later await still propagates them.
|
|
2232
|
+
void recordsPromise.catch(() => undefined)
|
|
2233
|
+
void heldPromise?.catch(() => undefined)
|
|
2234
|
+
// Validation resolves before discovery results are consumed, so a dependency
|
|
2235
|
+
// failure cannot mask a caller error.
|
|
2236
|
+
if (denomination)
|
|
2237
|
+
await Valuation.ratesFor(c, { currencies: [], denomination, oracle: options.oracle })
|
|
2238
|
+
const [records, held] = await Promise.all([recordsPromise, heldPromise])
|
|
2239
|
+
const registry = new Map(records.map((record) => [record.vaultAddress.toLowerCase(), record]))
|
|
2240
|
+
const vaults = held ?? [...registry.keys()].sort()
|
|
2241
|
+
const decoded = options.cursor ? Cursor.decode(options.cursor, ['address']) : undefined
|
|
2242
|
+
const cursor = decoded?.[0]?.toString().toLowerCase()
|
|
2243
|
+
const candidates = vaults.filter((vault) => !cursor || vault > cursor)
|
|
2244
|
+
const matches: listPositions.Match[] = []
|
|
2245
|
+
|
|
2246
|
+
let offset = 0
|
|
2247
|
+
// The deployment-scan bound also caps per-request position reads, so a long
|
|
2248
|
+
// exited-position history pages through candidates instead of probing them all.
|
|
2249
|
+
while (
|
|
2250
|
+
offset < candidates.length &&
|
|
2251
|
+
matches.length <= options.limit &&
|
|
2252
|
+
offset < maxScannedDeployments
|
|
2253
|
+
) {
|
|
2254
|
+
const batchSize = discoveryBatchLimit({
|
|
2255
|
+
// Zero-share candidates are read and then rejected, so batches over-fetch.
|
|
2256
|
+
filtered: true,
|
|
2257
|
+
limit: options.limit,
|
|
2258
|
+
matches: matches.length,
|
|
2259
|
+
remaining: Math.min(candidates.length, maxScannedDeployments) - offset,
|
|
2260
|
+
})
|
|
2261
|
+
const batch = candidates.slice(offset, offset + batchSize)
|
|
2262
|
+
offset += batch.length
|
|
2263
|
+
const read = await Timing.time(c, 'earn_positions', () =>
|
|
2264
|
+
Promise.all(
|
|
2265
|
+
batch.map(async (vault) => {
|
|
2266
|
+
const record = registry.get(vault)
|
|
2267
|
+
try {
|
|
2268
|
+
const position = await readPosition(c, {
|
|
2269
|
+
address: options.address,
|
|
2270
|
+
chainId: options.chainId,
|
|
2271
|
+
vaultAddress: Schema.Address.parse(vault),
|
|
2272
|
+
})
|
|
2273
|
+
return { position, record, vaultAddress: vault }
|
|
2274
|
+
} catch (cause) {
|
|
2275
|
+
if (!record && isIncompatible(cause)) return undefined
|
|
2276
|
+
throw cause
|
|
2277
|
+
}
|
|
2278
|
+
}),
|
|
2279
|
+
),
|
|
2280
|
+
)
|
|
2281
|
+
for (const match of read) {
|
|
2282
|
+
if (!match || match.position.shareBalance === 0n) continue
|
|
2283
|
+
matches.push(match)
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
const page = matches.slice(0, options.limit)
|
|
2288
|
+
const [tokens, snapshot] = await Promise.all([
|
|
2289
|
+
(async () => {
|
|
2290
|
+
const addresses = [
|
|
2291
|
+
...new Set(page.flatMap(({ position }) => [position.assetToken, position.shareToken])),
|
|
2292
|
+
]
|
|
2293
|
+
const groups: Tokens.resolveTokens.ReturnType[] = []
|
|
2294
|
+
for (let index = 0; index < addresses.length; index += tokenReadChunkSize)
|
|
2295
|
+
groups.push(
|
|
2296
|
+
await Tokens.resolveTokens(c, {
|
|
2297
|
+
addresses: addresses.slice(index, index + tokenReadChunkSize),
|
|
2298
|
+
chainId: options.chainId,
|
|
2299
|
+
include: ['token.verified'],
|
|
2300
|
+
}),
|
|
2301
|
+
)
|
|
2302
|
+
return new Map(groups.flatMap((group) => [...group]))
|
|
2303
|
+
})(),
|
|
2304
|
+
denomination ? VerifiedTokens.snapshot(c, options.chainId) : undefined,
|
|
2305
|
+
])
|
|
2306
|
+
const rates =
|
|
2307
|
+
denomination && snapshot
|
|
2308
|
+
? await Valuation.ratesFor(c, {
|
|
2309
|
+
currencies: page.flatMap(({ position }) => {
|
|
2310
|
+
const currency = snapshot.byAddress.get(position.assetToken.toLowerCase())?.currency
|
|
2311
|
+
return currency === undefined ? [] : [currency]
|
|
2312
|
+
}),
|
|
2313
|
+
denomination,
|
|
2314
|
+
oracle: options.oracle,
|
|
2315
|
+
})
|
|
2316
|
+
: undefined
|
|
2317
|
+
const last = matches[options.limit - 1]
|
|
2318
|
+
// A scan-bound exit resumes from the last probed candidate so a truncated
|
|
2319
|
+
// page never hides positions deeper in the candidate list.
|
|
2320
|
+
const next =
|
|
2321
|
+
matches.length > options.limit
|
|
2322
|
+
? last?.vaultAddress
|
|
2323
|
+
: offset < candidates.length
|
|
2324
|
+
? candidates[offset - 1]
|
|
2325
|
+
: undefined
|
|
2326
|
+
return {
|
|
2327
|
+
data: page.map((match) =>
|
|
2328
|
+
serializePosition({
|
|
2329
|
+
denomination,
|
|
2330
|
+
position: match.position,
|
|
2331
|
+
rates,
|
|
2332
|
+
snapshot,
|
|
2333
|
+
tokens,
|
|
2334
|
+
vaultAddress: match.vaultAddress,
|
|
2335
|
+
verified: match.record !== undefined,
|
|
2336
|
+
}),
|
|
2337
|
+
),
|
|
2338
|
+
...(rates ? { meta: { valuation: Valuation.pricing(rates, options.oracle) } } : {}),
|
|
2339
|
+
nextCursor: next ? Cursor.encode([next]) : null,
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
export declare namespace listPositions {
|
|
2344
|
+
/** One candidate vault holding a non-zero position. */
|
|
2345
|
+
type Match = {
|
|
2346
|
+
position: Actions.earn.getPosition.ReturnValue
|
|
2347
|
+
record?: RegistryRecord | undefined
|
|
2348
|
+
vaultAddress: string
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/** Options for listing an account's earn positions. */
|
|
2352
|
+
type Options = z.output<typeof schema.getEarnAddressPositions.Query> & {
|
|
2353
|
+
/** Account whose positions to list. */
|
|
2354
|
+
address: z.output<typeof Schema.Address>
|
|
2355
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
2356
|
+
/** FX rate oracle backing per-row valuation. */
|
|
2357
|
+
oracle: FxOracle.Oracle
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
/** Finds vaults whose earn share token the account has ever held, by vault address. */
|
|
2362
|
+
async function queryHeldVaults(
|
|
2363
|
+
c: Context<App.Environment>,
|
|
2364
|
+
options: queryHeldVaults.Options,
|
|
2365
|
+
): Promise<readonly string[]> {
|
|
2366
|
+
const tidx = c.get('getTidx')(options.chainId)
|
|
2367
|
+
// Each vault's newest deployment binds its current share token; joining it
|
|
2368
|
+
// against the account's holder deltas server-side turns holdings into vault
|
|
2369
|
+
// candidates in one round trip, with no literal list to overflow the request.
|
|
2370
|
+
// Earn shares are factory-minted TIP-20s, so the `0x20c0` prefix bounds the
|
|
2371
|
+
// subquery. Stale delta rows are fine: the live read drops them.
|
|
2372
|
+
const result = await Timing.time(c, 'earn_positions_discovery', () =>
|
|
2373
|
+
tidx.fetch({
|
|
2374
|
+
chainId: options.chainId,
|
|
2375
|
+
engine: 'clickhouse',
|
|
2376
|
+
query: `
|
|
2377
|
+
SELECT "earnVault"
|
|
2378
|
+
FROM (
|
|
2379
|
+
SELECT
|
|
2380
|
+
"earnVault",
|
|
2381
|
+
"earnShare",
|
|
2382
|
+
row_number() OVER (
|
|
2383
|
+
PARTITION BY lower("earnVault")
|
|
2384
|
+
ORDER BY block_num DESC, log_idx DESC
|
|
2385
|
+
) AS deployment_rank
|
|
2386
|
+
FROM EarnStackDeployed
|
|
2387
|
+
)
|
|
2388
|
+
WHERE deployment_rank = 1 AND lower("earnShare") IN (
|
|
2389
|
+
SELECT DISTINCT lower(token)
|
|
2390
|
+
FROM address_holder_deltas
|
|
2391
|
+
WHERE holder = '${options.address}' AND startsWith(token, '0x20c0')
|
|
2392
|
+
)
|
|
2393
|
+
ORDER BY lower("earnVault") ASC
|
|
2394
|
+
` as string,
|
|
2395
|
+
signatures: [deployedSignature],
|
|
2396
|
+
}),
|
|
2397
|
+
)
|
|
2398
|
+
const rows: readonly Record<string, unknown>[] = result.rows
|
|
2399
|
+
return rows.flatMap((row) => {
|
|
2400
|
+
const vault = Schema.Address.safeParse(Value.toText(row['earnVault']))
|
|
2401
|
+
return vault.success ? [vault.data.toLowerCase()] : []
|
|
2402
|
+
})
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
declare namespace queryHeldVaults {
|
|
2406
|
+
/** Inputs selecting an account's held earn share tokens. */
|
|
2407
|
+
type Options = {
|
|
2408
|
+
address: z.output<typeof Schema.Address>
|
|
2409
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/** Reads one account's live position in one vault. */
|
|
2414
|
+
function readPosition(
|
|
2415
|
+
c: Context<App.Environment>,
|
|
2416
|
+
options: readPosition.Options,
|
|
2417
|
+
): Promise<Actions.earn.getPosition.ReturnValue> {
|
|
2418
|
+
return c
|
|
2419
|
+
.get('getClient')(options.chainId)
|
|
2420
|
+
.earn.getPosition({ account: options.address, vault: options.vaultAddress })
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
declare namespace readPosition {
|
|
2424
|
+
/** Inputs selecting one vault position. */
|
|
2425
|
+
type Options = {
|
|
2426
|
+
address: z.output<typeof Schema.Address>
|
|
2427
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
2428
|
+
vaultAddress: z.output<typeof Schema.Address>
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/** Serializes one live position into the address-scoped row contract. */
|
|
2433
|
+
function serializePosition(options: serializePosition.Options) {
|
|
2434
|
+
const { position, tokens } = options
|
|
2435
|
+
const assetToken = getToken(tokens, position.assetToken)
|
|
2436
|
+
const shareToken = getToken(tokens, position.shareToken)
|
|
2437
|
+
const vaultAddress = Schema.Address.parse(options.vaultAddress.toLowerCase())
|
|
2438
|
+
return {
|
|
2439
|
+
assetAmount: valueFor(position.value, assetToken),
|
|
2440
|
+
assetToken,
|
|
2441
|
+
id: vaultAddress,
|
|
2442
|
+
shareAmount: valueFor(position.shareBalance, shareToken),
|
|
2443
|
+
shareToken,
|
|
2444
|
+
...(options.denomination
|
|
2445
|
+
? {
|
|
2446
|
+
valuation: Valuation.valuationFor({
|
|
2447
|
+
amount: position.value,
|
|
2448
|
+
denomination: options.denomination,
|
|
2449
|
+
rates: options.rates,
|
|
2450
|
+
token: options.snapshot?.byAddress.get(assetToken.address),
|
|
2451
|
+
}),
|
|
2452
|
+
}
|
|
2453
|
+
: {}),
|
|
2454
|
+
vaultAddress,
|
|
2455
|
+
verified: options.verified,
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
declare namespace serializePosition {
|
|
2460
|
+
/** Values used to serialize one account position row. */
|
|
2461
|
+
type Options = {
|
|
2462
|
+
/** Currency the row valuation is denominated in (uppercase). */
|
|
2463
|
+
denomination?: string | undefined
|
|
2464
|
+
position: Actions.earn.getPosition.ReturnValue
|
|
2465
|
+
/** Consulted FX rate set, or undefined when unavailable or unnecessary. */
|
|
2466
|
+
rates: Awaited<ReturnType<typeof Valuation.ratesFor>>
|
|
2467
|
+
/** Curated token snapshot backing valuation, present when requested. */
|
|
2468
|
+
snapshot?: VerifiedTokens.Snapshot | undefined
|
|
2469
|
+
tokens: Tokens.resolveTokens.ReturnType
|
|
2470
|
+
vaultAddress: string
|
|
2471
|
+
verified: boolean
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/** Renders a base-unit amount in the token's display currency and precision. */
|
|
2476
|
+
function valueFor(amount: bigint, token: z.output<typeof Tokens.schema.Token>) {
|
|
2477
|
+
return {
|
|
2478
|
+
amount: amount.toString(),
|
|
2479
|
+
currency: token.currency,
|
|
2480
|
+
decimals: token.decimals,
|
|
2481
|
+
formatted: core_Value.format(amount, token.decimals),
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2043
2485
|
/** Gets one compatible Earn vault. */
|
|
2044
2486
|
export async function getVault(c: Context<App.Environment>, options: getVault.Options) {
|
|
2045
2487
|
const db = Db.get(c.get('dbCached'))
|