tapimo 0.8.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 +7410 -7613
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +0 -1
- package/dist/App.js.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 +74 -74
- 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 +3120 -2782
- 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.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 +283 -52
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +291 -21
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +232 -66
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +4 -4
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/earn.d.ts +642 -126
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +627 -58
- 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/tokens.d.ts +2 -0
- 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 +40 -18
- 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 +127 -386
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +20 -5
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +141 -532
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +21 -103
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +4 -4
- package/dist/apps/funding/routes/transfers.js +5 -5
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- 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/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/projects.d.ts +20 -20
- package/dist/apps/management/routes/usage.d.ts +24 -24
- 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 +12 -0
- 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 +25 -15
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +18 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +46 -2
- package/dist/db/tables/fundingDeposits.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/Auth.d.ts +4 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +11 -2
- package/dist/internal/Auth.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 +10 -6
- 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 +229 -16
- 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 +18 -3
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/App.test.ts +32 -0
- package/src/App.ts +4 -1
- package/src/Client.test-d.ts +116 -38
- 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/composition.test.ts +39 -10
- 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 +161 -10
- package/src/apps/data/routes/balances.ts +339 -147
- package/src/apps/data/routes/blocks.test.ts +12 -6
- package/src/apps/data/routes/earn.test.ts +286 -9
- package/src/apps/data/routes/earn.ts +841 -58
- 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/App.ts +20 -5
- package/src/apps/funding/routes/deposit-addresses.test.ts +43 -22
- package/src/apps/funding/routes/deposit-addresses.ts +34 -117
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/funding/routes/transfers.test.ts +6 -25
- package/src/apps/funding/routes/transfers.ts +5 -5
- 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 +42 -19
- package/src/db/tables/fundingDeposits.test.ts +101 -0
- package/src/db/tables/fundingDeposits.ts +71 -2
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookSubscriptions.ts +116 -4
- package/src/internal/Auth.test.ts +28 -0
- package/src/internal/Auth.ts +15 -1
- 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 +16 -11
- package/src/internal/funding/Reconciliation.local.test.ts +3 -1
- package/src/internal/funding/Reconciliation.test.ts +1532 -54
- package/src/internal/funding/Reconciliation.ts +296 -53
- package/src/internal/funding/SourceObservation.test.ts +3 -2
- package/src/internal/funding/providers/relay.test.ts +1 -4
- package/src/internal/funding/providers/relay.ts +23 -3
|
@@ -31,6 +31,11 @@ const redeemRequestedSignature = 'event RedeemRequested(bytes32 indexed requestI
|
|
|
31
31
|
const venueSharesDepositedSignature = 'event VenueSharesDeposited(address indexed caller,address indexed receiver,uint256 requestedVenueShares,uint256 receivedVenueShares,uint256 earnShares)';
|
|
32
32
|
const maxScannedDeployments = 250;
|
|
33
33
|
const scanLimit = 50;
|
|
34
|
+
// Concurrent metadata reads in one macrotask collapse into one deployless
|
|
35
|
+
// multicall; an await between small chunks keeps each call under the RPC limit.
|
|
36
|
+
const tokenReadChunkSize = 20;
|
|
37
|
+
/** Block boundary that includes all indexed history. */
|
|
38
|
+
const beforeFirstBlockNumber = -1;
|
|
34
39
|
/** Vault TVL is reported in USD only; the API exposes no denomination parameter. */
|
|
35
40
|
const tvlDenomination = 'USD';
|
|
36
41
|
/** `Valuation.valuationFor` floors to 6 dp, so 6 is the precision a valued amount carries. */
|
|
@@ -83,7 +88,9 @@ export var schema;
|
|
|
83
88
|
inputTokens: z
|
|
84
89
|
.array(Schema.Address)
|
|
85
90
|
.check(z.describe('Tokens accepted by private deposits.'), z.meta({ examples: [['0x20c000000000000000000000ff04042ee92fd449']] })),
|
|
86
|
-
name: z
|
|
91
|
+
name: z
|
|
92
|
+
.string()
|
|
93
|
+
.check(z.describe('Private Zone name.'), z.meta({ examples: ['zone-moderato'] })),
|
|
87
94
|
outputTokens: z
|
|
88
95
|
.array(Schema.Address)
|
|
89
96
|
.check(z.describe('Tokens supported by private redemptions.'), z.meta({ examples: [['0x20c000000000000000000000ff04042ee92fd449']] })),
|
|
@@ -123,7 +130,7 @@ export var schema;
|
|
|
123
130
|
.check(z.describe('Vault display label.'), z.meta({ examples: ['btPATHUSD Earn'] })),
|
|
124
131
|
sharePrice: z
|
|
125
132
|
.nullable(value)
|
|
126
|
-
.check(z.describe('Assets returned for one whole
|
|
133
|
+
.check(z.describe('Assets returned for one whole earn share, in the asset display currency, or null when the engine cannot quote an exit.')),
|
|
127
134
|
shareToken: Tokens.schema.Token.check(z.describe('Token representing vault shares.')),
|
|
128
135
|
slug: z
|
|
129
136
|
.nullable(z.string())
|
|
@@ -135,13 +142,13 @@ export var schema;
|
|
|
135
142
|
vaultAddress: Schema.Address.check(z.describe('Earn vault contract address.'), z.meta({ examples: ['0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a'] })),
|
|
136
143
|
verified: z
|
|
137
144
|
.boolean()
|
|
138
|
-
.check(z.describe('Whether this vault is in the curated
|
|
145
|
+
.check(z.describe('Whether this vault is in the curated earn registry.'), z.meta({ examples: [true] })),
|
|
139
146
|
zone: z
|
|
140
147
|
.optional(z.nullable(zone))
|
|
141
148
|
.check(z.describe('Curated private Zone route, when requested, or null when absent.')),
|
|
142
149
|
});
|
|
143
150
|
/** One compatible Earn vault resolved from current chain state. */
|
|
144
|
-
schema.Vault = Schema.describe(vault, 'A compatible
|
|
151
|
+
schema.Vault = Schema.describe(vault, 'A compatible earn vault resolved from chain state.');
|
|
145
152
|
/** One registry-curated Earn vault. */
|
|
146
153
|
schema.VerifiedVault = Schema.describe(z.extend(vault, {
|
|
147
154
|
engine: z.extend(engine, { type: engineType }),
|
|
@@ -150,28 +157,43 @@ export var schema;
|
|
|
150
157
|
.check(z.describe('Stable curated vault slug.'), z.meta({ examples: ['btpathusd-earn-h8k2m4n6'] })),
|
|
151
158
|
verified: z
|
|
152
159
|
.literal(true)
|
|
153
|
-
.check(z.describe('This vault is in the curated
|
|
154
|
-
}), 'A registry-curated
|
|
160
|
+
.check(z.describe('This vault is in the curated earn registry.'), z.meta({ examples: [true] })),
|
|
161
|
+
}), 'A registry-curated earn vault resolved from chain state.');
|
|
155
162
|
const position = z.strictObject({
|
|
156
|
-
account: Schema.Address.check(z.describe('Account whose
|
|
163
|
+
account: Schema.Address.check(z.describe('Account whose earn position was read.'), z.meta({ examples: ['0xbe058e1c4df8a4366a387bf595b284246a93039e'] })),
|
|
157
164
|
assetAllowance: Schema.DecimalString.check(z.describe('Assets the vault may spend from this account, in asset base units.'), z.meta({ examples: ['0'] })),
|
|
158
165
|
assetBalance: Schema.DecimalString.check(z.describe('Assets this account currently holds, in asset base units.'), z.meta({ examples: ['250000000'] })),
|
|
159
166
|
assetToken: Schema.tokenAddress('0x20c000000000000000000000b9537d11c60e8b50').check(z.describe('TIP-20 asset token accepted by the vault.')),
|
|
160
167
|
id: Schema.Address.check(z.describe('Stable resource ID for this vault position, equal to the account address.'), z.meta({ examples: ['0xbe058e1c4df8a4366a387bf595b284246a93039e'] })),
|
|
161
168
|
shareAllowance: Schema.DecimalString.check(z.describe('Earn shares the vault may spend from this account, in share base units.'), z.meta({ examples: ['0'] })),
|
|
162
169
|
shareBalance: Schema.DecimalString.check(z.describe('Earn shares this account currently holds, in share base units.'), z.meta({ examples: ['99500000'] })),
|
|
163
|
-
shareToken: Schema.tokenAddress('0x20c000000000000000000000300e14ab91a10769').check(z.describe('TIP-20 token representing
|
|
164
|
-
value: Schema.DecimalString.check(z.describe('Current asset value of this account’s
|
|
170
|
+
shareToken: Schema.tokenAddress('0x20c000000000000000000000300e14ab91a10769').check(z.describe('TIP-20 token representing earn shares.')),
|
|
171
|
+
value: Schema.DecimalString.check(z.describe('Current asset value of this account’s earn shares, including fees, in asset base units.'), z.meta({ examples: ['100000000'] })),
|
|
172
|
+
});
|
|
173
|
+
const addressPosition = z.strictObject({
|
|
174
|
+
assetAmount: value.check(z.describe('Current asset value of the held shares, including fees, in `assetToken` units.')),
|
|
175
|
+
assetToken: Tokens.schema.Token.check(z.describe('Asset accepted by the vault.')),
|
|
176
|
+
id: Schema.Address.check(z.describe('Stable resource ID for this position, equal to the vault address.'), z.meta({ examples: ['0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a'] })),
|
|
177
|
+
shareAmount: value.check(z.describe('Earn share balance this account holds, in `shareToken` units.')),
|
|
178
|
+
shareToken: Tokens.schema.Token.check(z.describe('Token representing vault shares.')),
|
|
179
|
+
valuation: z
|
|
180
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
181
|
+
.check(z.describe('The position’s nominal value when `valuation.currency` is requested, or `null` when ' +
|
|
182
|
+
'the asset token is unverified, its display currency has no rate, or rates were unavailable.')),
|
|
183
|
+
vaultAddress: Schema.Address.check(z.describe('Earn vault contract address holding this position.'), z.meta({ examples: ['0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a'] })),
|
|
184
|
+
verified: z
|
|
185
|
+
.boolean()
|
|
186
|
+
.check(z.describe('Whether the vault is in the curated earn registry.'), z.meta({ examples: [true] })),
|
|
165
187
|
});
|
|
166
188
|
const earningsBase = z.strictObject({
|
|
167
189
|
account: Schema.Address.check(z.describe('Account whose vault earnings were calculated.'), z.meta({ examples: ['0x68f94cfca22e5969c0553bbeab00a2acde88939b'] })),
|
|
168
190
|
assetToken: Schema.TokenAddress.check(z.describe('TIP-20 asset token accepted by the vault.'), z.meta({ examples: ['0x20c000000000000000000000ff04042ee92fd449'] })),
|
|
169
|
-
currentValue: Schema.DecimalString.check(z.describe('Asset value of the account’s active
|
|
191
|
+
currentValue: Schema.DecimalString.check(z.describe('Asset value of the account’s active earn shares at the latest indexed block, in asset base units.'), z.meta({ examples: ['600000'] })),
|
|
170
192
|
id: Schema.Address.check(z.describe('Stable resource ID for these vault earnings, equal to the account address.'), z.meta({ examples: ['0x68f94cfca22e5969c0553bbeab00a2acde88939b'] })),
|
|
171
193
|
});
|
|
172
194
|
const earningsPeriod = z
|
|
173
|
-
.enum(['active', 'lifetime'])
|
|
174
|
-
.check(z.describe('Earnings period to calculate.'), z.meta({ examples: ['lifetime'] }));
|
|
195
|
+
.enum(['30d', 'active', 'lifetime'])
|
|
196
|
+
.check(z.describe('Earnings period to calculate: trailing 30 days, shares still held, or lifetime history.'), z.meta({ examples: ['lifetime'] }));
|
|
175
197
|
const earnings = z.union([
|
|
176
198
|
z.extend(earningsBase, {
|
|
177
199
|
lifetimeEarnings: z
|
|
@@ -195,16 +217,27 @@ export var schema;
|
|
|
195
217
|
.literal('complete')
|
|
196
218
|
.check(z.describe('Earnings include a complete asset-denominated cost basis.'), z.meta({ examples: ['complete'] })),
|
|
197
219
|
}),
|
|
220
|
+
z.extend(earningsBase, {
|
|
221
|
+
period: z
|
|
222
|
+
.literal('30d')
|
|
223
|
+
.check(z.describe('Returns cash-flow-adjusted earnings over the trailing 30 days.'), z.meta({ examples: ['30d'] })),
|
|
224
|
+
status: z
|
|
225
|
+
.literal('complete')
|
|
226
|
+
.check(z.describe('Earnings include a complete asset-denominated cost basis.'), z.meta({ examples: ['complete'] })),
|
|
227
|
+
windowEarnings: z
|
|
228
|
+
.string()
|
|
229
|
+
.check(z.regex(/^-?\d+$/), z.describe('Ending share value plus realized assets, minus deposits and opening share value, over the trailing 30 days.'), z.meta({ examples: ['0'] })),
|
|
230
|
+
}),
|
|
198
231
|
z.extend(earningsBase, {
|
|
199
232
|
period: earningsPeriod,
|
|
200
233
|
status: z
|
|
201
234
|
.literal('incomplete_cost_basis')
|
|
202
|
-
.check(z.describe('Earnings are unavailable when indexed activity cannot reconstruct the
|
|
235
|
+
.check(z.describe('Earnings are unavailable when indexed activity cannot reconstruct the asset cost basis for the requested period.'), z.meta({ examples: ['incomplete_cost_basis'] })),
|
|
203
236
|
}),
|
|
204
237
|
z.extend(earningsBase, {
|
|
205
238
|
period: z
|
|
206
|
-
.
|
|
207
|
-
.check(z.describe('
|
|
239
|
+
.enum(['30d', 'lifetime'])
|
|
240
|
+
.check(z.describe('Earnings period with an unresolved async redemption.'), z.meta({ examples: ['lifetime'] })),
|
|
208
241
|
status: z
|
|
209
242
|
.literal('pending_redemption')
|
|
210
243
|
.check(z.describe('Earnings are unavailable while an async redemption remains open.'), z.meta({ examples: ['pending_redemption'] })),
|
|
@@ -232,7 +265,7 @@ export var schema;
|
|
|
232
265
|
});
|
|
233
266
|
const collectionQuery = z
|
|
234
267
|
.extend(collectionFilters, { 'apy.window': EarnRates.schema.Window })
|
|
235
|
-
.check(z.describe('Query parameters for listing
|
|
268
|
+
.check(z.describe('Query parameters for listing earn vaults.'));
|
|
236
269
|
const vaultSelector = z.strictObject({
|
|
237
270
|
chainId: Schema.ChainIdQuery,
|
|
238
271
|
include: includeQuery,
|
|
@@ -247,17 +280,17 @@ export var schema;
|
|
|
247
280
|
.strictObject({
|
|
248
281
|
data: z
|
|
249
282
|
.array(schema.Vault)
|
|
250
|
-
.check(z.describe('Compatible deployed
|
|
283
|
+
.check(z.describe('Compatible deployed earn vaults in deployment order.')),
|
|
251
284
|
meta: z.optional(meta),
|
|
252
285
|
nextCursor: Schema.NextCursor,
|
|
253
286
|
})
|
|
254
|
-
.check(z.describe('A page of compatible deployed
|
|
287
|
+
.check(z.describe('A page of compatible deployed earn vaults.'));
|
|
255
288
|
})(getEarnVaults = schema.getEarnVaults || (schema.getEarnVaults = {}));
|
|
256
289
|
/** Schemas for the getVerifiedEarnVaults operation. */
|
|
257
290
|
let getVerifiedEarnVaults;
|
|
258
291
|
(function (getVerifiedEarnVaults) {
|
|
259
292
|
/** Collection query without rates or valuation, for surfaces that cannot measure them. */
|
|
260
|
-
getVerifiedEarnVaults.BaseQuery = Schema.describe(z.extend(collectionFilters, { include: baseIncludeQuery }), 'Query parameters for listing
|
|
293
|
+
getVerifiedEarnVaults.BaseQuery = Schema.describe(z.extend(collectionFilters, { include: baseIncludeQuery }), 'Query parameters for listing earn vaults.');
|
|
261
294
|
/** Query parameters for registry-curated Earn vaults. */
|
|
262
295
|
getVerifiedEarnVaults.Query = collectionQuery;
|
|
263
296
|
/** Paginated list of registry-curated Earn vaults. */
|
|
@@ -265,29 +298,71 @@ export var schema;
|
|
|
265
298
|
.strictObject({
|
|
266
299
|
data: z
|
|
267
300
|
.array(schema.VerifiedVault)
|
|
268
|
-
.check(z.describe('Registry-curated
|
|
301
|
+
.check(z.describe('Registry-curated earn vaults ordered by vault address.')),
|
|
269
302
|
meta: z.optional(meta),
|
|
270
303
|
nextCursor: Schema.NextCursor,
|
|
271
304
|
})
|
|
272
|
-
.check(z.describe('A page of registry-curated
|
|
305
|
+
.check(z.describe('A page of registry-curated earn vaults.'));
|
|
273
306
|
})(getVerifiedEarnVaults = schema.getVerifiedEarnVaults || (schema.getVerifiedEarnVaults = {}));
|
|
307
|
+
/** Schemas for the getEarnAddressPositions operation. */
|
|
308
|
+
let getEarnAddressPositions;
|
|
309
|
+
(function (getEarnAddressPositions) {
|
|
310
|
+
/** Path parameters identifying the account. */
|
|
311
|
+
getEarnAddressPositions.Params = z
|
|
312
|
+
.strictObject({
|
|
313
|
+
address: Schema.Address.check(z.describe('Account whose earn positions to list.')),
|
|
314
|
+
})
|
|
315
|
+
.check(z.describe('Path parameters identifying an account with earn positions.'));
|
|
316
|
+
/** Query parameters for listing an account's earn positions. */
|
|
317
|
+
getEarnAddressPositions.Query = z
|
|
318
|
+
.strictObject({
|
|
319
|
+
chainId: Schema.ChainIdQuery,
|
|
320
|
+
cursor: Schema.Cursor,
|
|
321
|
+
limit: Schema.Limit,
|
|
322
|
+
'valuation.currency': Schema.Denomination.check(z.describe('When present, include each position’s nominal value in this denomination. ' +
|
|
323
|
+
'Case-insensitive and must be priced by the configured FX oracle.')),
|
|
324
|
+
verified: z
|
|
325
|
+
.optional(Schema.booleanQuery())
|
|
326
|
+
.check(z.describe('When `true`, include only positions in registry-curated (verified) earn vaults.'), z.meta({ examples: [true] })),
|
|
327
|
+
})
|
|
328
|
+
.check(z.describe('Query parameters for listing an account’s earn positions.'));
|
|
329
|
+
/** Page-level valuation rate provenance. */
|
|
330
|
+
getEarnAddressPositions.Meta = z
|
|
331
|
+
.object({
|
|
332
|
+
valuation: z
|
|
333
|
+
.optional(Valuation.schema.Pricing)
|
|
334
|
+
.check(z.describe('Rate provenance for row valuations. Present when conversion rates were ' +
|
|
335
|
+
'consulted; absent when every value was identity-valued or rates were unavailable.')),
|
|
336
|
+
})
|
|
337
|
+
.check(z.describe('Page-level valuation rate provenance.'));
|
|
338
|
+
/** Paginated list of the account's earn vault positions. */
|
|
339
|
+
getEarnAddressPositions.Response = z
|
|
340
|
+
.strictObject({
|
|
341
|
+
data: z
|
|
342
|
+
.array(addressPosition)
|
|
343
|
+
.check(z.describe('Vaults where the account holds earn shares.')),
|
|
344
|
+
meta: z.optional(getEarnAddressPositions.Meta),
|
|
345
|
+
nextCursor: Schema.NextCursor,
|
|
346
|
+
})
|
|
347
|
+
.check(z.describe('A page of the account’s earn vault positions.'));
|
|
348
|
+
})(getEarnAddressPositions = schema.getEarnAddressPositions || (schema.getEarnAddressPositions = {}));
|
|
274
349
|
/** Schemas for the getEarnVault operation. */
|
|
275
350
|
let getEarnVault;
|
|
276
351
|
(function (getEarnVault) {
|
|
277
|
-
/** Path parameters identifying one
|
|
352
|
+
/** Path parameters identifying one earn vault. */
|
|
278
353
|
getEarnVault.Params = z
|
|
279
354
|
.strictObject({
|
|
280
355
|
vaultId: Schema.Address.check(z.describe('Earn vault contract address.')),
|
|
281
356
|
})
|
|
282
|
-
.check(z.describe('Path parameters identifying one
|
|
357
|
+
.check(z.describe('Path parameters identifying one earn vault.'));
|
|
283
358
|
/** Detail query without rates or valuation, for surfaces that cannot measure them. */
|
|
284
|
-
getEarnVault.BaseQuery = Schema.describe(z.extend(vaultSelector, { include: baseIncludeQuery }), 'Query parameters selecting one
|
|
359
|
+
getEarnVault.BaseQuery = Schema.describe(z.extend(vaultSelector, { include: baseIncludeQuery }), 'Query parameters selecting one earn vault.');
|
|
285
360
|
/** Query parameters selecting the chain containing the vault. */
|
|
286
361
|
getEarnVault.Query = z
|
|
287
362
|
.extend(vaultSelector, { 'apy.window': EarnRates.schema.Window })
|
|
288
|
-
.check(z.describe('Query parameters selecting one
|
|
363
|
+
.check(z.describe('Query parameters selecting one earn vault.'));
|
|
289
364
|
/** One compatible Earn vault resolved directly from current chain state. */
|
|
290
|
-
getEarnVault.Response = Schema.describe(z.extend(vault, { meta: z.optional(meta) }), 'A compatible
|
|
365
|
+
getEarnVault.Response = Schema.describe(z.extend(vault, { meta: z.optional(meta) }), 'A compatible earn vault resolved from chain state, with rate provenance for its valued fields.');
|
|
291
366
|
})(getEarnVault = schema.getEarnVault || (schema.getEarnVault = {}));
|
|
292
367
|
/** Schemas for the getEarnVaultPosition operation. */
|
|
293
368
|
let getEarnVaultPosition;
|
|
@@ -295,16 +370,16 @@ export var schema;
|
|
|
295
370
|
/** Path parameters identifying the Earn vault and account. */
|
|
296
371
|
getEarnVaultPosition.Params = z
|
|
297
372
|
.strictObject({
|
|
298
|
-
address: Schema.Address.check(z.describe('Account whose
|
|
373
|
+
address: Schema.Address.check(z.describe('Account whose earn position to read.')),
|
|
299
374
|
vaultId: Schema.address('0xdb48b00fb28d9a8db881fcd671c3fb1bf8c486b6').check(z.describe('Earn vault contract address.')),
|
|
300
375
|
})
|
|
301
|
-
.check(z.describe('Path parameters identifying an
|
|
376
|
+
.check(z.describe('Path parameters identifying an earn vault position.'));
|
|
302
377
|
/** Query parameters selecting the chain containing the vault. */
|
|
303
378
|
getEarnVaultPosition.Query = z
|
|
304
379
|
.strictObject({ chainId: Schema.ChainIdQuery })
|
|
305
|
-
.check(z.describe('Query parameters selecting an
|
|
380
|
+
.check(z.describe('Query parameters selecting an earn vault position.'));
|
|
306
381
|
/** Current asset and Earn-share state for one account in one vault. */
|
|
307
|
-
getEarnVaultPosition.Response = Schema.describe(position, 'Current asset and
|
|
382
|
+
getEarnVaultPosition.Response = Schema.describe(position, 'Current asset and earn-share balances, allowances, and asset value for an account in one earn vault.');
|
|
308
383
|
})(getEarnVaultPosition = schema.getEarnVaultPosition || (schema.getEarnVaultPosition = {}));
|
|
309
384
|
/** Schemas for the getEarnVaultEarnings operation. */
|
|
310
385
|
let getEarnVaultEarnings;
|
|
@@ -315,16 +390,16 @@ export var schema;
|
|
|
315
390
|
address: Schema.Address.check(z.describe('Account whose vault earnings to read.')),
|
|
316
391
|
vaultId: Schema.address('0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a').check(z.describe('Earn vault contract address.')),
|
|
317
392
|
})
|
|
318
|
-
.check(z.describe('Path parameters identifying an
|
|
393
|
+
.check(z.describe('Path parameters identifying an earn vault account.'));
|
|
319
394
|
/** Query parameters selecting the chain containing the vault. */
|
|
320
395
|
getEarnVaultEarnings.Query = z
|
|
321
396
|
.strictObject({
|
|
322
397
|
chainId: Schema.ChainIdQuery,
|
|
323
398
|
period: z._default(earningsPeriod, 'lifetime'),
|
|
324
399
|
})
|
|
325
|
-
.check(z.describe('Query parameters selecting an
|
|
400
|
+
.check(z.describe('Query parameters selecting an earn vault account.'));
|
|
326
401
|
/** Earnings for one account and period in one Earn vault. */
|
|
327
|
-
getEarnVaultEarnings.Response = Schema.describe(earnings, 'Indexed asset value and cost-basis-aware earnings for an account in one
|
|
402
|
+
getEarnVaultEarnings.Response = Schema.describe(earnings, 'Indexed asset value and cost-basis-aware earnings for an account in one earn vault.');
|
|
328
403
|
})(getEarnVaultEarnings = schema.getEarnVaultEarnings || (schema.getEarnVaultEarnings = {}));
|
|
329
404
|
})(schema || (schema = {}));
|
|
330
405
|
/** Creates the public Earn-vault handlers. */
|
|
@@ -335,12 +410,12 @@ export function earn(options = {}) {
|
|
|
335
410
|
code: 'query_invalid',
|
|
336
411
|
message: 'Invalid query parameters',
|
|
337
412
|
}), OpenApi.describeRoute({
|
|
338
|
-
description: 'Lists compatible
|
|
413
|
+
description: 'Lists compatible earn vaults discovered from deployment events and resolved from current chain state.',
|
|
339
414
|
operationId: 'getEarnVaults',
|
|
340
415
|
responses: OpenApi.responses({
|
|
341
|
-
errors: { 502: 'Could not read
|
|
416
|
+
errors: { 502: 'Could not read earn deployment or vault data.' },
|
|
342
417
|
success: {
|
|
343
|
-
description: 'A page of compatible deployed
|
|
418
|
+
description: 'A page of compatible deployed earn vaults.',
|
|
344
419
|
schema: schema.getEarnVaults.Response,
|
|
345
420
|
},
|
|
346
421
|
}),
|
|
@@ -375,12 +450,12 @@ export function earn(options = {}) {
|
|
|
375
450
|
code: 'query_invalid',
|
|
376
451
|
message: 'Invalid query parameters',
|
|
377
452
|
}), OpenApi.describeRoute({
|
|
378
|
-
description: 'Lists registry-curated
|
|
453
|
+
description: 'Lists registry-curated earn vaults after resolving their current onchain configuration.',
|
|
379
454
|
operationId: 'getVerifiedEarnVaults',
|
|
380
455
|
responses: OpenApi.responses({
|
|
381
|
-
errors: { 502: 'Could not read curated
|
|
456
|
+
errors: { 502: 'Could not read curated earn registry or vault data.' },
|
|
382
457
|
success: {
|
|
383
|
-
description: 'A page of registry-curated
|
|
458
|
+
description: 'A page of registry-curated earn vaults.',
|
|
384
459
|
schema: schema.getVerifiedEarnVaults.Response,
|
|
385
460
|
},
|
|
386
461
|
}),
|
|
@@ -407,6 +482,79 @@ export function earn(options = {}) {
|
|
|
407
482
|
catch (cause) {
|
|
408
483
|
return Response.upstream(c, cause);
|
|
409
484
|
}
|
|
485
|
+
})
|
|
486
|
+
.get('/v1/earn/addresses/:address/positions', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getEarnAddressPositions.Params, {
|
|
487
|
+
code: 'positions_invalid',
|
|
488
|
+
message: 'Invalid address positions',
|
|
489
|
+
}), OpenApi.validate('query', schema.getEarnAddressPositions.Query, {
|
|
490
|
+
code: 'query_invalid',
|
|
491
|
+
message: 'Invalid query parameters',
|
|
492
|
+
}), OpenApi.describeRoute({
|
|
493
|
+
description: 'Lists every earn vault where an account currently holds shares, with the current balance and value of each position.',
|
|
494
|
+
operationId: 'getEarnAddressPositions',
|
|
495
|
+
responses: OpenApi.responses({
|
|
496
|
+
errors: {
|
|
497
|
+
400: {
|
|
498
|
+
codes: [
|
|
499
|
+
'chain_id_invalid',
|
|
500
|
+
'chain_id_unsupported',
|
|
501
|
+
'positions_invalid',
|
|
502
|
+
'query_invalid',
|
|
503
|
+
],
|
|
504
|
+
},
|
|
505
|
+
502: 'Could not read earn deployment or position data.',
|
|
506
|
+
},
|
|
507
|
+
success: {
|
|
508
|
+
description: 'A page of the account’s earn vault positions.',
|
|
509
|
+
schema: schema.getEarnAddressPositions.Response,
|
|
510
|
+
},
|
|
511
|
+
}),
|
|
512
|
+
summary: 'List account positions',
|
|
513
|
+
tags: ['Earn'],
|
|
514
|
+
}),
|
|
515
|
+
// `noStore` matches the address balances listing: account holdings must
|
|
516
|
+
// read fresh immediately after a deposit or redemption.
|
|
517
|
+
Cache.response({
|
|
518
|
+
cacheControl: Cache.policies.noStore,
|
|
519
|
+
key: (c) => Cache.urlKey(c, schema.getEarnAddressPositions.Query),
|
|
520
|
+
name: 'tempo-api:earn-address-positions:v1',
|
|
521
|
+
}), async (c) => {
|
|
522
|
+
if (Auth.narrowAccess)
|
|
523
|
+
return Auth.paidAccessError(c);
|
|
524
|
+
if (OpenApi.narrowValidation)
|
|
525
|
+
return Response.error(c, {
|
|
526
|
+
code: 'chain_id_invalid',
|
|
527
|
+
message: 'Invalid chain id',
|
|
528
|
+
status: 400,
|
|
529
|
+
});
|
|
530
|
+
if (OpenApi.narrowValidation)
|
|
531
|
+
return Response.error(c, {
|
|
532
|
+
code: 'chain_id_unsupported',
|
|
533
|
+
message: 'Unsupported chain id',
|
|
534
|
+
status: 400,
|
|
535
|
+
});
|
|
536
|
+
if (OpenApi.narrowValidation)
|
|
537
|
+
return OpenApi.validationError(c, {
|
|
538
|
+
code: 'positions_invalid',
|
|
539
|
+
message: 'Invalid request parameters',
|
|
540
|
+
});
|
|
541
|
+
if (OpenApi.narrowValidation)
|
|
542
|
+
return OpenApi.validationError(c, {
|
|
543
|
+
code: 'query_invalid',
|
|
544
|
+
message: 'Invalid query parameters',
|
|
545
|
+
});
|
|
546
|
+
const { address } = c.req.valid('param');
|
|
547
|
+
const query = c.req.valid('query');
|
|
548
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
549
|
+
try {
|
|
550
|
+
const data = await listPositions(c, { ...query, address, chainId, oracle });
|
|
551
|
+
return c.json(Response.validated(schema.getEarnAddressPositions.Response, data), 200);
|
|
552
|
+
}
|
|
553
|
+
catch (cause) {
|
|
554
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
555
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 });
|
|
556
|
+
return Response.upstream(c, cause);
|
|
557
|
+
}
|
|
410
558
|
})
|
|
411
559
|
.get('/v1/earn/vaults/:vaultId/positions/:address', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getEarnVaultPosition.Params, {
|
|
412
560
|
code: 'position_invalid',
|
|
@@ -415,7 +563,7 @@ export function earn(options = {}) {
|
|
|
415
563
|
code: 'query_invalid',
|
|
416
564
|
message: 'Invalid query parameters',
|
|
417
565
|
}), OpenApi.describeRoute({
|
|
418
|
-
description: 'Gets an account’s asset and
|
|
566
|
+
description: 'Gets an account’s asset and earn-share balances, allowances, and current asset value for one earn vault.',
|
|
419
567
|
operationId: 'getEarnVaultPosition',
|
|
420
568
|
responses: OpenApi.responses({
|
|
421
569
|
errors: {
|
|
@@ -429,12 +577,12 @@ export function earn(options = {}) {
|
|
|
429
577
|
},
|
|
430
578
|
404: {
|
|
431
579
|
codes: ['earn_vault_not_found'],
|
|
432
|
-
description: 'No compatible
|
|
580
|
+
description: 'No compatible earn vault was found at the supplied vault address.',
|
|
433
581
|
},
|
|
434
|
-
502: 'Could not read the
|
|
582
|
+
502: 'Could not read the earn vault position.',
|
|
435
583
|
},
|
|
436
584
|
success: {
|
|
437
|
-
description: 'Current
|
|
585
|
+
description: 'Current earn position for one account in one vault.',
|
|
438
586
|
schema: schema.getEarnVaultPosition.Response,
|
|
439
587
|
},
|
|
440
588
|
}),
|
|
@@ -510,7 +658,7 @@ export function earn(options = {}) {
|
|
|
510
658
|
code: 'query_invalid',
|
|
511
659
|
message: 'Invalid query parameters',
|
|
512
660
|
}), OpenApi.describeRoute({
|
|
513
|
-
description: 'Gets an account’s indexed asset value and earnings for lifetime history or shares still held.',
|
|
661
|
+
description: 'Gets an account’s indexed asset value and earnings for lifetime history, the trailing 30 days, or shares still held.',
|
|
514
662
|
operationId: 'getEarnVaultEarnings',
|
|
515
663
|
responses: OpenApi.responses({
|
|
516
664
|
errors: {
|
|
@@ -524,9 +672,9 @@ export function earn(options = {}) {
|
|
|
524
672
|
},
|
|
525
673
|
404: {
|
|
526
674
|
codes: ['earn_vault_not_found'],
|
|
527
|
-
description: 'No compatible
|
|
675
|
+
description: 'No compatible earn vault was found at this address.',
|
|
528
676
|
},
|
|
529
|
-
502: 'Could not read the
|
|
677
|
+
502: 'Could not read the earn vault earnings.',
|
|
530
678
|
},
|
|
531
679
|
success: {
|
|
532
680
|
description: 'Cost-basis-aware earnings for one account in one vault.',
|
|
@@ -538,7 +686,7 @@ export function earn(options = {}) {
|
|
|
538
686
|
}), Cache.response({
|
|
539
687
|
cacheControl: Cache.policies.state,
|
|
540
688
|
key: (c) => Cache.urlKey(c, schema.getEarnVaultEarnings.Query),
|
|
541
|
-
name: 'tempo-api:earn-vault-earnings:
|
|
689
|
+
name: 'tempo-api:earn-vault-earnings:v3',
|
|
542
690
|
}), async (c) => {
|
|
543
691
|
if (Auth.narrowAccess)
|
|
544
692
|
return Auth.paidAccessError(c);
|
|
@@ -598,18 +746,18 @@ export function earn(options = {}) {
|
|
|
598
746
|
code: 'query_invalid',
|
|
599
747
|
message: 'Invalid query parameters',
|
|
600
748
|
}), OpenApi.describeRoute({
|
|
601
|
-
description: 'Resolves any compatible
|
|
749
|
+
description: 'Resolves any compatible earn vault directly from current chain state and enriches it when curated.',
|
|
602
750
|
operationId: 'getEarnVault',
|
|
603
751
|
responses: OpenApi.responses({
|
|
604
752
|
errors: {
|
|
605
753
|
404: {
|
|
606
754
|
codes: ['earn_vault_not_found'],
|
|
607
|
-
description: 'No compatible
|
|
755
|
+
description: 'No compatible earn vault was found at this address.',
|
|
608
756
|
},
|
|
609
|
-
502: 'Could not read
|
|
757
|
+
502: 'Could not read earn vault data.',
|
|
610
758
|
},
|
|
611
759
|
success: {
|
|
612
|
-
description: 'One compatible
|
|
760
|
+
description: 'One compatible earn vault.',
|
|
613
761
|
schema: schema.getEarnVault.Response,
|
|
614
762
|
},
|
|
615
763
|
}),
|
|
@@ -660,7 +808,17 @@ export function earn(options = {}) {
|
|
|
660
808
|
export async function getVaultEarnings(c, options) {
|
|
661
809
|
const client = c.get('getClient')(options.chainId);
|
|
662
810
|
const tidx = c.get('getTidx')(options.chainId);
|
|
663
|
-
const
|
|
811
|
+
const block = await getLatestIndexedBlock(tidx);
|
|
812
|
+
const blockNumber = block.number;
|
|
813
|
+
if (options.period === '30d')
|
|
814
|
+
return getWindowVaultEarnings(c, {
|
|
815
|
+
account: options.account,
|
|
816
|
+
blockNumber,
|
|
817
|
+
chainId: options.chainId,
|
|
818
|
+
period: options.period,
|
|
819
|
+
timestamp: block.timestamp,
|
|
820
|
+
vault: options.vault,
|
|
821
|
+
});
|
|
664
822
|
const position = client.earn.getPosition({
|
|
665
823
|
account: options.account,
|
|
666
824
|
blockNumber: BigInt(blockNumber),
|
|
@@ -694,6 +852,7 @@ export async function getVaultEarnings(c, options) {
|
|
|
694
852
|
getVaultEarningsHistory(tidx, {
|
|
695
853
|
account: options.account,
|
|
696
854
|
blockNumber,
|
|
855
|
+
startBlockNumber: beforeFirstBlockNumber,
|
|
697
856
|
vault: options.vault,
|
|
698
857
|
}),
|
|
699
858
|
position,
|
|
@@ -702,6 +861,7 @@ export async function getVaultEarnings(c, options) {
|
|
|
702
861
|
account: options.account,
|
|
703
862
|
blockNumber,
|
|
704
863
|
shareToken: current.shareToken,
|
|
864
|
+
startBlockNumber: beforeFirstBlockNumber,
|
|
705
865
|
vault: options.vault,
|
|
706
866
|
});
|
|
707
867
|
const base = {
|
|
@@ -718,15 +878,105 @@ export async function getVaultEarnings(c, options) {
|
|
|
718
878
|
const lifetimeEarnings = current.value + history.redeemed + history.withdrewExact + history.finalized - history.deposited;
|
|
719
879
|
return { ...base, lifetimeEarnings: lifetimeEarnings.toString(), status: 'complete' };
|
|
720
880
|
}
|
|
881
|
+
/** Reads cash-flow-adjusted earnings over a trailing indexed window. */
|
|
882
|
+
async function getWindowVaultEarnings(c, options) {
|
|
883
|
+
const client = c.get('getClient')(options.chainId);
|
|
884
|
+
const tidx = c.get('getTidx')(options.chainId);
|
|
885
|
+
const [current, startBlockNumber] = await Promise.all([
|
|
886
|
+
client.earn.getPosition({
|
|
887
|
+
account: options.account,
|
|
888
|
+
blockNumber: BigInt(options.blockNumber),
|
|
889
|
+
vault: options.vault,
|
|
890
|
+
}),
|
|
891
|
+
getWindowStartBlockNumber(tidx, { days: 30, timestamp: options.timestamp }),
|
|
892
|
+
]);
|
|
893
|
+
const [externalShareTransfer, history, opening] = await Promise.all([
|
|
894
|
+
hasExternalShareTransfer(tidx, {
|
|
895
|
+
account: options.account,
|
|
896
|
+
blockNumber: options.blockNumber,
|
|
897
|
+
shareToken: current.shareToken,
|
|
898
|
+
startBlockNumber,
|
|
899
|
+
vault: options.vault,
|
|
900
|
+
}),
|
|
901
|
+
getVaultEarningsHistory(tidx, {
|
|
902
|
+
account: options.account,
|
|
903
|
+
blockNumber: options.blockNumber,
|
|
904
|
+
startBlockNumber,
|
|
905
|
+
vault: options.vault,
|
|
906
|
+
}),
|
|
907
|
+
getWindowShareState(tidx, {
|
|
908
|
+
account: options.account,
|
|
909
|
+
blockNumber: options.blockNumber,
|
|
910
|
+
shareToken: current.shareToken,
|
|
911
|
+
startBlockNumber,
|
|
912
|
+
vault: options.vault,
|
|
913
|
+
}),
|
|
914
|
+
]);
|
|
915
|
+
const base = {
|
|
916
|
+
account: options.account,
|
|
917
|
+
assetToken: current.assetToken,
|
|
918
|
+
currentValue: current.value.toString(),
|
|
919
|
+
id: options.account,
|
|
920
|
+
period: options.period,
|
|
921
|
+
};
|
|
922
|
+
if (opening === undefined ||
|
|
923
|
+
externalShareTransfer ||
|
|
924
|
+
history.incompleteCostBasis ||
|
|
925
|
+
opening.crossBoundaryRedemption ||
|
|
926
|
+
opening.endShares !== current.shareBalance)
|
|
927
|
+
return { ...base, status: 'incomplete_cost_basis' };
|
|
928
|
+
if (history.pendingRedemption)
|
|
929
|
+
return { ...base, status: 'pending_redemption' };
|
|
930
|
+
const openingValue = opening.startShares === 0n
|
|
931
|
+
? 0n
|
|
932
|
+
: await client.earn.getRedeemQuote({
|
|
933
|
+
blockNumber: BigInt(startBlockNumber),
|
|
934
|
+
shareAmount: opening.startShares,
|
|
935
|
+
vault: options.vault,
|
|
936
|
+
});
|
|
937
|
+
const windowEarnings = calculateWindowEarnings({
|
|
938
|
+
deposited: history.deposited,
|
|
939
|
+
endingValue: current.value,
|
|
940
|
+
finalized: history.finalized,
|
|
941
|
+
openingValue,
|
|
942
|
+
redeemed: history.redeemed,
|
|
943
|
+
withdrewExact: history.withdrewExact,
|
|
944
|
+
});
|
|
945
|
+
return { ...base, status: 'complete', windowEarnings: windowEarnings.toString() };
|
|
946
|
+
}
|
|
947
|
+
/** Calculates cash-flow-adjusted earnings between two position snapshots. */
|
|
948
|
+
export function calculateWindowEarnings(options) {
|
|
949
|
+
return (options.endingValue +
|
|
950
|
+
options.redeemed +
|
|
951
|
+
options.withdrewExact +
|
|
952
|
+
options.finalized -
|
|
953
|
+
options.deposited -
|
|
954
|
+
options.openingValue);
|
|
955
|
+
}
|
|
721
956
|
/** Reads the latest block whose indexed logs back the earnings calculation. */
|
|
722
|
-
async function
|
|
957
|
+
async function getLatestIndexedBlock(tidx) {
|
|
723
958
|
const result = await tidx.fetch({
|
|
724
959
|
engine: 'clickhouse',
|
|
725
|
-
query: 'SELECT num FROM blocks ORDER BY num DESC LIMIT 1',
|
|
960
|
+
query: 'SELECT num, timestamp FROM blocks ORDER BY num DESC LIMIT 1',
|
|
726
961
|
});
|
|
727
|
-
const
|
|
728
|
-
|
|
962
|
+
const number = Value.toNumber(result.rows[0]?.['num']);
|
|
963
|
+
const timestamp = Value.toIsoDateTime(result.rows[0]?.['timestamp']);
|
|
964
|
+
if (number === undefined || timestamp === undefined)
|
|
729
965
|
throw new Error('TIDX returned no indexed block.');
|
|
966
|
+
return { number, timestamp };
|
|
967
|
+
}
|
|
968
|
+
/** Resolves the last indexed block at or before a trailing-day boundary. */
|
|
969
|
+
async function getWindowStartBlockNumber(tidx, options) {
|
|
970
|
+
const cutoff = new Date(Date.parse(options.timestamp) - Ttl.days(options.days)).toISOString();
|
|
971
|
+
const result = await tidx.fetch({
|
|
972
|
+
query: `SELECT num FROM blocks WHERE timestamp <= '${cutoff}' ORDER BY num DESC LIMIT 1`,
|
|
973
|
+
});
|
|
974
|
+
const row = result.rows[0];
|
|
975
|
+
if (!row)
|
|
976
|
+
return beforeFirstBlockNumber;
|
|
977
|
+
const blockNumber = Value.toNumber(row['num']);
|
|
978
|
+
if (blockNumber === undefined)
|
|
979
|
+
throw new Error('TIDX returned no Earn earnings boundary.');
|
|
730
980
|
return blockNumber;
|
|
731
981
|
}
|
|
732
982
|
/** Reads the indexed cash flows and exceptional states behind one earnings calculation. */
|
|
@@ -740,6 +990,7 @@ async function getVaultEarningsHistory(tidx, options) {
|
|
|
740
990
|
FROM Deposited
|
|
741
991
|
WHERE address = '${options.vault}'
|
|
742
992
|
AND receiver = '${options.account}'
|
|
993
|
+
AND block_num > ${options.startBlockNumber}
|
|
743
994
|
AND block_num <= ${options.blockNumber}
|
|
744
995
|
) AS deposited_assets,
|
|
745
996
|
(
|
|
@@ -750,6 +1001,7 @@ async function getVaultEarningsHistory(tidx, options) {
|
|
|
750
1001
|
AND requested."requestId" = finalized."requestId"
|
|
751
1002
|
WHERE finalized.address = '${options.vault}'
|
|
752
1003
|
AND requested.requester = '${options.account}'
|
|
1004
|
+
AND finalized.block_num > ${options.startBlockNumber}
|
|
753
1005
|
AND finalized.block_num <= ${options.blockNumber}
|
|
754
1006
|
AND requested.block_num <= ${options.blockNumber}
|
|
755
1007
|
) AS finalized_assets,
|
|
@@ -773,6 +1025,7 @@ async function getVaultEarningsHistory(tidx, options) {
|
|
|
773
1025
|
FROM Redeemed
|
|
774
1026
|
WHERE address = '${options.vault}'
|
|
775
1027
|
AND caller = '${options.account}'
|
|
1028
|
+
AND block_num > ${options.startBlockNumber}
|
|
776
1029
|
AND block_num <= ${options.blockNumber}
|
|
777
1030
|
) AS redeemed_assets,
|
|
778
1031
|
(
|
|
@@ -782,6 +1035,7 @@ async function getVaultEarningsHistory(tidx, options) {
|
|
|
782
1035
|
ON requested.address = cancelled.address
|
|
783
1036
|
AND requested."requestId" = cancelled."requestId"
|
|
784
1037
|
WHERE cancelled.address = '${options.vault}'
|
|
1038
|
+
AND cancelled.block_num > ${options.startBlockNumber}
|
|
785
1039
|
AND cancelled.block_num <= ${options.blockNumber}
|
|
786
1040
|
AND requested.block_num <= ${options.blockNumber}
|
|
787
1041
|
AND requested.requester != cancelled.receiver
|
|
@@ -795,6 +1049,7 @@ async function getVaultEarningsHistory(tidx, options) {
|
|
|
795
1049
|
FROM VenueSharesDeposited
|
|
796
1050
|
WHERE address = '${options.vault}'
|
|
797
1051
|
AND receiver = '${options.account}'
|
|
1052
|
+
AND block_num > ${options.startBlockNumber}
|
|
798
1053
|
AND block_num <= ${options.blockNumber}
|
|
799
1054
|
) AS venue_deposits,
|
|
800
1055
|
(
|
|
@@ -802,6 +1057,7 @@ async function getVaultEarningsHistory(tidx, options) {
|
|
|
802
1057
|
FROM WithdrewExact
|
|
803
1058
|
WHERE address = '${options.vault}'
|
|
804
1059
|
AND caller = '${options.account}'
|
|
1060
|
+
AND block_num > ${options.startBlockNumber}
|
|
805
1061
|
AND block_num <= ${options.blockNumber}
|
|
806
1062
|
) AS withdrew_exact
|
|
807
1063
|
`,
|
|
@@ -840,6 +1096,93 @@ async function getVaultEarningsHistory(tidx, options) {
|
|
|
840
1096
|
withdrewExact: BigInt(withdrewExact),
|
|
841
1097
|
};
|
|
842
1098
|
}
|
|
1099
|
+
/** Reads share balances and cross-boundary redemptions for a trailing window. */
|
|
1100
|
+
async function getWindowShareState(tidx, options) {
|
|
1101
|
+
const result = await tidx.fetch({
|
|
1102
|
+
engine: 'clickhouse',
|
|
1103
|
+
query: `
|
|
1104
|
+
SELECT
|
|
1105
|
+
(
|
|
1106
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1107
|
+
FROM token_transfers
|
|
1108
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1109
|
+
AND "to" = '${options.account}'
|
|
1110
|
+
AND block_num <= ${options.startBlockNumber}
|
|
1111
|
+
) AS received_start,
|
|
1112
|
+
(
|
|
1113
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1114
|
+
FROM token_transfers
|
|
1115
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1116
|
+
AND "from" = '${options.account}'
|
|
1117
|
+
AND block_num <= ${options.startBlockNumber}
|
|
1118
|
+
) AS sent_start,
|
|
1119
|
+
(
|
|
1120
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1121
|
+
FROM token_transfers
|
|
1122
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1123
|
+
AND "to" = '${options.account}'
|
|
1124
|
+
AND block_num <= ${options.blockNumber}
|
|
1125
|
+
) AS received_end,
|
|
1126
|
+
(
|
|
1127
|
+
SELECT toString(sum(toUInt256(amount)))
|
|
1128
|
+
FROM token_transfers
|
|
1129
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1130
|
+
AND "from" = '${options.account}'
|
|
1131
|
+
AND block_num <= ${options.blockNumber}
|
|
1132
|
+
) AS sent_end,
|
|
1133
|
+
(
|
|
1134
|
+
SELECT count()
|
|
1135
|
+
FROM (
|
|
1136
|
+
SELECT finalized."requestId"
|
|
1137
|
+
FROM RedeemFinalized AS finalized
|
|
1138
|
+
INNER JOIN RedeemRequested AS requested
|
|
1139
|
+
ON requested.address = finalized.address
|
|
1140
|
+
AND requested."requestId" = finalized."requestId"
|
|
1141
|
+
WHERE finalized.address = '${options.vault}'
|
|
1142
|
+
AND requested.requester = '${options.account}'
|
|
1143
|
+
AND requested.block_num <= ${options.startBlockNumber}
|
|
1144
|
+
AND finalized.block_num > ${options.startBlockNumber}
|
|
1145
|
+
AND finalized.block_num <= ${options.blockNumber}
|
|
1146
|
+
UNION ALL
|
|
1147
|
+
SELECT cancelled."requestId"
|
|
1148
|
+
FROM RedeemCancelled AS cancelled
|
|
1149
|
+
INNER JOIN RedeemRequested AS requested
|
|
1150
|
+
ON requested.address = cancelled.address
|
|
1151
|
+
AND requested."requestId" = cancelled."requestId"
|
|
1152
|
+
WHERE cancelled.address = '${options.vault}'
|
|
1153
|
+
AND requested.requester = '${options.account}'
|
|
1154
|
+
AND requested.block_num <= ${options.startBlockNumber}
|
|
1155
|
+
AND cancelled.block_num > ${options.startBlockNumber}
|
|
1156
|
+
AND cancelled.block_num <= ${options.blockNumber}
|
|
1157
|
+
) AS cross_boundary
|
|
1158
|
+
) AS cross_boundary_redemptions
|
|
1159
|
+
`,
|
|
1160
|
+
signatures: [redeemCancelledSignature, redeemFinalizedSignature, redeemRequestedSignature],
|
|
1161
|
+
});
|
|
1162
|
+
const row = result.rows[0];
|
|
1163
|
+
const crossBoundaryRedemptions = Value.toNumber(row?.['cross_boundary_redemptions']);
|
|
1164
|
+
const receivedEnd = Value.toIntegerString(row?.['received_end']);
|
|
1165
|
+
const receivedStart = Value.toIntegerString(row?.['received_start']);
|
|
1166
|
+
const sentEnd = Value.toIntegerString(row?.['sent_end']);
|
|
1167
|
+
const sentStart = Value.toIntegerString(row?.['sent_start']);
|
|
1168
|
+
if (crossBoundaryRedemptions === undefined ||
|
|
1169
|
+
receivedEnd === undefined ||
|
|
1170
|
+
receivedStart === undefined ||
|
|
1171
|
+
sentEnd === undefined ||
|
|
1172
|
+
sentStart === undefined)
|
|
1173
|
+
return undefined;
|
|
1174
|
+
const receivedEndValue = BigInt(receivedEnd);
|
|
1175
|
+
const receivedStartValue = BigInt(receivedStart);
|
|
1176
|
+
const sentEndValue = BigInt(sentEnd);
|
|
1177
|
+
const sentStartValue = BigInt(sentStart);
|
|
1178
|
+
if (sentEndValue > receivedEndValue || sentStartValue > receivedStartValue)
|
|
1179
|
+
return undefined;
|
|
1180
|
+
return {
|
|
1181
|
+
crossBoundaryRedemption: crossBoundaryRedemptions > 0,
|
|
1182
|
+
endShares: receivedEndValue - sentEndValue,
|
|
1183
|
+
startShares: receivedStartValue - sentStartValue,
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
843
1186
|
/** Returns whether an account sent or received Earn shares outside known vault flows. */
|
|
844
1187
|
async function hasExternalShareTransfer(tidx, options) {
|
|
845
1188
|
// Vault exits pull shares into the vault before burning. Reconcile counts by transaction and amount so unmatched direct sends remain external.
|
|
@@ -851,10 +1194,15 @@ async function hasExternalShareTransfer(tidx, options) {
|
|
|
851
1194
|
SELECT count()
|
|
852
1195
|
FROM token_transfers
|
|
853
1196
|
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1197
|
+
AND block_num > ${options.startBlockNumber}
|
|
854
1198
|
AND block_num <= ${options.blockNumber}
|
|
855
1199
|
AND (
|
|
856
1200
|
("from" = '${options.account}' AND "to" != '${options.vault}')
|
|
857
|
-
OR (
|
|
1201
|
+
OR (
|
|
1202
|
+
"to" = '${options.account}'
|
|
1203
|
+
AND "from" != '${zeroAddress}'
|
|
1204
|
+
AND "from" != '${options.vault}'
|
|
1205
|
+
)
|
|
858
1206
|
)
|
|
859
1207
|
) + (
|
|
860
1208
|
SELECT count()
|
|
@@ -864,6 +1212,7 @@ async function hasExternalShareTransfer(tidx, options) {
|
|
|
864
1212
|
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
865
1213
|
AND "from" = '${options.account}'
|
|
866
1214
|
AND "to" = '${options.vault}'
|
|
1215
|
+
AND block_num > ${options.startBlockNumber}
|
|
867
1216
|
AND block_num <= ${options.blockNumber}
|
|
868
1217
|
GROUP BY tx_hash, amount
|
|
869
1218
|
) AS candidate
|
|
@@ -874,27 +1223,58 @@ async function hasExternalShareTransfer(tidx, options) {
|
|
|
874
1223
|
FROM Redeemed
|
|
875
1224
|
WHERE address = '${options.vault}'
|
|
876
1225
|
AND caller = '${options.account}'
|
|
1226
|
+
AND block_num > ${options.startBlockNumber}
|
|
877
1227
|
AND block_num <= ${options.blockNumber}
|
|
878
1228
|
UNION ALL
|
|
879
1229
|
SELECT tx_hash, toUInt256(earnShares) AS amount
|
|
880
1230
|
FROM RedeemRequested
|
|
881
1231
|
WHERE address = '${options.vault}'
|
|
882
1232
|
AND requester = '${options.account}'
|
|
1233
|
+
AND block_num > ${options.startBlockNumber}
|
|
883
1234
|
AND block_num <= ${options.blockNumber}
|
|
884
1235
|
UNION ALL
|
|
885
1236
|
SELECT tx_hash, toUInt256(earnSharesBurned) AS amount
|
|
886
1237
|
FROM WithdrewExact
|
|
887
1238
|
WHERE address = '${options.vault}'
|
|
888
1239
|
AND caller = '${options.account}'
|
|
1240
|
+
AND block_num > ${options.startBlockNumber}
|
|
889
1241
|
AND block_num <= ${options.blockNumber}
|
|
890
1242
|
) AS protocol_exit
|
|
891
1243
|
GROUP BY tx_hash, amount
|
|
892
1244
|
) AS matched
|
|
893
1245
|
ON matched.tx_hash = candidate.tx_hash AND matched.amount = candidate.amount
|
|
894
1246
|
WHERE candidate.transfer_count > coalesce(matched.exit_count, 0)
|
|
1247
|
+
) + (
|
|
1248
|
+
SELECT count()
|
|
1249
|
+
FROM (
|
|
1250
|
+
SELECT tx_hash, toUInt256(amount) AS amount, count() AS transfer_count
|
|
1251
|
+
FROM token_transfers
|
|
1252
|
+
WHERE token = '${options.shareToken.toLowerCase()}'
|
|
1253
|
+
AND "from" = '${options.vault}'
|
|
1254
|
+
AND "to" = '${options.account}'
|
|
1255
|
+
AND block_num > ${options.startBlockNumber}
|
|
1256
|
+
AND block_num <= ${options.blockNumber}
|
|
1257
|
+
GROUP BY tx_hash, amount
|
|
1258
|
+
) AS candidate
|
|
1259
|
+
LEFT JOIN (
|
|
1260
|
+
SELECT tx_hash, toUInt256(earnShares) AS amount, count() AS cancel_count
|
|
1261
|
+
FROM RedeemCancelled
|
|
1262
|
+
WHERE address = '${options.vault}'
|
|
1263
|
+
AND receiver = '${options.account}'
|
|
1264
|
+
AND block_num > ${options.startBlockNumber}
|
|
1265
|
+
AND block_num <= ${options.blockNumber}
|
|
1266
|
+
GROUP BY tx_hash, amount
|
|
1267
|
+
) AS matched
|
|
1268
|
+
ON matched.tx_hash = candidate.tx_hash AND matched.amount = candidate.amount
|
|
1269
|
+
WHERE candidate.transfer_count > coalesce(matched.cancel_count, 0)
|
|
895
1270
|
) AS count
|
|
896
1271
|
`,
|
|
897
|
-
signatures: [
|
|
1272
|
+
signatures: [
|
|
1273
|
+
redeemCancelledSignature,
|
|
1274
|
+
redeemedSignature,
|
|
1275
|
+
redeemRequestedSignature,
|
|
1276
|
+
withdrewExactSignature,
|
|
1277
|
+
],
|
|
898
1278
|
});
|
|
899
1279
|
const count = Value.toNumber(result.rows[0]?.['count']);
|
|
900
1280
|
if (count === undefined)
|
|
@@ -1256,6 +1636,195 @@ export async function listVerifiedVaults(c, options) {
|
|
|
1256
1636
|
nextCursor: matches.length > options.limit && last ? Cursor.encode([last.record.vaultAddress]) : null,
|
|
1257
1637
|
};
|
|
1258
1638
|
}
|
|
1639
|
+
/** Lists every vault where an account holds earn shares, ordered by vault address. */
|
|
1640
|
+
export async function listPositions(c, options) {
|
|
1641
|
+
const denomination = options['valuation.currency'];
|
|
1642
|
+
const db = Db.get(c.get('dbCached'));
|
|
1643
|
+
const recordsPromise = core_EarnVaults.list(db, { chainId: options.chainId });
|
|
1644
|
+
// The verified page needs no index: the registry is the candidate set.
|
|
1645
|
+
const heldPromise = options.verified
|
|
1646
|
+
? undefined
|
|
1647
|
+
: queryHeldVaults(c, { address: options.address, chainId: options.chainId });
|
|
1648
|
+
// Observing these branches prevents unhandled rejections while the
|
|
1649
|
+
// denomination validates; the later await still propagates them.
|
|
1650
|
+
void recordsPromise.catch(() => undefined);
|
|
1651
|
+
void heldPromise?.catch(() => undefined);
|
|
1652
|
+
// Validation resolves before discovery results are consumed, so a dependency
|
|
1653
|
+
// failure cannot mask a caller error.
|
|
1654
|
+
if (denomination)
|
|
1655
|
+
await Valuation.ratesFor(c, { currencies: [], denomination, oracle: options.oracle });
|
|
1656
|
+
const [records, held] = await Promise.all([recordsPromise, heldPromise]);
|
|
1657
|
+
const registry = new Map(records.map((record) => [record.vaultAddress.toLowerCase(), record]));
|
|
1658
|
+
const vaults = held ?? [...registry.keys()].sort();
|
|
1659
|
+
const decoded = options.cursor ? Cursor.decode(options.cursor, ['address']) : undefined;
|
|
1660
|
+
const cursor = decoded?.[0]?.toString().toLowerCase();
|
|
1661
|
+
const candidates = vaults.filter((vault) => !cursor || vault > cursor);
|
|
1662
|
+
const matches = [];
|
|
1663
|
+
let offset = 0;
|
|
1664
|
+
// The deployment-scan bound also caps per-request position reads, so a long
|
|
1665
|
+
// exited-position history pages through candidates instead of probing them all.
|
|
1666
|
+
while (offset < candidates.length &&
|
|
1667
|
+
matches.length <= options.limit &&
|
|
1668
|
+
offset < maxScannedDeployments) {
|
|
1669
|
+
const batchSize = discoveryBatchLimit({
|
|
1670
|
+
// Zero-share candidates are read and then rejected, so batches over-fetch.
|
|
1671
|
+
filtered: true,
|
|
1672
|
+
limit: options.limit,
|
|
1673
|
+
matches: matches.length,
|
|
1674
|
+
remaining: Math.min(candidates.length, maxScannedDeployments) - offset,
|
|
1675
|
+
});
|
|
1676
|
+
const batch = candidates.slice(offset, offset + batchSize);
|
|
1677
|
+
offset += batch.length;
|
|
1678
|
+
const read = await Timing.time(c, 'earn_positions', () => Promise.all(batch.map(async (vault) => {
|
|
1679
|
+
const record = registry.get(vault);
|
|
1680
|
+
try {
|
|
1681
|
+
const position = await readPosition(c, {
|
|
1682
|
+
address: options.address,
|
|
1683
|
+
chainId: options.chainId,
|
|
1684
|
+
vaultAddress: Schema.Address.parse(vault),
|
|
1685
|
+
});
|
|
1686
|
+
return { position, record, vaultAddress: vault };
|
|
1687
|
+
}
|
|
1688
|
+
catch (cause) {
|
|
1689
|
+
if (!record && isIncompatible(cause))
|
|
1690
|
+
return undefined;
|
|
1691
|
+
throw cause;
|
|
1692
|
+
}
|
|
1693
|
+
})));
|
|
1694
|
+
for (const match of read) {
|
|
1695
|
+
if (!match || match.position.shareBalance === 0n)
|
|
1696
|
+
continue;
|
|
1697
|
+
matches.push(match);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
const page = matches.slice(0, options.limit);
|
|
1701
|
+
const [tokens, snapshot] = await Promise.all([
|
|
1702
|
+
(async () => {
|
|
1703
|
+
const addresses = [
|
|
1704
|
+
...new Set(page.flatMap(({ position }) => [position.assetToken, position.shareToken])),
|
|
1705
|
+
];
|
|
1706
|
+
const groups = [];
|
|
1707
|
+
for (let index = 0; index < addresses.length; index += tokenReadChunkSize)
|
|
1708
|
+
groups.push(await Tokens.resolveTokens(c, {
|
|
1709
|
+
addresses: addresses.slice(index, index + tokenReadChunkSize),
|
|
1710
|
+
chainId: options.chainId,
|
|
1711
|
+
include: ['token.verified'],
|
|
1712
|
+
}));
|
|
1713
|
+
return new Map(groups.flatMap((group) => [...group]));
|
|
1714
|
+
})(),
|
|
1715
|
+
denomination ? VerifiedTokens.snapshot(c, options.chainId) : undefined,
|
|
1716
|
+
]);
|
|
1717
|
+
const rates = denomination && snapshot
|
|
1718
|
+
? await Valuation.ratesFor(c, {
|
|
1719
|
+
currencies: page.flatMap(({ position }) => {
|
|
1720
|
+
const currency = snapshot.byAddress.get(position.assetToken.toLowerCase())?.currency;
|
|
1721
|
+
return currency === undefined ? [] : [currency];
|
|
1722
|
+
}),
|
|
1723
|
+
denomination,
|
|
1724
|
+
oracle: options.oracle,
|
|
1725
|
+
})
|
|
1726
|
+
: undefined;
|
|
1727
|
+
const last = matches[options.limit - 1];
|
|
1728
|
+
// A scan-bound exit resumes from the last probed candidate so a truncated
|
|
1729
|
+
// page never hides positions deeper in the candidate list.
|
|
1730
|
+
const next = matches.length > options.limit
|
|
1731
|
+
? last?.vaultAddress
|
|
1732
|
+
: offset < candidates.length
|
|
1733
|
+
? candidates[offset - 1]
|
|
1734
|
+
: undefined;
|
|
1735
|
+
return {
|
|
1736
|
+
data: page.map((match) => serializePosition({
|
|
1737
|
+
denomination,
|
|
1738
|
+
position: match.position,
|
|
1739
|
+
rates,
|
|
1740
|
+
snapshot,
|
|
1741
|
+
tokens,
|
|
1742
|
+
vaultAddress: match.vaultAddress,
|
|
1743
|
+
verified: match.record !== undefined,
|
|
1744
|
+
})),
|
|
1745
|
+
...(rates ? { meta: { valuation: Valuation.pricing(rates, options.oracle) } } : {}),
|
|
1746
|
+
nextCursor: next ? Cursor.encode([next]) : null,
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
/** Finds vaults whose earn share token the account has ever held, by vault address. */
|
|
1750
|
+
async function queryHeldVaults(c, options) {
|
|
1751
|
+
const tidx = c.get('getTidx')(options.chainId);
|
|
1752
|
+
// Each vault's newest deployment binds its current share token; joining it
|
|
1753
|
+
// against the account's holder deltas server-side turns holdings into vault
|
|
1754
|
+
// candidates in one round trip, with no literal list to overflow the request.
|
|
1755
|
+
// Earn shares are factory-minted TIP-20s, so the `0x20c0` prefix bounds the
|
|
1756
|
+
// subquery. Stale delta rows are fine: the live read drops them.
|
|
1757
|
+
const result = await Timing.time(c, 'earn_positions_discovery', () => tidx.fetch({
|
|
1758
|
+
chainId: options.chainId,
|
|
1759
|
+
engine: 'clickhouse',
|
|
1760
|
+
query: `
|
|
1761
|
+
SELECT "earnVault"
|
|
1762
|
+
FROM (
|
|
1763
|
+
SELECT
|
|
1764
|
+
"earnVault",
|
|
1765
|
+
"earnShare",
|
|
1766
|
+
row_number() OVER (
|
|
1767
|
+
PARTITION BY lower("earnVault")
|
|
1768
|
+
ORDER BY block_num DESC, log_idx DESC
|
|
1769
|
+
) AS deployment_rank
|
|
1770
|
+
FROM EarnStackDeployed
|
|
1771
|
+
)
|
|
1772
|
+
WHERE deployment_rank = 1 AND lower("earnShare") IN (
|
|
1773
|
+
SELECT DISTINCT lower(token)
|
|
1774
|
+
FROM address_holder_deltas
|
|
1775
|
+
WHERE holder = '${options.address}' AND startsWith(token, '0x20c0')
|
|
1776
|
+
)
|
|
1777
|
+
ORDER BY lower("earnVault") ASC
|
|
1778
|
+
`,
|
|
1779
|
+
signatures: [deployedSignature],
|
|
1780
|
+
}));
|
|
1781
|
+
const rows = result.rows;
|
|
1782
|
+
return rows.flatMap((row) => {
|
|
1783
|
+
const vault = Schema.Address.safeParse(Value.toText(row['earnVault']));
|
|
1784
|
+
return vault.success ? [vault.data.toLowerCase()] : [];
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
/** Reads one account's live position in one vault. */
|
|
1788
|
+
function readPosition(c, options) {
|
|
1789
|
+
return c
|
|
1790
|
+
.get('getClient')(options.chainId)
|
|
1791
|
+
.earn.getPosition({ account: options.address, vault: options.vaultAddress });
|
|
1792
|
+
}
|
|
1793
|
+
/** Serializes one live position into the address-scoped row contract. */
|
|
1794
|
+
function serializePosition(options) {
|
|
1795
|
+
const { position, tokens } = options;
|
|
1796
|
+
const assetToken = getToken(tokens, position.assetToken);
|
|
1797
|
+
const shareToken = getToken(tokens, position.shareToken);
|
|
1798
|
+
const vaultAddress = Schema.Address.parse(options.vaultAddress.toLowerCase());
|
|
1799
|
+
return {
|
|
1800
|
+
assetAmount: valueFor(position.value, assetToken),
|
|
1801
|
+
assetToken,
|
|
1802
|
+
id: vaultAddress,
|
|
1803
|
+
shareAmount: valueFor(position.shareBalance, shareToken),
|
|
1804
|
+
shareToken,
|
|
1805
|
+
...(options.denomination
|
|
1806
|
+
? {
|
|
1807
|
+
valuation: Valuation.valuationFor({
|
|
1808
|
+
amount: position.value,
|
|
1809
|
+
denomination: options.denomination,
|
|
1810
|
+
rates: options.rates,
|
|
1811
|
+
token: options.snapshot?.byAddress.get(assetToken.address),
|
|
1812
|
+
}),
|
|
1813
|
+
}
|
|
1814
|
+
: {}),
|
|
1815
|
+
vaultAddress,
|
|
1816
|
+
verified: options.verified,
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
/** Renders a base-unit amount in the token's display currency and precision. */
|
|
1820
|
+
function valueFor(amount, token) {
|
|
1821
|
+
return {
|
|
1822
|
+
amount: amount.toString(),
|
|
1823
|
+
currency: token.currency,
|
|
1824
|
+
decimals: token.decimals,
|
|
1825
|
+
formatted: core_Value.format(amount, token.decimals),
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1259
1828
|
/** Gets one compatible Earn vault. */
|
|
1260
1829
|
export async function getVault(c, options) {
|
|
1261
1830
|
const db = Db.get(c.get('dbCached'));
|