tapimo 0.11.0 → 0.13.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 +6396 -4575
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +39 -4
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +15 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +7 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Scope.d.ts +2 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +17 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +176 -78
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +84 -4
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +54 -0
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +123 -9
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +60 -7
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +4 -2
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +90 -90
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +71 -71
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/Zones.d.ts +16 -16
- package/dist/apps/Zones.js +4 -4
- package/dist/apps/data/App.d.ts +1699 -749
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +58 -9
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/ZoneSelection.d.ts +62 -0
- package/dist/apps/data/ZoneSelection.d.ts.map +1 -0
- package/dist/apps/data/ZoneSelection.js +44 -0
- package/dist/apps/data/ZoneSelection.js.map +1 -0
- package/dist/apps/data/routes/activities.d.ts +2417 -50
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +1896 -1293
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +471 -340
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +12 -12
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +44 -17
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +42 -42
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +22 -12
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +179 -179
- package/dist/apps/data/routes/earn.d.ts +144 -19
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +160 -28
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +719 -13
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +16 -13
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +2 -2
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +9 -11
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +34 -34
- package/dist/apps/data/routes/receipts.d.ts +363 -263
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +29 -11
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.js +24 -10
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +13 -13
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +16 -15
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +112 -98
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +85 -35
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +7 -2
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +38 -18
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +1 -1
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +25 -21
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +1 -1
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +12 -7
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +3 -3
- package/dist/apps/data/routes/webhooks.d.ts +15 -15
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +2 -2
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +32 -32
- package/dist/apps/data/routes/zones.js +1 -1
- package/dist/apps/funding/App.d.ts +38 -27
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/chains.d.ts.map +1 -1
- package/dist/apps/funding/routes/chains.js +5 -4
- package/dist/apps/funding/routes/chains.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +16 -16
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +24 -19
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/providers.d.ts +13 -13
- package/dist/apps/funding/routes/providers.d.ts.map +1 -1
- package/dist/apps/funding/routes/providers.js +3 -2
- package/dist/apps/funding/routes/providers.js.map +1 -1
- package/dist/apps/funding/routes/quotes.d.ts.map +1 -1
- package/dist/apps/funding/routes/quotes.js +2 -2
- package/dist/apps/funding/routes/quotes.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +60 -48
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
- package/dist/apps/funding/routes/transfers.js +27 -19
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +4558 -3700
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +19 -6
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +786 -175
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +312 -34
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +103 -30
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +91 -32
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +35 -35
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +23 -12
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +46 -13
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +79 -29
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +120 -75
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +100 -41
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +5 -5
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +14 -10
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +99 -78
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +37 -18
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +48 -8
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +48 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +108 -46
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +72 -21
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +8 -5
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +82 -82
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +41 -31
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +35 -7
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.js +36 -17
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
- package/dist/apps/management/routes/webhooks.d.ts +200 -183
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +80 -35
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +12 -0
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -1
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +26 -3
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +24 -5
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earnVaults.d.ts +5 -1
- package/dist/db/tables/earnVaults.d.ts.map +1 -1
- package/dist/db/tables/earnVaults.js +5 -0
- package/dist/db/tables/earnVaults.js.map +1 -1
- package/dist/db/tables/fundingCatalog.d.ts +4 -2
- package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
- package/dist/db/tables/fundingCatalog.js +5 -1
- package/dist/db/tables/fundingCatalog.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +10 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/db/tables/webhookQueueCompletions.d.ts +15 -0
- package/dist/db/tables/webhookQueueCompletions.d.ts.map +1 -0
- package/dist/db/tables/webhookQueueCompletions.js +32 -0
- package/dist/db/tables/webhookQueueCompletions.js.map +1 -0
- package/dist/db/tables/webhookQueueEvents.d.ts +14 -20
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
- package/dist/db/tables/webhookQueueEvents.js +68 -47
- package/dist/db/tables/webhookQueueEvents.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +16 -5
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +29 -5
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +9 -0
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/internal/Auth.d.ts +11 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +19 -6
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +11 -7
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EarnVaults.d.ts +43 -4
- package/dist/internal/EarnVaults.d.ts.map +1 -1
- package/dist/internal/EarnVaults.js +167 -0
- package/dist/internal/EarnVaults.js.map +1 -1
- package/dist/internal/Fetch.d.ts +23 -0
- package/dist/internal/Fetch.d.ts.map +1 -0
- package/dist/internal/Fetch.js +26 -0
- package/dist/internal/Fetch.js.map +1 -0
- package/dist/internal/Log.d.ts +3 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +16 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +30 -24
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +140 -41
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Response.d.ts +14 -9
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +16 -5
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +11 -10
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +14 -9
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +28 -6
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +121 -25
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts +2 -0
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +23 -6
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Timeout.d.ts +36 -0
- package/dist/internal/Timeout.d.ts.map +1 -0
- package/dist/internal/Timeout.js +37 -0
- package/dist/internal/Timeout.js.map +1 -0
- package/dist/internal/Timing.d.ts +9 -0
- package/dist/internal/Timing.d.ts.map +1 -1
- package/dist/internal/Timing.js +7 -0
- package/dist/internal/Timing.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +3 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +29 -12
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +37 -20
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Action.d.ts +9 -0
- package/dist/internal/funding/Action.d.ts.map +1 -1
- package/dist/internal/funding/Action.js +16 -8
- package/dist/internal/funding/Action.js.map +1 -1
- package/dist/internal/funding/Catalog.d.ts +9 -2
- package/dist/internal/funding/Catalog.d.ts.map +1 -1
- package/dist/internal/funding/Catalog.js +19 -8
- package/dist/internal/funding/Catalog.js.map +1 -1
- package/dist/internal/funding/Deposit.d.ts +2 -2
- package/dist/internal/funding/Deposit.d.ts.map +1 -1
- package/dist/internal/funding/Deposit.js +9 -8
- package/dist/internal/funding/Deposit.js.map +1 -1
- package/dist/internal/funding/DepositAddress.d.ts +4 -4
- package/dist/internal/funding/DepositAddress.d.ts.map +1 -1
- package/dist/internal/funding/DepositAddress.js +9 -8
- package/dist/internal/funding/DepositAddress.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +68 -13
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +42 -20
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Transfer.d.ts +13 -12
- package/dist/internal/funding/Transfer.d.ts.map +1 -1
- package/dist/internal/funding/Transfer.js +23 -21
- package/dist/internal/funding/Transfer.js.map +1 -1
- package/dist/internal/funding/providers/stargate.d.ts +38 -0
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -0
- package/dist/internal/funding/providers/stargate.js +277 -0
- package/dist/internal/funding/providers/stargate.js.map +1 -0
- 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/providers/index.d.ts +7 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -1
- package/package.json +3 -3
- package/src/App.test.ts +148 -0
- package/src/App.ts +45 -4
- package/src/Assets.test.ts +18 -0
- package/src/Assets.ts +22 -0
- package/src/Client.test-d.ts +311 -16
- package/src/Scope.test.ts +20 -0
- package/src/Scope.ts +16 -0
- package/src/admin/apps/api-keys.test.ts +75 -0
- package/src/admin/apps/api-keys.ts +64 -0
- package/src/admin/apps/earn-vaults.test.ts +143 -0
- package/src/admin/apps/earn-vaults.ts +84 -7
- package/src/admin/apps/organizations.ts +6 -2
- package/src/admin/ui/src/lib/api-keys.ts +19 -0
- package/src/admin/ui/src/routes/api-keys.tsx +125 -2
- package/src/admin/ui.generated.ts +71 -71
- package/src/apps/Zones.test.ts +4 -4
- package/src/apps/Zones.ts +4 -4
- package/src/apps/composition.test.ts +1 -1
- package/src/apps/data/App.test.ts +72 -0
- package/src/apps/data/App.ts +58 -10
- package/src/apps/data/ZoneSelection.test.ts +196 -0
- package/src/apps/data/ZoneSelection.ts +108 -0
- package/src/apps/data/routes/activities.test.ts +1843 -2
- package/src/apps/data/routes/activities.ts +2211 -1388
- package/src/apps/data/routes/balances.ts +134 -98
- package/src/apps/data/routes/blocks.test.ts +533 -0
- package/src/apps/data/routes/blocks.ts +36 -17
- package/src/apps/data/routes/earn.test.ts +187 -25
- package/src/apps/data/routes/earn.ts +228 -32
- package/src/apps/data/routes/earn.zone-access.test.ts +46 -0
- package/src/apps/data/routes/exchanges.test.ts +31 -1
- package/src/apps/data/routes/exchanges.ts +19 -15
- package/src/apps/data/routes/fee-amm.ts +18 -11
- package/src/apps/data/routes/receipts.ts +48 -17
- package/src/apps/data/routes/rpc.test.ts +75 -0
- package/src/apps/data/routes/rpc.ts +23 -9
- package/src/apps/data/routes/tokenlist.ts +21 -18
- package/src/apps/data/routes/tokens.test.ts +607 -0
- package/src/apps/data/routes/tokens.ts +312 -231
- package/src/apps/data/routes/transactions.ts +69 -30
- package/src/apps/data/routes/transfers.test.ts +424 -3
- package/src/apps/data/routes/transfers.ts +57 -36
- package/src/apps/data/routes/valuation.ts +94 -78
- package/src/apps/data/routes/webhooks.ts +6 -2
- package/src/apps/data/routes/zones.test.ts +21 -15
- package/src/apps/data/routes/zones.ts +1 -1
- package/src/apps/funding/routes/chains.test.ts +211 -7
- package/src/apps/funding/routes/chains.ts +19 -12
- package/src/apps/funding/routes/deposit-addresses.test.ts +416 -0
- package/src/apps/funding/routes/deposit-addresses.ts +101 -84
- package/src/apps/funding/routes/providers.ts +9 -5
- package/src/apps/funding/routes/quotes.test.ts +42 -0
- package/src/apps/funding/routes/quotes.ts +8 -5
- package/src/apps/funding/routes/transfers.test.ts +430 -40
- package/src/apps/funding/routes/transfers.ts +76 -59
- package/src/apps/management/Billing.test.ts +26 -0
- package/src/apps/management/Billing.ts +24 -7
- package/src/apps/management/routes/api-keys.test.ts +398 -0
- package/src/apps/management/routes/api-keys.ts +481 -145
- package/src/apps/management/routes/billing.test.ts +209 -0
- package/src/apps/management/routes/billing.ts +254 -177
- package/src/apps/management/routes/faucet.test.ts +39 -0
- package/src/apps/management/routes/faucet.ts +75 -41
- package/src/apps/management/routes/invitations.test.ts +216 -0
- package/src/apps/management/routes/invitations.ts +186 -109
- package/src/apps/management/routes/invite-links.test.ts +263 -1
- package/src/apps/management/routes/invite-links.ts +295 -214
- package/src/apps/management/routes/me.test.ts +49 -0
- package/src/apps/management/routes/me.ts +42 -32
- package/src/apps/management/routes/members.test.ts +124 -1
- package/src/apps/management/routes/members.ts +81 -51
- package/src/apps/management/routes/orgs.test.ts +176 -0
- package/src/apps/management/routes/orgs.ts +101 -59
- package/src/apps/management/routes/projects.test.ts +183 -0
- package/src/apps/management/routes/projects.ts +124 -60
- package/src/apps/management/routes/scopes.test.ts +44 -0
- package/src/apps/management/routes/scopes.ts +37 -28
- package/src/apps/management/routes/usage.test.ts +92 -0
- package/src/apps/management/routes/usage.ts +186 -151
- package/src/apps/management/routes/verified-token-requests.test.ts +114 -0
- package/src/apps/management/routes/verified-token-requests.ts +166 -133
- package/src/apps/management/routes/webhooks.test.ts +234 -0
- package/src/apps/management/routes/webhooks.ts +191 -78
- package/src/apps/mpp/internal/Adapter.ts +13 -1
- package/src/db/Db.test.ts +41 -0
- package/src/db/Db.ts +75 -1
- package/src/db/Schema.ts +33 -5
- package/src/db/tables/earnVaults.test.ts +22 -0
- package/src/db/tables/earnVaults.ts +11 -1
- package/src/db/tables/fundingCatalog.test.ts +16 -2
- package/src/db/tables/fundingCatalog.ts +9 -3
- package/src/db/tables/webhookDeliveries.ts +13 -0
- package/src/db/tables/webhookQueueCompletions.ts +55 -0
- package/src/db/tables/webhookQueueEvents.ts +95 -61
- package/src/db/tables/webhookSubscriptions.ts +45 -6
- package/src/handlers/internal/utils.test.ts +53 -0
- package/src/handlers/internal/utils.ts +15 -0
- package/src/internal/Auth.ts +35 -9
- package/src/internal/Cache.ts +14 -7
- package/src/internal/EarnVaults.capabilities.test.ts +33 -0
- package/src/internal/EarnVaults.test.ts +79 -0
- package/src/internal/EarnVaults.ts +277 -0
- package/src/internal/Fetch.test.ts +46 -0
- package/src/internal/Fetch.ts +56 -0
- package/src/internal/Log.test.ts +21 -0
- package/src/internal/Log.ts +20 -5
- package/src/internal/OpenApi.ts +177 -51
- package/src/internal/Response.test.ts +95 -0
- package/src/internal/Response.ts +41 -3
- package/src/internal/Schema.test.ts +18 -0
- package/src/internal/Schema.ts +19 -9
- package/src/internal/Store.test.ts +364 -0
- package/src/internal/Store.ts +153 -27
- package/src/internal/Tidx.test.ts +21 -1
- package/src/internal/Tidx.ts +23 -6
- package/src/internal/Timeout.test.ts +35 -0
- package/src/internal/Timeout.ts +73 -0
- package/src/internal/Timing.ts +16 -0
- package/src/internal/WebhookDestination.ts +3 -0
- package/src/internal/Webhooks.test.ts +122 -8
- package/src/internal/Webhooks.ts +59 -29
- package/src/internal/funding/Action.test.ts +41 -1
- package/src/internal/funding/Action.ts +69 -41
- package/src/internal/funding/Catalog.test.ts +34 -0
- package/src/internal/funding/Catalog.ts +28 -11
- package/src/internal/funding/Deposit.ts +71 -64
- package/src/internal/funding/DepositAddress.ts +50 -43
- package/src/internal/funding/Provider.test.ts +73 -0
- package/src/internal/funding/Provider.ts +246 -170
- package/src/internal/funding/Transfer.ts +133 -110
- package/src/internal/funding/providers/index.test.ts +22 -0
- package/src/internal/funding/providers/relay.test.ts +2 -2
- package/src/internal/funding/providers/stargate.test.ts +163 -0
- package/src/internal/funding/providers/stargate.ts +409 -0
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
- package/src/providers/index.test.ts +1 -0
- package/src/providers/index.ts +9 -0
|
@@ -22,6 +22,7 @@ import * as Ttl from '../../../internal/Ttl.js'
|
|
|
22
22
|
import * as Value from '../../../internal/Value.js'
|
|
23
23
|
import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
|
|
24
24
|
import * as FxOracle from '../FxOracle.js'
|
|
25
|
+
import * as ZoneSelection from '../ZoneSelection.js'
|
|
25
26
|
import * as Tokens from './tokens.js'
|
|
26
27
|
import * as Transfers from './transfers.js'
|
|
27
28
|
import * as Valuation from './valuation.js'
|
|
@@ -29,7 +30,7 @@ import * as Valuation from './valuation.js'
|
|
|
29
30
|
/** Example transaction hash surfaced in OpenAPI docs. */
|
|
30
31
|
const exampleHash = '0x4845ae2098724ab26a5d89370dce5124d044be6a5acc164afc348662de67d474'
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
+
const includeFields = ['token.logoUri', 'token.verified', 'zones'] as const
|
|
33
34
|
|
|
34
35
|
// TIP-20 tokens live at deterministic `0x20c0…` addresses. Other contracts
|
|
35
36
|
// can emit the same `Transfer` signature but do not expose TIP-20 metadata.
|
|
@@ -45,47 +46,49 @@ export namespace schema {
|
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
/** A token with required RPC metadata and optional curated fields. */
|
|
48
|
-
export const Token =
|
|
49
|
-
Tokens.schema.TokenReference,
|
|
50
|
-
'
|
|
49
|
+
export const Token = OpenApi.component(
|
|
50
|
+
Schema.describe(Tokens.schema.TokenReference, 'A token referenced by an activity item.'),
|
|
51
|
+
'ActivityToken',
|
|
51
52
|
)
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
const ValuedAmount = Schema.describe(
|
|
55
|
+
z.extend(Schema.TokenAmount, {
|
|
56
|
+
valuation: z
|
|
57
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
58
|
+
.check(
|
|
59
|
+
z.describe(
|
|
60
|
+
'The amount’s nominal value in the requested `valuation.currency`. `null` when the token is ' +
|
|
61
|
+
'unverified, its display currency has no rate, or rates were unavailable.',
|
|
62
|
+
),
|
|
61
63
|
),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/** Self-contained amount for a destination token. */
|
|
66
|
-
const DestinationAmount = ValuedAmount.check(
|
|
67
|
-
z.describe('Amount associated with `destinationToken`.'),
|
|
64
|
+
}),
|
|
65
|
+
'A token amount carrying its nominal value in the requested denomination.',
|
|
68
66
|
)
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
const valuedAmount = OpenApi.component(ValuedAmount, 'ActivityValuedAmount')
|
|
69
|
+
|
|
70
|
+
const DestinationAmount = valuedAmount
|
|
71
|
+
|
|
72
|
+
const RefundAmount = valuedAmount
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
const SourceAmount = ValuedAmount.check(z.describe('Amount associated with `sourceToken`.'))
|
|
74
|
+
const SourceAmount = valuedAmount
|
|
75
75
|
|
|
76
76
|
/** Page-level resources: valuation rate provenance. */
|
|
77
|
-
export const Meta =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'
|
|
77
|
+
export const Meta = OpenApi.component(
|
|
78
|
+
z
|
|
79
|
+
.object({
|
|
80
|
+
valuation: z
|
|
81
|
+
.optional(Valuation.schema.Pricing)
|
|
82
|
+
.check(
|
|
83
|
+
z.describe(
|
|
84
|
+
'Rate provenance for amount valuations. Present when conversion rates were ' +
|
|
85
|
+
'consulted; absent when every value was identity-valued or rates were unavailable.',
|
|
86
|
+
),
|
|
85
87
|
),
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
})
|
|
89
|
+
.check(z.describe('Page-level resources attached to this response.')),
|
|
90
|
+
'ActivityMeta',
|
|
91
|
+
)
|
|
89
92
|
|
|
90
93
|
/** A token transfer with subject-relative direction and signer fields. */
|
|
91
94
|
export const Transfer = z
|
|
@@ -159,6 +162,8 @@ export namespace schema {
|
|
|
159
162
|
z.describe('One token transfer involving the requested address.'),
|
|
160
163
|
)
|
|
161
164
|
|
|
165
|
+
const activityTransfer = OpenApi.component(Transfer, 'ActivityTransfer')
|
|
166
|
+
|
|
162
167
|
/**
|
|
163
168
|
* One transaction log, decoded into a known Tempo event when possible. The
|
|
164
169
|
* raw fields (`address`/`data`/`logIndex`/`topics`) are always present so no
|
|
@@ -195,6 +200,8 @@ export namespace schema {
|
|
|
195
200
|
),
|
|
196
201
|
)
|
|
197
202
|
|
|
203
|
+
const activityEvent = OpenApi.component(event, 'ActivityEvent')
|
|
204
|
+
|
|
198
205
|
/**
|
|
199
206
|
* Fields shared by every activity item. `transactionHash`/`timestamp` identify and sort
|
|
200
207
|
* the entry; `data` carries the type-specific payload — shaped like the
|
|
@@ -202,6 +209,12 @@ export namespace schema {
|
|
|
202
209
|
* transfer's `direction`/`memo`/`attribution`/`signer`) the resource lacks.
|
|
203
210
|
*/
|
|
204
211
|
const base = {
|
|
212
|
+
chainId: z
|
|
213
|
+
.optional(Schema.ChainId)
|
|
214
|
+
.check(
|
|
215
|
+
z.describe('Chain ID containing this activity. Returned when `include=zones`.'),
|
|
216
|
+
z.meta({ examples: [421700001] }),
|
|
217
|
+
),
|
|
205
218
|
perspective: z
|
|
206
219
|
.enum(['incoming', 'outgoing'])
|
|
207
220
|
.check(
|
|
@@ -210,7 +223,7 @@ export namespace schema {
|
|
|
210
223
|
),
|
|
211
224
|
),
|
|
212
225
|
events: z
|
|
213
|
-
.optional(z.array(
|
|
226
|
+
.optional(z.array(activityEvent))
|
|
214
227
|
.check(
|
|
215
228
|
z.describe(
|
|
216
229
|
'Every log in the transaction, decoded when possible, so nothing is hidden. Returned only when the request sets `logs=true`, except on `unknown` items where it is always present.',
|
|
@@ -246,8 +259,7 @@ export namespace schema {
|
|
|
246
259
|
transactionHash: Schema.Hash.check(z.describe('Transaction hash for this activity.')),
|
|
247
260
|
}
|
|
248
261
|
|
|
249
|
-
|
|
250
|
-
export const items = [
|
|
262
|
+
const itemSchemas = [
|
|
251
263
|
z
|
|
252
264
|
.object({
|
|
253
265
|
...base,
|
|
@@ -391,7 +403,7 @@ export namespace schema {
|
|
|
391
403
|
z
|
|
392
404
|
.object({
|
|
393
405
|
...base,
|
|
394
|
-
data:
|
|
406
|
+
data: activityTransfer.check(
|
|
395
407
|
z.describe('Transfer details with feed-relative direction and signer fields.'),
|
|
396
408
|
),
|
|
397
409
|
type: z
|
|
@@ -989,9 +1001,7 @@ export namespace schema {
|
|
|
989
1001
|
logoUri: z.string().check(
|
|
990
1002
|
z.describe('New logo URL for the token.'),
|
|
991
1003
|
z.meta({
|
|
992
|
-
examples: [
|
|
993
|
-
'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
|
|
994
|
-
],
|
|
1004
|
+
examples: [Tokens.tokenExample.logoUri],
|
|
995
1005
|
}),
|
|
996
1006
|
),
|
|
997
1007
|
signer: Signer,
|
|
@@ -1474,14 +1484,31 @@ export namespace schema {
|
|
|
1474
1484
|
.check(z.meta({ title: 'Unknown' })),
|
|
1475
1485
|
] as const
|
|
1476
1486
|
|
|
1487
|
+
/** The classified activity item variants (discriminated by `type`). */
|
|
1488
|
+
export const items = itemSchemas.map((item) => {
|
|
1489
|
+
const [type] = item.shape.type.def.values
|
|
1490
|
+
if (typeof type !== 'string') throw new Error('Activity item schemas require a string type.')
|
|
1491
|
+
const name = `ActivityItem${type
|
|
1492
|
+
.replace(/[^A-Za-z0-9]+(.)/g, (_, character: string) => character.toUpperCase())
|
|
1493
|
+
.replace(/^(.)/, (character) => character.toUpperCase())}`
|
|
1494
|
+
return OpenApi.component(item, name)
|
|
1495
|
+
}) as unknown as typeof itemSchemas
|
|
1496
|
+
|
|
1477
1497
|
/** A single classified activity item. */
|
|
1478
|
-
export const Item =
|
|
1479
|
-
.union(items)
|
|
1480
|
-
|
|
1498
|
+
export const Item = OpenApi.component(
|
|
1499
|
+
z.union(items).check(z.describe('One classified activity item in the address feed.')),
|
|
1500
|
+
'ActivityItem',
|
|
1501
|
+
)
|
|
1481
1502
|
|
|
1482
1503
|
/** A folded access-key group: consecutive same-signer items on one UTC day. */
|
|
1483
1504
|
export const Group = z
|
|
1484
1505
|
.object({
|
|
1506
|
+
chainId: z
|
|
1507
|
+
.optional(Schema.ChainId)
|
|
1508
|
+
.check(
|
|
1509
|
+
z.describe('Chain ID containing this activity group. Returned when `include=zones`.'),
|
|
1510
|
+
z.meta({ examples: [421700001] }),
|
|
1511
|
+
),
|
|
1485
1512
|
data: z
|
|
1486
1513
|
.object({
|
|
1487
1514
|
items: z.array(Item).check(z.describe('Activity items included in this group.')),
|
|
@@ -1518,20 +1545,27 @@ export namespace schema {
|
|
|
1518
1545
|
z.describe('A group of consecutive activity items signed by the same access key.'),
|
|
1519
1546
|
)
|
|
1520
1547
|
|
|
1548
|
+
const activityGroup = OpenApi.component(Group, 'ActivityGroup')
|
|
1549
|
+
|
|
1521
1550
|
/** A single activity item, or a folded access-key group. */
|
|
1522
|
-
export const Entry =
|
|
1523
|
-
|
|
1524
|
-
|
|
1551
|
+
export const Entry = OpenApi.component(
|
|
1552
|
+
z
|
|
1553
|
+
.union([Item, activityGroup])
|
|
1554
|
+
.check(
|
|
1555
|
+
z.describe('Either one activity item, or a grouped set of access-key activity items.'),
|
|
1556
|
+
),
|
|
1557
|
+
'ActivityEntry',
|
|
1558
|
+
)
|
|
1525
1559
|
|
|
1526
|
-
/** Optional
|
|
1560
|
+
/** Optional activity resources and selection modes. */
|
|
1527
1561
|
export const Include = z
|
|
1528
|
-
.enum(
|
|
1529
|
-
.check(z.describe('Additional
|
|
1562
|
+
.enum(includeFields)
|
|
1563
|
+
.check(z.describe('Additional activity resources and selection modes to include.'))
|
|
1530
1564
|
|
|
1531
|
-
/** Parses comma-separated optional
|
|
1565
|
+
/** Parses comma-separated optional activity resources and selection modes. */
|
|
1532
1566
|
export const includeQuery = Schema.includeQuery(
|
|
1533
1567
|
Include,
|
|
1534
|
-
'Comma-separated
|
|
1568
|
+
'Comma-separated activity options. `zones` combines the selected parent chain with every readable Zone.',
|
|
1535
1569
|
)
|
|
1536
1570
|
|
|
1537
1571
|
/** Schemas for the getAddressActivities operation. */
|
|
@@ -1585,15 +1619,18 @@ export namespace schema {
|
|
|
1585
1619
|
* `group=true`) plus the root `nextCursor` pagination field shared with
|
|
1586
1620
|
* other list endpoints.
|
|
1587
1621
|
*/
|
|
1588
|
-
export const Response =
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1622
|
+
export const Response = OpenApi.component(
|
|
1623
|
+
z
|
|
1624
|
+
.object({
|
|
1625
|
+
data: z.array(Entry).check(z.describe('Activity feed entries for the address.')),
|
|
1626
|
+
meta: z
|
|
1627
|
+
.optional(schema.Meta)
|
|
1628
|
+
.check(z.describe('Page-level resources, such as valuation rate provenance.')),
|
|
1629
|
+
nextCursor: Schema.NextCursor,
|
|
1630
|
+
})
|
|
1631
|
+
.check(z.describe('A page of human-readable activity for the address.')),
|
|
1632
|
+
'AddressActivityList',
|
|
1633
|
+
)
|
|
1597
1634
|
}
|
|
1598
1635
|
|
|
1599
1636
|
/** Schemas for the getTransactionActivities operation. */
|
|
@@ -1629,19 +1666,26 @@ export namespace schema {
|
|
|
1629
1666
|
.check(z.describe('Query parameters for the transaction activity request.'))
|
|
1630
1667
|
|
|
1631
1668
|
/**
|
|
1632
|
-
* The classified activity for one transaction: a list of `data` items
|
|
1633
|
-
*
|
|
1634
|
-
*
|
|
1635
|
-
*
|
|
1669
|
+
* The classified activity for one transaction: a list of `data` items,
|
|
1670
|
+
* one per independent activity, describing what the transaction did
|
|
1671
|
+
* onchain. Unpaginated (a single transaction is finite), so there is no
|
|
1672
|
+
* `nextCursor`.
|
|
1636
1673
|
*/
|
|
1637
|
-
export const Response =
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1674
|
+
export const Response = OpenApi.component(
|
|
1675
|
+
z
|
|
1676
|
+
.object({
|
|
1677
|
+
chainId: Schema.ChainId.check(
|
|
1678
|
+
z.describe('Chain ID containing the transaction.'),
|
|
1679
|
+
z.meta({ examples: [4217] }),
|
|
1680
|
+
),
|
|
1681
|
+
data: z.array(Item).check(z.describe('Activity items classified from the transaction.')),
|
|
1682
|
+
meta: z
|
|
1683
|
+
.optional(schema.Meta)
|
|
1684
|
+
.check(z.describe('Page-level resources, such as valuation rate provenance.')),
|
|
1685
|
+
})
|
|
1686
|
+
.check(z.describe('The human-readable activity that happened on a transaction.')),
|
|
1687
|
+
'TransactionActivityList',
|
|
1688
|
+
)
|
|
1645
1689
|
}
|
|
1646
1690
|
}
|
|
1647
1691
|
|
|
@@ -1773,6 +1817,7 @@ const titleByEventName: Record<string, string> = {
|
|
|
1773
1817
|
BlacklistUpdated: 'Blacklist updated',
|
|
1774
1818
|
Burn: 'Token burned',
|
|
1775
1819
|
BurnBlocked: 'Burn blocked',
|
|
1820
|
+
Deposit: 'Assets deposited',
|
|
1776
1821
|
Deposited: 'Assets deposited',
|
|
1777
1822
|
EarnDeposit: 'Private assets deposited',
|
|
1778
1823
|
EarnRedeem: 'Private shares redeemed',
|
|
@@ -1781,6 +1826,7 @@ const titleByEventName: Record<string, string> = {
|
|
|
1781
1826
|
RedeemRequested: 'Share redemption requested',
|
|
1782
1827
|
Redeemed: 'Shares redeemed',
|
|
1783
1828
|
VenueSharesDeposited: 'Shares deposited',
|
|
1829
|
+
Withdraw: 'Assets withdrawn',
|
|
1784
1830
|
WithdrewExact: 'Assets withdrawn',
|
|
1785
1831
|
ChannelClosed: 'Channel closed',
|
|
1786
1832
|
ChannelOpened: 'Channel opened',
|
|
@@ -1848,6 +1894,33 @@ const accessKeySpendSelector = '0xe0815e3aaadddf4dd75bde97fc060f0c38afe18e87a169
|
|
|
1848
1894
|
/** TIP-20 / ERC-20 `Transfer` event signature for the decoded CTE. */
|
|
1849
1895
|
const transferSignature = 'event Transfer(address indexed from, address indexed to, uint256 value)'
|
|
1850
1896
|
|
|
1897
|
+
// Standard ERC-4626 vault events, absent from the merged `viem/tempo` ABI.
|
|
1898
|
+
// Decoding them lets external vault deposits/withdrawals classify instead of
|
|
1899
|
+
// falling through to the share-token mint/burn legs.
|
|
1900
|
+
const erc4626Events = [
|
|
1901
|
+
{
|
|
1902
|
+
inputs: [
|
|
1903
|
+
{ indexed: true, name: 'sender', type: 'address' },
|
|
1904
|
+
{ indexed: true, name: 'owner', type: 'address' },
|
|
1905
|
+
{ indexed: false, name: 'assets', type: 'uint256' },
|
|
1906
|
+
{ indexed: false, name: 'shares', type: 'uint256' },
|
|
1907
|
+
],
|
|
1908
|
+
name: 'Deposit',
|
|
1909
|
+
type: 'event',
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
inputs: [
|
|
1913
|
+
{ indexed: true, name: 'sender', type: 'address' },
|
|
1914
|
+
{ indexed: true, name: 'receiver', type: 'address' },
|
|
1915
|
+
{ indexed: true, name: 'owner', type: 'address' },
|
|
1916
|
+
{ indexed: false, name: 'assets', type: 'uint256' },
|
|
1917
|
+
{ indexed: false, name: 'shares', type: 'uint256' },
|
|
1918
|
+
],
|
|
1919
|
+
name: 'Withdraw',
|
|
1920
|
+
type: 'event',
|
|
1921
|
+
},
|
|
1922
|
+
] as const
|
|
1923
|
+
|
|
1851
1924
|
// TIP-20 payment-channel reserve precompile. Not exported by `viem/tempo`
|
|
1852
1925
|
// `Addresses`, so the known address is pinned here: channel events
|
|
1853
1926
|
// (`ChannelOpened`/`Settled`/`TopUp`/`ChannelClosed`) are emitted here, and
|
|
@@ -1855,6 +1928,14 @@ const transferSignature = 'event Transfer(address indexed from, address indexed
|
|
|
1855
1928
|
// is how `channel-funded`/`channel-settled`/`channel-closed` recover the token.
|
|
1856
1929
|
const tip20ChannelReserve = '0x4d50500000000000000000000000000000000000' as Address.Address
|
|
1857
1930
|
|
|
1931
|
+
// Tempo system addresses other than the DEX are never swap-route
|
|
1932
|
+
// counterparties; a newly shipped precompile joins the exclusion automatically.
|
|
1933
|
+
const nonRouteSystemAddresses = new Set(
|
|
1934
|
+
[...Object.values(Addresses), tip20ChannelReserve]
|
|
1935
|
+
.filter((value) => value !== Addresses.stablecoinDex)
|
|
1936
|
+
.map((value) => value.toLowerCase()),
|
|
1937
|
+
)
|
|
1938
|
+
|
|
1858
1939
|
/** Number of grouped entries to target before stopping the page-fetch loop. */
|
|
1859
1940
|
const minGroupEntries = 5
|
|
1860
1941
|
|
|
@@ -1981,7 +2062,12 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
1981
2062
|
'Get a human-readable feed of what an address has been doing onchain, including payments sent and received, swaps, approvals, and more.',
|
|
1982
2063
|
operationId: 'getAddressActivities',
|
|
1983
2064
|
responses: OpenApi.responses({
|
|
1984
|
-
errors: {
|
|
2065
|
+
errors: {
|
|
2066
|
+
400: {
|
|
2067
|
+
codes: ['address_invalid', 'chain_id_invalid', 'chain_id_unsupported', 'query_invalid'],
|
|
2068
|
+
},
|
|
2069
|
+
502: 'Tempo could not read activity or token data from an upstream service.',
|
|
2070
|
+
},
|
|
1985
2071
|
success: {
|
|
1986
2072
|
description: 'A page of address activity entries.',
|
|
1987
2073
|
schema: schema.getAddressActivities.Response,
|
|
@@ -1991,7 +2077,7 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
1991
2077
|
tags: ['Activities'],
|
|
1992
2078
|
}),
|
|
1993
2079
|
Cache.response<App.Environment>({
|
|
1994
|
-
cacheControl: Cache.policies.
|
|
2080
|
+
cacheControl: Cache.policies.noStore,
|
|
1995
2081
|
name: 'tempo-api:addresses:v1',
|
|
1996
2082
|
key: (c) => Cache.urlKey(c, schema.getAddressActivities.Query),
|
|
1997
2083
|
}),
|
|
@@ -2011,19 +2097,19 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
2011
2097
|
const { address } = c.req.valid('param')
|
|
2012
2098
|
const query = c.req.valid('query')
|
|
2013
2099
|
|
|
2014
|
-
|
|
2015
|
-
|
|
2100
|
+
if (!query.include.includes('zones'))
|
|
2101
|
+
Cache.setPolicy(
|
|
2102
|
+
c,
|
|
2103
|
+
query.cursor === undefined ? Cache.policies.feed : Cache.policies.metadata,
|
|
2104
|
+
)
|
|
2016
2105
|
|
|
2017
2106
|
try {
|
|
2018
2107
|
const chainId = query.chainId ?? c.get('chainId')
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
Store.memoize(
|
|
2025
|
-
() =>
|
|
2026
|
-
list(provider, {
|
|
2108
|
+
const listed = await Timing.time(c, 'address_activities', async () => {
|
|
2109
|
+
if (!query.include.includes('zones')) {
|
|
2110
|
+
const result = await Store.memoize(
|
|
2111
|
+
(signal) =>
|
|
2112
|
+
list(tidx({ chainId, signal, tidx: c.get('getTidx')(chainId) }), {
|
|
2027
2113
|
address: address as Hex.Hex,
|
|
2028
2114
|
cursor: query.cursor,
|
|
2029
2115
|
group: query.group,
|
|
@@ -2036,18 +2122,56 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
2036
2122
|
store: c.get('store'),
|
|
2037
2123
|
ttl: Ttl.seconds(15),
|
|
2038
2124
|
},
|
|
2039
|
-
)
|
|
2040
|
-
|
|
2125
|
+
)
|
|
2126
|
+
return { chainId, kind: 'chain' as const, ...result }
|
|
2127
|
+
}
|
|
2041
2128
|
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
chainId,
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2129
|
+
const zoneChainIds = c.get('zoneChainIds')
|
|
2130
|
+
if (!zoneChainIds) throw new Error('Inferred Zone selection was not resolved.')
|
|
2131
|
+
const chainIds = [chainId, ...zoneChainIds]
|
|
2132
|
+
const result = await Store.memoize(
|
|
2133
|
+
(signal) =>
|
|
2134
|
+
listChains({
|
|
2135
|
+
address: address as Hex.Hex,
|
|
2136
|
+
cursor: query.cursor,
|
|
2137
|
+
group: query.group,
|
|
2138
|
+
limit: query.limit,
|
|
2139
|
+
logs: query.logs,
|
|
2140
|
+
providers: chainIds.map((chainId) => ({
|
|
2141
|
+
chainId,
|
|
2142
|
+
provider: tidx({
|
|
2143
|
+
chainId,
|
|
2144
|
+
signal,
|
|
2145
|
+
tidx: c.get('getTidx')(chainId),
|
|
2146
|
+
}),
|
|
2147
|
+
})),
|
|
2148
|
+
store: c.get('store'),
|
|
2149
|
+
}),
|
|
2150
|
+
{
|
|
2151
|
+
key: `activities:zones:v2:${chainIds.join(',')}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
|
|
2152
|
+
store: c.get('store'),
|
|
2153
|
+
ttl: Ttl.seconds(15),
|
|
2154
|
+
},
|
|
2155
|
+
)
|
|
2156
|
+
return { kind: 'zones' as const, ...result }
|
|
2049
2157
|
})
|
|
2050
2158
|
|
|
2159
|
+
const { items, rates } =
|
|
2160
|
+
listed.kind === 'zones'
|
|
2161
|
+
? await enrichChainTokens(c, {
|
|
2162
|
+
denomination: query['valuation.currency'],
|
|
2163
|
+
include: query.include,
|
|
2164
|
+
items: listed.items,
|
|
2165
|
+
oracle,
|
|
2166
|
+
})
|
|
2167
|
+
: await enrichTokens(c, {
|
|
2168
|
+
chainId: listed.chainId,
|
|
2169
|
+
denomination: query['valuation.currency'],
|
|
2170
|
+
include: query.include,
|
|
2171
|
+
items: listed.items,
|
|
2172
|
+
oracle,
|
|
2173
|
+
})
|
|
2174
|
+
|
|
2051
2175
|
// Fold consecutive access-key-signed items into `group` entries only
|
|
2052
2176
|
// when requested; otherwise every item is returned at the root.
|
|
2053
2177
|
const data = query.group ? groupByAccessKey(items) : items
|
|
@@ -2056,7 +2180,7 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
2056
2180
|
Response.validated(schema.getAddressActivities.Response, {
|
|
2057
2181
|
data,
|
|
2058
2182
|
...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
|
|
2059
|
-
nextCursor,
|
|
2183
|
+
nextCursor: listed.nextCursor,
|
|
2060
2184
|
}),
|
|
2061
2185
|
200,
|
|
2062
2186
|
)
|
|
@@ -2084,7 +2208,7 @@ export declare namespace addresses {
|
|
|
2084
2208
|
* Runs the same `groupByTx` → `classifyGroup` pipeline the address feed uses
|
|
2085
2209
|
* over a single transaction's logs, anchored on the transaction sender as the
|
|
2086
2210
|
* viewer (a transaction-scoped feed has no subject account). Returns the
|
|
2087
|
-
* classified
|
|
2211
|
+
* classified items (one per independent activity) with the full per-tx
|
|
2088
2212
|
* event set available via `logs=true`.
|
|
2089
2213
|
*/
|
|
2090
2214
|
export function transactions(options: transactions.Options = {}) {
|
|
@@ -2106,10 +2230,22 @@ export function transactions(options: transactions.Options = {}) {
|
|
|
2106
2230
|
operationId: 'getTransactionActivities',
|
|
2107
2231
|
responses: OpenApi.responses({
|
|
2108
2232
|
errors: {
|
|
2233
|
+
400: {
|
|
2234
|
+
codes: [
|
|
2235
|
+
'chain_id_invalid',
|
|
2236
|
+
'chain_id_unsupported',
|
|
2237
|
+
'query_invalid',
|
|
2238
|
+
'transaction_invalid',
|
|
2239
|
+
],
|
|
2240
|
+
},
|
|
2109
2241
|
404: {
|
|
2110
2242
|
description: 'No mined transaction was found for that hash.',
|
|
2111
2243
|
codes: ['transaction_not_found'],
|
|
2112
2244
|
},
|
|
2245
|
+
409: {
|
|
2246
|
+
description: 'More than one readable chain contains that transaction hash.',
|
|
2247
|
+
codes: ['transaction_ambiguous'],
|
|
2248
|
+
},
|
|
2113
2249
|
502: 'Tempo could not read activity or token data from an upstream service.',
|
|
2114
2250
|
},
|
|
2115
2251
|
success: {
|
|
@@ -2142,48 +2278,126 @@ export function transactions(options: transactions.Options = {}) {
|
|
|
2142
2278
|
|
|
2143
2279
|
const { transactionHash } = c.req.valid('param')
|
|
2144
2280
|
const query = c.req.valid('query')
|
|
2145
|
-
const chainId = query.chainId ?? c.get('chainId')
|
|
2146
2281
|
|
|
2147
2282
|
try {
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
()
|
|
2151
|
-
|
|
2152
|
-
chainId,
|
|
2153
|
-
|
|
2283
|
+
const result = await Timing.time(c, 'transaction_activities', async () => {
|
|
2284
|
+
const selected = await (async () => {
|
|
2285
|
+
if (!query.include.includes('zones'))
|
|
2286
|
+
return {
|
|
2287
|
+
chainId: query.chainId ?? c.get('chainId'),
|
|
2288
|
+
kind: 'selected' as const,
|
|
2289
|
+
receipt: null,
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
const zoneChainIds = c.get('zoneChainIds')
|
|
2293
|
+
if (!zoneChainIds) throw new Error('Inferred Zone selection was not resolved.')
|
|
2294
|
+
const chainIds = [query.chainId ?? c.get('chainId'), ...zoneChainIds]
|
|
2295
|
+
// Cache only positive resolution. Missing transactions must remain
|
|
2296
|
+
// visible as soon as any selected indexer catches up.
|
|
2297
|
+
const match = await Store.memoize(
|
|
2298
|
+
async () => {
|
|
2299
|
+
const probes: readonly ZoneSelection.uniqueMatch.Probe<TransactionReceipt>[] =
|
|
2300
|
+
await Promise.all(
|
|
2301
|
+
chainIds.map(async (chainId) => {
|
|
2302
|
+
try {
|
|
2303
|
+
return {
|
|
2304
|
+
chainId,
|
|
2305
|
+
value: await getTransactionReceipt({
|
|
2306
|
+
chainId,
|
|
2307
|
+
tidx: c.get('getTidx')(chainId),
|
|
2308
|
+
transactionHash,
|
|
2309
|
+
}),
|
|
2310
|
+
}
|
|
2311
|
+
} catch (cause) {
|
|
2312
|
+
return { cause, chainId }
|
|
2313
|
+
}
|
|
2314
|
+
}),
|
|
2315
|
+
)
|
|
2316
|
+
return ZoneSelection.uniqueMatch<TransactionReceipt>(probes)
|
|
2317
|
+
},
|
|
2318
|
+
{
|
|
2319
|
+
key: `activities:zones:v2:${chainIds.join(',')}:tx:${transactionHash.toLowerCase()}`,
|
|
2320
|
+
shouldCache: (match) => match.kind === 'selected',
|
|
2154
2321
|
store: c.get('store'),
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2322
|
+
ttl: Ttl.seconds(15),
|
|
2323
|
+
},
|
|
2324
|
+
)
|
|
2325
|
+
if (match.kind === 'failed') {
|
|
2326
|
+
c.set(
|
|
2327
|
+
'providerFailures',
|
|
2328
|
+
match.failures.map(({ cause, chainId }) => ({
|
|
2329
|
+
...Tidx.providerFailure(cause),
|
|
2330
|
+
chainId,
|
|
2331
|
+
})),
|
|
2332
|
+
)
|
|
2333
|
+
throw match.failures[0]!.cause
|
|
2334
|
+
}
|
|
2335
|
+
if (match.kind !== 'selected') return match
|
|
2336
|
+
return { chainId: match.chainId, kind: 'selected' as const, receipt: match.value }
|
|
2337
|
+
})()
|
|
2338
|
+
|
|
2339
|
+
if (selected.kind !== 'selected') return selected
|
|
2340
|
+
const { chainId } = selected
|
|
2341
|
+
const unenrichedItems = await Store.memoize(
|
|
2342
|
+
() =>
|
|
2343
|
+
selected.receipt === null
|
|
2344
|
+
? getTransactionActivities_inner({
|
|
2345
|
+
chainId,
|
|
2346
|
+
logs: query.logs,
|
|
2347
|
+
store: c.get('store'),
|
|
2348
|
+
tidx: c.get('getTidx')(chainId),
|
|
2349
|
+
time: (name, fn) => Timing.time(c, name, fn),
|
|
2350
|
+
transactionHash,
|
|
2351
|
+
})
|
|
2352
|
+
: classifyTransactionActivities({
|
|
2353
|
+
chainId,
|
|
2354
|
+
logs: query.logs,
|
|
2355
|
+
receipt: selected.receipt,
|
|
2356
|
+
store: c.get('store'),
|
|
2357
|
+
tidx: c.get('getTidx')(chainId),
|
|
2358
|
+
time: (name, fn) => Timing.time(c, name, fn),
|
|
2359
|
+
transactionHash,
|
|
2360
|
+
}),
|
|
2158
2361
|
{
|
|
2159
2362
|
key: `activities:v1:${chainId}:tx:${transactionHash.toLowerCase()}:${query.logs ?? false}`,
|
|
2160
2363
|
store: c.get('store'),
|
|
2161
2364
|
ttl: Ttl.seconds(15),
|
|
2162
2365
|
},
|
|
2163
|
-
)
|
|
2164
|
-
|
|
2366
|
+
)
|
|
2367
|
+
return unenrichedItems === null
|
|
2368
|
+
? ({ kind: 'not_found' } as const)
|
|
2369
|
+
: ({ chainId, items: unenrichedItems, kind: 'found' } as const)
|
|
2370
|
+
})
|
|
2165
2371
|
|
|
2166
|
-
if (
|
|
2372
|
+
if (result.kind === 'not_found')
|
|
2167
2373
|
return Response.error(c, {
|
|
2168
2374
|
code: 'transaction_not_found',
|
|
2169
2375
|
message: 'Transaction not found',
|
|
2170
2376
|
status: 404,
|
|
2171
2377
|
})
|
|
2378
|
+
if (result.kind === 'ambiguous')
|
|
2379
|
+
return Response.error(c, {
|
|
2380
|
+
code: 'transaction_ambiguous',
|
|
2381
|
+
message: 'Transaction hash matched more than one readable chain',
|
|
2382
|
+
status: 409,
|
|
2383
|
+
})
|
|
2172
2384
|
|
|
2173
2385
|
// Mined activity can carry current valuations and verification data.
|
|
2174
|
-
Cache.setPolicy(c, Cache.policies.metadata)
|
|
2386
|
+
if (!query.include.includes('zones')) Cache.setPolicy(c, Cache.policies.metadata)
|
|
2387
|
+
c.set('chainId', result.chainId)
|
|
2175
2388
|
|
|
2176
2389
|
// Build immutable token and structured amount copies from RPC metadata.
|
|
2177
2390
|
const { items, rates } = await enrichTokens(c, {
|
|
2178
|
-
chainId,
|
|
2391
|
+
chainId: result.chainId,
|
|
2179
2392
|
denomination: query['valuation.currency'],
|
|
2180
2393
|
include: query.include,
|
|
2181
|
-
items:
|
|
2394
|
+
items: result.items,
|
|
2182
2395
|
oracle,
|
|
2183
2396
|
})
|
|
2184
2397
|
|
|
2185
2398
|
return c.json(
|
|
2186
2399
|
Response.validated(schema.getTransactionActivities.Response, {
|
|
2400
|
+
chainId: result.chainId,
|
|
2187
2401
|
data: items,
|
|
2188
2402
|
...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
|
|
2189
2403
|
}),
|
|
@@ -2219,7 +2433,26 @@ export declare namespace transactions {
|
|
|
2219
2433
|
export async function getTransactionActivities(
|
|
2220
2434
|
options: getTransactionActivities.Options,
|
|
2221
2435
|
): Promise<UnenrichedItem[] | null> {
|
|
2222
|
-
|
|
2436
|
+
return getTransactionActivities_inner({
|
|
2437
|
+
...options,
|
|
2438
|
+
time: async (_name, fn) => fn(),
|
|
2439
|
+
})
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
async function getTransactionActivities_inner(
|
|
2443
|
+
options: TransactionActivityOptions,
|
|
2444
|
+
): Promise<UnenrichedItem[] | null> {
|
|
2445
|
+
const receipt = await options.time('transaction_activity_receipt', () =>
|
|
2446
|
+
getTransactionReceipt(options),
|
|
2447
|
+
)
|
|
2448
|
+
if (!receipt) return null
|
|
2449
|
+
return classifyTransactionActivities({ ...options, receipt })
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
async function getTransactionReceipt(
|
|
2453
|
+
options: getTransactionReceipt.Options,
|
|
2454
|
+
): Promise<TransactionReceipt | null> {
|
|
2455
|
+
const { chainId, tidx: tidxClient } = options
|
|
2223
2456
|
const hash = options.transactionHash.toLowerCase()
|
|
2224
2457
|
|
|
2225
2458
|
// The `receipts` table is indexed by `tx_hash`, so this point lookup needs no
|
|
@@ -2234,17 +2467,45 @@ export async function getTransactionActivities(
|
|
|
2234
2467
|
const receipt = receiptResult.rows[0]
|
|
2235
2468
|
if (!receipt) return null
|
|
2236
2469
|
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2470
|
+
return {
|
|
2471
|
+
blockNum: Number(Value.toNumber(receipt['block_num'])),
|
|
2472
|
+
feePayer: lowerOrNull(receipt['fee_payer']),
|
|
2473
|
+
txSender: lowerOrNull(receipt['from']),
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
declare namespace getTransactionReceipt {
|
|
2478
|
+
type Options = Pick<getTransactionActivities.Options, 'chainId' | 'tidx' | 'transactionHash'>
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
type TransactionReceipt = {
|
|
2482
|
+
/** Block number containing the transaction. */
|
|
2483
|
+
blockNum: number
|
|
2484
|
+
/** Account that paid the transaction fee, when indexed. */
|
|
2485
|
+
feePayer: Address.Address | null
|
|
2486
|
+
/** Account that sent the transaction, when indexed. */
|
|
2487
|
+
txSender: Address.Address | null
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
async function classifyTransactionActivities(
|
|
2491
|
+
options: classifyTransactionActivities.Options,
|
|
2492
|
+
): Promise<UnenrichedItem[]> {
|
|
2493
|
+
const { chainId, logs = false, receipt, tidx: tidxClient } = options
|
|
2494
|
+
const hash = options.transactionHash.toLowerCase()
|
|
2495
|
+
const { blockNum, feePayer, txSender } = receipt
|
|
2496
|
+
const fingerprintMap_promise = options
|
|
2497
|
+
.time('transaction_activity_mpp', () => Mpp.fingerprintMap({ store: options.store }))
|
|
2498
|
+
.catch(() => ({}))
|
|
2240
2499
|
|
|
2241
2500
|
// `logs` is sorted by block position, so a bare `tx_hash =` scans to genesis;
|
|
2242
2501
|
// pair it with the receipt's block number (see `embedReceipts` in receipts.ts).
|
|
2243
|
-
const logsResult = await
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2502
|
+
const logsResult = await options.time('transaction_activity_logs', () =>
|
|
2503
|
+
tidxClient.fetch({
|
|
2504
|
+
chainId,
|
|
2505
|
+
query:
|
|
2506
|
+
`SELECT tx_hash, block_num, block_timestamp, tx_idx, log_idx, address, selector, topic1, topic2, topic3, data FROM logs WHERE block_num = ${blockNum} AND tx_hash = '${hash}' ORDER BY log_idx ASC` as string,
|
|
2507
|
+
}),
|
|
2508
|
+
)
|
|
2248
2509
|
|
|
2249
2510
|
const rows: Row[] = logsResult.rows.map((r) => ({
|
|
2250
2511
|
blockNum: Number(Value.toNumber(r['block_num'])),
|
|
@@ -2269,7 +2530,7 @@ export async function getTransactionActivities(
|
|
|
2269
2530
|
|
|
2270
2531
|
// Best-effort MPP service directory: a fetch failure yields an empty map, so
|
|
2271
2532
|
// enrichment never blocks classification.
|
|
2272
|
-
const fingerprintMap = await
|
|
2533
|
+
const fingerprintMap = await fingerprintMap_promise
|
|
2273
2534
|
|
|
2274
2535
|
// Anchor the viewer on the transaction sender: the sender initiated every
|
|
2275
2536
|
// activity in the transaction, so `perspective` is `outgoing` and `signer`
|
|
@@ -2279,6 +2540,13 @@ export async function getTransactionActivities(
|
|
|
2279
2540
|
return classifyGroup(group, viewer, fingerprintMap, logs).map(withTitle)
|
|
2280
2541
|
}
|
|
2281
2542
|
|
|
2543
|
+
declare namespace classifyTransactionActivities {
|
|
2544
|
+
type Options = TransactionActivityOptions & {
|
|
2545
|
+
/** Indexed receipt fields used to bound the log lookup. */
|
|
2546
|
+
receipt: TransactionReceipt
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2282
2550
|
export declare namespace getTransactionActivities {
|
|
2283
2551
|
/** Options for {@link getTransactionActivities}. */
|
|
2284
2552
|
type Options = {
|
|
@@ -2295,11 +2563,18 @@ export declare namespace getTransactionActivities {
|
|
|
2295
2563
|
}
|
|
2296
2564
|
}
|
|
2297
2565
|
|
|
2566
|
+
type TransactionActivityOptions = getTransactionActivities.Options & {
|
|
2567
|
+
time: <value>(
|
|
2568
|
+
name: 'transaction_activity_logs' | 'transaction_activity_mpp' | 'transaction_activity_receipt',
|
|
2569
|
+
fn: () => Promise<value> | value,
|
|
2570
|
+
) => Promise<value>
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2298
2573
|
/**
|
|
2299
2574
|
* Lists classified activity for an address by running the shared pipeline
|
|
2300
2575
|
* (`groupByTx` → `classifyGroup` → `enrichItem`) over rows from a
|
|
2301
|
-
* {@link Provider}. Returns
|
|
2302
|
-
*
|
|
2576
|
+
* {@link Provider}. Returns classified items plus the next cursor; callers run
|
|
2577
|
+
* {@link groupByAccessKey} over the result.
|
|
2303
2578
|
*/
|
|
2304
2579
|
export async function list(provider: Provider, options: list.Options): Promise<list.ReturnType> {
|
|
2305
2580
|
const { address, cursor, group = false, limit = 20, logs = false } = options
|
|
@@ -2307,7 +2582,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
|
|
|
2307
2582
|
|
|
2308
2583
|
// Best-effort MPP service directory: a fetch failure yields an empty map, so
|
|
2309
2584
|
// enrichment never blocks the feed.
|
|
2310
|
-
const
|
|
2585
|
+
const fingerprintMap_promise = Mpp.fingerprintMap({ store: options.store }).catch(() => ({}))
|
|
2311
2586
|
|
|
2312
2587
|
let currentCursor: string | undefined = cursor
|
|
2313
2588
|
const allItems: UnenrichedItem[] = []
|
|
@@ -2326,6 +2601,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
|
|
|
2326
2601
|
hasMore = txGroups.length > limit
|
|
2327
2602
|
const page = txGroups.slice(0, limit)
|
|
2328
2603
|
|
|
2604
|
+
const fingerprintMap = await fingerprintMap_promise
|
|
2329
2605
|
if (page.length === 0) break
|
|
2330
2606
|
|
|
2331
2607
|
allItems.push(
|
|
@@ -2374,6 +2650,175 @@ export declare namespace list {
|
|
|
2374
2650
|
}
|
|
2375
2651
|
}
|
|
2376
2652
|
|
|
2653
|
+
const chainCursorVersion = 2
|
|
2654
|
+
|
|
2655
|
+
type ChainPageState = {
|
|
2656
|
+
/** Chain ID backing this page state. */
|
|
2657
|
+
chainId: number
|
|
2658
|
+
/** Whether the current buffer reaches the end of this chain feed. */
|
|
2659
|
+
exhausted: boolean
|
|
2660
|
+
/** Unconsumed transaction groups already fetched from this chain. */
|
|
2661
|
+
groups: TxGroup[]
|
|
2662
|
+
/** Activity data source for this chain. */
|
|
2663
|
+
provider: Provider
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
/**
|
|
2667
|
+
* Lists address activity across multiple chains in one stable timestamp-ordered
|
|
2668
|
+
* feed. Its opaque cursor retains each provider's native continuation.
|
|
2669
|
+
*/
|
|
2670
|
+
export async function listChains(options: listChains.Options): Promise<listChains.ReturnType> {
|
|
2671
|
+
const { address, cursor, limit = 20, logs = false, providers, store } = options
|
|
2672
|
+
const group = options.group ?? false
|
|
2673
|
+
const orderedProviders = [...providers].sort((a, b) => a.chainId - b.chainId)
|
|
2674
|
+
const chainIds = orderedProviders.map(({ chainId }) => chainId)
|
|
2675
|
+
const cursors = decodeChainCursor(cursor, chainIds)
|
|
2676
|
+
const viewer = address.toLowerCase() as Address.Address
|
|
2677
|
+
const fingerprintMap_promise = Mpp.fingerprintMap({ store }).catch(() => ({}))
|
|
2678
|
+
const states: ChainPageState[] = orderedProviders.map(({ chainId, provider }) => ({
|
|
2679
|
+
chainId,
|
|
2680
|
+
exhausted: false,
|
|
2681
|
+
groups: [],
|
|
2682
|
+
provider,
|
|
2683
|
+
}))
|
|
2684
|
+
const allItems: listChains.Item[] = []
|
|
2685
|
+
let hasMore = false
|
|
2686
|
+
|
|
2687
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2688
|
+
await Promise.all(
|
|
2689
|
+
states.map(async (state) => {
|
|
2690
|
+
if (state.exhausted || state.groups.length > 0) return
|
|
2691
|
+
const { rows } = await state.provider.fetchRows({
|
|
2692
|
+
address: viewer,
|
|
2693
|
+
cursor: cursors.get(state.chainId),
|
|
2694
|
+
limit,
|
|
2695
|
+
})
|
|
2696
|
+
const groups = groupByTx(rows).slice(0, limit + 1)
|
|
2697
|
+
state.exhausted = groups.length <= limit
|
|
2698
|
+
state.groups.push(...groups)
|
|
2699
|
+
}),
|
|
2700
|
+
)
|
|
2701
|
+
const candidates = states
|
|
2702
|
+
.flatMap((state) => state.groups.map((txGroup) => ({ state, txGroup })))
|
|
2703
|
+
.sort((a, b) => {
|
|
2704
|
+
if (a.txGroup.blockTimestamp !== b.txGroup.blockTimestamp)
|
|
2705
|
+
return b.txGroup.blockTimestamp - a.txGroup.blockTimestamp
|
|
2706
|
+
if (a.state.chainId !== b.state.chainId) return a.state.chainId - b.state.chainId
|
|
2707
|
+
if (a.txGroup.blockNum !== b.txGroup.blockNum)
|
|
2708
|
+
return b.txGroup.blockNum - a.txGroup.blockNum
|
|
2709
|
+
return b.txGroup.txIdx - a.txGroup.txIdx
|
|
2710
|
+
})
|
|
2711
|
+
const page = candidates.slice(0, limit)
|
|
2712
|
+
const fingerprintMap = await fingerprintMap_promise
|
|
2713
|
+
if (page.length === 0) break
|
|
2714
|
+
|
|
2715
|
+
for (const { state, txGroup } of page) {
|
|
2716
|
+
state.groups.splice(state.groups.indexOf(txGroup), 1)
|
|
2717
|
+
cursors.set(state.chainId, state.provider.cursorForGroup(txGroup))
|
|
2718
|
+
allItems.push(
|
|
2719
|
+
...classifyGroup(txGroup, viewer, fingerprintMap, logs).map((item) => ({
|
|
2720
|
+
...withTitle(item),
|
|
2721
|
+
chainId: state.chainId,
|
|
2722
|
+
})),
|
|
2723
|
+
)
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
hasMore = states.some((state) => !state.exhausted || state.groups.length > 0)
|
|
2727
|
+
const enough = group
|
|
2728
|
+
? groupByAccessKeyItems(allItems).length >= minGroupEntries
|
|
2729
|
+
: allItems.length >= limit
|
|
2730
|
+
if (enough || !hasMore) break
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// A consumed sentinel proves only that the provider may have more rows.
|
|
2734
|
+
// Probe drained buffers so `nextCursor` remains the end-of-list signal.
|
|
2735
|
+
await Promise.all(
|
|
2736
|
+
states.map(async (state) => {
|
|
2737
|
+
if (state.exhausted || state.groups.length > 0) return
|
|
2738
|
+
const { rows } = await state.provider.fetchRows({
|
|
2739
|
+
address: viewer,
|
|
2740
|
+
cursor: cursors.get(state.chainId),
|
|
2741
|
+
limit: 1,
|
|
2742
|
+
})
|
|
2743
|
+
const group = groupByTx(rows)[0]
|
|
2744
|
+
if (group) state.groups.push(group)
|
|
2745
|
+
else state.exhausted = true
|
|
2746
|
+
}),
|
|
2747
|
+
)
|
|
2748
|
+
hasMore = states.some((state) => !state.exhausted || state.groups.length > 0)
|
|
2749
|
+
|
|
2750
|
+
return {
|
|
2751
|
+
items: allItems,
|
|
2752
|
+
nextCursor: hasMore ? encodeChainCursor(cursors, chainIds) : null,
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
export declare namespace listChains {
|
|
2757
|
+
/** One chain activity provider. */
|
|
2758
|
+
type ChainProvider = {
|
|
2759
|
+
/** Chain ID. */
|
|
2760
|
+
chainId: number
|
|
2761
|
+
/** Activity data source for the chain. */
|
|
2762
|
+
provider: Provider
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
/** A classified item tagged with its chain ID. */
|
|
2766
|
+
type Item = UnenrichedItem & {
|
|
2767
|
+
/** Chain ID containing the activity. */
|
|
2768
|
+
chainId: number
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
/** Options for {@link listChains}. */
|
|
2772
|
+
type Options = {
|
|
2773
|
+
/** Subject account address. */
|
|
2774
|
+
address: Address.Address
|
|
2775
|
+
/** Opaque multi-chain cursor from a previous page. */
|
|
2776
|
+
cursor?: string | undefined
|
|
2777
|
+
/** Whether the caller will fold access-key items. */
|
|
2778
|
+
group?: boolean | undefined
|
|
2779
|
+
/** Target number of transaction groups per page. */
|
|
2780
|
+
limit?: number | undefined
|
|
2781
|
+
/** Attach each item's full transaction event capture. */
|
|
2782
|
+
logs?: boolean | undefined
|
|
2783
|
+
/** Chain providers participating in the merged feed. */
|
|
2784
|
+
providers: readonly ChainProvider[]
|
|
2785
|
+
/** Cache store for the MPP fingerprint-directory lookup. */
|
|
2786
|
+
store: Store.Store
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
/** Multi-chain classified page before token enrichment. */
|
|
2790
|
+
type ReturnType = {
|
|
2791
|
+
/** Timestamp-ordered, chain-tagged activity items. */
|
|
2792
|
+
items: Item[]
|
|
2793
|
+
/** Opaque continuation for every chain, or null at the end. */
|
|
2794
|
+
nextCursor: string | null
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
function decodeChainCursor(cursor: string | undefined, chainIds: readonly number[]) {
|
|
2799
|
+
const fields: Cursor.Field[] = ['int']
|
|
2800
|
+
for (const _chainId of chainIds) fields.push('int', 'id')
|
|
2801
|
+
const decoded = cursor ? Cursor.decode(cursor, fields) : undefined
|
|
2802
|
+
const cursors = new Map<number, string>()
|
|
2803
|
+
if (!decoded || decoded[0] !== chainCursorVersion) return cursors
|
|
2804
|
+
|
|
2805
|
+
for (let index = 0; index < chainIds.length; index++) {
|
|
2806
|
+
const chainId = chainIds[index]!
|
|
2807
|
+
const offset = index * 2 + 1
|
|
2808
|
+
if (decoded[offset] !== chainId) return new Map()
|
|
2809
|
+
const value = decoded[offset + 1] as string
|
|
2810
|
+
if (value !== 'head') cursors.set(chainId, value)
|
|
2811
|
+
}
|
|
2812
|
+
return cursors
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
function encodeChainCursor(cursors: ReadonlyMap<number, string>, chainIds: readonly number[]) {
|
|
2816
|
+
return Cursor.encode([
|
|
2817
|
+
chainCursorVersion,
|
|
2818
|
+
...chainIds.flatMap((chainId) => [chainId, cursors.get(chainId) ?? 'head']),
|
|
2819
|
+
])
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2377
2822
|
/**
|
|
2378
2823
|
* Creates a TIDX-backed {@link Provider} that runs three sub-queries in
|
|
2379
2824
|
* parallel and merges them:
|
|
@@ -2384,7 +2829,18 @@ export declare namespace list {
|
|
|
2384
2829
|
* (catches access-key-signed sends where `tx.from` differs from the address).
|
|
2385
2830
|
*/
|
|
2386
2831
|
export function tidx(deps: tidx.Deps): Provider {
|
|
2387
|
-
const { chainId, tidx: tidxClient } = deps
|
|
2832
|
+
const { chainId, signal, tidx: tidxClient } = deps
|
|
2833
|
+
|
|
2834
|
+
const fetch: Tidx.Client['fetch'] = (options) => {
|
|
2835
|
+
const requestSignal =
|
|
2836
|
+
signal && options.signal
|
|
2837
|
+
? AbortSignal.any([signal, options.signal])
|
|
2838
|
+
: (signal ?? options.signal)
|
|
2839
|
+
return tidxClient.fetch({
|
|
2840
|
+
...options,
|
|
2841
|
+
...(requestSignal ? { signal: requestSignal } : {}),
|
|
2842
|
+
})
|
|
2843
|
+
}
|
|
2388
2844
|
|
|
2389
2845
|
function parseCursor(cursor: string): { blockNum: number; txIdx: number } | undefined {
|
|
2390
2846
|
// Opaque `[blockNum, txIdx]` keyset cursor, the same encoding every other
|
|
@@ -2410,7 +2866,7 @@ export function tidx(deps: tidx.Deps): Provider {
|
|
|
2410
2866
|
: ''
|
|
2411
2867
|
const queryLimit = limit + 1
|
|
2412
2868
|
const fetchReceipts = (side: 'fee_payer' | 'from') =>
|
|
2413
|
-
|
|
2869
|
+
fetch({
|
|
2414
2870
|
chainId,
|
|
2415
2871
|
// Raw-table query (no signature-decoded CTE), so the inline SQL is cast
|
|
2416
2872
|
// to `string`.
|
|
@@ -2454,7 +2910,7 @@ export function tidx(deps: tidx.Deps): Provider {
|
|
|
2454
2910
|
|
|
2455
2911
|
// `logs` is not keyed by transaction hash, so pair the bounded hash list
|
|
2456
2912
|
// with the selected receipts' block range to avoid a full archive scan.
|
|
2457
|
-
const logs = await
|
|
2913
|
+
const logs = await fetch({
|
|
2458
2914
|
chainId,
|
|
2459
2915
|
// Raw-table query (no signature-decoded CTE), so the inline SQL is cast
|
|
2460
2916
|
// to `string`.
|
|
@@ -2514,7 +2970,7 @@ export function tidx(deps: tidx.Deps): Provider {
|
|
|
2514
2970
|
`
|
|
2515
2971
|
const result = await (async () => {
|
|
2516
2972
|
try {
|
|
2517
|
-
return await
|
|
2973
|
+
return await fetch({
|
|
2518
2974
|
chainId,
|
|
2519
2975
|
query: query('block_num'),
|
|
2520
2976
|
signatures: [transferSignature],
|
|
@@ -2523,14 +2979,14 @@ export function tidx(deps: tidx.Deps): Provider {
|
|
|
2523
2979
|
if (!Tidx.isDeterministicError(error)) throw error
|
|
2524
2980
|
try {
|
|
2525
2981
|
// The expression blocks a mis-selected block-ordered top-N walk.
|
|
2526
|
-
return await
|
|
2982
|
+
return await fetch({
|
|
2527
2983
|
chainId,
|
|
2528
2984
|
query: query('block_num + 0'),
|
|
2529
2985
|
signatures: [transferSignature],
|
|
2530
2986
|
})
|
|
2531
2987
|
} catch (fallbackError) {
|
|
2532
2988
|
if (!Tidx.isDeterministicError(fallbackError)) throw fallbackError
|
|
2533
|
-
const result = await
|
|
2989
|
+
const result = await fetch({
|
|
2534
2990
|
chainId,
|
|
2535
2991
|
query: query(),
|
|
2536
2992
|
signatures: [transferSignature],
|
|
@@ -2585,6 +3041,8 @@ export declare namespace tidx {
|
|
|
2585
3041
|
type Deps = {
|
|
2586
3042
|
/** Source chain id. */
|
|
2587
3043
|
chainId: number
|
|
3044
|
+
/** Optional deadline signal shared by this provider's TIDX reads. */
|
|
3045
|
+
signal?: AbortSignal | undefined
|
|
2588
3046
|
/** TIDX query client. */
|
|
2589
3047
|
tidx: Tidx.Client
|
|
2590
3048
|
}
|
|
@@ -2599,8 +3057,10 @@ export function mergeAndSort(q1: readonly Row[], q2: readonly Row[], q3: readonl
|
|
|
2599
3057
|
const q1Hashes = new Set(q1.map((r) => r.txHash))
|
|
2600
3058
|
const filteredQ2 = q2.filter((r) => !q1Hashes.has(r.txHash))
|
|
2601
3059
|
const filteredQ3 = q3.filter((r) => !q1Hashes.has(r.txHash))
|
|
2602
|
-
|
|
2603
|
-
|
|
3060
|
+
// Dedup by row, not transaction: a tx that both credits and debits the
|
|
3061
|
+
// viewer keeps its rows from both sides, one item each.
|
|
3062
|
+
const q2Keys = new Set(filteredQ2.map((r) => `${r.txHash}:${r.logIdx}`))
|
|
3063
|
+
const dedupedQ3 = filteredQ3.filter((r) => !q2Keys.has(`${r.txHash}:${r.logIdx}`))
|
|
2604
3064
|
return [...q1, ...filteredQ2, ...dedupedQ3].sort((a, b) => {
|
|
2605
3065
|
if (a.blockNum !== b.blockNum) return b.blockNum - a.blockNum
|
|
2606
3066
|
return b.txIdx - a.txIdx
|
|
@@ -2677,34 +3137,38 @@ function classifyGroup(
|
|
|
2677
3137
|
viewer,
|
|
2678
3138
|
})
|
|
2679
3139
|
|
|
2680
|
-
// Q2-only
|
|
2681
|
-
//
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
3140
|
+
// Q2-only: transfer rows without a joined receipt, one item per row so a
|
|
3141
|
+
// batch paying the viewer several times surfaces every payment. Classify by
|
|
3142
|
+
// comparing `from`/`to` against the viewer (zero-address sides mint/burn).
|
|
3143
|
+
if (q2Rows.length > 0)
|
|
3144
|
+
return [...q2Rows]
|
|
3145
|
+
.sort((a, b) => a.logIdx - b.logIdx)
|
|
3146
|
+
.map((r) => {
|
|
3147
|
+
const base = itemBase(r.logIdx)
|
|
3148
|
+
const from = r.transferFrom!
|
|
3149
|
+
const to = r.transferTo!
|
|
3150
|
+
const sourceToken = token(r.transferAmount, r.transferToken!)
|
|
3151
|
+
if (Address.isEqual(from, zeroAddress as Address.Address))
|
|
3152
|
+
return { ...base, data: { recipient: viewer, sourceToken }, type: 'mint' }
|
|
3153
|
+
if (Address.isEqual(to, zeroAddress as Address.Address))
|
|
3154
|
+
return { ...base, data: { sender: from, signer, sourceToken }, type: 'burn' }
|
|
3155
|
+
// Q2-only rows carry no memo, so MPP service attribution is unavailable
|
|
3156
|
+
// here; `attribution` is resolved only on the log-bearing (Q1) path.
|
|
3157
|
+
if (Address.isEqual(from, viewer))
|
|
3158
|
+
return transfer(base, blockNum, {
|
|
3159
|
+
direction: 'out',
|
|
3160
|
+
recipient: to,
|
|
3161
|
+
sender: viewer,
|
|
3162
|
+
signer,
|
|
3163
|
+
sourceToken,
|
|
3164
|
+
})
|
|
3165
|
+
return transfer(base, blockNum, {
|
|
3166
|
+
direction: 'in',
|
|
3167
|
+
recipient: viewer,
|
|
3168
|
+
sender: from,
|
|
2701
3169
|
sourceToken,
|
|
2702
|
-
})
|
|
2703
|
-
|
|
2704
|
-
return [
|
|
2705
|
-
transfer(base, blockNum, { direction: 'in', recipient: viewer, sender: from, sourceToken }),
|
|
2706
|
-
]
|
|
2707
|
-
}
|
|
3170
|
+
})
|
|
3171
|
+
})
|
|
2708
3172
|
|
|
2709
3173
|
// Fallback — should not happen (a group with neither q1 nor q2 rows).
|
|
2710
3174
|
return [
|
|
@@ -2743,7 +3207,7 @@ function classifyFromLogs(
|
|
|
2743
3207
|
})()
|
|
2744
3208
|
// When the caller requested raw logs, attach the full per-transaction log set
|
|
2745
3209
|
// (decoded when possible) to every classified item so no co-located log is
|
|
2746
|
-
// hidden
|
|
3210
|
+
// hidden. `unknown` items already carry it.
|
|
2747
3211
|
if (!ctx.includeLogs) return items
|
|
2748
3212
|
const events = buildTxEvents(rows, decoded)
|
|
2749
3213
|
return items.map((item) => (item.type === 'unknown' ? item : { ...item, events }))
|
|
@@ -2767,7 +3231,11 @@ function signerForRows(
|
|
|
2767
3231
|
: options.fallback
|
|
2768
3232
|
}
|
|
2769
3233
|
|
|
2770
|
-
/**
|
|
3234
|
+
/**
|
|
3235
|
+
* Classifies every activity in a Q1 (log-bearing) transaction. Recognizers run
|
|
3236
|
+
* in priority order over a pool of decoded events, each emitting one item per
|
|
3237
|
+
* matched anchor and claiming the exact logs backing it.
|
|
3238
|
+
*/
|
|
2771
3239
|
function classifyFromLogsCore(
|
|
2772
3240
|
rows: Row[],
|
|
2773
3241
|
ctx: {
|
|
@@ -2785,97 +3253,196 @@ function classifyFromLogsCore(
|
|
|
2785
3253
|
|
|
2786
3254
|
const minLogIndex = Math.min(...rows.map((r) => r.logIdx))
|
|
2787
3255
|
|
|
2788
|
-
//
|
|
2789
|
-
//
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
const refundTransfer = events.find(
|
|
2795
|
-
(e) =>
|
|
2796
|
-
(e.eventName === 'Transfer' || e.eventName === 'TransferWithMemo') &&
|
|
2797
|
-
'to' in e.args &&
|
|
2798
|
-
Address.isEqual((e.args as { to: Address.Address }).to, viewer),
|
|
2799
|
-
)
|
|
2800
|
-
const refund = refundTransfer
|
|
2801
|
-
? token(
|
|
2802
|
-
(refundTransfer.args as { amount: bigint }).amount,
|
|
2803
|
-
refundTransfer.address as Address.Address,
|
|
2804
|
-
)
|
|
2805
|
-
: undefined
|
|
2806
|
-
return [
|
|
2807
|
-
{
|
|
2808
|
-
...itemBase(closeRequestedLogIndex),
|
|
2809
|
-
data: { ...(refund ? { refund } : {}), signer },
|
|
2810
|
-
type: 'session-closed',
|
|
2811
|
-
},
|
|
2812
|
-
]
|
|
2813
|
-
}
|
|
2814
|
-
|
|
2815
|
-
// Dedup preference: TransferWithMemo beats Transfer; Mint/Burn beat
|
|
2816
|
-
// zero-address Transfer.
|
|
2817
|
-
const preferenceMap = new Map<string, string>()
|
|
3256
|
+
// Pair folds are counted: each TransferWithMemo folds one matching plain
|
|
3257
|
+
// Transfer and each Mint/Burn event folds one zero-address transfer, so an
|
|
3258
|
+
// extra identical payment survives as its own item. Keys require the full
|
|
3259
|
+
// event shape; a malformed event must not fold away a classifiable leg.
|
|
3260
|
+
const foldCounts = new Map<string, number>()
|
|
3261
|
+
const addFold = (key: string) => foldCounts.set(key, (foldCounts.get(key) ?? 0) + 1)
|
|
2818
3262
|
for (const event of events) {
|
|
2819
3263
|
if (event.eventName === 'TransferWithMemo' && 'from' in event.args && 'to' in event.args) {
|
|
2820
3264
|
const args = event.args as { amount: bigint; from: Address.Address; to: Address.Address }
|
|
2821
|
-
|
|
3265
|
+
addFold(`${event.address}:${args.from}:${args.to}:${args.amount}`)
|
|
2822
3266
|
}
|
|
2823
|
-
if (event.eventName === 'Mint' && 'amount' in event.args)
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
3267
|
+
if (event.eventName === 'Mint' && 'amount' in event.args && 'to' in event.args)
|
|
3268
|
+
addFold(`mint:${event.address}:${(event.args as { amount: bigint }).amount}`)
|
|
3269
|
+
if (event.eventName === 'Burn' && 'amount' in event.args && 'from' in event.args)
|
|
3270
|
+
addFold(`burn:${event.address}:${(event.args as { amount: bigint }).amount}`)
|
|
3271
|
+
}
|
|
3272
|
+
const takeFold = (key: string) => {
|
|
3273
|
+
const count = foldCounts.get(key) ?? 0
|
|
3274
|
+
if (count === 0) return false
|
|
3275
|
+
foldCounts.set(key, count - 1)
|
|
3276
|
+
return true
|
|
2833
3277
|
}
|
|
2834
3278
|
|
|
2835
|
-
const
|
|
2836
|
-
if (event.eventName !== 'Transfer') return true
|
|
3279
|
+
const finalEvents = events.filter((event) => {
|
|
3280
|
+
if (event.eventName !== 'Transfer' && event.eventName !== 'TransferWithMemo') return true
|
|
3281
|
+
if (!('from' in event.args) || !('to' in event.args)) return true
|
|
2837
3282
|
const args = event.args as { amount: bigint; from: Address.Address; to: Address.Address }
|
|
2838
|
-
if (preferenceMap.has(`${args.from}:${args.to}:${args.amount}`)) return false
|
|
2839
|
-
if (
|
|
2840
|
-
Address.isEqual(args.from, zeroAddress as Address.Address) &&
|
|
2841
|
-
preferenceMap.has(`mint:${event.address}:${args.amount}`)
|
|
2842
|
-
)
|
|
2843
|
-
return false
|
|
2844
3283
|
if (
|
|
2845
|
-
|
|
2846
|
-
|
|
3284
|
+
event.eventName === 'Transfer' &&
|
|
3285
|
+
takeFold(`${event.address}:${args.from}:${args.to}:${args.amount}`)
|
|
2847
3286
|
)
|
|
2848
3287
|
return false
|
|
2849
|
-
return true
|
|
2850
|
-
})
|
|
2851
|
-
|
|
2852
|
-
const finalEvents = dedupedEvents.filter((event) => {
|
|
2853
|
-
if (event.eventName !== 'TransferWithMemo') return true
|
|
2854
|
-
const args = event.args as { amount: bigint; from: Address.Address; to: Address.Address }
|
|
2855
3288
|
if (
|
|
2856
3289
|
Address.isEqual(args.from, zeroAddress as Address.Address) &&
|
|
2857
|
-
|
|
3290
|
+
takeFold(`mint:${event.address}:${args.amount}`)
|
|
2858
3291
|
)
|
|
2859
3292
|
return false
|
|
2860
3293
|
if (
|
|
2861
3294
|
Address.isEqual(args.to, zeroAddress as Address.Address) &&
|
|
2862
|
-
|
|
3295
|
+
takeFold(`burn:${event.address}:${args.amount}`)
|
|
2863
3296
|
)
|
|
2864
3297
|
return false
|
|
2865
3298
|
return true
|
|
2866
3299
|
})
|
|
2867
3300
|
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
3301
|
+
type Decoded = (typeof finalEvents)[number]
|
|
3302
|
+
|
|
3303
|
+
const pool = [...finalEvents].sort((a, b) => a.logIndex - b.logIndex)
|
|
3304
|
+
const claimed = new Set<number>()
|
|
3305
|
+
const items: RawItem[] = []
|
|
3306
|
+
// Claims made while building an item are journaled, so a build that throws
|
|
3307
|
+
// on a malformed event releases its legs and consumes only the anchor.
|
|
3308
|
+
let journal: number[] | undefined
|
|
3309
|
+
const claim = (event: Decoded) => {
|
|
3310
|
+
claimed.add(event.logIndex)
|
|
3311
|
+
journal?.push(event.logIndex)
|
|
3312
|
+
}
|
|
3313
|
+
// A predicate throwing on a malformed decode must not discard the whole
|
|
3314
|
+
// transaction; treat that event as a non-match instead.
|
|
3315
|
+
const unclaimed = (predicate?: (event: Decoded) => boolean) =>
|
|
3316
|
+
pool.filter((event) => {
|
|
3317
|
+
if (claimed.has(event.logIndex)) return false
|
|
3318
|
+
if (!predicate) return true
|
|
3319
|
+
try {
|
|
3320
|
+
return predicate(event)
|
|
3321
|
+
} catch {
|
|
3322
|
+
return false
|
|
3323
|
+
}
|
|
3324
|
+
})
|
|
3325
|
+
const lc = (value: Address.Address) => value.toLowerCase() as Address.Address
|
|
3326
|
+
const hx = (value: Hex.Hex) => value.toLowerCase() as Hex.Hex
|
|
3327
|
+
|
|
3328
|
+
// Non-strict decoding can surface Transfer-shaped events with malformed
|
|
3329
|
+
// args; validating (once per event) keeps predicate comparisons throw-free.
|
|
3330
|
+
const transferIndexes = new Set(
|
|
3331
|
+
pool
|
|
3332
|
+
.filter((event) => {
|
|
3333
|
+
if (event.eventName !== 'Transfer' && event.eventName !== 'TransferWithMemo') return false
|
|
3334
|
+
const args = event.args as { amount?: unknown; from?: unknown; to?: unknown }
|
|
3335
|
+
return (
|
|
3336
|
+
typeof args.amount === 'bigint' &&
|
|
3337
|
+
typeof args.from === 'string' &&
|
|
3338
|
+
Address.validate(args.from) &&
|
|
3339
|
+
typeof args.to === 'string' &&
|
|
3340
|
+
Address.validate(args.to)
|
|
3341
|
+
)
|
|
3342
|
+
})
|
|
3343
|
+
.map((event) => event.logIndex),
|
|
2874
3344
|
)
|
|
3345
|
+
const isTransfer = (event: Decoded) => transferIndexes.has(event.logIndex)
|
|
3346
|
+
const transferArgs = (event: Decoded) =>
|
|
3347
|
+
event.args as {
|
|
3348
|
+
amount: bigint
|
|
3349
|
+
from: Address.Address
|
|
3350
|
+
memo?: Hex.Hex | undefined
|
|
3351
|
+
to: Address.Address
|
|
3352
|
+
}
|
|
3353
|
+
// Finds and claims the first unclaimed event matching `predicate`, so a
|
|
3354
|
+
// later identical anchor pairs with the next matching leg instead of
|
|
3355
|
+
// reusing this one.
|
|
3356
|
+
const takeEvent = (predicate: (event: Decoded) => boolean) => {
|
|
3357
|
+
for (const event of pool) {
|
|
3358
|
+
if (claimed.has(event.logIndex)) continue
|
|
3359
|
+
const match = (() => {
|
|
3360
|
+
try {
|
|
3361
|
+
return predicate(event)
|
|
3362
|
+
} catch {
|
|
3363
|
+
return false
|
|
3364
|
+
}
|
|
3365
|
+
})()
|
|
3366
|
+
if (!match) continue
|
|
3367
|
+
claim(event)
|
|
3368
|
+
return event
|
|
3369
|
+
}
|
|
3370
|
+
return undefined
|
|
3371
|
+
}
|
|
3372
|
+
const takeTransfer = (predicate: (event: Decoded) => boolean) =>
|
|
3373
|
+
takeEvent((event) => isTransfer(event) && predicate(event))
|
|
3374
|
+
|
|
3375
|
+
// Runs `build` once per unclaimed matching event, in log order; `build`
|
|
3376
|
+
// claims its legs and returns the item(s) to emit, or nothing for
|
|
3377
|
+
// fold-only recognitions. A throwing build consumes only its anchor.
|
|
3378
|
+
const recognize = (
|
|
3379
|
+
predicate: (event: Decoded) => boolean,
|
|
3380
|
+
build: (event: Decoded) => RawItem | readonly RawItem[] | undefined,
|
|
3381
|
+
) => {
|
|
3382
|
+
for (const anchor of unclaimed(predicate)) {
|
|
3383
|
+
// A build may claim a later anchor of the same pass (or a log decoded
|
|
3384
|
+
// into duplicate ABI copies); never process a claimed anchor again.
|
|
3385
|
+
if (claimed.has(anchor.logIndex)) continue
|
|
3386
|
+
claim(anchor)
|
|
3387
|
+
journal = []
|
|
3388
|
+
try {
|
|
3389
|
+
const built = build(anchor)
|
|
3390
|
+
if (built) items.push(...(Array.isArray(built) ? built : [built as RawItem]))
|
|
3391
|
+
} catch {
|
|
3392
|
+
// Indexed logs can match a known signature without satisfying that
|
|
3393
|
+
// event's expected shape; release the legs and skip the anchor.
|
|
3394
|
+
for (const index of journal ?? []) claimed.delete(index)
|
|
3395
|
+
} finally {
|
|
3396
|
+
journal = undefined
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
const named = (name: string) => (event: Decoded) => event.eventName === name
|
|
3401
|
+
|
|
3402
|
+
// Claims and returns a vault operation's token legs, matched by role and
|
|
3403
|
+
// amount: asset legs in or out of the vault, share mint/burn legs on it.
|
|
3404
|
+
const claimVaultLegs = (vault: Address.Address, amounts: readonly bigint[]) => {
|
|
3405
|
+
const legs: Decoded[] = []
|
|
3406
|
+
for (const event of unclaimed()) {
|
|
3407
|
+
const isVaultMintBurn =
|
|
3408
|
+
(event.eventName === 'Mint' || event.eventName === 'Burn') &&
|
|
3409
|
+
Address.isEqual(event.address, vault)
|
|
3410
|
+
const isVaultLeg = (() => {
|
|
3411
|
+
if (!isTransfer(event)) return false
|
|
3412
|
+
const args = transferArgs(event)
|
|
3413
|
+
if (Address.isEqual(args.from, vault) || Address.isEqual(args.to, vault)) return true
|
|
3414
|
+
return (
|
|
3415
|
+
Address.isEqual(event.address, vault) &&
|
|
3416
|
+
(Address.isEqual(args.from, zeroAddress as Address.Address) ||
|
|
3417
|
+
Address.isEqual(args.to, zeroAddress as Address.Address))
|
|
3418
|
+
)
|
|
3419
|
+
})()
|
|
3420
|
+
if (!isVaultMintBurn && !isVaultLeg) continue
|
|
3421
|
+
const amount = (event.args as { amount?: bigint }).amount
|
|
3422
|
+
if (amount === undefined || !amounts.includes(amount)) continue
|
|
3423
|
+
claim(event)
|
|
3424
|
+
legs.push(event)
|
|
3425
|
+
}
|
|
3426
|
+
// The allowance granted for this operation (approve+deposit multicall) is
|
|
3427
|
+
// internal: exact amount, spender is the vault, on a token a leg moved.
|
|
3428
|
+
const legTokens = new Set(legs.map((leg) => leg.address.toLowerCase()))
|
|
3429
|
+
for (const event of unclaimed()) {
|
|
3430
|
+
if (event.eventName !== 'Approval' || !('spender' in event.args)) continue
|
|
3431
|
+
if (!legTokens.has(event.address.toLowerCase())) continue
|
|
3432
|
+
if (!Address.isEqual((event.args as { spender: Address.Address }).spender, vault)) continue
|
|
3433
|
+
const amount = (event.args as { amount?: bigint }).amount
|
|
3434
|
+
if (amount === undefined || !amounts.includes(amount)) continue
|
|
3435
|
+
claim(event)
|
|
3436
|
+
legs.push(event)
|
|
3437
|
+
}
|
|
3438
|
+
return legs
|
|
3439
|
+
}
|
|
2875
3440
|
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
3441
|
+
// Private Earn router operations. Each wraps a public vault operation on the
|
|
3442
|
+
// vault it names; fold that vault's own event and token legs, leaving vault
|
|
3443
|
+
// activity elsewhere in the batch unclaimed.
|
|
3444
|
+
recognize(named('EarnDeposit'), (event) => {
|
|
3445
|
+
const args = event.args as {
|
|
2879
3446
|
actionId: Hex.Hex
|
|
2880
3447
|
earnShares: bigint
|
|
2881
3448
|
earnVault: Address.Address
|
|
@@ -2884,28 +3451,38 @@ function classifyFromLogsCore(
|
|
|
2884
3451
|
vaultAssets: bigint
|
|
2885
3452
|
zoneDepositHash: Hex.Hex
|
|
2886
3453
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3454
|
+
const vault = lc(args.earnVault)
|
|
3455
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
3456
|
+
const data = {
|
|
3457
|
+
actionId: args.actionId,
|
|
3458
|
+
assets: args.vaultAssets.toString(),
|
|
3459
|
+
inputAmount: args.inputAmount.toString(),
|
|
3460
|
+
inputToken: lc(args.inputToken),
|
|
3461
|
+
shares: args.earnShares.toString(),
|
|
3462
|
+
signer,
|
|
3463
|
+
status: 'completed' as const,
|
|
3464
|
+
vault,
|
|
3465
|
+
zoneDepositHash: args.zoneDepositHash,
|
|
3466
|
+
}
|
|
3467
|
+
// Fold the vault event this router call produced: the nearest preceding
|
|
3468
|
+
// amount match, so a separate same-vault deposit keeps its own.
|
|
3469
|
+
const nested = unclaimed(
|
|
3470
|
+
(e) =>
|
|
3471
|
+
e.eventName === 'Deposited' &&
|
|
3472
|
+
e.logIndex < event.logIndex &&
|
|
3473
|
+
Address.isEqual(e.address, vault) &&
|
|
3474
|
+
(e.args as { assets?: bigint }).assets === args.vaultAssets,
|
|
3475
|
+
).at(-1)
|
|
3476
|
+
if (nested) claim(nested)
|
|
3477
|
+
else takeEvent((e) => e.eventName === 'Deposited' && Address.isEqual(e.address, vault))
|
|
3478
|
+
claimVaultLegs(vault, [args.inputAmount, args.vaultAssets, args.earnShares])
|
|
3479
|
+
// The funding leg pays the router (the emitting contract), not the vault.
|
|
3480
|
+
claimVaultLegs(event.address as Address.Address, [args.inputAmount])
|
|
3481
|
+
return { ...itemBase(event.logIndex), data, type: 'private-assets-deposited' }
|
|
3482
|
+
})
|
|
2905
3483
|
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
const args = privateRedeem.args as {
|
|
3484
|
+
recognize(named('EarnRedeem'), (event) => {
|
|
3485
|
+
const args = event.args as {
|
|
2909
3486
|
actionId: Hex.Hex
|
|
2910
3487
|
earnShares: bigint
|
|
2911
3488
|
earnVault: Address.Address
|
|
@@ -2914,782 +3491,1025 @@ function classifyFromLogsCore(
|
|
|
2914
3491
|
vaultAssets: bigint
|
|
2915
3492
|
zoneDepositHash: Hex.Hex
|
|
2916
3493
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3494
|
+
const vault = lc(args.earnVault)
|
|
3495
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
3496
|
+
const data = {
|
|
3497
|
+
actionId: args.actionId,
|
|
3498
|
+
assets: args.vaultAssets.toString(),
|
|
3499
|
+
outputAmount: args.outputAmount.toString(),
|
|
3500
|
+
outputToken: lc(args.outputToken),
|
|
3501
|
+
shares: args.earnShares.toString(),
|
|
3502
|
+
signer,
|
|
3503
|
+
status: 'completed' as const,
|
|
3504
|
+
vault,
|
|
3505
|
+
zoneDepositHash: args.zoneDepositHash,
|
|
3506
|
+
}
|
|
3507
|
+
// Fold the vault event this router call produced: the nearest preceding
|
|
3508
|
+
// amount match, so a separate same-vault redemption keeps its own.
|
|
3509
|
+
const nested = unclaimed(
|
|
3510
|
+
(e) =>
|
|
3511
|
+
(e.eventName === 'Redeemed' || e.eventName === 'WithdrewExact') &&
|
|
3512
|
+
e.logIndex < event.logIndex &&
|
|
3513
|
+
Address.isEqual(e.address, vault) &&
|
|
3514
|
+
(e.args as { assets?: bigint }).assets === args.vaultAssets,
|
|
3515
|
+
).at(-1)
|
|
3516
|
+
if (nested) claim(nested)
|
|
3517
|
+
else
|
|
3518
|
+
takeEvent(
|
|
3519
|
+
(e) =>
|
|
3520
|
+
(e.eventName === 'Redeemed' || e.eventName === 'WithdrewExact') &&
|
|
3521
|
+
Address.isEqual(e.address, vault),
|
|
3522
|
+
)
|
|
3523
|
+
claimVaultLegs(vault, [args.outputAmount, args.vaultAssets, args.earnShares])
|
|
3524
|
+
// The payout leg leaves through the router (the emitting contract).
|
|
3525
|
+
claimVaultLegs(event.address as Address.Address, [args.outputAmount])
|
|
3526
|
+
return { ...itemBase(event.logIndex), data, type: 'private-shares-redeemed' }
|
|
3527
|
+
})
|
|
2935
3528
|
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
const args =
|
|
3529
|
+
// Earn vault operations, each claiming its own token legs.
|
|
3530
|
+
recognize(named('Deposited'), (event) => {
|
|
3531
|
+
const args = event.args as {
|
|
2939
3532
|
assets: bigint
|
|
2940
3533
|
caller: Address.Address
|
|
2941
3534
|
earnShares: bigint
|
|
2942
3535
|
receiver: Address.Address
|
|
2943
3536
|
}
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
},
|
|
2956
|
-
type: 'assets-deposited',
|
|
3537
|
+
claimVaultLegs(event.address as Address.Address, [args.assets, args.earnShares])
|
|
3538
|
+
return {
|
|
3539
|
+
...itemBase(event.logIndex),
|
|
3540
|
+
data: {
|
|
3541
|
+
assets: args.assets.toString(),
|
|
3542
|
+
caller: lc(args.caller),
|
|
3543
|
+
receiver: lc(args.receiver),
|
|
3544
|
+
shares: args.earnShares.toString(),
|
|
3545
|
+
signer,
|
|
3546
|
+
status: 'completed',
|
|
3547
|
+
vault: lc(event.address),
|
|
2957
3548
|
},
|
|
2958
|
-
|
|
2959
|
-
|
|
3549
|
+
type: 'assets-deposited',
|
|
3550
|
+
}
|
|
3551
|
+
})
|
|
2960
3552
|
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
const args = venueDeposit.args as {
|
|
3553
|
+
recognize(named('VenueSharesDeposited'), (event) => {
|
|
3554
|
+
const args = event.args as {
|
|
2964
3555
|
caller: Address.Address
|
|
2965
3556
|
earnShares: bigint
|
|
2966
3557
|
receivedEngineShares: bigint
|
|
2967
3558
|
receiver: Address.Address
|
|
2968
3559
|
requestedVenueShares: bigint
|
|
2969
3560
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3561
|
+
claimVaultLegs(event.address as Address.Address, [
|
|
3562
|
+
args.earnShares,
|
|
3563
|
+
args.receivedEngineShares,
|
|
3564
|
+
args.requestedVenueShares,
|
|
3565
|
+
])
|
|
3566
|
+
return {
|
|
3567
|
+
...itemBase(event.logIndex),
|
|
3568
|
+
data: {
|
|
3569
|
+
caller: lc(args.caller),
|
|
3570
|
+
receivedEngineShares: args.receivedEngineShares.toString(),
|
|
3571
|
+
receiver: lc(args.receiver),
|
|
3572
|
+
requestedVenueShares: args.requestedVenueShares.toString(),
|
|
3573
|
+
shares: args.earnShares.toString(),
|
|
3574
|
+
signer,
|
|
3575
|
+
status: 'completed',
|
|
3576
|
+
vault: lc(event.address),
|
|
2984
3577
|
},
|
|
2985
|
-
|
|
2986
|
-
|
|
3578
|
+
type: 'shares-deposited',
|
|
3579
|
+
}
|
|
3580
|
+
})
|
|
2987
3581
|
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
const args = redeemed.args as {
|
|
3582
|
+
recognize(named('Redeemed'), (event) => {
|
|
3583
|
+
const args = event.args as {
|
|
2991
3584
|
assets: bigint
|
|
2992
3585
|
caller: Address.Address
|
|
2993
3586
|
earnShares: bigint
|
|
2994
3587
|
receiver: Address.Address
|
|
2995
3588
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
},
|
|
3008
|
-
type: 'shares-redeemed',
|
|
3589
|
+
claimVaultLegs(event.address as Address.Address, [args.assets, args.earnShares])
|
|
3590
|
+
return {
|
|
3591
|
+
...itemBase(event.logIndex),
|
|
3592
|
+
data: {
|
|
3593
|
+
assets: args.assets.toString(),
|
|
3594
|
+
caller: lc(args.caller),
|
|
3595
|
+
receiver: lc(args.receiver),
|
|
3596
|
+
shares: args.earnShares.toString(),
|
|
3597
|
+
signer,
|
|
3598
|
+
status: 'completed',
|
|
3599
|
+
vault: lc(event.address),
|
|
3009
3600
|
},
|
|
3010
|
-
|
|
3011
|
-
|
|
3601
|
+
type: 'shares-redeemed',
|
|
3602
|
+
}
|
|
3603
|
+
})
|
|
3012
3604
|
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
const args = withdrew.args as {
|
|
3605
|
+
recognize(named('WithdrewExact'), (event) => {
|
|
3606
|
+
const args = event.args as {
|
|
3016
3607
|
assets: bigint
|
|
3017
3608
|
caller: Address.Address
|
|
3018
3609
|
earnSharesBurned: bigint
|
|
3019
3610
|
receiver: Address.Address
|
|
3020
3611
|
}
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
},
|
|
3033
|
-
type: 'assets-withdrawn',
|
|
3612
|
+
claimVaultLegs(event.address as Address.Address, [args.assets, args.earnSharesBurned])
|
|
3613
|
+
return {
|
|
3614
|
+
...itemBase(event.logIndex),
|
|
3615
|
+
data: {
|
|
3616
|
+
assets: args.assets.toString(),
|
|
3617
|
+
caller: lc(args.caller),
|
|
3618
|
+
receiver: lc(args.receiver),
|
|
3619
|
+
sharesBurned: args.earnSharesBurned.toString(),
|
|
3620
|
+
signer,
|
|
3621
|
+
status: 'completed',
|
|
3622
|
+
vault: lc(event.address),
|
|
3034
3623
|
},
|
|
3035
|
-
|
|
3036
|
-
|
|
3624
|
+
type: 'assets-withdrawn',
|
|
3625
|
+
}
|
|
3626
|
+
})
|
|
3037
3627
|
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
const args = requested.args as {
|
|
3628
|
+
recognize(named('RedeemRequested'), (event) => {
|
|
3629
|
+
const args = event.args as {
|
|
3041
3630
|
earnShares: bigint
|
|
3042
3631
|
receiver: Address.Address
|
|
3043
3632
|
requestId: Hex.Hex
|
|
3044
3633
|
requester: Address.Address
|
|
3045
3634
|
}
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
},
|
|
3058
|
-
type: 'shares-redemption-requested',
|
|
3635
|
+
claimVaultLegs(event.address as Address.Address, [args.earnShares])
|
|
3636
|
+
return {
|
|
3637
|
+
...itemBase(event.logIndex),
|
|
3638
|
+
data: {
|
|
3639
|
+
receiver: lc(args.receiver),
|
|
3640
|
+
requestId: args.requestId,
|
|
3641
|
+
requester: lc(args.requester),
|
|
3642
|
+
shares: args.earnShares.toString(),
|
|
3643
|
+
signer,
|
|
3644
|
+
status: 'pending',
|
|
3645
|
+
vault: lc(event.address),
|
|
3059
3646
|
},
|
|
3060
|
-
|
|
3061
|
-
|
|
3647
|
+
type: 'shares-redemption-requested',
|
|
3648
|
+
}
|
|
3649
|
+
})
|
|
3062
3650
|
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
const args = finalized.args as {
|
|
3651
|
+
recognize(named('RedeemFinalized'), (event) => {
|
|
3652
|
+
const args = event.args as {
|
|
3066
3653
|
asset: Address.Address
|
|
3067
3654
|
assets: bigint
|
|
3068
3655
|
earnShares: bigint
|
|
3069
3656
|
receiver: Address.Address
|
|
3070
3657
|
requestId: Hex.Hex
|
|
3071
3658
|
}
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
},
|
|
3085
|
-
type: 'shares-redemption-finalized',
|
|
3659
|
+
claimVaultLegs(event.address as Address.Address, [args.assets, args.earnShares])
|
|
3660
|
+
return {
|
|
3661
|
+
...itemBase(event.logIndex),
|
|
3662
|
+
data: {
|
|
3663
|
+
asset: lc(args.asset),
|
|
3664
|
+
assets: args.assets.toString(),
|
|
3665
|
+
receiver: lc(args.receiver),
|
|
3666
|
+
requestId: args.requestId,
|
|
3667
|
+
shares: args.earnShares.toString(),
|
|
3668
|
+
signer,
|
|
3669
|
+
status: 'completed',
|
|
3670
|
+
vault: lc(event.address),
|
|
3086
3671
|
},
|
|
3087
|
-
|
|
3088
|
-
|
|
3672
|
+
type: 'shares-redemption-finalized',
|
|
3673
|
+
}
|
|
3674
|
+
})
|
|
3089
3675
|
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
const args = cancelled.args as {
|
|
3676
|
+
recognize(named('RedeemCancelled'), (event) => {
|
|
3677
|
+
const args = event.args as {
|
|
3093
3678
|
earnShares: bigint
|
|
3094
3679
|
receiver: Address.Address
|
|
3095
3680
|
requestId: Hex.Hex
|
|
3096
3681
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
},
|
|
3108
|
-
type: 'shares-redemption-cancelled',
|
|
3682
|
+
claimVaultLegs(event.address as Address.Address, [args.earnShares])
|
|
3683
|
+
return {
|
|
3684
|
+
...itemBase(event.logIndex),
|
|
3685
|
+
data: {
|
|
3686
|
+
receiver: lc(args.receiver),
|
|
3687
|
+
requestId: args.requestId,
|
|
3688
|
+
shares: args.earnShares.toString(),
|
|
3689
|
+
signer,
|
|
3690
|
+
status: 'cancelled',
|
|
3691
|
+
vault: lc(event.address),
|
|
3109
3692
|
},
|
|
3110
|
-
|
|
3111
|
-
|
|
3693
|
+
type: 'shares-redemption-cancelled',
|
|
3694
|
+
}
|
|
3695
|
+
})
|
|
3112
3696
|
|
|
3113
|
-
//
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
const
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
toDex.address as Address.Address,
|
|
3697
|
+
// External ERC-4626 vaults. Claim the share and asset legs unconditionally
|
|
3698
|
+
// so nested vault plumbing never resurfaces as burns or transfers, but emit
|
|
3699
|
+
// only operations the viewer is a party to. Fold-only recognitions are
|
|
3700
|
+
// buffered and surface only when nothing else classified.
|
|
3701
|
+
const suppressedVaultItems: RawItem[] = []
|
|
3702
|
+
const viewerInvolved = (parties: readonly Address.Address[], legs: readonly Decoded[]) =>
|
|
3703
|
+
parties.some((party) => Address.isEqual(party, viewer)) ||
|
|
3704
|
+
legs.some(
|
|
3705
|
+
(leg) =>
|
|
3706
|
+
isTransfer(leg) &&
|
|
3707
|
+
(Address.isEqual(transferArgs(leg).from, viewer) ||
|
|
3708
|
+
Address.isEqual(transferArgs(leg).to, viewer)),
|
|
3126
3709
|
)
|
|
3710
|
+
recognize(named('Withdraw'), (event) => {
|
|
3711
|
+
const args = event.args as {
|
|
3712
|
+
assets: bigint
|
|
3713
|
+
owner: Address.Address
|
|
3714
|
+
receiver: Address.Address
|
|
3715
|
+
sender: Address.Address
|
|
3716
|
+
shares: bigint
|
|
3717
|
+
}
|
|
3718
|
+
const vault = event.address as Address.Address
|
|
3719
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
3720
|
+
const data = {
|
|
3721
|
+
assets: args.assets.toString(),
|
|
3722
|
+
caller: lc(args.sender),
|
|
3723
|
+
receiver: lc(args.receiver),
|
|
3724
|
+
sharesBurned: args.shares.toString(),
|
|
3725
|
+
signer,
|
|
3726
|
+
status: 'completed' as const,
|
|
3727
|
+
vault: lc(vault),
|
|
3728
|
+
}
|
|
3729
|
+
const legs = claimVaultLegs(vault, [args.assets, args.shares])
|
|
3730
|
+
const item = { ...itemBase(event.logIndex), data, type: 'assets-withdrawn' as const }
|
|
3731
|
+
if (viewerInvolved([args.sender, args.receiver, args.owner], legs)) return item
|
|
3732
|
+
suppressedVaultItems.push(item)
|
|
3733
|
+
return undefined
|
|
3734
|
+
})
|
|
3127
3735
|
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
memo?: Hex.Hex | undefined
|
|
3146
|
-
to: Address.Address
|
|
3147
|
-
}
|
|
3148
|
-
const rawMemo = delivery.eventName === 'TransferWithMemo' ? args.memo : undefined
|
|
3149
|
-
const memo = rawMemo ? Transfers.memoToString(rawMemo) : undefined
|
|
3150
|
-
const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo })
|
|
3151
|
-
return [
|
|
3152
|
-
transfer(itemBase(logIndexOfEvent(delivery)), blockNum, {
|
|
3153
|
-
attribution,
|
|
3154
|
-
destinationToken: token(args.amount, delivery.address as Address.Address),
|
|
3155
|
-
direction: Address.isEqual(args.to, viewer) ? 'in' : 'out',
|
|
3156
|
-
memo,
|
|
3157
|
-
recipient: args.to.toLowerCase() as Address.Address,
|
|
3158
|
-
sender: swapper.toLowerCase() as Address.Address,
|
|
3159
|
-
signer,
|
|
3160
|
-
sourceToken,
|
|
3161
|
-
}),
|
|
3162
|
-
]
|
|
3736
|
+
recognize(named('Deposit'), (event) => {
|
|
3737
|
+
const args = event.args as {
|
|
3738
|
+
assets: bigint
|
|
3739
|
+
owner: Address.Address
|
|
3740
|
+
sender: Address.Address
|
|
3741
|
+
shares: bigint
|
|
3742
|
+
}
|
|
3743
|
+
const vault = event.address as Address.Address
|
|
3744
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
3745
|
+
const data = {
|
|
3746
|
+
assets: args.assets.toString(),
|
|
3747
|
+
caller: lc(args.sender),
|
|
3748
|
+
receiver: lc(args.owner),
|
|
3749
|
+
shares: args.shares.toString(),
|
|
3750
|
+
signer,
|
|
3751
|
+
status: 'completed' as const,
|
|
3752
|
+
vault: lc(vault),
|
|
3163
3753
|
}
|
|
3754
|
+
const legs = claimVaultLegs(vault, [args.assets, args.shares])
|
|
3755
|
+
const item = { ...itemBase(event.logIndex), data, type: 'assets-deposited' as const }
|
|
3756
|
+
if (viewerInvolved([args.sender, args.owner], legs)) return item
|
|
3757
|
+
suppressedVaultItems.push(item)
|
|
3758
|
+
return undefined
|
|
3759
|
+
})
|
|
3164
3760
|
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3761
|
+
// DEX order placed: `OrderPlaced` carries the escrow token/amount and side;
|
|
3762
|
+
// claim the escrow transfer that funded it.
|
|
3763
|
+
const orderPlacedItem = (event: Decoded): RawItem => {
|
|
3764
|
+
const args = event.args as {
|
|
3765
|
+
amount: bigint
|
|
3766
|
+
isBid: boolean
|
|
3767
|
+
orderId: bigint
|
|
3768
|
+
tick: number
|
|
3769
|
+
token: Address.Address
|
|
3770
|
+
}
|
|
3771
|
+
const escrow = (leg: Decoded) =>
|
|
3772
|
+
isTransfer(leg) &&
|
|
3773
|
+
Address.isEqual(leg.address, args.token) &&
|
|
3774
|
+
Address.isEqual(transferArgs(leg).to, event.address as Address.Address)
|
|
3775
|
+
// The escrow funds the placement, so it is the nearest preceding match; a
|
|
3776
|
+
// same-token earlier swap input must not be taken instead.
|
|
3777
|
+
const escrowLeg =
|
|
3778
|
+
unclaimed(
|
|
3779
|
+
(leg) =>
|
|
3780
|
+
escrow(leg) && leg.logIndex < event.logIndex && transferArgs(leg).amount === args.amount,
|
|
3781
|
+
).at(-1) ?? unclaimed((leg) => escrow(leg) && leg.logIndex < event.logIndex).at(-1)
|
|
3782
|
+
if (escrowLeg) claim(escrowLeg)
|
|
3783
|
+
return {
|
|
3784
|
+
...itemBase(event.logIndex),
|
|
3785
|
+
data: {
|
|
3786
|
+
orderId: args.orderId.toString(),
|
|
3787
|
+
side: args.isBid ? 'bid' : 'ask',
|
|
3788
|
+
signer,
|
|
3789
|
+
sourceToken: token(args.amount, args.token),
|
|
3790
|
+
tick: Number(args.tick),
|
|
3174
3791
|
},
|
|
3175
|
-
|
|
3792
|
+
type: 'order-placed',
|
|
3793
|
+
}
|
|
3176
3794
|
}
|
|
3795
|
+
const isFlipOrder = (event: Decoded) =>
|
|
3796
|
+
'isFlipOrder' in event.args && (event.args as { isFlipOrder: boolean }).isFlipOrder === true
|
|
3797
|
+
// Non-flip order escrow legs claim before swap pairing so they cannot
|
|
3798
|
+
// mispair with an unrelated swap's output leg.
|
|
3799
|
+
recognize((event) => event.eventName === 'OrderPlaced' && !isFlipOrder(event), orderPlacedItem)
|
|
3800
|
+
|
|
3801
|
+
// Swap: a transfer into a route paired with a different token coming back
|
|
3802
|
+
// out. The DEX matches any swapper; other routes must be viewer-bounded
|
|
3803
|
+
// with route-emitted logs between the legs.
|
|
3804
|
+
{
|
|
3805
|
+
// Outgoing transfers indexed by lowercase sender, for O(candidates)
|
|
3806
|
+
// destination lookups. Claimed entries are skipped at scan time.
|
|
3807
|
+
const transfersByFrom = new Map<string, Decoded[]>()
|
|
3808
|
+
for (const event of pool) {
|
|
3809
|
+
if (!isTransfer(event)) continue
|
|
3810
|
+
const key = transferArgs(event).from.toLowerCase()
|
|
3811
|
+
const list = transfersByFrom.get(key)
|
|
3812
|
+
if (list) list.push(event)
|
|
3813
|
+
else transfersByFrom.set(key, [event])
|
|
3814
|
+
}
|
|
3815
|
+
// One forward pass: claims only shrink the pool, so a source that finds
|
|
3816
|
+
// no destination now never finds one later, and scanning each source once
|
|
3817
|
+
// keeps the cost linear in sources instead of restarting per pair.
|
|
3818
|
+
for (const source of unclaimed(isTransfer)) {
|
|
3819
|
+
if (claimed.has(source.logIndex)) continue
|
|
3820
|
+
const sourceArgs = transferArgs(source)
|
|
3821
|
+
const route = sourceArgs.to
|
|
3822
|
+
const routeKey = route.toLowerCase()
|
|
3823
|
+
// Tempo system addresses (the reserve, fee manager, outbox, and every
|
|
3824
|
+
// other non-DEX precompile) are never swap routes; new precompiles join
|
|
3825
|
+
// the exclusion automatically.
|
|
3826
|
+
if (nonRouteSystemAddresses.has(routeKey)) continue
|
|
3827
|
+
if (Address.isEqual(route, zeroAddress as Address.Address)) continue
|
|
3828
|
+
if (Address.isEqual(route, viewer)) continue
|
|
3829
|
+
const isDex = Address.isEqual(route, Addresses.stablecoinDex)
|
|
3830
|
+
if (!isDex && !Address.isEqual(sourceArgs.from, viewer)) continue
|
|
3831
|
+
const candidates = (transfersByFrom.get(routeKey) ?? []).filter((event) => {
|
|
3832
|
+
if (claimed.has(event.logIndex) || event.logIndex <= source.logIndex) return false
|
|
3833
|
+
if (Address.isEqual(event.address, source.address)) return false
|
|
3834
|
+
return !Address.isEqual(transferArgs(event).to, Addresses.feeManager)
|
|
3835
|
+
})
|
|
3836
|
+
// Prefer the output paying the swapper back (so interleaved swaps by
|
|
3837
|
+
// different accounts keep their own outputs); otherwise a DEX pairs
|
|
3838
|
+
// first-in-first-out, and a route's last leg is a direct delivery
|
|
3839
|
+
// (earlier different-token legs are intermediate hops).
|
|
3840
|
+
const destination =
|
|
3841
|
+
candidates.find((event) => Address.isEqual(transferArgs(event).to, sourceArgs.from)) ??
|
|
3842
|
+
(isDex ? candidates[0] : candidates.at(-1))
|
|
3843
|
+
if (!destination) continue
|
|
3844
|
+
// Route evidence for non-precompile routes: the route itself emitted a
|
|
3845
|
+
// log between the legs (raw rows, so undecoded router events count). A
|
|
3846
|
+
// counterparty that merely received and later paid back is not a route.
|
|
3847
|
+
if (!isDex) {
|
|
3848
|
+
const evidence = rows.some(
|
|
3849
|
+
(r) =>
|
|
3850
|
+
r.logIdx > source.logIndex &&
|
|
3851
|
+
r.logIdx < destination.logIndex &&
|
|
3852
|
+
r.logAddress.toLowerCase() === routeKey,
|
|
3853
|
+
)
|
|
3854
|
+
if (!evidence) continue
|
|
3855
|
+
}
|
|
3856
|
+
journal = []
|
|
3857
|
+
try {
|
|
3858
|
+
claim(source)
|
|
3859
|
+
claim(destination)
|
|
3860
|
+
const destinationArgs = transferArgs(destination)
|
|
3861
|
+
const swapper = isDex ? destinationArgs.to : sourceArgs.from
|
|
3862
|
+
const destTokenAddress = destination.address as Address.Address
|
|
3863
|
+
const sourceToken = token(sourceArgs.amount, source.address as Address.Address)
|
|
3864
|
+
|
|
3865
|
+
// Router plumbing between the legs: route-touching or zero-address
|
|
3866
|
+
// token moves and router-side approvals, never viewer-party transfers.
|
|
3867
|
+
// DEX-precompile pairs claim nothing here, so an interleaved swap by
|
|
3868
|
+
// another account keeps its own legs.
|
|
3869
|
+
if (!isDex)
|
|
3870
|
+
for (const event of unclaimed()) {
|
|
3871
|
+
if (event.logIndex <= source.logIndex || event.logIndex >= destination.logIndex)
|
|
3872
|
+
continue
|
|
3873
|
+
if (isTransfer(event)) {
|
|
3874
|
+
const args = transferArgs(event)
|
|
3875
|
+
if (Address.isEqual(args.from, viewer) || Address.isEqual(args.to, viewer)) continue
|
|
3876
|
+
if (
|
|
3877
|
+
Address.isEqual(args.from, route) ||
|
|
3878
|
+
Address.isEqual(args.to, route) ||
|
|
3879
|
+
Address.isEqual(args.from, zeroAddress as Address.Address) ||
|
|
3880
|
+
Address.isEqual(args.to, zeroAddress as Address.Address)
|
|
3881
|
+
)
|
|
3882
|
+
claim(event)
|
|
3883
|
+
} else if (
|
|
3884
|
+
event.eventName === 'Approval' &&
|
|
3885
|
+
'owner' in event.args &&
|
|
3886
|
+
!Address.isEqual((event.args as { owner: Address.Address }).owner, viewer)
|
|
3887
|
+
)
|
|
3888
|
+
claim(event)
|
|
3889
|
+
}
|
|
3177
3890
|
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3891
|
+
// The allowance granted for this swap: same token, owner, and amount,
|
|
3892
|
+
// and causally tied to the input leg (spender is the route, or the
|
|
3893
|
+
// approval immediately precedes it). Unrelated approvals stay.
|
|
3894
|
+
unclaimed(
|
|
3895
|
+
(event) =>
|
|
3896
|
+
event.eventName === 'Approval' &&
|
|
3897
|
+
'amount' in event.args &&
|
|
3898
|
+
'owner' in event.args &&
|
|
3899
|
+
'spender' in event.args &&
|
|
3900
|
+
Address.isEqual(event.address, source.address) &&
|
|
3901
|
+
Address.isEqual((event.args as { owner: Address.Address }).owner, sourceArgs.from) &&
|
|
3902
|
+
(event.args as { amount: bigint }).amount === sourceArgs.amount &&
|
|
3903
|
+
event.logIndex < source.logIndex &&
|
|
3904
|
+
(Address.isEqual((event.args as { spender: Address.Address }).spender, route) ||
|
|
3905
|
+
event.logIndex === source.logIndex - 1),
|
|
3906
|
+
)
|
|
3907
|
+
.slice(-1)
|
|
3908
|
+
.forEach(claim)
|
|
3909
|
+
|
|
3910
|
+
// DEX bookkeeping this swap produced: fills against resting orders and
|
|
3911
|
+
// the auto-flip re-placement, acted by the swapper or its route account.
|
|
3912
|
+
// A fill of the viewer's own resting order stays its own item.
|
|
3913
|
+
const isBookkeepingActor = (value: Address.Address) =>
|
|
3914
|
+
Address.isEqual(value, swapper) || Address.isEqual(value, route)
|
|
3915
|
+
unclaimed(
|
|
3916
|
+
(event) =>
|
|
3917
|
+
(Address.isEqual(event.address, route) ||
|
|
3918
|
+
Address.isEqual(event.address, Addresses.stablecoinDex)) &&
|
|
3919
|
+
((event.eventName === 'OrderFilled' &&
|
|
3920
|
+
'taker' in event.args &&
|
|
3921
|
+
isBookkeepingActor((event.args as { taker: Address.Address }).taker) &&
|
|
3922
|
+
!(
|
|
3923
|
+
'maker' in event.args &&
|
|
3924
|
+
Address.isEqual((event.args as { maker: Address.Address }).maker, viewer)
|
|
3925
|
+
)) ||
|
|
3926
|
+
(event.eventName === 'OrderPlaced' &&
|
|
3927
|
+
isFlipOrder(event) &&
|
|
3928
|
+
'maker' in event.args &&
|
|
3929
|
+
isBookkeepingActor((event.args as { maker: Address.Address }).maker))),
|
|
3930
|
+
).forEach(claim)
|
|
3931
|
+
|
|
3932
|
+
// Cross-token transfer: the swapped-out token is forwarded whole from
|
|
3933
|
+
// the swapper to a new recipient after the swap (the account server's
|
|
3934
|
+
// approve+swap+transfer multicall), or the route delivered it to the
|
|
3935
|
+
// recipient directly; fold both shapes into one `transfer`. A leg
|
|
3936
|
+
// whose recipient later forwards a different token is the input of a
|
|
3937
|
+
// chained swap, not a delivery.
|
|
3938
|
+
const forwardsOn = (candidate: Decoded) => {
|
|
3939
|
+
const args = transferArgs(candidate)
|
|
3940
|
+
return (transfersByFrom.get(args.to.toLowerCase()) ?? []).some(
|
|
3941
|
+
(onward) =>
|
|
3942
|
+
!claimed.has(onward.logIndex) &&
|
|
3943
|
+
onward.logIndex > candidate.logIndex &&
|
|
3944
|
+
!Address.isEqual(onward.address, candidate.address),
|
|
3945
|
+
)
|
|
3946
|
+
}
|
|
3947
|
+
const delivery =
|
|
3948
|
+
!isDex && !Address.isEqual(destinationArgs.to, viewer)
|
|
3949
|
+
? destination
|
|
3950
|
+
: takeTransfer((event) => {
|
|
3951
|
+
const args = transferArgs(event)
|
|
3952
|
+
return (
|
|
3953
|
+
event.logIndex > destination.logIndex &&
|
|
3954
|
+
Address.isEqual(event.address, destTokenAddress) &&
|
|
3955
|
+
Address.isEqual(args.from, swapper) &&
|
|
3956
|
+
args.amount === destinationArgs.amount &&
|
|
3957
|
+
!Address.isEqual(args.to, route) &&
|
|
3958
|
+
!Address.isEqual(args.to, swapper) &&
|
|
3959
|
+
!forwardsOn(event)
|
|
3960
|
+
)
|
|
3961
|
+
})
|
|
3962
|
+
// A malformed delivery memo degrades to a plain swap item; the claimed
|
|
3963
|
+
// legs must still produce an activity.
|
|
3964
|
+
const item = (() => {
|
|
3965
|
+
if (!delivery) return undefined
|
|
3966
|
+
try {
|
|
3967
|
+
const args = transferArgs(delivery)
|
|
3968
|
+
const rawMemo = delivery.eventName === 'TransferWithMemo' ? args.memo : undefined
|
|
3969
|
+
const memo = rawMemo ? Transfers.memoToString(rawMemo) : undefined
|
|
3970
|
+
const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo })
|
|
3971
|
+
return transfer(itemBase(delivery.logIndex), blockNum, {
|
|
3972
|
+
attribution,
|
|
3973
|
+
destinationToken: token(args.amount, delivery.address as Address.Address),
|
|
3974
|
+
direction: Address.isEqual(args.to, viewer) ? ('in' as const) : ('out' as const),
|
|
3975
|
+
memo,
|
|
3976
|
+
recipient: lc(args.to),
|
|
3977
|
+
sender: lc(swapper),
|
|
3978
|
+
signer,
|
|
3979
|
+
sourceToken,
|
|
3980
|
+
})
|
|
3981
|
+
} catch {
|
|
3982
|
+
return undefined
|
|
3983
|
+
}
|
|
3984
|
+
})()
|
|
3985
|
+
items.push(
|
|
3986
|
+
item ?? {
|
|
3987
|
+
...itemBase(source.logIndex),
|
|
3988
|
+
data: {
|
|
3989
|
+
destinationToken: token(destinationArgs.amount, destTokenAddress),
|
|
3990
|
+
signer,
|
|
3991
|
+
sourceToken,
|
|
3992
|
+
},
|
|
3993
|
+
type: 'swap',
|
|
3994
|
+
},
|
|
3995
|
+
)
|
|
3996
|
+
} catch {
|
|
3997
|
+
// A malformed leg costs only this pair; release its claims so the
|
|
3998
|
+
// legs fall through to the later recognizers.
|
|
3999
|
+
for (const index of journal ?? []) claimed.delete(index)
|
|
4000
|
+
} finally {
|
|
4001
|
+
journal = undefined
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
3197
4004
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
4005
|
+
|
|
4006
|
+
// Mint: explicit `Mint` events (not the fee manager) and remaining transfers
|
|
4007
|
+
// from the zero address (the dedup above already folded event/transfer pairs).
|
|
4008
|
+
const mintItem = (event: Decoded, args: { amount: bigint; to: Address.Address }): RawItem => ({
|
|
4009
|
+
...itemBase(event.logIndex),
|
|
4010
|
+
data: {
|
|
4011
|
+
recipient: lc(args.to),
|
|
4012
|
+
sourceToken: token(args.amount, event.address as Address.Address),
|
|
4013
|
+
},
|
|
4014
|
+
type: 'mint',
|
|
4015
|
+
})
|
|
4016
|
+
recognize(
|
|
4017
|
+
(event) =>
|
|
4018
|
+
event.eventName === 'Mint' &&
|
|
4019
|
+
'amount' in event.args &&
|
|
4020
|
+
'to' in event.args &&
|
|
4021
|
+
!Address.isEqual(event.address, Addresses.feeManager),
|
|
4022
|
+
(event) => mintItem(event, event.args as { amount: bigint; to: Address.Address }),
|
|
4023
|
+
)
|
|
4024
|
+
recognize(
|
|
4025
|
+
(event) =>
|
|
4026
|
+
isTransfer(event) &&
|
|
4027
|
+
Address.isEqual(transferArgs(event).from, zeroAddress as Address.Address),
|
|
4028
|
+
(event) => mintItem(event, transferArgs(event)),
|
|
3200
4029
|
)
|
|
3201
|
-
if (zeroTransferIn) {
|
|
3202
|
-
const args = zeroTransferIn.args as { amount: bigint; to: Address.Address }
|
|
3203
|
-
return [
|
|
3204
|
-
{
|
|
3205
|
-
...itemBase(logIndexOfEvent(zeroTransferIn)),
|
|
3206
|
-
data: {
|
|
3207
|
-
recipient: (args.to as string).toLowerCase() as Address.Address,
|
|
3208
|
-
sourceToken: token(args.amount, zeroTransferIn.address as Address.Address),
|
|
3209
|
-
},
|
|
3210
|
-
type: 'mint',
|
|
3211
|
-
},
|
|
3212
|
-
]
|
|
3213
|
-
}
|
|
3214
4030
|
|
|
3215
4031
|
// Burn blocked: a compliance burn of receive-policy-blocked funds. It also
|
|
3216
|
-
// emits a zero-address `Transfer
|
|
3217
|
-
//
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
4032
|
+
// emits a zero-address `Transfer`; claim that paired leg so it cannot
|
|
4033
|
+
// classify as a plain `burn`.
|
|
4034
|
+
recognize(named('BurnBlocked'), (event) => {
|
|
4035
|
+
const args = event.args as { amount: bigint; from: Address.Address }
|
|
4036
|
+
takeTransfer(
|
|
4037
|
+
(leg) =>
|
|
4038
|
+
Address.isEqual(leg.address, event.address) &&
|
|
4039
|
+
Address.isEqual(transferArgs(leg).to, zeroAddress as Address.Address) &&
|
|
4040
|
+
transferArgs(leg).amount === args.amount,
|
|
4041
|
+
)
|
|
4042
|
+
return {
|
|
4043
|
+
...itemBase(event.logIndex),
|
|
4044
|
+
data: {
|
|
4045
|
+
sender: lc(args.from),
|
|
4046
|
+
signer,
|
|
4047
|
+
sourceToken: token(args.amount, event.address as Address.Address),
|
|
3230
4048
|
},
|
|
3231
|
-
|
|
3232
|
-
|
|
4049
|
+
type: 'burn-blocked',
|
|
4050
|
+
}
|
|
4051
|
+
})
|
|
3233
4052
|
|
|
3234
|
-
// Burn: explicit `Burn`
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
4053
|
+
// Burn: explicit `Burn` events and remaining transfers to the zero address.
|
|
4054
|
+
const burnItem = (event: Decoded, args: { amount: bigint; from: Address.Address }): RawItem => ({
|
|
4055
|
+
...itemBase(event.logIndex),
|
|
4056
|
+
data: {
|
|
4057
|
+
sender: lc(args.from),
|
|
4058
|
+
signer,
|
|
4059
|
+
sourceToken: token(args.amount, event.address as Address.Address),
|
|
4060
|
+
},
|
|
4061
|
+
type: 'burn',
|
|
4062
|
+
})
|
|
4063
|
+
recognize(
|
|
4064
|
+
(event) => event.eventName === 'Burn' && 'amount' in event.args && 'from' in event.args,
|
|
4065
|
+
(event) => burnItem(event, event.args as { amount: bigint; from: Address.Address }),
|
|
4066
|
+
)
|
|
4067
|
+
recognize(
|
|
4068
|
+
(event) =>
|
|
4069
|
+
isTransfer(event) && Address.isEqual(transferArgs(event).to, zeroAddress as Address.Address),
|
|
4070
|
+
(event) => burnItem(event, transferArgs(event)),
|
|
3252
4071
|
)
|
|
3253
|
-
if (zeroTransferOut) {
|
|
3254
|
-
const args = zeroTransferOut.args as { amount: bigint; from: Address.Address }
|
|
3255
|
-
return [
|
|
3256
|
-
{
|
|
3257
|
-
...itemBase(logIndexOfEvent(zeroTransferOut)),
|
|
3258
|
-
data: {
|
|
3259
|
-
sender: (args.from as string).toLowerCase() as Address.Address,
|
|
3260
|
-
signer,
|
|
3261
|
-
sourceToken: token(args.amount, zeroTransferOut.address as Address.Address),
|
|
3262
|
-
},
|
|
3263
|
-
type: 'burn',
|
|
3264
|
-
},
|
|
3265
|
-
]
|
|
3266
|
-
}
|
|
3267
4072
|
|
|
3268
|
-
// Approval (excluding zone-outbox spenders)
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
4073
|
+
// Approval (excluding zone-outbox spenders): only viewer-party approvals
|
|
4074
|
+
// surface here; the rest stay for the nothing-else-classified fallback.
|
|
4075
|
+
const isErc20Approval = (event: Decoded) => {
|
|
4076
|
+
if (event.eventName !== 'Approval') return false
|
|
4077
|
+
const args = event.args as { spender?: unknown }
|
|
4078
|
+
if (typeof args.spender !== 'string' || !Address.validate(args.spender)) return false
|
|
4079
|
+
// ERC-721 uses the same selector but indexes its token ID as topic3. In
|
|
4080
|
+
// non-strict mode viem otherwise decodes the missing ERC-20 amount as zero.
|
|
4081
|
+
return (
|
|
3274
4082
|
rows.some(
|
|
3275
4083
|
(row) =>
|
|
3276
|
-
row.logIdx ===
|
|
4084
|
+
row.logIdx === event.logIndex &&
|
|
3277
4085
|
row.topic3 === null &&
|
|
3278
4086
|
/^0x[0-9a-fA-F]{64}$/.test(row.data ?? ''),
|
|
3279
|
-
) &&
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
const args =
|
|
3284
|
-
return
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
spender: args.spender.toLowerCase() as Address.Address,
|
|
3291
|
-
},
|
|
3292
|
-
type: 'approval',
|
|
4087
|
+
) && !Address.isEqual(args.spender as Address.Address, Addresses.zoneOutbox)
|
|
4088
|
+
)
|
|
4089
|
+
}
|
|
4090
|
+
const approvalItem = (event: Decoded): RawItem => {
|
|
4091
|
+
const args = event.args as { amount: bigint; spender: Address.Address }
|
|
4092
|
+
return {
|
|
4093
|
+
...itemBase(event.logIndex),
|
|
4094
|
+
data: {
|
|
4095
|
+
signer,
|
|
4096
|
+
sourceToken: token(args.amount, event.address as Address.Address),
|
|
4097
|
+
spender: lc(args.spender),
|
|
3293
4098
|
},
|
|
3294
|
-
|
|
4099
|
+
type: 'approval',
|
|
4100
|
+
}
|
|
3295
4101
|
}
|
|
4102
|
+
recognize(
|
|
4103
|
+
(event) =>
|
|
4104
|
+
isErc20Approval(event) &&
|
|
4105
|
+
'owner' in event.args &&
|
|
4106
|
+
(Address.isEqual((event.args as { owner: Address.Address }).owner, viewer) ||
|
|
4107
|
+
Address.isEqual((event.args as { spender: Address.Address }).spender, viewer)),
|
|
4108
|
+
approvalItem,
|
|
4109
|
+
)
|
|
3296
4110
|
|
|
3297
|
-
//
|
|
3298
|
-
//
|
|
3299
|
-
//
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
// by log adjacency.
|
|
3303
|
-
|
|
3304
|
-
// Payment channel opened: `ChannelOpened` carries the deposit token/amount
|
|
3305
|
-
// directly, so no paired-transfer lookup is needed.
|
|
3306
|
-
const channelOpened = finalEvents.find((e) => e.eventName === 'ChannelOpened')
|
|
3307
|
-
if (channelOpened) {
|
|
3308
|
-
const args = channelOpened.args as {
|
|
4111
|
+
// Payment channels. The reserve doesn't put the token on `TopUp`/`Settled`/
|
|
4112
|
+
// `ChannelClosed`, so each anchor claims its own reserve leg to recover it;
|
|
4113
|
+
// batched channel operations pair with their own legs one at a time.
|
|
4114
|
+
recognize(named('ChannelOpened'), (event) => {
|
|
4115
|
+
const args = event.args as {
|
|
3309
4116
|
channelId: Hex.Hex
|
|
3310
4117
|
deposit: bigint
|
|
3311
4118
|
payee: Address.Address
|
|
3312
4119
|
payer: Address.Address
|
|
3313
4120
|
token: Address.Address
|
|
3314
4121
|
}
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
4122
|
+
takeTransfer(
|
|
4123
|
+
(leg) =>
|
|
4124
|
+
Address.isEqual(leg.address, args.token) &&
|
|
4125
|
+
Address.isEqual(transferArgs(leg).to, tip20ChannelReserve) &&
|
|
4126
|
+
transferArgs(leg).amount === args.deposit,
|
|
4127
|
+
)
|
|
4128
|
+
return {
|
|
4129
|
+
...itemBase(event.logIndex),
|
|
4130
|
+
data: {
|
|
4131
|
+
channelId: hx(args.channelId),
|
|
4132
|
+
payee: lc(args.payee),
|
|
4133
|
+
payer: lc(args.payer),
|
|
4134
|
+
signer,
|
|
4135
|
+
sourceToken: token(args.deposit, args.token),
|
|
3326
4136
|
},
|
|
3327
|
-
|
|
3328
|
-
|
|
4137
|
+
type: 'channel-opened',
|
|
4138
|
+
}
|
|
4139
|
+
})
|
|
3329
4140
|
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
4141
|
+
recognize(named('TopUp'), (event) => {
|
|
4142
|
+
const args = event.args as { additionalDeposit: bigint; channelId: Hex.Hex }
|
|
4143
|
+
const inflow =
|
|
4144
|
+
takeTransfer(
|
|
4145
|
+
(leg) =>
|
|
4146
|
+
Address.isEqual(transferArgs(leg).to, tip20ChannelReserve) &&
|
|
4147
|
+
transferArgs(leg).amount === args.additionalDeposit,
|
|
4148
|
+
) ?? takeTransfer((leg) => Address.isEqual(transferArgs(leg).to, tip20ChannelReserve))
|
|
4149
|
+
// A top-up cancels any pending close request on the channel; fold the
|
|
4150
|
+
// cancellation event it bundles.
|
|
4151
|
+
unclaimed(
|
|
3337
4152
|
(e) =>
|
|
3338
|
-
|
|
3339
|
-
|
|
4153
|
+
e.eventName === 'CloseRequestCancelled' &&
|
|
4154
|
+
'channelId' in e.args &&
|
|
4155
|
+
(e.args as { channelId: Hex.Hex }).channelId === args.channelId,
|
|
3340
4156
|
)
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
if (topUp) {
|
|
3346
|
-
const args = topUp.args as { additionalDeposit: bigint; channelId: Hex.Hex }
|
|
3347
|
-
const tokenAddress = reserveInflow
|
|
3348
|
-
? (reserveInflow.address as Address.Address)
|
|
4157
|
+
.slice(0, 1)
|
|
4158
|
+
.forEach(claim)
|
|
4159
|
+
const tokenAddress = inflow
|
|
4160
|
+
? (inflow.address as Address.Address)
|
|
3349
4161
|
: (zeroAddress as Address.Address)
|
|
3350
|
-
return
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
sourceToken: token(args.additionalDeposit, tokenAddress),
|
|
3357
|
-
},
|
|
3358
|
-
type: 'channel-funded',
|
|
4162
|
+
return {
|
|
4163
|
+
...itemBase(event.logIndex),
|
|
4164
|
+
data: {
|
|
4165
|
+
channelId: hx(args.channelId),
|
|
4166
|
+
signer,
|
|
4167
|
+
sourceToken: token(args.additionalDeposit, tokenAddress),
|
|
3359
4168
|
},
|
|
3360
|
-
|
|
3361
|
-
|
|
4169
|
+
type: 'channel-funded',
|
|
4170
|
+
}
|
|
4171
|
+
})
|
|
3362
4172
|
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
const settled = finalEvents.find((e) => e.eventName === 'Settled')
|
|
3366
|
-
if (settled) {
|
|
3367
|
-
const args = settled.args as {
|
|
4173
|
+
recognize(named('Settled'), (event) => {
|
|
4174
|
+
const args = event.args as {
|
|
3368
4175
|
channelId: Hex.Hex
|
|
3369
4176
|
deltaPaid: bigint
|
|
3370
4177
|
payee: Address.Address
|
|
3371
4178
|
payer: Address.Address
|
|
3372
4179
|
}
|
|
3373
|
-
const
|
|
4180
|
+
const payout = (leg: Decoded) =>
|
|
4181
|
+
Address.isEqual(transferArgs(leg).from, tip20ChannelReserve) &&
|
|
4182
|
+
Address.isEqual(transferArgs(leg).to, args.payee)
|
|
4183
|
+
// Prefer the amount-matched payout so batched channel operations sharing
|
|
4184
|
+
// a payee keep their own legs.
|
|
4185
|
+
const payeeLeg =
|
|
4186
|
+
takeTransfer((leg) => payout(leg) && transferArgs(leg).amount === args.deltaPaid) ??
|
|
4187
|
+
takeTransfer(payout)
|
|
3374
4188
|
const tokenAddress = payeeLeg
|
|
3375
4189
|
? (payeeLeg.address as Address.Address)
|
|
3376
4190
|
: (zeroAddress as Address.Address)
|
|
3377
|
-
return
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
sourceToken: token(args.deltaPaid, tokenAddress),
|
|
3386
|
-
},
|
|
3387
|
-
type: 'channel-settled',
|
|
4191
|
+
return {
|
|
4192
|
+
...itemBase(event.logIndex),
|
|
4193
|
+
data: {
|
|
4194
|
+
channelId: hx(args.channelId),
|
|
4195
|
+
payee: lc(args.payee),
|
|
4196
|
+
payer: lc(args.payer),
|
|
4197
|
+
signer,
|
|
4198
|
+
sourceToken: token(args.deltaPaid, tokenAddress),
|
|
3388
4199
|
},
|
|
3389
|
-
|
|
3390
|
-
|
|
4200
|
+
type: 'channel-settled',
|
|
4201
|
+
}
|
|
4202
|
+
})
|
|
3391
4203
|
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
const channelClosed = finalEvents.find((e) => e.eventName === 'ChannelClosed')
|
|
3395
|
-
if (channelClosed) {
|
|
3396
|
-
const args = channelClosed.args as {
|
|
4204
|
+
recognize(named('ChannelClosed'), (event) => {
|
|
4205
|
+
const args = event.args as {
|
|
3397
4206
|
channelId: Hex.Hex
|
|
3398
4207
|
payee: Address.Address
|
|
3399
4208
|
payer: Address.Address
|
|
3400
4209
|
refundedToPayer: bigint
|
|
3401
4210
|
settledToPayee: bigint
|
|
3402
4211
|
}
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
4212
|
+
// A zero settlement or refund has no leg; claiming one would steal it
|
|
4213
|
+
// from a batched sibling operation.
|
|
4214
|
+
const reserveLegTo = (to: Address.Address, amount: bigint) => {
|
|
4215
|
+
if (amount === 0n) return undefined
|
|
4216
|
+
const payout = (leg: Decoded) =>
|
|
4217
|
+
Address.isEqual(transferArgs(leg).from, tip20ChannelReserve) &&
|
|
4218
|
+
Address.isEqual(transferArgs(leg).to, to)
|
|
4219
|
+
return (
|
|
4220
|
+
takeTransfer((leg) => payout(leg) && transferArgs(leg).amount === amount) ??
|
|
4221
|
+
takeTransfer(payout)
|
|
4222
|
+
)
|
|
4223
|
+
}
|
|
4224
|
+
const payeeLeg = reserveLegTo(args.payee, args.settledToPayee)
|
|
4225
|
+
const payerLeg = reserveLegTo(args.payer, args.refundedToPayer)
|
|
4226
|
+
return {
|
|
4227
|
+
...itemBase(event.logIndex),
|
|
4228
|
+
data: {
|
|
4229
|
+
channelId: hx(args.channelId),
|
|
4230
|
+
payee: lc(args.payee),
|
|
4231
|
+
payer: lc(args.payer),
|
|
4232
|
+
...(payerLeg && args.refundedToPayer > 0n
|
|
4233
|
+
? { refund: token(args.refundedToPayer, payerLeg.address as Address.Address) }
|
|
4234
|
+
: {}),
|
|
4235
|
+
signer,
|
|
4236
|
+
...(payeeLeg
|
|
4237
|
+
? { sourceToken: token(args.settledToPayee, payeeLeg.address as Address.Address) }
|
|
4238
|
+
: {}),
|
|
3421
4239
|
},
|
|
3422
|
-
|
|
3423
|
-
|
|
4240
|
+
type: 'channel-closed',
|
|
4241
|
+
}
|
|
4242
|
+
})
|
|
3424
4243
|
|
|
3425
|
-
// Payment channel close cancelled:
|
|
3426
|
-
//
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
if (closeCancelled) {
|
|
3430
|
-
const args = closeCancelled.args as {
|
|
4244
|
+
// Payment channel close cancelled: carries no transfer, so the event itself
|
|
4245
|
+
// is the signal. Cancellations bundled into a top-up were claimed above.
|
|
4246
|
+
recognize(named('CloseRequestCancelled'), (event) => {
|
|
4247
|
+
const args = event.args as {
|
|
3431
4248
|
channelId: Hex.Hex
|
|
3432
4249
|
payee: Address.Address
|
|
3433
4250
|
payer: Address.Address
|
|
3434
4251
|
}
|
|
3435
|
-
return
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
signer,
|
|
3443
|
-
},
|
|
3444
|
-
type: 'channel-close-cancelled',
|
|
4252
|
+
return {
|
|
4253
|
+
...itemBase(event.logIndex),
|
|
4254
|
+
data: {
|
|
4255
|
+
channelId: hx(args.channelId),
|
|
4256
|
+
payee: lc(args.payee),
|
|
4257
|
+
payer: lc(args.payer),
|
|
4258
|
+
signer,
|
|
3445
4259
|
},
|
|
3446
|
-
|
|
3447
|
-
}
|
|
3448
|
-
|
|
3449
|
-
// DEX order placed: `OrderPlaced` carries the escrow token/amount and side.
|
|
3450
|
-
// (Auto-flip re-places are inside swaps, which already returned above.)
|
|
3451
|
-
const orderPlaced = finalEvents.find((e) => e.eventName === 'OrderPlaced')
|
|
3452
|
-
if (orderPlaced) {
|
|
3453
|
-
const args = orderPlaced.args as {
|
|
3454
|
-
amount: bigint
|
|
3455
|
-
isBid: boolean
|
|
3456
|
-
orderId: bigint
|
|
3457
|
-
tick: number
|
|
3458
|
-
token: Address.Address
|
|
4260
|
+
type: 'channel-close-cancelled',
|
|
3459
4261
|
}
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
4262
|
+
})
|
|
4263
|
+
|
|
4264
|
+
// Payment-channel close requested: matched on the raw selector (the event
|
|
4265
|
+
// is not in the merged ABI). Runs after the channel recognizers claimed
|
|
4266
|
+
// their own reserve legs, then takes the leftover refund back to the viewer.
|
|
4267
|
+
for (const row of rows.filter((r) => r.selector === closeRequestedSelector)) {
|
|
4268
|
+
const refundLeg = takeTransfer((event) => {
|
|
4269
|
+
const args = transferArgs(event)
|
|
4270
|
+
if (!Address.isEqual(args.to, viewer)) return false
|
|
4271
|
+
return (
|
|
4272
|
+
Address.isEqual(args.from, tip20ChannelReserve) ||
|
|
4273
|
+
Address.isEqual(args.from, row.logAddress)
|
|
4274
|
+
)
|
|
4275
|
+
})
|
|
4276
|
+
const refund = refundLeg
|
|
4277
|
+
? token(transferArgs(refundLeg).amount, refundLeg.address as Address.Address)
|
|
4278
|
+
: undefined
|
|
4279
|
+
items.push({
|
|
4280
|
+
...itemBase(row.logIdx),
|
|
4281
|
+
data: { ...(refund ? { refund } : {}), signer },
|
|
4282
|
+
type: 'session-closed',
|
|
4283
|
+
})
|
|
3473
4284
|
}
|
|
3474
4285
|
|
|
4286
|
+
// Auto-flip order placements not folded into a swap above.
|
|
4287
|
+
recognize(named('OrderPlaced'), orderPlacedItem)
|
|
4288
|
+
|
|
3475
4289
|
// DEX order cancelled: refund is credited to the internal DEX balance, so
|
|
3476
|
-
// there is no co-located transfer
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
{
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
},
|
|
3486
|
-
]
|
|
3487
|
-
}
|
|
4290
|
+
// there is no co-located transfer; the event itself is the signal.
|
|
4291
|
+
recognize(named('OrderCancelled'), (event) => {
|
|
4292
|
+
const args = event.args as { orderId: bigint }
|
|
4293
|
+
return {
|
|
4294
|
+
...itemBase(event.logIndex),
|
|
4295
|
+
data: { orderId: args.orderId.toString(), signer },
|
|
4296
|
+
type: 'order-cancelled',
|
|
4297
|
+
}
|
|
4298
|
+
})
|
|
3488
4299
|
|
|
3489
|
-
// Fee-AMM rebalance swap: `validatorToken` in, `userToken` out
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
const args =
|
|
4300
|
+
// Fee-AMM rebalance swap: `validatorToken` in, `userToken` out; claim both
|
|
4301
|
+
// fee-manager legs so neither resurfaces as a plain transfer.
|
|
4302
|
+
recognize(named('RebalanceSwap'), (event) => {
|
|
4303
|
+
const args = event.args as {
|
|
3493
4304
|
amountIn: bigint
|
|
3494
4305
|
amountOut: bigint
|
|
3495
4306
|
swapper: Address.Address
|
|
3496
4307
|
userToken: Address.Address
|
|
3497
4308
|
validatorToken: Address.Address
|
|
3498
4309
|
}
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
4310
|
+
takeTransfer(
|
|
4311
|
+
(leg) =>
|
|
4312
|
+
Address.isEqual(leg.address, args.validatorToken) &&
|
|
4313
|
+
Address.isEqual(transferArgs(leg).from, args.swapper) &&
|
|
4314
|
+
Address.isEqual(transferArgs(leg).to, Addresses.feeManager) &&
|
|
4315
|
+
transferArgs(leg).amount === args.amountIn,
|
|
4316
|
+
)
|
|
4317
|
+
takeTransfer(
|
|
4318
|
+
(leg) =>
|
|
4319
|
+
Address.isEqual(leg.address, args.userToken) &&
|
|
4320
|
+
Address.isEqual(transferArgs(leg).from, Addresses.feeManager) &&
|
|
4321
|
+
Address.isEqual(transferArgs(leg).to, args.swapper) &&
|
|
4322
|
+
transferArgs(leg).amount === args.amountOut,
|
|
4323
|
+
)
|
|
4324
|
+
return {
|
|
4325
|
+
...itemBase(event.logIndex),
|
|
4326
|
+
data: {
|
|
4327
|
+
destinationToken: token(args.amountOut, args.userToken),
|
|
4328
|
+
signer,
|
|
4329
|
+
sourceToken: token(args.amountIn, args.validatorToken),
|
|
4330
|
+
swapper: lc(args.swapper),
|
|
3509
4331
|
},
|
|
3510
|
-
|
|
3511
|
-
|
|
4332
|
+
type: 'fee-rebalance-swap',
|
|
4333
|
+
}
|
|
4334
|
+
})
|
|
3512
4335
|
|
|
3513
|
-
// Validator fees distributed.
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
const args = feesDistributed.args as {
|
|
4336
|
+
// Validator fees distributed; claim the fee-manager payout leg.
|
|
4337
|
+
recognize(named('FeesDistributed'), (event) => {
|
|
4338
|
+
const args = event.args as {
|
|
3517
4339
|
amount: bigint
|
|
3518
4340
|
token: Address.Address
|
|
3519
4341
|
validator: Address.Address
|
|
3520
4342
|
}
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
4343
|
+
takeTransfer(
|
|
4344
|
+
(leg) =>
|
|
4345
|
+
Address.isEqual(leg.address, args.token) &&
|
|
4346
|
+
Address.isEqual(transferArgs(leg).from, Addresses.feeManager) &&
|
|
4347
|
+
Address.isEqual(transferArgs(leg).to, args.validator) &&
|
|
4348
|
+
transferArgs(leg).amount === args.amount,
|
|
4349
|
+
)
|
|
4350
|
+
return {
|
|
4351
|
+
...itemBase(event.logIndex),
|
|
4352
|
+
data: {
|
|
4353
|
+
signer,
|
|
4354
|
+
sourceToken: token(args.amount, args.token),
|
|
4355
|
+
validator: lc(args.validator),
|
|
3530
4356
|
},
|
|
3531
|
-
|
|
3532
|
-
|
|
4357
|
+
type: 'fees-distributed',
|
|
4358
|
+
}
|
|
4359
|
+
})
|
|
3533
4360
|
|
|
3534
|
-
// Reward pool funded: the funded token is the emitting
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
const args =
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
4361
|
+
// Reward pool funded: the funded token is the emitting contract itself;
|
|
4362
|
+
// claim the funding transfer.
|
|
4363
|
+
recognize(named('RewardDistributed'), (event) => {
|
|
4364
|
+
const args = event.args as { amount: bigint; funder: Address.Address }
|
|
4365
|
+
takeTransfer(
|
|
4366
|
+
(leg) =>
|
|
4367
|
+
Address.isEqual(leg.address, event.address as Address.Address) &&
|
|
4368
|
+
Address.isEqual(transferArgs(leg).from, args.funder) &&
|
|
4369
|
+
Address.isEqual(transferArgs(leg).to, event.address as Address.Address) &&
|
|
4370
|
+
transferArgs(leg).amount === args.amount,
|
|
4371
|
+
)
|
|
4372
|
+
return {
|
|
4373
|
+
...itemBase(event.logIndex),
|
|
4374
|
+
data: {
|
|
4375
|
+
funder: lc(args.funder),
|
|
4376
|
+
signer,
|
|
4377
|
+
sourceToken: token(args.amount, event.address as Address.Address),
|
|
3547
4378
|
},
|
|
3548
|
-
|
|
3549
|
-
|
|
4379
|
+
type: 'reward-distributed',
|
|
4380
|
+
}
|
|
4381
|
+
})
|
|
3550
4382
|
|
|
3551
4383
|
// Reward recipient redirected.
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
{
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
recipient: args.recipient.toLowerCase() as Address.Address,
|
|
3561
|
-
signer,
|
|
3562
|
-
},
|
|
3563
|
-
type: 'reward-recipient-set',
|
|
4384
|
+
recognize(named('RewardRecipientSet'), (event) => {
|
|
4385
|
+
const args = event.args as { holder: Address.Address; recipient: Address.Address }
|
|
4386
|
+
return {
|
|
4387
|
+
...itemBase(event.logIndex),
|
|
4388
|
+
data: {
|
|
4389
|
+
holder: lc(args.holder),
|
|
4390
|
+
recipient: lc(args.recipient),
|
|
4391
|
+
signer,
|
|
3564
4392
|
},
|
|
3565
|
-
|
|
3566
|
-
|
|
4393
|
+
type: 'reward-recipient-set',
|
|
4394
|
+
}
|
|
4395
|
+
})
|
|
3567
4396
|
|
|
3568
4397
|
// Access-key spending limit updated: `newLimit` is carried as the token
|
|
3569
4398
|
// amount so it enriches with the token's metadata.
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
const args = spendingLimitUpdated.args as {
|
|
4399
|
+
recognize(named('SpendingLimitUpdated'), (event) => {
|
|
4400
|
+
const args = event.args as {
|
|
3573
4401
|
account: Address.Address
|
|
3574
4402
|
newLimit: bigint
|
|
3575
4403
|
publicKey: Address.Address
|
|
3576
4404
|
token: Address.Address
|
|
3577
4405
|
}
|
|
3578
|
-
return
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
sourceToken: token(args.newLimit, args.token),
|
|
3586
|
-
},
|
|
3587
|
-
type: 'spending-limit-updated',
|
|
4406
|
+
return {
|
|
4407
|
+
...itemBase(event.logIndex),
|
|
4408
|
+
data: {
|
|
4409
|
+
account: lc(args.account),
|
|
4410
|
+
publicKey: lc(args.publicKey),
|
|
4411
|
+
signer,
|
|
4412
|
+
sourceToken: token(args.newLimit, args.token),
|
|
3588
4413
|
},
|
|
3589
|
-
|
|
3590
|
-
|
|
4414
|
+
type: 'spending-limit-updated',
|
|
4415
|
+
}
|
|
4416
|
+
})
|
|
3591
4417
|
|
|
3592
|
-
// Access-key lifecycle events
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
if (
|
|
4418
|
+
// Access-key lifecycle events.
|
|
4419
|
+
recognize(
|
|
4420
|
+
(event) =>
|
|
3596
4421
|
event.eventName === 'KeyAuthorized' &&
|
|
3597
|
-
Address.isEqual(event.address, Addresses.accountKeychain)
|
|
3598
|
-
) {
|
|
4422
|
+
Address.isEqual(event.address, Addresses.accountKeychain),
|
|
4423
|
+
(event) => {
|
|
3599
4424
|
const args = event.args as {
|
|
3600
4425
|
account: Address.Address
|
|
3601
4426
|
expiry: bigint
|
|
3602
4427
|
publicKey: Address.Address
|
|
3603
4428
|
signatureType: number
|
|
3604
4429
|
}
|
|
3605
|
-
|
|
3606
|
-
...itemBase(
|
|
4430
|
+
return {
|
|
4431
|
+
...itemBase(event.logIndex),
|
|
3607
4432
|
data: {
|
|
3608
|
-
account: args.account
|
|
4433
|
+
account: lc(args.account),
|
|
3609
4434
|
expiry: Number(args.expiry),
|
|
3610
|
-
publicKey: args.publicKey
|
|
4435
|
+
publicKey: lc(args.publicKey),
|
|
3611
4436
|
signatureType: Number(args.signatureType),
|
|
3612
4437
|
},
|
|
3613
4438
|
type: 'access-key-created',
|
|
3614
|
-
}
|
|
3615
|
-
}
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
)
|
|
4439
|
+
}
|
|
4440
|
+
},
|
|
4441
|
+
)
|
|
4442
|
+
recognize(
|
|
4443
|
+
(event) =>
|
|
4444
|
+
event.eventName === 'KeyRevoked' && Address.isEqual(event.address, Addresses.accountKeychain),
|
|
4445
|
+
(event) => {
|
|
3619
4446
|
const args = event.args as { account: Address.Address; publicKey: Address.Address }
|
|
3620
|
-
|
|
3621
|
-
...itemBase(
|
|
4447
|
+
return {
|
|
4448
|
+
...itemBase(event.logIndex),
|
|
3622
4449
|
data: {
|
|
3623
|
-
account: args.account
|
|
3624
|
-
publicKey: args.publicKey
|
|
4450
|
+
account: lc(args.account),
|
|
4451
|
+
publicKey: lc(args.publicKey),
|
|
3625
4452
|
},
|
|
3626
4453
|
type: 'access-key-revoked',
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
}
|
|
3630
|
-
|
|
3631
|
-
// Token creation.
|
|
3632
|
-
const tokenCreated = finalEvents.find(
|
|
3633
|
-
(e) => e.eventName === 'TokenCreated' && Address.isEqual(e.address, Addresses.tip20Factory),
|
|
4454
|
+
}
|
|
4455
|
+
},
|
|
3634
4456
|
)
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
{
|
|
3644
|
-
|
|
4457
|
+
|
|
4458
|
+
// Token creation. Factory initialization (role grants, config) precedes the
|
|
4459
|
+
// creation event; claim those, leaving later admin actions on the token as
|
|
4460
|
+
// independent activities.
|
|
4461
|
+
recognize(
|
|
4462
|
+
(event) =>
|
|
4463
|
+
event.eventName === 'TokenCreated' && Address.isEqual(event.address, Addresses.tip20Factory),
|
|
4464
|
+
(event) => {
|
|
4465
|
+
const args = event.args as {
|
|
4466
|
+
currency: string
|
|
4467
|
+
name: string
|
|
4468
|
+
symbol: string
|
|
4469
|
+
token: Address.Address
|
|
4470
|
+
}
|
|
4471
|
+
unclaimed(
|
|
4472
|
+
(e) => e.logIndex < event.logIndex && Address.isEqual(e.address, args.token),
|
|
4473
|
+
).forEach(claim)
|
|
4474
|
+
return {
|
|
4475
|
+
...itemBase(event.logIndex),
|
|
3645
4476
|
data: {
|
|
3646
|
-
address: args.token
|
|
4477
|
+
address: lc(args.token),
|
|
3647
4478
|
currency: args.currency,
|
|
3648
4479
|
name: args.name,
|
|
3649
4480
|
symbol: args.symbol,
|
|
3650
4481
|
},
|
|
3651
4482
|
type: 'token-created',
|
|
3652
|
-
}
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
}
|
|
4483
|
+
}
|
|
4484
|
+
},
|
|
4485
|
+
)
|
|
3656
4486
|
|
|
3657
4487
|
// Governance / admin / DEX-bookkeeping events. Each maps a single decoded
|
|
3658
|
-
// event to a typed item
|
|
3659
|
-
//
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
const hx = (value: Hex.Hex) => value.toLowerCase() as Hex.Hex
|
|
3663
|
-
const findEvent = (name: string) => finalEvents.find((e) => e.eventName === name)
|
|
3664
|
-
|
|
3665
|
-
// DEX bookkeeping (taker swaps and `OrderPlaced` already returned above).
|
|
3666
|
-
const orderFilled = findEvent('OrderFilled')
|
|
3667
|
-
if (orderFilled) {
|
|
3668
|
-
const a = orderFilled.args as {
|
|
4488
|
+
// event to a typed item; every remaining occurrence emits. These carry no
|
|
4489
|
+
// token movement, so the event itself is the whole signal.
|
|
4490
|
+
recognize(named('OrderFilled'), (event) => {
|
|
4491
|
+
const a = event.args as {
|
|
3669
4492
|
amountFilled: bigint
|
|
3670
4493
|
maker: Address.Address
|
|
3671
4494
|
orderId: bigint
|
|
3672
4495
|
partialFill: boolean
|
|
3673
4496
|
taker: Address.Address
|
|
3674
4497
|
}
|
|
3675
|
-
return
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
taker: lc(a.taker),
|
|
3685
|
-
},
|
|
3686
|
-
type: 'order-filled',
|
|
4498
|
+
return {
|
|
4499
|
+
...itemBase(event.logIndex),
|
|
4500
|
+
data: {
|
|
4501
|
+
amountFilled: a.amountFilled.toString(),
|
|
4502
|
+
maker: lc(a.maker),
|
|
4503
|
+
orderId: a.orderId.toString(),
|
|
4504
|
+
partialFill: a.partialFill,
|
|
4505
|
+
signer,
|
|
4506
|
+
taker: lc(a.taker),
|
|
3687
4507
|
},
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
const a =
|
|
4508
|
+
type: 'order-filled',
|
|
4509
|
+
}
|
|
4510
|
+
})
|
|
4511
|
+
recognize(named('OrderFlipped'), (event) => {
|
|
4512
|
+
const a = event.args as {
|
|
3693
4513
|
amount: bigint
|
|
3694
4514
|
flipTick: number
|
|
3695
4515
|
isBid: boolean
|
|
@@ -3698,342 +4518,299 @@ function classifyFromLogsCore(
|
|
|
3698
4518
|
tick: number
|
|
3699
4519
|
token: Address.Address
|
|
3700
4520
|
}
|
|
3701
|
-
return
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
tick: Number(a.tick),
|
|
3712
|
-
},
|
|
3713
|
-
type: 'order-flipped',
|
|
3714
|
-
},
|
|
3715
|
-
]
|
|
3716
|
-
}
|
|
3717
|
-
const pairCreated = findEvent('PairCreated')
|
|
3718
|
-
if (pairCreated) {
|
|
3719
|
-
const a = pairCreated.args as { base: Address.Address; key: Hex.Hex; quote: Address.Address }
|
|
3720
|
-
return [
|
|
3721
|
-
{
|
|
3722
|
-
...itemBase(logIndexOfEvent(pairCreated)),
|
|
3723
|
-
data: { base: lc(a.base), key: hx(a.key), quote: lc(a.quote), signer },
|
|
3724
|
-
type: 'pair-created',
|
|
4521
|
+
return {
|
|
4522
|
+
...itemBase(event.logIndex),
|
|
4523
|
+
data: {
|
|
4524
|
+
flipTick: Number(a.flipTick),
|
|
4525
|
+
maker: lc(a.maker),
|
|
4526
|
+
orderId: a.orderId.toString(),
|
|
4527
|
+
side: a.isBid ? 'bid' : 'ask',
|
|
4528
|
+
signer,
|
|
4529
|
+
sourceToken: token(a.amount, a.token),
|
|
4530
|
+
tick: Number(a.tick),
|
|
3725
4531
|
},
|
|
3726
|
-
|
|
3727
|
-
|
|
4532
|
+
type: 'order-flipped',
|
|
4533
|
+
}
|
|
4534
|
+
})
|
|
4535
|
+
recognize(named('PairCreated'), (event) => {
|
|
4536
|
+
const a = event.args as { base: Address.Address; key: Hex.Hex; quote: Address.Address }
|
|
4537
|
+
return {
|
|
4538
|
+
...itemBase(event.logIndex),
|
|
4539
|
+
data: { base: lc(a.base), key: hx(a.key), quote: lc(a.quote), signer },
|
|
4540
|
+
type: 'pair-created',
|
|
4541
|
+
}
|
|
4542
|
+
})
|
|
3728
4543
|
|
|
3729
4544
|
// TIP-20 token configuration / admin.
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
{
|
|
3735
|
-
|
|
3736
|
-
data: { paused: a.isPaused, signer, updater: lc(a.updater) },
|
|
3737
|
-
type: 'token-pause-set',
|
|
3738
|
-
},
|
|
3739
|
-
]
|
|
3740
|
-
}
|
|
3741
|
-
const supplyCapUpdate = findEvent('SupplyCapUpdate')
|
|
3742
|
-
if (supplyCapUpdate) {
|
|
3743
|
-
const a = supplyCapUpdate.args as { newSupplyCap: bigint; updater: Address.Address }
|
|
3744
|
-
return [
|
|
3745
|
-
{
|
|
3746
|
-
...itemBase(logIndexOfEvent(supplyCapUpdate)),
|
|
3747
|
-
data: { signer, supplyCap: a.newSupplyCap.toString(), updater: lc(a.updater) },
|
|
3748
|
-
type: 'token-supply-cap-set',
|
|
3749
|
-
},
|
|
3750
|
-
]
|
|
3751
|
-
}
|
|
3752
|
-
const transferPolicyUpdate = findEvent('TransferPolicyUpdate')
|
|
3753
|
-
if (transferPolicyUpdate) {
|
|
3754
|
-
const a = transferPolicyUpdate.args as { newPolicyId: bigint; updater: Address.Address }
|
|
3755
|
-
return [
|
|
3756
|
-
{
|
|
3757
|
-
...itemBase(logIndexOfEvent(transferPolicyUpdate)),
|
|
3758
|
-
data: { policyId: a.newPolicyId.toString(), signer, updater: lc(a.updater) },
|
|
3759
|
-
type: 'token-transfer-policy-set',
|
|
3760
|
-
},
|
|
3761
|
-
]
|
|
3762
|
-
}
|
|
3763
|
-
const quoteTokenUpdate = findEvent('QuoteTokenUpdate')
|
|
3764
|
-
if (quoteTokenUpdate) {
|
|
3765
|
-
const a = quoteTokenUpdate.args as { newQuoteToken: Address.Address; updater: Address.Address }
|
|
3766
|
-
return [
|
|
3767
|
-
{
|
|
3768
|
-
...itemBase(logIndexOfEvent(quoteTokenUpdate)),
|
|
3769
|
-
data: { quoteToken: lc(a.newQuoteToken), signer, updater: lc(a.updater) },
|
|
3770
|
-
type: 'token-quote-token-set',
|
|
3771
|
-
},
|
|
3772
|
-
]
|
|
3773
|
-
}
|
|
3774
|
-
const nextQuoteTokenSet = findEvent('NextQuoteTokenSet')
|
|
3775
|
-
if (nextQuoteTokenSet) {
|
|
3776
|
-
const a = nextQuoteTokenSet.args as {
|
|
3777
|
-
nextQuoteToken: Address.Address
|
|
3778
|
-
updater: Address.Address
|
|
4545
|
+
recognize(named('PauseStateUpdate'), (event) => {
|
|
4546
|
+
const a = event.args as { isPaused: boolean; updater: Address.Address }
|
|
4547
|
+
return {
|
|
4548
|
+
...itemBase(event.logIndex),
|
|
4549
|
+
data: { paused: a.isPaused, signer, updater: lc(a.updater) },
|
|
4550
|
+
type: 'token-pause-set',
|
|
3779
4551
|
}
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
},
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
const a =
|
|
3791
|
-
return
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
4552
|
+
})
|
|
4553
|
+
recognize(named('SupplyCapUpdate'), (event) => {
|
|
4554
|
+
const a = event.args as { newSupplyCap: bigint; updater: Address.Address }
|
|
4555
|
+
return {
|
|
4556
|
+
...itemBase(event.logIndex),
|
|
4557
|
+
data: { signer, supplyCap: a.newSupplyCap.toString(), updater: lc(a.updater) },
|
|
4558
|
+
type: 'token-supply-cap-set',
|
|
4559
|
+
}
|
|
4560
|
+
})
|
|
4561
|
+
recognize(named('TransferPolicyUpdate'), (event) => {
|
|
4562
|
+
const a = event.args as { newPolicyId: bigint; updater: Address.Address }
|
|
4563
|
+
return {
|
|
4564
|
+
...itemBase(event.logIndex),
|
|
4565
|
+
data: { policyId: a.newPolicyId.toString(), signer, updater: lc(a.updater) },
|
|
4566
|
+
type: 'token-transfer-policy-set',
|
|
4567
|
+
}
|
|
4568
|
+
})
|
|
4569
|
+
recognize(named('QuoteTokenUpdate'), (event) => {
|
|
4570
|
+
const a = event.args as { newQuoteToken: Address.Address; updater: Address.Address }
|
|
4571
|
+
return {
|
|
4572
|
+
...itemBase(event.logIndex),
|
|
4573
|
+
data: { quoteToken: lc(a.newQuoteToken), signer, updater: lc(a.updater) },
|
|
4574
|
+
type: 'token-quote-token-set',
|
|
4575
|
+
}
|
|
4576
|
+
})
|
|
4577
|
+
recognize(named('NextQuoteTokenSet'), (event) => {
|
|
4578
|
+
const a = event.args as { nextQuoteToken: Address.Address; updater: Address.Address }
|
|
4579
|
+
return {
|
|
4580
|
+
...itemBase(event.logIndex),
|
|
4581
|
+
data: { nextQuoteToken: lc(a.nextQuoteToken), signer, updater: lc(a.updater) },
|
|
4582
|
+
type: 'token-next-quote-token-set',
|
|
4583
|
+
}
|
|
4584
|
+
})
|
|
4585
|
+
recognize(named('LogoURIUpdated'), (event) => {
|
|
4586
|
+
const a = event.args as { newLogoURI: string; updater: Address.Address }
|
|
4587
|
+
return {
|
|
4588
|
+
...itemBase(event.logIndex),
|
|
4589
|
+
data: { logoUri: a.newLogoURI, signer, updater: lc(a.updater) },
|
|
4590
|
+
type: 'token-logo-set',
|
|
4591
|
+
}
|
|
4592
|
+
})
|
|
4593
|
+
recognize(named('RoleMembershipUpdated'), (event) => {
|
|
4594
|
+
const a = event.args as {
|
|
3802
4595
|
account: Address.Address
|
|
3803
4596
|
hasRole: boolean
|
|
3804
4597
|
role: Hex.Hex
|
|
3805
4598
|
sender: Address.Address
|
|
3806
4599
|
}
|
|
3807
|
-
return
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
signer,
|
|
3816
|
-
},
|
|
3817
|
-
type: 'role-membership-set',
|
|
4600
|
+
return {
|
|
4601
|
+
...itemBase(event.logIndex),
|
|
4602
|
+
data: {
|
|
4603
|
+
account: lc(a.account),
|
|
4604
|
+
granted: a.hasRole,
|
|
4605
|
+
role: hx(a.role),
|
|
4606
|
+
sender: lc(a.sender),
|
|
4607
|
+
signer,
|
|
3818
4608
|
},
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
const a =
|
|
4609
|
+
type: 'role-membership-set',
|
|
4610
|
+
}
|
|
4611
|
+
})
|
|
4612
|
+
recognize(named('RoleAdminUpdated'), (event) => {
|
|
4613
|
+
const a = event.args as {
|
|
3824
4614
|
newAdminRole: Hex.Hex
|
|
3825
4615
|
role: Hex.Hex
|
|
3826
4616
|
sender: Address.Address
|
|
3827
4617
|
}
|
|
3828
|
-
return
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
signer,
|
|
3836
|
-
},
|
|
3837
|
-
type: 'role-admin-set',
|
|
4618
|
+
return {
|
|
4619
|
+
...itemBase(event.logIndex),
|
|
4620
|
+
data: {
|
|
4621
|
+
newAdminRole: hx(a.newAdminRole),
|
|
4622
|
+
role: hx(a.role),
|
|
4623
|
+
sender: lc(a.sender),
|
|
4624
|
+
signer,
|
|
3838
4625
|
},
|
|
3839
|
-
|
|
3840
|
-
|
|
4626
|
+
type: 'role-admin-set',
|
|
4627
|
+
}
|
|
4628
|
+
})
|
|
3841
4629
|
|
|
3842
4630
|
// Fee-manager token configuration.
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
{
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
...itemBase(logIndexOfEvent(validatorTokenSet)),
|
|
3860
|
-
data: { signer, token: lc(a.token), validator: lc(a.validator) },
|
|
3861
|
-
type: 'fee-validator-token-set',
|
|
3862
|
-
},
|
|
3863
|
-
]
|
|
3864
|
-
}
|
|
4631
|
+
recognize(named('UserTokenSet'), (event) => {
|
|
4632
|
+
const a = event.args as { token: Address.Address; user: Address.Address }
|
|
4633
|
+
return {
|
|
4634
|
+
...itemBase(event.logIndex),
|
|
4635
|
+
data: { signer, token: lc(a.token), user: lc(a.user) },
|
|
4636
|
+
type: 'fee-user-token-set',
|
|
4637
|
+
}
|
|
4638
|
+
})
|
|
4639
|
+
recognize(named('ValidatorTokenSet'), (event) => {
|
|
4640
|
+
const a = event.args as { token: Address.Address; validator: Address.Address }
|
|
4641
|
+
return {
|
|
4642
|
+
...itemBase(event.logIndex),
|
|
4643
|
+
data: { signer, token: lc(a.token), validator: lc(a.validator) },
|
|
4644
|
+
type: 'fee-validator-token-set',
|
|
4645
|
+
}
|
|
4646
|
+
})
|
|
3865
4647
|
|
|
3866
|
-
// TIP-403 policy registry
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
const a =
|
|
4648
|
+
// TIP-403 policy registry. Creation events claim the member events they
|
|
4649
|
+
// bundle (which precede the anchor); later member updates stay independent.
|
|
4650
|
+
recognize(named('CompoundPolicyCreated'), (event) => {
|
|
4651
|
+
const a = event.args as {
|
|
3870
4652
|
creator: Address.Address
|
|
3871
4653
|
mintRecipientPolicyId: bigint
|
|
3872
4654
|
policyId: bigint
|
|
3873
4655
|
recipientPolicyId: bigint
|
|
3874
4656
|
senderPolicyId: bigint
|
|
3875
4657
|
}
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
4658
|
+
const componentIds = new Set(
|
|
4659
|
+
[a.mintRecipientPolicyId, a.recipientPolicyId, a.senderPolicyId].map(String),
|
|
4660
|
+
)
|
|
4661
|
+
unclaimed(
|
|
4662
|
+
(e) =>
|
|
4663
|
+
e.logIndex < event.logIndex &&
|
|
4664
|
+
Address.isEqual(e.address, event.address) &&
|
|
4665
|
+
['BlacklistUpdated', 'PolicyAdminUpdated', 'PolicyCreated', 'WhitelistUpdated'].includes(
|
|
4666
|
+
e.eventName,
|
|
4667
|
+
) &&
|
|
4668
|
+
'policyId' in e.args &&
|
|
4669
|
+
componentIds.has(String((e.args as { policyId: bigint }).policyId)),
|
|
4670
|
+
).forEach(claim)
|
|
4671
|
+
return {
|
|
4672
|
+
...itemBase(event.logIndex),
|
|
4673
|
+
data: {
|
|
4674
|
+
creator: lc(a.creator),
|
|
4675
|
+
mintRecipientPolicyId: a.mintRecipientPolicyId.toString(),
|
|
4676
|
+
policyId: a.policyId.toString(),
|
|
4677
|
+
recipientPolicyId: a.recipientPolicyId.toString(),
|
|
4678
|
+
senderPolicyId: a.senderPolicyId.toString(),
|
|
4679
|
+
signer,
|
|
3888
4680
|
},
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
const a =
|
|
4681
|
+
type: 'compound-policy-created',
|
|
4682
|
+
}
|
|
4683
|
+
})
|
|
4684
|
+
recognize(named('PolicyCreated'), (event) => {
|
|
4685
|
+
const a = event.args as {
|
|
3894
4686
|
policyId: bigint
|
|
3895
4687
|
policyType: number
|
|
3896
4688
|
updater: Address.Address
|
|
3897
4689
|
}
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
4690
|
+
unclaimed(
|
|
4691
|
+
(e) =>
|
|
4692
|
+
e.logIndex < event.logIndex &&
|
|
4693
|
+
Address.isEqual(e.address, event.address) &&
|
|
4694
|
+
['BlacklistUpdated', 'PolicyAdminUpdated', 'WhitelistUpdated'].includes(e.eventName) &&
|
|
4695
|
+
'policyId' in e.args &&
|
|
4696
|
+
(e.args as { policyId: bigint }).policyId === a.policyId,
|
|
4697
|
+
).forEach(claim)
|
|
4698
|
+
return {
|
|
4699
|
+
...itemBase(event.logIndex),
|
|
4700
|
+
data: {
|
|
4701
|
+
policyId: a.policyId.toString(),
|
|
4702
|
+
policyType: Number(a.policyType),
|
|
4703
|
+
signer,
|
|
4704
|
+
updater: lc(a.updater),
|
|
3908
4705
|
},
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
const a =
|
|
4706
|
+
type: 'policy-created',
|
|
4707
|
+
}
|
|
4708
|
+
})
|
|
4709
|
+
recognize(named('PolicyAdminUpdated'), (event) => {
|
|
4710
|
+
const a = event.args as {
|
|
3914
4711
|
admin: Address.Address
|
|
3915
4712
|
policyId: bigint
|
|
3916
4713
|
updater: Address.Address
|
|
3917
4714
|
}
|
|
3918
|
-
return
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
updater: lc(a.updater),
|
|
3926
|
-
},
|
|
3927
|
-
type: 'policy-admin-set',
|
|
4715
|
+
return {
|
|
4716
|
+
...itemBase(event.logIndex),
|
|
4717
|
+
data: {
|
|
4718
|
+
admin: lc(a.admin),
|
|
4719
|
+
policyId: a.policyId.toString(),
|
|
4720
|
+
signer,
|
|
4721
|
+
updater: lc(a.updater),
|
|
3928
4722
|
},
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
const a =
|
|
4723
|
+
type: 'policy-admin-set',
|
|
4724
|
+
}
|
|
4725
|
+
})
|
|
4726
|
+
recognize(named('WhitelistUpdated'), (event) => {
|
|
4727
|
+
const a = event.args as {
|
|
3934
4728
|
account: Address.Address
|
|
3935
4729
|
allowed: boolean
|
|
3936
4730
|
policyId: bigint
|
|
3937
4731
|
updater: Address.Address
|
|
3938
4732
|
}
|
|
3939
|
-
return
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
updater: lc(a.updater),
|
|
3948
|
-
},
|
|
3949
|
-
type: 'whitelist-updated',
|
|
4733
|
+
return {
|
|
4734
|
+
...itemBase(event.logIndex),
|
|
4735
|
+
data: {
|
|
4736
|
+
account: lc(a.account),
|
|
4737
|
+
allowed: a.allowed,
|
|
4738
|
+
policyId: a.policyId.toString(),
|
|
4739
|
+
signer,
|
|
4740
|
+
updater: lc(a.updater),
|
|
3950
4741
|
},
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
const a =
|
|
4742
|
+
type: 'whitelist-updated',
|
|
4743
|
+
}
|
|
4744
|
+
})
|
|
4745
|
+
recognize(named('BlacklistUpdated'), (event) => {
|
|
4746
|
+
const a = event.args as {
|
|
3956
4747
|
account: Address.Address
|
|
3957
4748
|
policyId: bigint
|
|
3958
4749
|
restricted: boolean
|
|
3959
4750
|
updater: Address.Address
|
|
3960
4751
|
}
|
|
3961
|
-
return
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
updater: lc(a.updater),
|
|
3970
|
-
},
|
|
3971
|
-
type: 'blacklist-updated',
|
|
4752
|
+
return {
|
|
4753
|
+
...itemBase(event.logIndex),
|
|
4754
|
+
data: {
|
|
4755
|
+
account: lc(a.account),
|
|
4756
|
+
policyId: a.policyId.toString(),
|
|
4757
|
+
restricted: a.restricted,
|
|
4758
|
+
signer,
|
|
4759
|
+
updater: lc(a.updater),
|
|
3972
4760
|
},
|
|
3973
|
-
|
|
3974
|
-
|
|
4761
|
+
type: 'blacklist-updated',
|
|
4762
|
+
}
|
|
4763
|
+
})
|
|
3975
4764
|
|
|
3976
4765
|
// Address / nonce registry.
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
{
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
}
|
|
3988
|
-
const nonceIncremented = findEvent('NonceIncremented')
|
|
3989
|
-
if (nonceIncremented) {
|
|
3990
|
-
const a = nonceIncremented.args as {
|
|
4766
|
+
recognize(named('MasterRegistered'), (event) => {
|
|
4767
|
+
const a = event.args as { masterAddress: Address.Address; masterId: Hex.Hex }
|
|
4768
|
+
return {
|
|
4769
|
+
...itemBase(event.logIndex),
|
|
4770
|
+
data: { masterAddress: lc(a.masterAddress), masterId: hx(a.masterId), signer },
|
|
4771
|
+
type: 'master-registered',
|
|
4772
|
+
}
|
|
4773
|
+
})
|
|
4774
|
+
recognize(named('NonceIncremented'), (event) => {
|
|
4775
|
+
const a = event.args as {
|
|
3991
4776
|
account: Address.Address
|
|
3992
4777
|
newNonce: bigint
|
|
3993
4778
|
nonceKey: bigint
|
|
3994
4779
|
}
|
|
3995
|
-
return
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
signer,
|
|
4003
|
-
},
|
|
4004
|
-
type: 'nonce-incremented',
|
|
4780
|
+
return {
|
|
4781
|
+
...itemBase(event.logIndex),
|
|
4782
|
+
data: {
|
|
4783
|
+
account: lc(a.account),
|
|
4784
|
+
nonce: a.newNonce.toString(),
|
|
4785
|
+
nonceKey: a.nonceKey.toString(),
|
|
4786
|
+
signer,
|
|
4005
4787
|
},
|
|
4006
|
-
|
|
4007
|
-
|
|
4788
|
+
type: 'nonce-incremented',
|
|
4789
|
+
}
|
|
4790
|
+
})
|
|
4008
4791
|
|
|
4009
4792
|
// Access-key authorization witnesses.
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
{
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
...itemBase(logIndexOfEvent(witnessBurned)),
|
|
4027
|
-
data: { account: lc(a.account), signer, witness: hx(a.witness) },
|
|
4028
|
-
type: 'key-authorization-witness-burned',
|
|
4029
|
-
},
|
|
4030
|
-
]
|
|
4031
|
-
}
|
|
4793
|
+
recognize(named('KeyAuthorizationWitness'), (event) => {
|
|
4794
|
+
const a = event.args as { account: Address.Address; witness: Hex.Hex }
|
|
4795
|
+
return {
|
|
4796
|
+
...itemBase(event.logIndex),
|
|
4797
|
+
data: { account: lc(a.account), signer, witness: hx(a.witness) },
|
|
4798
|
+
type: 'key-authorization-witness',
|
|
4799
|
+
}
|
|
4800
|
+
})
|
|
4801
|
+
recognize(named('KeyAuthorizationWitnessBurned'), (event) => {
|
|
4802
|
+
const a = event.args as { account: Address.Address; witness: Hex.Hex }
|
|
4803
|
+
return {
|
|
4804
|
+
...itemBase(event.logIndex),
|
|
4805
|
+
data: { account: lc(a.account), signer, witness: hx(a.witness) },
|
|
4806
|
+
type: 'key-authorization-witness-burned',
|
|
4807
|
+
}
|
|
4808
|
+
})
|
|
4032
4809
|
|
|
4033
|
-
// Validator configuration
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
const a =
|
|
4810
|
+
// Validator configuration. Rotation claims the add/deactivate pair it
|
|
4811
|
+
// bundles, matched by validator index.
|
|
4812
|
+
recognize(named('ValidatorRotated'), (event) => {
|
|
4813
|
+
const a = event.args as {
|
|
4037
4814
|
caller: Address.Address
|
|
4038
4815
|
deactivatedIndex: bigint
|
|
4039
4816
|
egress: string
|
|
@@ -4043,27 +4820,32 @@ function classifyFromLogsCore(
|
|
|
4043
4820
|
oldPublicKey: Hex.Hex
|
|
4044
4821
|
validatorAddress: Address.Address
|
|
4045
4822
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4823
|
+
unclaimed((e) => {
|
|
4824
|
+
if (!('index' in e.args)) return false
|
|
4825
|
+
const index = (e.args as { index: bigint }).index
|
|
4826
|
+
return (
|
|
4827
|
+
(e.eventName === 'ValidatorAdded' && index === a.index) ||
|
|
4828
|
+
(e.eventName === 'ValidatorDeactivated' && index === a.deactivatedIndex)
|
|
4829
|
+
)
|
|
4830
|
+
}).forEach(claim)
|
|
4831
|
+
return {
|
|
4832
|
+
...itemBase(event.logIndex),
|
|
4833
|
+
data: {
|
|
4834
|
+
caller: lc(a.caller),
|
|
4835
|
+
deactivatedIndex: a.deactivatedIndex.toString(),
|
|
4836
|
+
egress: a.egress,
|
|
4837
|
+
index: a.index.toString(),
|
|
4838
|
+
ingress: a.ingress,
|
|
4839
|
+
newPublicKey: hx(a.newPublicKey),
|
|
4840
|
+
oldPublicKey: hx(a.oldPublicKey),
|
|
4841
|
+
signer,
|
|
4842
|
+
validator: lc(a.validatorAddress),
|
|
4061
4843
|
},
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
const a =
|
|
4844
|
+
type: 'validator-rotated',
|
|
4845
|
+
}
|
|
4846
|
+
})
|
|
4847
|
+
recognize(named('ValidatorAdded'), (event) => {
|
|
4848
|
+
const a = event.args as {
|
|
4067
4849
|
egress: string
|
|
4068
4850
|
feeRecipient: Address.Address
|
|
4069
4851
|
index: bigint
|
|
@@ -4071,236 +4853,207 @@ function classifyFromLogsCore(
|
|
|
4071
4853
|
publicKey: Hex.Hex
|
|
4072
4854
|
validatorAddress: Address.Address
|
|
4073
4855
|
}
|
|
4074
|
-
return
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
validator: lc(a.validatorAddress),
|
|
4085
|
-
},
|
|
4086
|
-
type: 'validator-added',
|
|
4087
|
-
},
|
|
4088
|
-
]
|
|
4089
|
-
}
|
|
4090
|
-
const validatorDeactivated = findEvent('ValidatorDeactivated')
|
|
4091
|
-
if (validatorDeactivated) {
|
|
4092
|
-
const a = validatorDeactivated.args as { index: bigint; validatorAddress: Address.Address }
|
|
4093
|
-
return [
|
|
4094
|
-
{
|
|
4095
|
-
...itemBase(logIndexOfEvent(validatorDeactivated)),
|
|
4096
|
-
data: { index: a.index.toString(), signer, validator: lc(a.validatorAddress) },
|
|
4097
|
-
type: 'validator-deactivated',
|
|
4856
|
+
return {
|
|
4857
|
+
...itemBase(event.logIndex),
|
|
4858
|
+
data: {
|
|
4859
|
+
egress: a.egress,
|
|
4860
|
+
feeRecipient: lc(a.feeRecipient),
|
|
4861
|
+
index: a.index.toString(),
|
|
4862
|
+
ingress: a.ingress,
|
|
4863
|
+
publicKey: hx(a.publicKey),
|
|
4864
|
+
signer,
|
|
4865
|
+
validator: lc(a.validatorAddress),
|
|
4098
4866
|
},
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
const a =
|
|
4867
|
+
type: 'validator-added',
|
|
4868
|
+
}
|
|
4869
|
+
})
|
|
4870
|
+
recognize(named('ValidatorDeactivated'), (event) => {
|
|
4871
|
+
const a = event.args as { index: bigint; validatorAddress: Address.Address }
|
|
4872
|
+
return {
|
|
4873
|
+
...itemBase(event.logIndex),
|
|
4874
|
+
data: { index: a.index.toString(), signer, validator: lc(a.validatorAddress) },
|
|
4875
|
+
type: 'validator-deactivated',
|
|
4876
|
+
}
|
|
4877
|
+
})
|
|
4878
|
+
recognize(named('ValidatorMigrated'), (event) => {
|
|
4879
|
+
const a = event.args as {
|
|
4104
4880
|
index: bigint
|
|
4105
4881
|
publicKey: Hex.Hex
|
|
4106
4882
|
validatorAddress: Address.Address
|
|
4107
4883
|
}
|
|
4108
|
-
return
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
validator: lc(a.validatorAddress),
|
|
4116
|
-
},
|
|
4117
|
-
type: 'validator-migrated',
|
|
4884
|
+
return {
|
|
4885
|
+
...itemBase(event.logIndex),
|
|
4886
|
+
data: {
|
|
4887
|
+
index: a.index.toString(),
|
|
4888
|
+
publicKey: hx(a.publicKey),
|
|
4889
|
+
signer,
|
|
4890
|
+
validator: lc(a.validatorAddress),
|
|
4118
4891
|
},
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
const a =
|
|
4892
|
+
type: 'validator-migrated',
|
|
4893
|
+
}
|
|
4894
|
+
})
|
|
4895
|
+
recognize(named('SkippedValidatorMigration'), (event) => {
|
|
4896
|
+
const a = event.args as {
|
|
4124
4897
|
index: bigint
|
|
4125
4898
|
publicKey: Hex.Hex
|
|
4126
4899
|
validatorAddress: Address.Address
|
|
4127
4900
|
}
|
|
4128
|
-
return
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
validator: lc(a.validatorAddress),
|
|
4136
|
-
},
|
|
4137
|
-
type: 'validator-migration-skipped',
|
|
4901
|
+
return {
|
|
4902
|
+
...itemBase(event.logIndex),
|
|
4903
|
+
data: {
|
|
4904
|
+
index: a.index.toString(),
|
|
4905
|
+
publicKey: hx(a.publicKey),
|
|
4906
|
+
signer,
|
|
4907
|
+
validator: lc(a.validatorAddress),
|
|
4138
4908
|
},
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
const a =
|
|
4909
|
+
type: 'validator-migration-skipped',
|
|
4910
|
+
}
|
|
4911
|
+
})
|
|
4912
|
+
recognize(named('FeeRecipientUpdated'), (event) => {
|
|
4913
|
+
const a = event.args as {
|
|
4144
4914
|
caller: Address.Address
|
|
4145
4915
|
feeRecipient: Address.Address
|
|
4146
4916
|
index: bigint
|
|
4147
4917
|
}
|
|
4148
|
-
return
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
signer,
|
|
4156
|
-
},
|
|
4157
|
-
type: 'validator-fee-recipient-set',
|
|
4918
|
+
return {
|
|
4919
|
+
...itemBase(event.logIndex),
|
|
4920
|
+
data: {
|
|
4921
|
+
caller: lc(a.caller),
|
|
4922
|
+
feeRecipient: lc(a.feeRecipient),
|
|
4923
|
+
index: a.index.toString(),
|
|
4924
|
+
signer,
|
|
4158
4925
|
},
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
const a =
|
|
4926
|
+
type: 'validator-fee-recipient-set',
|
|
4927
|
+
}
|
|
4928
|
+
})
|
|
4929
|
+
recognize(named('IpAddressesUpdated'), (event) => {
|
|
4930
|
+
const a = event.args as {
|
|
4164
4931
|
caller: Address.Address
|
|
4165
4932
|
egress: string
|
|
4166
4933
|
index: bigint
|
|
4167
4934
|
ingress: string
|
|
4168
4935
|
}
|
|
4169
|
-
return
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
signer,
|
|
4178
|
-
},
|
|
4179
|
-
type: 'validator-ip-set',
|
|
4936
|
+
return {
|
|
4937
|
+
...itemBase(event.logIndex),
|
|
4938
|
+
data: {
|
|
4939
|
+
caller: lc(a.caller),
|
|
4940
|
+
egress: a.egress,
|
|
4941
|
+
index: a.index.toString(),
|
|
4942
|
+
ingress: a.ingress,
|
|
4943
|
+
signer,
|
|
4180
4944
|
},
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
const a =
|
|
4945
|
+
type: 'validator-ip-set',
|
|
4946
|
+
}
|
|
4947
|
+
})
|
|
4948
|
+
recognize(named('ValidatorOwnershipTransferred'), (event) => {
|
|
4949
|
+
const a = event.args as {
|
|
4186
4950
|
caller: Address.Address
|
|
4187
4951
|
index: bigint
|
|
4188
4952
|
newAddress: Address.Address
|
|
4189
4953
|
oldAddress: Address.Address
|
|
4190
4954
|
}
|
|
4191
|
-
return
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
signer,
|
|
4200
|
-
},
|
|
4201
|
-
type: 'validator-ownership-transferred',
|
|
4955
|
+
return {
|
|
4956
|
+
...itemBase(event.logIndex),
|
|
4957
|
+
data: {
|
|
4958
|
+
caller: lc(a.caller),
|
|
4959
|
+
index: a.index.toString(),
|
|
4960
|
+
newAddress: lc(a.newAddress),
|
|
4961
|
+
oldAddress: lc(a.oldAddress),
|
|
4962
|
+
signer,
|
|
4202
4963
|
},
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
const a =
|
|
4964
|
+
type: 'validator-ownership-transferred',
|
|
4965
|
+
}
|
|
4966
|
+
})
|
|
4967
|
+
recognize(named('OwnershipTransferred'), (event) => {
|
|
4968
|
+
const a = event.args as {
|
|
4208
4969
|
newOwner: Address.Address
|
|
4209
4970
|
oldOwner: Address.Address
|
|
4210
4971
|
}
|
|
4211
|
-
return
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
data: {
|
|
4226
|
-
nextEpoch: a.nextEpoch.toString(),
|
|
4227
|
-
previousEpoch: a.previousEpoch.toString(),
|
|
4228
|
-
signer,
|
|
4229
|
-
},
|
|
4230
|
-
type: 'network-identity-rotation-epoch-set',
|
|
4231
|
-
},
|
|
4232
|
-
]
|
|
4233
|
-
}
|
|
4234
|
-
const initialized = findEvent('Initialized')
|
|
4235
|
-
if (initialized) {
|
|
4236
|
-
const a = initialized.args as { height: bigint }
|
|
4237
|
-
return [
|
|
4238
|
-
{
|
|
4239
|
-
...itemBase(logIndexOfEvent(initialized)),
|
|
4240
|
-
data: { height: a.height.toString(), signer },
|
|
4241
|
-
type: 'initialized',
|
|
4972
|
+
return {
|
|
4973
|
+
...itemBase(event.logIndex),
|
|
4974
|
+
data: { newOwner: lc(a.newOwner), oldOwner: lc(a.oldOwner), signer },
|
|
4975
|
+
type: 'ownership-transferred',
|
|
4976
|
+
}
|
|
4977
|
+
})
|
|
4978
|
+
recognize(named('NetworkIdentityRotationEpochSet'), (event) => {
|
|
4979
|
+
const a = event.args as { nextEpoch: bigint; previousEpoch: bigint }
|
|
4980
|
+
return {
|
|
4981
|
+
...itemBase(event.logIndex),
|
|
4982
|
+
data: {
|
|
4983
|
+
nextEpoch: a.nextEpoch.toString(),
|
|
4984
|
+
previousEpoch: a.previousEpoch.toString(),
|
|
4985
|
+
signer,
|
|
4242
4986
|
},
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
const args = t.args as {
|
|
4253
|
-
amount: bigint
|
|
4254
|
-
from: Address.Address
|
|
4255
|
-
memo?: Hex.Hex | undefined
|
|
4256
|
-
to: Address.Address
|
|
4987
|
+
type: 'network-identity-rotation-epoch-set',
|
|
4988
|
+
}
|
|
4989
|
+
})
|
|
4990
|
+
recognize(named('Initialized'), (event) => {
|
|
4991
|
+
const a = event.args as { height: bigint }
|
|
4992
|
+
return {
|
|
4993
|
+
...itemBase(event.logIndex),
|
|
4994
|
+
data: { height: a.height.toString(), signer },
|
|
4995
|
+
type: 'initialized',
|
|
4257
4996
|
}
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4997
|
+
})
|
|
4998
|
+
|
|
4999
|
+
// Plain transfers: every remaining viewer-party transfer is its own
|
|
5000
|
+
// activity. Non-viewer transfers are internal legs of what classified
|
|
5001
|
+
// above and surface only via the fallback below.
|
|
5002
|
+
const buildTransfer = (event: Decoded): RawItem => {
|
|
5003
|
+
const args = transferArgs(event)
|
|
5004
|
+
const to = lc(args.to)
|
|
5005
|
+
const from = lc(args.from)
|
|
5006
|
+
const rawMemo = event.eventName === 'TransferWithMemo' ? args.memo : undefined
|
|
4261
5007
|
// The raw memo (not the suppressed display string) carries the MPP
|
|
4262
5008
|
// attribution fingerprint, so resolve the service name from it.
|
|
4263
5009
|
const memo = rawMemo ? Transfers.memoToString(rawMemo) : undefined
|
|
4264
5010
|
const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo })
|
|
4265
|
-
const sourceToken = token(args.amount,
|
|
5011
|
+
const sourceToken = token(args.amount, event.address as Address.Address)
|
|
4266
5012
|
if (Address.isEqual(to, viewer))
|
|
4267
|
-
return
|
|
4268
|
-
transfer(itemBase(logIndexOfEvent(t)), blockNum, {
|
|
4269
|
-
attribution,
|
|
4270
|
-
direction: 'in',
|
|
4271
|
-
memo,
|
|
4272
|
-
recipient: viewer,
|
|
4273
|
-
sender: from,
|
|
4274
|
-
sourceToken,
|
|
4275
|
-
}),
|
|
4276
|
-
...accessKeyItems,
|
|
4277
|
-
]
|
|
4278
|
-
return [
|
|
4279
|
-
transfer(itemBase(logIndexOfEvent(t)), blockNum, {
|
|
5013
|
+
return transfer(itemBase(event.logIndex), blockNum, {
|
|
4280
5014
|
attribution,
|
|
4281
|
-
direction: '
|
|
5015
|
+
direction: 'in',
|
|
4282
5016
|
memo,
|
|
4283
|
-
recipient:
|
|
5017
|
+
recipient: viewer,
|
|
4284
5018
|
sender: from,
|
|
4285
|
-
signer,
|
|
4286
5019
|
sourceToken,
|
|
4287
|
-
})
|
|
4288
|
-
|
|
4289
|
-
|
|
5020
|
+
})
|
|
5021
|
+
return transfer(itemBase(event.logIndex), blockNum, {
|
|
5022
|
+
attribution,
|
|
5023
|
+
direction: 'out',
|
|
5024
|
+
memo,
|
|
5025
|
+
recipient: to,
|
|
5026
|
+
sender: from,
|
|
5027
|
+
signer,
|
|
5028
|
+
sourceToken,
|
|
5029
|
+
})
|
|
4290
5030
|
}
|
|
5031
|
+
const isUserTransfer = (event: Decoded) =>
|
|
5032
|
+
isTransfer(event) && !Address.isEqual(transferArgs(event).to, Addresses.feeManager)
|
|
5033
|
+
recognize(
|
|
5034
|
+
(event) =>
|
|
5035
|
+
isUserTransfer(event) &&
|
|
5036
|
+
(Address.isEqual(transferArgs(event).from, viewer) ||
|
|
5037
|
+
Address.isEqual(transferArgs(event).to, viewer)),
|
|
5038
|
+
buildTransfer,
|
|
5039
|
+
)
|
|
4291
5040
|
|
|
4292
|
-
//
|
|
4293
|
-
|
|
5041
|
+
// Fallback when nothing viewer-facing classified: surface fold-only vault
|
|
5042
|
+
// items, third-party approvals, and transfers (e.g. a sponsored batch)
|
|
5043
|
+
// instead of reporting `unknown`.
|
|
5044
|
+
if (items.length === 0) {
|
|
5045
|
+
items.push(...suppressedVaultItems)
|
|
5046
|
+
recognize(isErc20Approval, approvalItem)
|
|
5047
|
+
recognize(isUserTransfer, buildTransfer)
|
|
5048
|
+
}
|
|
4294
5049
|
|
|
4295
|
-
// Unclassified: a tx the address sent or paid for whose logs matched no
|
|
4296
|
-
// classifier. Surface it honestly as an `unknown` item carrying every
|
|
4297
|
-
// (decoded when possible), rather than fabricating a zero transfer.
|
|
4298
|
-
|
|
4299
|
-
}
|
|
5050
|
+
// Unclassified: a tx the address sent or paid for whose logs matched no
|
|
5051
|
+
// known classifier. Surface it honestly as an `unknown` item carrying every
|
|
5052
|
+
// log (decoded when possible), rather than fabricating a zero transfer.
|
|
5053
|
+
if (items.length === 0)
|
|
5054
|
+
return [unknown(itemBase(minLogIndex), { events: buildTxEvents(rows, events), signer })]
|
|
4300
5055
|
|
|
4301
|
-
|
|
4302
|
-
function logIndexOfEvent(event: { logIndex: number }): number {
|
|
4303
|
-
return event.logIndex
|
|
5056
|
+
return items.sort((a, b) => a.logIndex - b.logIndex)
|
|
4304
5057
|
}
|
|
4305
5058
|
|
|
4306
5059
|
/**
|
|
@@ -4364,8 +5117,8 @@ function unknown(
|
|
|
4364
5117
|
|
|
4365
5118
|
/** Decodes a group's raw rows into `viem/tempo` events via `parseEventLogs` (non-strict). */
|
|
4366
5119
|
function decodeRows(rows: readonly Row[]) {
|
|
4367
|
-
|
|
4368
|
-
|
|
5120
|
+
const logs = rows.map(
|
|
5121
|
+
(r): Log => ({
|
|
4369
5122
|
address: r.logAddress as `0x${string}`,
|
|
4370
5123
|
blockHash: '0x0' as Hex.Hex,
|
|
4371
5124
|
blockNumber: BigInt(r.blockNum),
|
|
@@ -4375,17 +5128,21 @@ function decodeRows(rows: readonly Row[]) {
|
|
|
4375
5128
|
topics: [r.selector, r.topic1, r.topic2, r.topic3].filter(Boolean) as [Hex.Hex, ...Hex.Hex[]],
|
|
4376
5129
|
transactionHash: r.txHash as `0x${string}`,
|
|
4377
5130
|
transactionIndex: r.txIdx,
|
|
4378
|
-
}
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
5131
|
+
}),
|
|
5132
|
+
)
|
|
5133
|
+
try {
|
|
5134
|
+
// One batched call: viem re-processes the merged ABI per invocation, so
|
|
5135
|
+
// per-row calls cost ~55x more. Non-strict decoding skips undecodable
|
|
5136
|
+
// logs per log and can yield `args: undefined`; normalize so `in`-guards
|
|
5137
|
+
// never throw.
|
|
5138
|
+
return parseEventLogs({
|
|
5139
|
+
abi: [...Abis.abis, ...Abis.earnRouter, ...Abis.earnVault, ...erc4626Events],
|
|
5140
|
+
logs,
|
|
5141
|
+
strict: false,
|
|
5142
|
+
}).map((event) => ({ ...event, args: event.args ?? {} }))
|
|
5143
|
+
} catch {
|
|
5144
|
+
return []
|
|
5145
|
+
}
|
|
4389
5146
|
}
|
|
4390
5147
|
|
|
4391
5148
|
/**
|
|
@@ -4521,49 +5278,63 @@ async function enrichTokens(
|
|
|
4521
5278
|
for (const item of items) collectTokens(item, addresses)
|
|
4522
5279
|
const includeLogoUri = include.includes('token.logoUri')
|
|
4523
5280
|
const includeVerified = include.includes('token.verified')
|
|
5281
|
+
const resolved_promise =
|
|
5282
|
+
addresses.size > 0
|
|
5283
|
+
? Timing.time(c, 'tokens', () =>
|
|
5284
|
+
Promise.all(
|
|
5285
|
+
[...addresses].map(async (address) => {
|
|
5286
|
+
const [logoUri, metadata] = await Promise.all([
|
|
5287
|
+
includeLogoUri
|
|
5288
|
+
? Tokens.getTokenLogo(c, { address, chainId }).catch(() => undefined)
|
|
5289
|
+
: undefined,
|
|
5290
|
+
getActivityTokenMetadata(c, { address, chainId }),
|
|
5291
|
+
])
|
|
5292
|
+
return { address: address.toLowerCase(), logoUri, metadata }
|
|
5293
|
+
}),
|
|
5294
|
+
),
|
|
5295
|
+
)
|
|
5296
|
+
: undefined
|
|
4524
5297
|
// Curated data is needed only for requested valuation or token fields.
|
|
4525
|
-
const
|
|
5298
|
+
const snapshot_promise =
|
|
4526
5299
|
addresses.size > 0 && (denomination !== undefined || includeLogoUri || includeVerified)
|
|
4527
|
-
?
|
|
4528
|
-
: undefined
|
|
4529
|
-
const
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
5300
|
+
? VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
5301
|
+
: Promise.resolve(undefined)
|
|
5302
|
+
const rates_promise = (async () => {
|
|
5303
|
+
if (!denomination) return undefined
|
|
5304
|
+
const snapshot = await snapshot_promise
|
|
5305
|
+
return Valuation.ratesFor(c, {
|
|
5306
|
+
currencies: snapshot
|
|
5307
|
+
? [...addresses].flatMap((address) => {
|
|
5308
|
+
const held = snapshot.byAddress.get(address)?.currency
|
|
5309
|
+
return held === undefined ? [] : [held]
|
|
5310
|
+
})
|
|
5311
|
+
: [],
|
|
5312
|
+
denomination,
|
|
5313
|
+
oracle,
|
|
5314
|
+
})
|
|
5315
|
+
})()
|
|
5316
|
+
if (!resolved_promise) return { items: items as Item[], rates: await rates_promise }
|
|
5317
|
+
|
|
5318
|
+
const [resolved_raw, rates, snapshot] = await Promise.all([
|
|
5319
|
+
resolved_promise,
|
|
5320
|
+
rates_promise,
|
|
5321
|
+
snapshot_promise,
|
|
5322
|
+
])
|
|
5323
|
+
const resolved = resolved_raw.map(
|
|
5324
|
+
({ address, logoUri, metadata }) =>
|
|
5325
|
+
[
|
|
5326
|
+
address,
|
|
5327
|
+
{
|
|
5328
|
+
currency: metadata.currency,
|
|
5329
|
+
decimals: metadata.decimals,
|
|
5330
|
+
logoUri: includeLogoUri
|
|
5331
|
+
? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
|
|
4549
5332
|
: undefined,
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
currency: metadata.currency,
|
|
4556
|
-
decimals: metadata.decimals,
|
|
4557
|
-
logoUri: includeLogoUri
|
|
4558
|
-
? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
|
|
4559
|
-
: undefined,
|
|
4560
|
-
name: metadata.name,
|
|
4561
|
-
symbol: metadata.symbol,
|
|
4562
|
-
verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
|
|
4563
|
-
},
|
|
4564
|
-
] as const
|
|
4565
|
-
}),
|
|
4566
|
-
),
|
|
5333
|
+
name: metadata.name,
|
|
5334
|
+
symbol: metadata.symbol,
|
|
5335
|
+
verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
|
|
5336
|
+
},
|
|
5337
|
+
] as const,
|
|
4567
5338
|
)
|
|
4568
5339
|
const byAddress = new Map(resolved)
|
|
4569
5340
|
return {
|
|
@@ -4584,6 +5355,54 @@ declare namespace enrichTokens {
|
|
|
4584
5355
|
}
|
|
4585
5356
|
}
|
|
4586
5357
|
|
|
5358
|
+
/** Resolves token metadata on each item's source chain without changing feed order. */
|
|
5359
|
+
async function enrichChainTokens(
|
|
5360
|
+
c: Context<App.Environment>,
|
|
5361
|
+
options: enrichChainTokens.Options,
|
|
5362
|
+
): Promise<{ items: Item[]; rates: FxOracle.RateSet | undefined }> {
|
|
5363
|
+
const byChain = new Map<number, listChains.Item[]>()
|
|
5364
|
+
for (const item of options.items) {
|
|
5365
|
+
const items = byChain.get(item.chainId) ?? []
|
|
5366
|
+
items.push(item)
|
|
5367
|
+
byChain.set(item.chainId, items)
|
|
5368
|
+
}
|
|
5369
|
+
const results = await Promise.all(
|
|
5370
|
+
[...byChain].map(async ([chainId, items]) => ({
|
|
5371
|
+
chainId,
|
|
5372
|
+
...(await enrichTokens(c, {
|
|
5373
|
+
chainId,
|
|
5374
|
+
denomination: options.denomination,
|
|
5375
|
+
include: options.include,
|
|
5376
|
+
items,
|
|
5377
|
+
oracle: options.oracle,
|
|
5378
|
+
})),
|
|
5379
|
+
})),
|
|
5380
|
+
)
|
|
5381
|
+
type Bucket = { index: number; items: Item[] }
|
|
5382
|
+
const buckets = new Map<number, Bucket>()
|
|
5383
|
+
for (const result of results) buckets.set(result.chainId, { index: 0, items: result.items })
|
|
5384
|
+
return {
|
|
5385
|
+
items: options.items.map((item) => {
|
|
5386
|
+
const bucket = buckets.get(item.chainId)!
|
|
5387
|
+
return bucket.items[bucket.index++]!
|
|
5388
|
+
}),
|
|
5389
|
+
rates: results.find((result) => result.rates !== undefined)?.rates,
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
declare namespace enrichChainTokens {
|
|
5394
|
+
type Options = {
|
|
5395
|
+
/** Currency the amount valuations are denominated in. */
|
|
5396
|
+
denomination?: string | undefined
|
|
5397
|
+
/** Optional token fields to resolve. */
|
|
5398
|
+
include: readonly z.output<typeof schema.Include>[]
|
|
5399
|
+
/** Chain-tagged items awaiting token enrichment. */
|
|
5400
|
+
items: readonly listChains.Item[]
|
|
5401
|
+
/** FX rate oracle backing amount valuation. */
|
|
5402
|
+
oracle: FxOracle.Oracle
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
|
|
4587
5406
|
/** Returns an enriched copy of an item without mutating its cached token stubs. */
|
|
4588
5407
|
function enrichItem(
|
|
4589
5408
|
item: UnenrichedItem,
|
|
@@ -4651,6 +5470,7 @@ export function groupByAccessKey(items: Item[]): Entry[] {
|
|
|
4651
5470
|
}
|
|
4652
5471
|
|
|
4653
5472
|
type GroupableItem = {
|
|
5473
|
+
chainId?: number | undefined
|
|
4654
5474
|
data: object
|
|
4655
5475
|
id: string
|
|
4656
5476
|
perspective: 'incoming' | 'outgoing'
|
|
@@ -4660,6 +5480,7 @@ type GroupableItem = {
|
|
|
4660
5480
|
type GroupedItem<item extends GroupableItem> =
|
|
4661
5481
|
| item
|
|
4662
5482
|
| {
|
|
5483
|
+
chainId?: number | undefined
|
|
4663
5484
|
data: { items: item[]; signer: Address.Address }
|
|
4664
5485
|
id: string
|
|
4665
5486
|
perspective: 'incoming' | 'outgoing'
|
|
@@ -4678,6 +5499,7 @@ function groupByAccessKeyItems<item extends GroupableItem>(items: item[]): Group
|
|
|
4678
5499
|
if (itemSigner !== 'self') {
|
|
4679
5500
|
if (
|
|
4680
5501
|
currentGroup &&
|
|
5502
|
+
currentGroup.items[0]!.chainId === item.chainId &&
|
|
4681
5503
|
Address.isEqual(currentGroup.signer, itemSigner) &&
|
|
4682
5504
|
sameCalendarDay(currentGroup.items[0]!.timestamp, item.timestamp)
|
|
4683
5505
|
) {
|
|
@@ -4717,6 +5539,7 @@ function flushGroup<item extends GroupableItem>(group: {
|
|
|
4717
5539
|
const firstItem = group.items[0]!
|
|
4718
5540
|
const lastItem = group.items[group.items.length - 1]!
|
|
4719
5541
|
return {
|
|
5542
|
+
...(firstItem.chainId === undefined ? {} : { chainId: firstItem.chainId }),
|
|
4720
5543
|
data: { items: group.items, signer: group.signer },
|
|
4721
5544
|
perspective: firstItem.perspective,
|
|
4722
5545
|
id: `${firstItem.id}..${lastItem.id}:${group.items.length}`,
|