tapimo 0.10.1 → 0.11.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 +5249 -3572
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +1 -76
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +2 -144
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +74 -74
- package/dist/admin/apps/earn-vaults.d.ts +1 -1
- package/dist/admin/apps/organizations.d.ts +3 -3
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +206 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +4 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +2 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +3618 -2954
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +7 -2
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +1 -1
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +55 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +11 -11
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +2 -6
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +5 -5
- package/dist/apps/data/routes/earn.d.ts +4 -4
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +63 -34
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +8 -4
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +115 -77
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +26 -26
- package/dist/apps/data/routes/receipts.d.ts +240 -239
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +15 -14
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +4 -3
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +282 -282
- package/dist/apps/data/routes/transfers.d.ts +13 -13
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +25 -7
- 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/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +1519 -643
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +134 -740
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +3 -3
- package/dist/apps/funding/App.d.ts +233 -0
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +331 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +207 -11
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +33 -33
- package/dist/apps/management/App.d.ts +793 -16
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +50 -50
- package/dist/apps/management/routes/faucet.d.ts +2 -2
- package/dist/apps/management/routes/invite-links.d.ts +42 -42
- package/dist/apps/management/routes/members.d.ts +2 -2
- package/dist/apps/management/routes/projects.d.ts +40 -40
- package/dist/apps/management/routes/webhooks.d.ts +1031 -59
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +27 -4
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +53 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +7 -1
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +9 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +17 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +41 -0
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +69 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/db/tables/webhookQueueEvents.d.ts +3 -1
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
- package/dist/db/tables/webhookQueueEvents.js +4 -0
- package/dist/db/tables/webhookQueueEvents.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +33 -69
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +63 -100
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -26
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +0 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Credential.d.ts +27 -0
- package/dist/internal/Credential.d.ts.map +1 -0
- package/dist/internal/Credential.js +34 -0
- package/dist/internal/Credential.js.map +1 -0
- package/dist/internal/EarnRates.js +11 -7
- package/dist/internal/EarnRates.js.map +1 -1
- package/dist/internal/Log.d.ts +8 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +50 -116
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +60 -106
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.d.ts +3 -1
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Tidx.d.ts +4 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +2 -2
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +4 -3
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +3 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +1 -55
- package/dist/internal/WebhookTransfer.d.ts.map +1 -1
- package/dist/internal/WebhookTransfer.js +1 -239
- package/dist/internal/WebhookTransfer.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +85 -44
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +94 -73
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts +25 -0
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
- package/dist/internal/funding/DepositWebhook.js +74 -0
- package/dist/internal/funding/DepositWebhook.js.map +1 -0
- package/dist/internal/funding/Reconciliation.d.ts +6 -0
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +42 -10
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
- package/dist/internal/webhookDestinations/slack.js +16 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -1
- package/package.json +1 -1
- package/src/App.test.ts +30 -0
- package/src/App.ts +3 -0
- package/src/Client.test-d.ts +55 -2
- package/src/Webhooks.ts +2 -221
- package/src/analytics/Analytics.test.ts +34 -0
- package/src/analytics/Analytics.ts +206 -0
- package/src/analytics/tables/requestEvents.ts +6 -0
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.ts +10 -2
- package/src/apps/data/routes/activities.test.ts +107 -11
- package/src/apps/data/routes/activities.ts +64 -32
- package/src/apps/data/routes/balances.ts +2 -6
- package/src/apps/data/routes/earn.ts +64 -34
- package/src/apps/data/routes/exchanges.test.ts +8 -3
- package/src/apps/data/routes/exchanges.ts +134 -82
- package/src/apps/data/routes/receipts.ts +16 -14
- package/src/apps/data/routes/tokens.ts +3 -2
- package/src/apps/data/routes/transactions.bench.ts +8 -0
- package/src/apps/data/routes/transfers.ts +36 -10
- package/src/apps/data/routes/webhooks.test.ts +179 -1497
- package/src/apps/data/routes/webhooks.ts +172 -975
- package/src/apps/funding/routes/deposit-addresses.test.ts +216 -2
- package/src/apps/funding/routes/deposit-addresses.ts +254 -18
- package/src/apps/funding/routes/transfers.test.ts +17 -2
- package/src/apps/management/routes/webhooks.test.ts +79 -1
- package/src/apps/management/routes/webhooks.ts +40 -4
- package/src/db/Db.test.ts +1 -5
- package/src/db/Db.ts +59 -0
- package/src/db/Schema.ts +13 -0
- package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
- package/src/db/tables/fundingDepositAddresses.ts +39 -0
- package/src/db/tables/fundingDeposits.test.ts +82 -0
- package/src/db/tables/fundingDeposits.ts +130 -0
- package/src/db/tables/organizations.test.ts +5 -2
- package/src/db/tables/webhookQueueEvents.ts +7 -1
- package/src/db/tables/webhookSubscriptions.ts +89 -138
- package/src/internal/Auth.test.ts +0 -48
- package/src/internal/Auth.ts +0 -59
- package/src/internal/Credential.test.ts +51 -0
- package/src/internal/Credential.ts +58 -0
- package/src/internal/EarnRates.ts +13 -7
- package/src/internal/Log.test.ts +3 -1
- package/src/internal/Log.ts +14 -0
- package/src/internal/MetricSink.test.ts +75 -358
- package/src/internal/MetricSink.ts +117 -246
- package/src/internal/Schema.test.ts +18 -0
- package/src/internal/Schema.ts +13 -2
- package/src/internal/Tidx.ts +5 -4
- package/src/internal/Viem.test.ts +4 -0
- package/src/internal/Viem.ts +6 -4
- package/src/internal/WebhookTransfer.ts +1 -351
- package/src/internal/Webhooks.test.ts +111 -139
- package/src/internal/Webhooks.ts +181 -105
- package/src/internal/funding/DepositWebhook.test.ts +131 -0
- package/src/internal/funding/DepositWebhook.ts +101 -0
- package/src/internal/funding/Reconciliation.test.ts +14 -0
- package/src/internal/funding/Reconciliation.ts +161 -86
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
- package/src/internal/webhookDestinations/slack.ts +15 -0
- package/src/Webhooks.bench.ts +0 -714
- package/src/Webhooks.memory.bench.ts +0 -250
- package/src/Webhooks.test.ts +0 -146
|
@@ -35,8 +35,6 @@ const liveAccounts = {
|
|
|
35
35
|
channel: '0x5217c502adf3c96a9551770af4caa5f5144a6984',
|
|
36
36
|
// TokenCreated + Mint + Transfer.
|
|
37
37
|
creator: '0x253fb3bf1e3d75ad6ee1554f68f4ea0568f7d24e',
|
|
38
|
-
// Token burns.
|
|
39
|
-
burner: '0x69519497b59bd64e109d993157d45f14455cc7e6',
|
|
40
38
|
// KeyAuthorized + KeyRevoked + Approval (+ transfer/mint/exchange).
|
|
41
39
|
keychain: '0xfe776fd28e06ecc71a157398b2c9991318f1ea2e',
|
|
42
40
|
// Swaps through the stablecoin DEX (output kept, so it stays a plain `swap`).
|
|
@@ -50,27 +48,29 @@ const accounts =
|
|
|
50
48
|
? {
|
|
51
49
|
channel: localAccounts.keychain,
|
|
52
50
|
creator: localAccounts.creator,
|
|
53
|
-
burner: localAccounts.burner,
|
|
54
51
|
keychain: localAccounts.keychain,
|
|
55
52
|
swapper: localAccounts.swapper,
|
|
56
53
|
}
|
|
57
54
|
: liveAccounts
|
|
58
55
|
|
|
59
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* The account expected to surface each activity type. Burns are seeded per run
|
|
58
|
+
* instead: a live burner keeps accruing activity until its last burn falls off
|
|
59
|
+
* the first page.
|
|
60
|
+
*/
|
|
60
61
|
const coverage =
|
|
61
|
-
runtime.mode === 'localnet'
|
|
62
|
+
runtime.mode === 'localnet' && localAccounts
|
|
62
63
|
? {
|
|
63
|
-
approval:
|
|
64
|
-
burn:
|
|
65
|
-
mint:
|
|
66
|
-
'token-created':
|
|
67
|
-
transfer:
|
|
64
|
+
approval: localAccounts.keychain,
|
|
65
|
+
burn: localAccounts.burner,
|
|
66
|
+
mint: localAccounts.creator,
|
|
67
|
+
'token-created': localAccounts.creator,
|
|
68
|
+
transfer: localAccounts.creator,
|
|
68
69
|
}
|
|
69
70
|
: {
|
|
70
71
|
'access-key-created': accounts.keychain,
|
|
71
72
|
'access-key-revoked': accounts.keychain,
|
|
72
73
|
approval: accounts.keychain,
|
|
73
|
-
burn: accounts.burner,
|
|
74
74
|
mint: accounts.creator,
|
|
75
75
|
'session-closed': accounts.channel,
|
|
76
76
|
swap: accounts.swapper,
|
|
@@ -165,6 +165,54 @@ describe('GET /addresses/:address/activities', () => {
|
|
|
165
165
|
expect(find(body.data, type)).toBeDefined()
|
|
166
166
|
})
|
|
167
167
|
|
|
168
|
+
// Localnet seeds its burner through the shared fixtures, so this covers the
|
|
169
|
+
// hosted testnet, where no address stays burn-topped for long.
|
|
170
|
+
test.skipIf(runtime.mode === 'localnet')(
|
|
171
|
+
'classifies a seeded burn',
|
|
172
|
+
async () => {
|
|
173
|
+
const account = Tempo.accounts[11]
|
|
174
|
+
const wallet = Tempo.getClient({ account })
|
|
175
|
+
await Actions.faucet.fundSync(Tempo.client, { account, timeout: 60_000 })
|
|
176
|
+
|
|
177
|
+
// Burning needs the `issuer` role, so the account burns a token it owns.
|
|
178
|
+
const { token } = await Actions.token.createSync(wallet, {
|
|
179
|
+
currency: 'USD',
|
|
180
|
+
name: 'Seeded Burn USD',
|
|
181
|
+
symbol: 'SBUSD',
|
|
182
|
+
})
|
|
183
|
+
await sendTransactionSync(wallet, {
|
|
184
|
+
account,
|
|
185
|
+
calls: [
|
|
186
|
+
Actions.token.grantRoles.call(wallet, { role: 'issuer', to: account.address, token }),
|
|
187
|
+
Actions.token.mint.call(wallet, { amount: 1_000_000n, to: account.address, token }),
|
|
188
|
+
],
|
|
189
|
+
})
|
|
190
|
+
const { receipt } = await Actions.token.burnSync(wallet, { amount: 100_000n, token })
|
|
191
|
+
|
|
192
|
+
const deadline = Date.now() + 90_000
|
|
193
|
+
let burn: Activities.Entry | undefined
|
|
194
|
+
while (Date.now() < deadline) {
|
|
195
|
+
const response = await client.v1.addresses[':address'].activities.$get(
|
|
196
|
+
{ param: { address: account.address }, query: { limit: '50' } },
|
|
197
|
+
{ headers: { 'cache-control': 'no-store', ...TestApp.auth.headers } },
|
|
198
|
+
)
|
|
199
|
+
if (response.status === 200) {
|
|
200
|
+
const body = await TestApp.json(response, Activities.schema.getAddressActivities.Response)
|
|
201
|
+
burn = find(body.data, 'burn')
|
|
202
|
+
if (burn?.type === 'burn' && burn.transactionHash === receipt.transactionHash) break
|
|
203
|
+
}
|
|
204
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
205
|
+
}
|
|
206
|
+
expect(burn?.type).toBe('burn')
|
|
207
|
+
if (burn?.type !== 'burn') return
|
|
208
|
+
expect(burn.transactionHash).toBe(receipt.transactionHash)
|
|
209
|
+
expect(burn.data.sender.toLowerCase()).toBe(account.address.toLowerCase())
|
|
210
|
+
expect(burn.data.sourceToken.address.toLowerCase()).toBe(token.toLowerCase())
|
|
211
|
+
expect(burn.data.sourceAmount.baseUnits).toBe('100000')
|
|
212
|
+
},
|
|
213
|
+
180_000,
|
|
214
|
+
)
|
|
215
|
+
|
|
168
216
|
test('transfer data mirrors the `Transfer` resource', async () => {
|
|
169
217
|
const transfer = find((await activities(accounts.creator)).data, 'transfer')
|
|
170
218
|
expect(transfer?.type).toBe('transfer')
|
|
@@ -666,6 +714,54 @@ describe('Activities.list classification', () => {
|
|
|
666
714
|
expect(log.data).toBe('0x1234')
|
|
667
715
|
expect(log.topics).toEqual([unknownSelector, pad(viewer)])
|
|
668
716
|
})
|
|
717
|
+
|
|
718
|
+
test('degrades a topic-encoded approval with a fee transfer to `unknown`', async () => {
|
|
719
|
+
const approvalToken = '0x4120c7ec7e9ead9dcf44b35b5134ebdbfc6e3de9' as Address.Address
|
|
720
|
+
const feeToken = '0x20c0000000000000000000000000000000000000' as Address.Address
|
|
721
|
+
const feeManager = '0xfeec000000000000000000000000000000000000'
|
|
722
|
+
const approvalSelector = '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925'
|
|
723
|
+
const transferSelector = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
|
|
724
|
+
const pad = (value: string) => `0x${value.slice(2).padStart(64, '0')}`
|
|
725
|
+
const txHash = '0xd2439ad8016d4ffd6aebe835734ee4d118a688714a4f9aebcc11f87a2b22e896' as Hex.Hex
|
|
726
|
+
const common = {
|
|
727
|
+
blockNum: 15_781_805,
|
|
728
|
+
blockTimestamp: 1_776_703_284,
|
|
729
|
+
feePayer: viewer,
|
|
730
|
+
source: 'q1' as const,
|
|
731
|
+
txHash,
|
|
732
|
+
txIdx: 0,
|
|
733
|
+
txSender: viewer,
|
|
734
|
+
}
|
|
735
|
+
const { items } = await Activities.list(
|
|
736
|
+
provider([
|
|
737
|
+
{
|
|
738
|
+
...common,
|
|
739
|
+
data: '0x',
|
|
740
|
+
logAddress: approvalToken,
|
|
741
|
+
logIdx: 0,
|
|
742
|
+
selector: approvalSelector,
|
|
743
|
+
topic1: pad(viewer),
|
|
744
|
+
topic2: pad('0x6063259f720ee125e5bff693779ab8118b0118a7'),
|
|
745
|
+
topic3: '0x2a9063ed52b7d417e441f378325359c9ce274f5d6c3ecf11b7d42f24b9c90b7f',
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
...common,
|
|
749
|
+
data: `0x${(5_815).toString(16).padStart(64, '0')}`,
|
|
750
|
+
logAddress: feeToken,
|
|
751
|
+
logIdx: 1,
|
|
752
|
+
selector: transferSelector,
|
|
753
|
+
topic1: pad(viewer),
|
|
754
|
+
topic2: pad(feeManager),
|
|
755
|
+
topic3: null,
|
|
756
|
+
},
|
|
757
|
+
]),
|
|
758
|
+
{ address: viewer, store: store() },
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
expect(items).toHaveLength(1)
|
|
762
|
+
expect(items[0]?.type).toBe('unknown')
|
|
763
|
+
expect(items[0]?.events).toHaveLength(2)
|
|
764
|
+
})
|
|
669
765
|
})
|
|
670
766
|
|
|
671
767
|
describe('Activities.list precompile classification', () => {
|
|
@@ -2727,15 +2727,46 @@ function classifyFromLogs(
|
|
|
2727
2727
|
viewer: Address.Address
|
|
2728
2728
|
},
|
|
2729
2729
|
): RawItem[] {
|
|
2730
|
-
const
|
|
2730
|
+
const decoded = decodeRows(rows)
|
|
2731
|
+
const items = (() => {
|
|
2732
|
+
try {
|
|
2733
|
+
return classifyFromLogsCore(rows, ctx, decoded)
|
|
2734
|
+
} catch {
|
|
2735
|
+
// Indexed logs can match a known signature without satisfying that event's expected shape.
|
|
2736
|
+
return [
|
|
2737
|
+
unknown(ctx.itemBase(Math.min(...rows.map((row) => row.logIdx))), {
|
|
2738
|
+
events: buildTxEvents(rows, decoded),
|
|
2739
|
+
signer: signerForRows(rows, { fallback: ctx.signer, viewer: ctx.viewer }),
|
|
2740
|
+
}),
|
|
2741
|
+
]
|
|
2742
|
+
}
|
|
2743
|
+
})()
|
|
2731
2744
|
// When the caller requested raw logs, attach the full per-transaction log set
|
|
2732
2745
|
// (decoded when possible) to every classified item so no co-located log is
|
|
2733
2746
|
// hidden behind the headline. `unknown` items already carry it.
|
|
2734
2747
|
if (!ctx.includeLogs) return items
|
|
2735
|
-
const events = buildTxEvents(rows,
|
|
2748
|
+
const events = buildTxEvents(rows, decoded)
|
|
2736
2749
|
return items.map((item) => (item.type === 'unknown' ? item : { ...item, events }))
|
|
2737
2750
|
}
|
|
2738
2751
|
|
|
2752
|
+
/** Resolves an access-key signer from raw rows, falling back to the transaction signer. */
|
|
2753
|
+
function signerForRows(
|
|
2754
|
+
rows: readonly Row[],
|
|
2755
|
+
options: { fallback: 'self' | Address.Address; viewer: Address.Address },
|
|
2756
|
+
) {
|
|
2757
|
+
const accessKeySpendRow = rows.find(
|
|
2758
|
+
(row) =>
|
|
2759
|
+
row.selector === accessKeySpendSelector &&
|
|
2760
|
+
Address.isEqual(row.logAddress, Addresses.accountKeychain),
|
|
2761
|
+
)
|
|
2762
|
+
const accessKeySigner = accessKeySpendRow?.topic2
|
|
2763
|
+
? (`0x${accessKeySpendRow.topic2.slice(-40)}`.toLowerCase() as Address.Address)
|
|
2764
|
+
: null
|
|
2765
|
+
return accessKeySigner && !Address.isEqual(accessKeySigner, options.viewer)
|
|
2766
|
+
? accessKeySigner
|
|
2767
|
+
: options.fallback
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2739
2770
|
/** Classifies a Q1 (log-bearing) group via `parseEventLogs`, picking one headline item. */
|
|
2740
2771
|
function classifyFromLogsCore(
|
|
2741
2772
|
rows: Row[],
|
|
@@ -2746,24 +2777,11 @@ function classifyFromLogsCore(
|
|
|
2746
2777
|
signer: 'self' | Address.Address
|
|
2747
2778
|
viewer: Address.Address
|
|
2748
2779
|
},
|
|
2780
|
+
events = decodeRows(rows),
|
|
2749
2781
|
): RawItem[] {
|
|
2750
2782
|
const { blockNum, fingerprintMap, itemBase, viewer } = ctx
|
|
2751
2783
|
|
|
2752
|
-
|
|
2753
|
-
// is the access-key publicKey. Use it as the signer so successive access-key
|
|
2754
|
-
// actions group together.
|
|
2755
|
-
const accessKeySpendRow = rows.find(
|
|
2756
|
-
(r) =>
|
|
2757
|
-
r.selector === accessKeySpendSelector &&
|
|
2758
|
-
Address.isEqual(r.logAddress, Addresses.accountKeychain),
|
|
2759
|
-
)
|
|
2760
|
-
const accessKeySigner = accessKeySpendRow?.topic2
|
|
2761
|
-
? (`0x${accessKeySpendRow.topic2.slice(-40)}`.toLowerCase() as Address.Address)
|
|
2762
|
-
: null
|
|
2763
|
-
const signer: 'self' | Address.Address =
|
|
2764
|
-
accessKeySigner && !Address.isEqual(accessKeySigner, viewer) ? accessKeySigner : ctx.signer
|
|
2765
|
-
|
|
2766
|
-
const events = decodeRows(rows)
|
|
2784
|
+
const signer = signerForRows(rows, { fallback: ctx.signer, viewer })
|
|
2767
2785
|
|
|
2768
2786
|
const minLogIndex = Math.min(...rows.map((r) => r.logIdx))
|
|
2769
2787
|
|
|
@@ -3251,6 +3269,14 @@ function classifyFromLogsCore(
|
|
|
3251
3269
|
const approvalEvent = finalEvents.find(
|
|
3252
3270
|
(e) =>
|
|
3253
3271
|
e.eventName === 'Approval' &&
|
|
3272
|
+
// ERC-721 uses the same selector but indexes its token ID as topic3. In
|
|
3273
|
+
// non-strict mode viem otherwise decodes the missing ERC-20 amount as zero.
|
|
3274
|
+
rows.some(
|
|
3275
|
+
(row) =>
|
|
3276
|
+
row.logIdx === e.logIndex &&
|
|
3277
|
+
row.topic3 === null &&
|
|
3278
|
+
/^0x[0-9a-fA-F]{64}$/.test(row.data ?? ''),
|
|
3279
|
+
) &&
|
|
3254
3280
|
!Address.isEqual((e.args as { spender: Address.Address }).spender, Addresses.zoneOutbox),
|
|
3255
3281
|
)
|
|
3256
3282
|
if (approvalEvent) {
|
|
@@ -4338,21 +4364,27 @@ function unknown(
|
|
|
4338
4364
|
|
|
4339
4365
|
/** Decodes a group's raw rows into `viem/tempo` events via `parseEventLogs` (non-strict). */
|
|
4340
4366
|
function decodeRows(rows: readonly Row[]) {
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4367
|
+
return rows.flatMap((r) => {
|
|
4368
|
+
const log: Log = {
|
|
4369
|
+
address: r.logAddress as `0x${string}`,
|
|
4370
|
+
blockHash: '0x0' as Hex.Hex,
|
|
4371
|
+
blockNumber: BigInt(r.blockNum),
|
|
4372
|
+
data: (r.data ?? '0x') as Hex.Hex,
|
|
4373
|
+
logIndex: r.logIdx,
|
|
4374
|
+
removed: false,
|
|
4375
|
+
topics: [r.selector, r.topic1, r.topic2, r.topic3].filter(Boolean) as [Hex.Hex, ...Hex.Hex[]],
|
|
4376
|
+
transactionHash: r.txHash as `0x${string}`,
|
|
4377
|
+
transactionIndex: r.txIdx,
|
|
4378
|
+
}
|
|
4379
|
+
try {
|
|
4380
|
+
return parseEventLogs({
|
|
4381
|
+
abi: [...Abis.abis, ...Abis.earnRouter, ...Abis.earnVault],
|
|
4382
|
+
logs: [log],
|
|
4383
|
+
strict: false,
|
|
4384
|
+
})
|
|
4385
|
+
} catch {
|
|
4386
|
+
return []
|
|
4387
|
+
}
|
|
4356
4388
|
})
|
|
4357
4389
|
}
|
|
4358
4390
|
|
|
@@ -375,9 +375,7 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
375
375
|
),
|
|
376
376
|
)
|
|
377
377
|
const client = c.get('getClient')(chainId)
|
|
378
|
-
const caller = c.get('zones').has(chainId)
|
|
379
|
-
? (await client.zone.getZoneInfo()).sequencers[0]
|
|
380
|
-
: undefined
|
|
378
|
+
const caller = c.get('zones').has(chainId) ? address : undefined
|
|
381
379
|
const [balance, feeTokens, snapshot, resolvedToken] = await Promise.all([
|
|
382
380
|
Timing.time(c, 'balance_rpc', async () => {
|
|
383
381
|
const call = Actions.token.getBalance.call(client, {
|
|
@@ -516,9 +514,7 @@ async function getAddressBalances(
|
|
|
516
514
|
const correctedPromise = holdingsPromise.then(async (holdings) => {
|
|
517
515
|
if (holdings.length === 0) return []
|
|
518
516
|
const client = c.get('getClient')(chainId)
|
|
519
|
-
const caller = c.get('zones').has(chainId)
|
|
520
|
-
? (await client.zone.getZoneInfo()).sequencers[0]
|
|
521
|
-
: undefined
|
|
517
|
+
const caller = c.get('zones').has(chainId) ? address : undefined
|
|
522
518
|
return Timing.time(c, 'balances_rpc', async () => {
|
|
523
519
|
// Concurrent reads enter viem's shared batch window and become one
|
|
524
520
|
// deployless multicall. A failed read fails the request rather than serving
|
|
@@ -2034,6 +2034,8 @@ export async function listVaults(c: Context<App.Environment>, options: listVault
|
|
|
2034
2034
|
core_EarnVaults.list(db, { chainId: options.chainId }),
|
|
2035
2035
|
VerifiedTokens.snapshot(c, options.chainId),
|
|
2036
2036
|
])
|
|
2037
|
+
const include = vaultInclude(options)
|
|
2038
|
+
const apyByVault = new Map<string, EarnRates.Apy | null>()
|
|
2037
2039
|
const registry = new Map(records.map((record) => [record.vaultAddress.toLowerCase(), record]))
|
|
2038
2040
|
const decoded = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined
|
|
2039
2041
|
let position = decoded
|
|
@@ -2062,22 +2064,44 @@ export async function listVaults(c: Context<App.Environment>, options: listVault
|
|
|
2062
2064
|
}
|
|
2063
2065
|
scanned += page.items.length
|
|
2064
2066
|
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2067
|
+
const apyVaults =
|
|
2068
|
+
apyByVault.size === 0 &&
|
|
2069
|
+
include.includes('apy') &&
|
|
2070
|
+
options.capability.length === 0 &&
|
|
2071
|
+
options['engine.type'] === undefined
|
|
2072
|
+
? page.items
|
|
2073
|
+
.slice(0, options.limit - matches.length)
|
|
2074
|
+
.map(({ vaultAddress }) => vaultAddress)
|
|
2075
|
+
: []
|
|
2076
|
+
// Rate boundaries and historical quotes depend only on candidate addresses,
|
|
2077
|
+
// so the unfiltered page can resolve them alongside live vault discovery.
|
|
2078
|
+
const [discovered, prefetchedApy] = await Promise.all([
|
|
2079
|
+
Promise.all(
|
|
2080
|
+
page.items.map(async (item) => {
|
|
2081
|
+
const record = registry.get(item.vaultAddress.toLowerCase())
|
|
2082
|
+
try {
|
|
2083
|
+
const discovery = await resolveVaultDiscovery(c, {
|
|
2084
|
+
chainId: options.chainId,
|
|
2085
|
+
include: discoveryInclude(options),
|
|
2086
|
+
vaultAddress: item.vaultAddress,
|
|
2087
|
+
})
|
|
2088
|
+
return { discovery, position: item.position, record }
|
|
2089
|
+
} catch (cause) {
|
|
2090
|
+
if (!record && isIncompatible(cause)) return undefined
|
|
2091
|
+
throw cause
|
|
2092
|
+
}
|
|
2093
|
+
}),
|
|
2094
|
+
),
|
|
2095
|
+
apyVaults.length > 0
|
|
2096
|
+
? resolvePageApy(c, {
|
|
2070
2097
|
chainId: options.chainId,
|
|
2071
|
-
include
|
|
2072
|
-
|
|
2098
|
+
include,
|
|
2099
|
+
vaults: apyVaults,
|
|
2100
|
+
window: options['apy.window'],
|
|
2073
2101
|
})
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
throw cause
|
|
2078
|
-
}
|
|
2079
|
-
}),
|
|
2080
|
-
)
|
|
2102
|
+
: undefined,
|
|
2103
|
+
])
|
|
2104
|
+
for (const [vault, apy] of prefetchedApy ?? []) apyByVault.set(vault, apy)
|
|
2081
2105
|
for (const match of discovered) {
|
|
2082
2106
|
if (!match || !matchesVaultFilters(match.discovery, options)) continue
|
|
2083
2107
|
matches.push(match)
|
|
@@ -2089,17 +2113,25 @@ export async function listVaults(c: Context<App.Environment>, options: listVault
|
|
|
2089
2113
|
|
|
2090
2114
|
const page = matches.slice(0, options.limit)
|
|
2091
2115
|
const discoveries = page.map(({ discovery }) => discovery)
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2116
|
+
const missingApyVaults = include.includes('apy')
|
|
2117
|
+
? discoveries.flatMap(({ vaultAddress }) =>
|
|
2118
|
+
apyByVault.has(vaultAddress.toLowerCase()) ? [] : [vaultAddress],
|
|
2119
|
+
)
|
|
2120
|
+
: []
|
|
2121
|
+
const [missingApy, rates, tokens] = await Promise.all([
|
|
2122
|
+
missingApyVaults.length > 0
|
|
2123
|
+
? resolvePageApy(c, {
|
|
2124
|
+
chainId: options.chainId,
|
|
2125
|
+
include,
|
|
2126
|
+
vaults: missingApyVaults,
|
|
2127
|
+
window: options['apy.window'],
|
|
2128
|
+
})
|
|
2129
|
+
: undefined,
|
|
2100
2130
|
resolvePageRates(c, { discoveries, include, oracle: options.oracle, snapshot }),
|
|
2101
2131
|
resolvePageTokens(c, { chainId: options.chainId, discoveries, include, snapshot }),
|
|
2102
2132
|
])
|
|
2133
|
+
for (const [vault, apy] of missingApy ?? []) apyByVault.set(vault, apy)
|
|
2134
|
+
const apy = include.includes('apy') ? apyByVault : undefined
|
|
2103
2135
|
const data = page.map((match) =>
|
|
2104
2136
|
resolveVault(c, { ...match, apy, include, rates, snapshot, tokens }),
|
|
2105
2137
|
)
|
|
@@ -2181,8 +2213,8 @@ export async function listVerifiedVaults(
|
|
|
2181
2213
|
const [apy, rates, tokens] = await Promise.all([
|
|
2182
2214
|
resolvePageApy(c, {
|
|
2183
2215
|
chainId: options.chainId,
|
|
2184
|
-
discoveries,
|
|
2185
2216
|
include,
|
|
2217
|
+
vaults: discoveries.map(({ vaultAddress }) => vaultAddress),
|
|
2186
2218
|
window: options['apy.window'],
|
|
2187
2219
|
}),
|
|
2188
2220
|
resolvePageRates(c, { discoveries, include, oracle: options.oracle, snapshot }),
|
|
@@ -2503,8 +2535,8 @@ export async function getVault(c: Context<App.Environment>, options: getVault.Op
|
|
|
2503
2535
|
const [apy, rates, tokens] = await Promise.all([
|
|
2504
2536
|
resolvePageApy(c, {
|
|
2505
2537
|
chainId: options.chainId,
|
|
2506
|
-
discoveries,
|
|
2507
2538
|
include,
|
|
2539
|
+
vaults: discoveries.map(({ vaultAddress }) => vaultAddress),
|
|
2508
2540
|
window: options['apy.window'],
|
|
2509
2541
|
}),
|
|
2510
2542
|
resolvePageRates(c, { discoveries, include, oracle: options.oracle, snapshot }),
|
|
@@ -2616,7 +2648,7 @@ function resolvePageApy(c: Context<App.Environment>, options: resolvePageApy.Opt
|
|
|
2616
2648
|
getClient: c.get('getClient'),
|
|
2617
2649
|
getTidx: c.get('getTidx'),
|
|
2618
2650
|
store: c.get('store'),
|
|
2619
|
-
vaults: options.
|
|
2651
|
+
vaults: options.vaults,
|
|
2620
2652
|
window: options.window,
|
|
2621
2653
|
}),
|
|
2622
2654
|
)
|
|
@@ -2625,8 +2657,8 @@ function resolvePageApy(c: Context<App.Environment>, options: resolvePageApy.Opt
|
|
|
2625
2657
|
declare namespace resolvePageApy {
|
|
2626
2658
|
type Options = {
|
|
2627
2659
|
chainId: z.output<typeof Schema.ChainId>
|
|
2628
|
-
discoveries: readonly EarnVaults.Discovery[]
|
|
2629
2660
|
include: z.output<typeof schema.getEarnVault.Query>['include']
|
|
2661
|
+
vaults: readonly z.output<typeof Schema.Address>[]
|
|
2630
2662
|
window: z.output<typeof schema.getEarnVault.Query>['apy.window']
|
|
2631
2663
|
}
|
|
2632
2664
|
}
|
|
@@ -2837,7 +2869,7 @@ async function queryDeployments(
|
|
|
2837
2869
|
c: Context<App.Environment>,
|
|
2838
2870
|
options: queryDeployments.Options,
|
|
2839
2871
|
): Promise<queryDeployments.Result> {
|
|
2840
|
-
const filters
|
|
2872
|
+
const filters: string[] = []
|
|
2841
2873
|
if (options.asset) filters.push(`lower(asset) = '${options.asset.toLowerCase()}'`)
|
|
2842
2874
|
if (options.cursor)
|
|
2843
2875
|
filters.push(
|
|
@@ -2855,7 +2887,7 @@ async function queryDeployments(
|
|
|
2855
2887
|
]),
|
|
2856
2888
|
)
|
|
2857
2889
|
|
|
2858
|
-
//
|
|
2890
|
+
// Deduplicate before outer filters so every vault is filtered by its latest deployment.
|
|
2859
2891
|
const result = await c
|
|
2860
2892
|
.get('getTidx')(options.chainId)
|
|
2861
2893
|
.fetch({
|
|
@@ -2868,14 +2900,12 @@ async function queryDeployments(
|
|
|
2868
2900
|
"earnVault",
|
|
2869
2901
|
asset,
|
|
2870
2902
|
block_num,
|
|
2871
|
-
log_idx
|
|
2872
|
-
row_number() OVER (
|
|
2873
|
-
PARTITION BY lower("earnVault")
|
|
2874
|
-
ORDER BY block_num DESC, log_idx DESC
|
|
2875
|
-
) AS deployment_rank
|
|
2903
|
+
log_idx
|
|
2876
2904
|
FROM EarnStackDeployed
|
|
2905
|
+
ORDER BY block_num DESC, log_idx DESC
|
|
2906
|
+
LIMIT 1 BY lower("earnVault")
|
|
2877
2907
|
)
|
|
2878
|
-
WHERE ${filters.join(' AND ')}
|
|
2908
|
+
${filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''}
|
|
2879
2909
|
ORDER BY block_num DESC, log_idx DESC
|
|
2880
2910
|
LIMIT ${options.limit + 1}
|
|
2881
2911
|
` as string,
|
|
@@ -531,7 +531,7 @@ describe('GET /exchange/orders?base=… (pair-scoped orders)', () => {
|
|
|
531
531
|
})
|
|
532
532
|
|
|
533
533
|
describe('GET /exchange/pairs/:base/depth', () => {
|
|
534
|
-
test('returns orderbook depth
|
|
534
|
+
test('returns orderbook depth when token metadata is requested', async () => {
|
|
535
535
|
const client = TestApp.client()
|
|
536
536
|
const pairsResponse = await client.v1.exchange.pairs.$get(
|
|
537
537
|
{ query: { limit: '5' } },
|
|
@@ -547,21 +547,25 @@ describe('GET /exchange/pairs/:base/depth', () => {
|
|
|
547
547
|
// throttles the multicall response.
|
|
548
548
|
let body: z.output<typeof Exchanges.schema.getPairDepth.Response> | null = null
|
|
549
549
|
let pair: (typeof pairs.data)[number] | null = null
|
|
550
|
+
let timing: string | null = null
|
|
550
551
|
for (const candidate of pairs.data) {
|
|
551
552
|
const response = await client.v1.exchange.pairs[':base'].depth.$get(
|
|
552
|
-
{ param: { base: candidate.base.address }, query: {} },
|
|
553
|
+
{ param: { base: candidate.base.address }, query: { include: 'tokens' } },
|
|
553
554
|
TestApp.auth,
|
|
554
555
|
)
|
|
555
556
|
if (response.status === 502) return
|
|
556
557
|
expect(response.status).toBe(200)
|
|
557
558
|
body = await TestApp.json(response, Exchanges.schema.getPairDepth.Response)
|
|
558
559
|
pair = candidate
|
|
560
|
+
timing = response.headers.get('server-timing')
|
|
559
561
|
if (body.bids.length > 0 || body.asks.length > 0) break
|
|
560
562
|
}
|
|
561
563
|
if (!body || !pair) return
|
|
562
564
|
|
|
563
565
|
expect(body.base.address).toBe(pair.base.address)
|
|
564
566
|
expect(body.quote.address).toBe(pair.quote.address)
|
|
567
|
+
expect(timing).toContain('pair_lookup;dur=')
|
|
568
|
+
expect(timing).not.toContain('pair_index;dur=')
|
|
565
569
|
|
|
566
570
|
// Asks are emitted lowest-tick first (best ask outward); bids are
|
|
567
571
|
// emitted highest-tick first (best bid outward). Per-row `cumulativeSize`
|
|
@@ -689,9 +693,9 @@ describe('GET /exchange/swaps', () => {
|
|
|
689
693
|
const response = await client.v1.exchange.swaps.$get({ query: { limit: '10' } }, TestApp.auth)
|
|
690
694
|
expect(response.status).toBe(200)
|
|
691
695
|
const body = await TestApp.json(response, Exchanges.schema.getSwaps.Response)
|
|
696
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
692
697
|
expect(body.data.length).toBeLessThanOrEqual(10)
|
|
693
698
|
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
694
|
-
if (body.data.length === 0) return
|
|
695
699
|
|
|
696
700
|
for (const swap of body.data) {
|
|
697
701
|
expect(swap.taker).toMatch(/^0x[0-9a-f]{40}$/)
|
|
@@ -827,6 +831,7 @@ describe('GET /exchange/swaps', () => {
|
|
|
827
831
|
TestApp.auth,
|
|
828
832
|
)
|
|
829
833
|
expect(response.status).toBe(200)
|
|
834
|
+
expect(response.headers.get('server-timing')).not.toContain('token_logo_uri;dur=')
|
|
830
835
|
const body = await TestApp.json(response, Exchanges.schema.getSwaps.Response)
|
|
831
836
|
if (body.data.length === 0) return
|
|
832
837
|
|