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
|
@@ -19,12 +19,13 @@ import * as Ttl from '../../../internal/Ttl.js';
|
|
|
19
19
|
import * as Value from '../../../internal/Value.js';
|
|
20
20
|
import * as VerifiedTokens from '../../../internal/VerifiedTokens.js';
|
|
21
21
|
import * as FxOracle from '../FxOracle.js';
|
|
22
|
+
import * as ZoneSelection from '../ZoneSelection.js';
|
|
22
23
|
import * as Tokens from './tokens.js';
|
|
23
24
|
import * as Transfers from './transfers.js';
|
|
24
25
|
import * as Valuation from './valuation.js';
|
|
25
26
|
/** Example transaction hash surfaced in OpenAPI docs. */
|
|
26
27
|
const exampleHash = '0x4845ae2098724ab26a5d89370dce5124d044be6a5acc164afc348662de67d474';
|
|
27
|
-
const
|
|
28
|
+
const includeFields = ['token.logoUri', 'token.verified', 'zones'];
|
|
28
29
|
// TIP-20 tokens live at deterministic `0x20c0…` addresses. Other contracts
|
|
29
30
|
// can emit the same `Transfer` signature but do not expose TIP-20 metadata.
|
|
30
31
|
const tip20AddressPrefix = '0x20c0';
|
|
@@ -36,29 +37,26 @@ export var schema;
|
|
|
36
37
|
.union([z.literal('self'), Schema.Address])
|
|
37
38
|
.check(z.describe('Who signed the activity: `self`, or the access-key address used to sign it.'));
|
|
38
39
|
/** A token with required RPC metadata and optional curated fields. */
|
|
39
|
-
schema.Token = Schema.describe(Tokens.schema.TokenReference, 'A token referenced by an activity item.');
|
|
40
|
-
|
|
41
|
-
const ValuedAmount = z.extend(Schema.TokenAmount, {
|
|
40
|
+
schema.Token = OpenApi.component(Schema.describe(Tokens.schema.TokenReference, 'A token referenced by an activity item.'), 'ActivityToken');
|
|
41
|
+
const ValuedAmount = Schema.describe(z.extend(Schema.TokenAmount, {
|
|
42
42
|
valuation: z
|
|
43
43
|
.optional(z.nullable(Valuation.schema.Value))
|
|
44
44
|
.check(z.describe('The amount’s nominal value in the requested `valuation.currency`. `null` when the token is ' +
|
|
45
45
|
'unverified, its display currency has no rate, or rates were unavailable.')),
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const DestinationAmount =
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
/** Self-contained amount for a source token. */
|
|
52
|
-
const SourceAmount = ValuedAmount.check(z.describe('Amount associated with `sourceToken`.'));
|
|
46
|
+
}), 'A token amount carrying its nominal value in the requested denomination.');
|
|
47
|
+
const valuedAmount = OpenApi.component(ValuedAmount, 'ActivityValuedAmount');
|
|
48
|
+
const DestinationAmount = valuedAmount;
|
|
49
|
+
const RefundAmount = valuedAmount;
|
|
50
|
+
const SourceAmount = valuedAmount;
|
|
53
51
|
/** Page-level resources: valuation rate provenance. */
|
|
54
|
-
schema.Meta = z
|
|
52
|
+
schema.Meta = OpenApi.component(z
|
|
55
53
|
.object({
|
|
56
54
|
valuation: z
|
|
57
55
|
.optional(Valuation.schema.Pricing)
|
|
58
56
|
.check(z.describe('Rate provenance for amount valuations. Present when conversion rates were ' +
|
|
59
57
|
'consulted; absent when every value was identity-valued or rates were unavailable.')),
|
|
60
58
|
})
|
|
61
|
-
.check(z.describe('Page-level resources attached to this response.'));
|
|
59
|
+
.check(z.describe('Page-level resources attached to this response.')), 'ActivityMeta');
|
|
62
60
|
/** A token transfer with subject-relative direction and signer fields. */
|
|
63
61
|
schema.Transfer = z
|
|
64
62
|
.object({
|
|
@@ -96,6 +94,7 @@ export var schema;
|
|
|
96
94
|
error: '`destinationAmount` and `destinationToken` must be returned together.',
|
|
97
95
|
path: ['destinationAmount'],
|
|
98
96
|
}), z.describe('One token transfer involving the requested address.'));
|
|
97
|
+
const activityTransfer = OpenApi.component(schema.Transfer, 'ActivityTransfer');
|
|
99
98
|
/**
|
|
100
99
|
* One transaction log, decoded into a known Tempo event when possible. The
|
|
101
100
|
* raw fields (`address`/`data`/`logIndex`/`topics`) are always present so no
|
|
@@ -123,6 +122,7 @@ export var schema;
|
|
|
123
122
|
.check(z.describe('Raw log topics; the first topic is the event signature hash.')),
|
|
124
123
|
})
|
|
125
124
|
.check(z.describe('One transaction log. Decoded fields (`eventName`/`args`/`title`) are present when it matched a known Tempo ABI; otherwise only the raw fields are set.'));
|
|
125
|
+
const activityEvent = OpenApi.component(event, 'ActivityEvent');
|
|
126
126
|
/**
|
|
127
127
|
* Fields shared by every activity item. `transactionHash`/`timestamp` identify and sort
|
|
128
128
|
* the entry; `data` carries the type-specific payload — shaped like the
|
|
@@ -130,11 +130,14 @@ export var schema;
|
|
|
130
130
|
* transfer's `direction`/`memo`/`attribution`/`signer`) the resource lacks.
|
|
131
131
|
*/
|
|
132
132
|
const base = {
|
|
133
|
+
chainId: z
|
|
134
|
+
.optional(Schema.ChainId)
|
|
135
|
+
.check(z.describe('Chain ID containing this activity. Returned when `include=zones`.'), z.meta({ examples: [421700001] })),
|
|
133
136
|
perspective: z
|
|
134
137
|
.enum(['incoming', 'outgoing'])
|
|
135
138
|
.check(z.describe('Your role in the transaction: `outgoing` when you sent or paid for it, `incoming` when it happened to you (someone else initiated it). For a `transfer`, `data.direction` is the finer-grained token-flow view.')),
|
|
136
139
|
events: z
|
|
137
|
-
.optional(z.array(
|
|
140
|
+
.optional(z.array(activityEvent))
|
|
138
141
|
.check(z.describe('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.')),
|
|
139
142
|
id: z.string().check(z.describe('Stable activity ID built from the transaction hash and log index (`${transactionHash}-${logIndex}`).'), z.meta({
|
|
140
143
|
examples: ['0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-3'],
|
|
@@ -150,8 +153,7 @@ export var schema;
|
|
|
150
153
|
.check(z.describe('Human-readable label for the activity `type`, suitable for display in a feed. Past-tense, sentence case (e.g. `Token transferred`, `Tokens swapped`, `Burn blocked`).'), z.meta({ examples: ['Token transferred'] })),
|
|
151
154
|
transactionHash: Schema.Hash.check(z.describe('Transaction hash for this activity.')),
|
|
152
155
|
};
|
|
153
|
-
|
|
154
|
-
schema.items = [
|
|
156
|
+
const itemSchemas = [
|
|
155
157
|
z
|
|
156
158
|
.object({
|
|
157
159
|
...base,
|
|
@@ -295,7 +297,7 @@ export var schema;
|
|
|
295
297
|
z
|
|
296
298
|
.object({
|
|
297
299
|
...base,
|
|
298
|
-
data:
|
|
300
|
+
data: activityTransfer.check(z.describe('Transfer details with feed-relative direction and signer fields.')),
|
|
299
301
|
type: z
|
|
300
302
|
.literal('transfer')
|
|
301
303
|
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
@@ -820,9 +822,7 @@ export var schema;
|
|
|
820
822
|
data: z
|
|
821
823
|
.object({
|
|
822
824
|
logoUri: z.string().check(z.describe('New logo URL for the token.'), z.meta({
|
|
823
|
-
examples: [
|
|
824
|
-
'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
|
|
825
|
-
],
|
|
825
|
+
examples: [Tokens.tokenExample.logoUri],
|
|
826
826
|
})),
|
|
827
827
|
signer: Signer,
|
|
828
828
|
updater: Schema.Address.check(z.describe('Address that changed the logo.')),
|
|
@@ -1254,13 +1254,24 @@ export var schema;
|
|
|
1254
1254
|
})
|
|
1255
1255
|
.check(z.meta({ title: 'Unknown' })),
|
|
1256
1256
|
];
|
|
1257
|
+
/** The classified activity item variants (discriminated by `type`). */
|
|
1258
|
+
schema.items = itemSchemas.map((item) => {
|
|
1259
|
+
const [type] = item.shape.type.def.values;
|
|
1260
|
+
if (typeof type !== 'string')
|
|
1261
|
+
throw new Error('Activity item schemas require a string type.');
|
|
1262
|
+
const name = `ActivityItem${type
|
|
1263
|
+
.replace(/[^A-Za-z0-9]+(.)/g, (_, character) => character.toUpperCase())
|
|
1264
|
+
.replace(/^(.)/, (character) => character.toUpperCase())}`;
|
|
1265
|
+
return OpenApi.component(item, name);
|
|
1266
|
+
});
|
|
1257
1267
|
/** A single classified activity item. */
|
|
1258
|
-
schema.Item = z
|
|
1259
|
-
.union(schema.items)
|
|
1260
|
-
.check(z.describe('One classified activity item in the address feed.'));
|
|
1268
|
+
schema.Item = OpenApi.component(z.union(schema.items).check(z.describe('One classified activity item in the address feed.')), 'ActivityItem');
|
|
1261
1269
|
/** A folded access-key group: consecutive same-signer items on one UTC day. */
|
|
1262
1270
|
schema.Group = z
|
|
1263
1271
|
.object({
|
|
1272
|
+
chainId: z
|
|
1273
|
+
.optional(Schema.ChainId)
|
|
1274
|
+
.check(z.describe('Chain ID containing this activity group. Returned when `include=zones`.'), z.meta({ examples: [421700001] })),
|
|
1264
1275
|
data: z
|
|
1265
1276
|
.object({
|
|
1266
1277
|
items: z.array(schema.Item).check(z.describe('Activity items included in this group.')),
|
|
@@ -1284,16 +1295,17 @@ export var schema;
|
|
|
1284
1295
|
type: z.literal('group'),
|
|
1285
1296
|
})
|
|
1286
1297
|
.check(z.meta({ title: 'Group' }), z.describe('A group of consecutive activity items signed by the same access key.'));
|
|
1298
|
+
const activityGroup = OpenApi.component(schema.Group, 'ActivityGroup');
|
|
1287
1299
|
/** A single activity item, or a folded access-key group. */
|
|
1288
|
-
schema.Entry = z
|
|
1289
|
-
.union([
|
|
1290
|
-
.check(z.describe('Either one activity item, or a grouped set of access-key activity items.'));
|
|
1291
|
-
/** Optional
|
|
1300
|
+
schema.Entry = OpenApi.component(z
|
|
1301
|
+
.union([schema.Item, activityGroup])
|
|
1302
|
+
.check(z.describe('Either one activity item, or a grouped set of access-key activity items.')), 'ActivityEntry');
|
|
1303
|
+
/** Optional activity resources and selection modes. */
|
|
1292
1304
|
schema.Include = z
|
|
1293
|
-
.enum(
|
|
1294
|
-
.check(z.describe('Additional
|
|
1295
|
-
/** Parses comma-separated optional
|
|
1296
|
-
schema.includeQuery = Schema.includeQuery(schema.Include, 'Comma-separated
|
|
1305
|
+
.enum(includeFields)
|
|
1306
|
+
.check(z.describe('Additional activity resources and selection modes to include.'));
|
|
1307
|
+
/** Parses comma-separated optional activity resources and selection modes. */
|
|
1308
|
+
schema.includeQuery = Schema.includeQuery(schema.Include, 'Comma-separated activity options. `zones` combines the selected parent chain with every readable Zone.');
|
|
1297
1309
|
/** Schemas for the getAddressActivities operation. */
|
|
1298
1310
|
let getAddressActivities;
|
|
1299
1311
|
(function (getAddressActivities) {
|
|
@@ -1328,7 +1340,7 @@ export var schema;
|
|
|
1328
1340
|
* `group=true`) plus the root `nextCursor` pagination field shared with
|
|
1329
1341
|
* other list endpoints.
|
|
1330
1342
|
*/
|
|
1331
|
-
getAddressActivities.Response = z
|
|
1343
|
+
getAddressActivities.Response = OpenApi.component(z
|
|
1332
1344
|
.object({
|
|
1333
1345
|
data: z.array(schema.Entry).check(z.describe('Activity feed entries for the address.')),
|
|
1334
1346
|
meta: z
|
|
@@ -1336,7 +1348,7 @@ export var schema;
|
|
|
1336
1348
|
.check(z.describe('Page-level resources, such as valuation rate provenance.')),
|
|
1337
1349
|
nextCursor: Schema.NextCursor,
|
|
1338
1350
|
})
|
|
1339
|
-
.check(z.describe('A page of human-readable activity for the address.'));
|
|
1351
|
+
.check(z.describe('A page of human-readable activity for the address.')), 'AddressActivityList');
|
|
1340
1352
|
})(getAddressActivities = schema.getAddressActivities || (schema.getAddressActivities = {}));
|
|
1341
1353
|
/** Schemas for the getTransactionActivities operation. */
|
|
1342
1354
|
let getTransactionActivities;
|
|
@@ -1361,19 +1373,20 @@ export var schema;
|
|
|
1361
1373
|
})
|
|
1362
1374
|
.check(z.describe('Query parameters for the transaction activity request.'));
|
|
1363
1375
|
/**
|
|
1364
|
-
* The classified activity for one transaction: a list of `data` items
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1376
|
+
* The classified activity for one transaction: a list of `data` items,
|
|
1377
|
+
* one per independent activity, describing what the transaction did
|
|
1378
|
+
* onchain. Unpaginated (a single transaction is finite), so there is no
|
|
1379
|
+
* `nextCursor`.
|
|
1368
1380
|
*/
|
|
1369
|
-
getTransactionActivities.Response = z
|
|
1381
|
+
getTransactionActivities.Response = OpenApi.component(z
|
|
1370
1382
|
.object({
|
|
1383
|
+
chainId: Schema.ChainId.check(z.describe('Chain ID containing the transaction.'), z.meta({ examples: [4217] })),
|
|
1371
1384
|
data: z.array(schema.Item).check(z.describe('Activity items classified from the transaction.')),
|
|
1372
1385
|
meta: z
|
|
1373
1386
|
.optional(schema.Meta)
|
|
1374
1387
|
.check(z.describe('Page-level resources, such as valuation rate provenance.')),
|
|
1375
1388
|
})
|
|
1376
|
-
.check(z.describe('The human-readable activity that happened on a transaction.'));
|
|
1389
|
+
.check(z.describe('The human-readable activity that happened on a transaction.')), 'TransactionActivityList');
|
|
1377
1390
|
})(getTransactionActivities = schema.getTransactionActivities || (schema.getTransactionActivities = {}));
|
|
1378
1391
|
})(schema || (schema = {}));
|
|
1379
1392
|
/**
|
|
@@ -1462,6 +1475,7 @@ const titleByEventName = {
|
|
|
1462
1475
|
BlacklistUpdated: 'Blacklist updated',
|
|
1463
1476
|
Burn: 'Token burned',
|
|
1464
1477
|
BurnBlocked: 'Burn blocked',
|
|
1478
|
+
Deposit: 'Assets deposited',
|
|
1465
1479
|
Deposited: 'Assets deposited',
|
|
1466
1480
|
EarnDeposit: 'Private assets deposited',
|
|
1467
1481
|
EarnRedeem: 'Private shares redeemed',
|
|
@@ -1470,6 +1484,7 @@ const titleByEventName = {
|
|
|
1470
1484
|
RedeemRequested: 'Share redemption requested',
|
|
1471
1485
|
Redeemed: 'Shares redeemed',
|
|
1472
1486
|
VenueSharesDeposited: 'Shares deposited',
|
|
1487
|
+
Withdraw: 'Assets withdrawn',
|
|
1473
1488
|
WithdrewExact: 'Assets withdrawn',
|
|
1474
1489
|
ChannelClosed: 'Channel closed',
|
|
1475
1490
|
ChannelOpened: 'Channel opened',
|
|
@@ -1533,12 +1548,43 @@ const closeRequestedSelector = '0xf5a36fc00a96cbb9cf1f8f59299165e1d8ffffe94396d8
|
|
|
1533
1548
|
const accessKeySpendSelector = '0xe0815e3aaadddf4dd75bde97fc060f0c38afe18e87a169be86a3f5c28247f192';
|
|
1534
1549
|
/** TIP-20 / ERC-20 `Transfer` event signature for the decoded CTE. */
|
|
1535
1550
|
const transferSignature = 'event Transfer(address indexed from, address indexed to, uint256 value)';
|
|
1551
|
+
// Standard ERC-4626 vault events, absent from the merged `viem/tempo` ABI.
|
|
1552
|
+
// Decoding them lets external vault deposits/withdrawals classify instead of
|
|
1553
|
+
// falling through to the share-token mint/burn legs.
|
|
1554
|
+
const erc4626Events = [
|
|
1555
|
+
{
|
|
1556
|
+
inputs: [
|
|
1557
|
+
{ indexed: true, name: 'sender', type: 'address' },
|
|
1558
|
+
{ indexed: true, name: 'owner', type: 'address' },
|
|
1559
|
+
{ indexed: false, name: 'assets', type: 'uint256' },
|
|
1560
|
+
{ indexed: false, name: 'shares', type: 'uint256' },
|
|
1561
|
+
],
|
|
1562
|
+
name: 'Deposit',
|
|
1563
|
+
type: 'event',
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
inputs: [
|
|
1567
|
+
{ indexed: true, name: 'sender', type: 'address' },
|
|
1568
|
+
{ indexed: true, name: 'receiver', type: 'address' },
|
|
1569
|
+
{ indexed: true, name: 'owner', type: 'address' },
|
|
1570
|
+
{ indexed: false, name: 'assets', type: 'uint256' },
|
|
1571
|
+
{ indexed: false, name: 'shares', type: 'uint256' },
|
|
1572
|
+
],
|
|
1573
|
+
name: 'Withdraw',
|
|
1574
|
+
type: 'event',
|
|
1575
|
+
},
|
|
1576
|
+
];
|
|
1536
1577
|
// TIP-20 payment-channel reserve precompile. Not exported by `viem/tempo`
|
|
1537
1578
|
// `Addresses`, so the known address is pinned here: channel events
|
|
1538
1579
|
// (`ChannelOpened`/`Settled`/`TopUp`/`ChannelClosed`) are emitted here, and
|
|
1539
1580
|
// their paired TIP-20 `Transfer` legs use it as the funding counterparty, which
|
|
1540
1581
|
// is how `channel-funded`/`channel-settled`/`channel-closed` recover the token.
|
|
1541
1582
|
const tip20ChannelReserve = '0x4d50500000000000000000000000000000000000';
|
|
1583
|
+
// Tempo system addresses other than the DEX are never swap-route
|
|
1584
|
+
// counterparties; a newly shipped precompile joins the exclusion automatically.
|
|
1585
|
+
const nonRouteSystemAddresses = new Set([...Object.values(Addresses), tip20ChannelReserve]
|
|
1586
|
+
.filter((value) => value !== Addresses.stablecoinDex)
|
|
1587
|
+
.map((value) => value.toLowerCase()));
|
|
1542
1588
|
/** Number of grouped entries to target before stopping the page-fetch loop. */
|
|
1543
1589
|
const minGroupEntries = 5;
|
|
1544
1590
|
/** Largest number of items folded into a single access-key group. */
|
|
@@ -1590,7 +1636,12 @@ export function addresses(options = {}) {
|
|
|
1590
1636
|
description: 'Get a human-readable feed of what an address has been doing onchain, including payments sent and received, swaps, approvals, and more.',
|
|
1591
1637
|
operationId: 'getAddressActivities',
|
|
1592
1638
|
responses: OpenApi.responses({
|
|
1593
|
-
errors: {
|
|
1639
|
+
errors: {
|
|
1640
|
+
400: {
|
|
1641
|
+
codes: ['address_invalid', 'chain_id_invalid', 'chain_id_unsupported', 'query_invalid'],
|
|
1642
|
+
},
|
|
1643
|
+
502: 'Tempo could not read activity or token data from an upstream service.',
|
|
1644
|
+
},
|
|
1594
1645
|
success: {
|
|
1595
1646
|
description: 'A page of address activity entries.',
|
|
1596
1647
|
schema: schema.getAddressActivities.Response,
|
|
@@ -1599,7 +1650,7 @@ export function addresses(options = {}) {
|
|
|
1599
1650
|
summary: 'List address activities',
|
|
1600
1651
|
tags: ['Activities'],
|
|
1601
1652
|
}), Cache.response({
|
|
1602
|
-
cacheControl: Cache.policies.
|
|
1653
|
+
cacheControl: Cache.policies.noStore,
|
|
1603
1654
|
name: 'tempo-api:addresses:v1',
|
|
1604
1655
|
key: (c) => Cache.urlKey(c, schema.getAddressActivities.Query),
|
|
1605
1656
|
}), async (c) => {
|
|
@@ -1617,39 +1668,73 @@ export function addresses(options = {}) {
|
|
|
1617
1668
|
});
|
|
1618
1669
|
const { address } = c.req.valid('param');
|
|
1619
1670
|
const query = c.req.valid('query');
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
Cache.setPolicy(c, Cache.policies.metadata);
|
|
1671
|
+
if (!query.include.includes('zones'))
|
|
1672
|
+
Cache.setPolicy(c, query.cursor === undefined ? Cache.policies.feed : Cache.policies.metadata);
|
|
1623
1673
|
try {
|
|
1624
1674
|
const chainId = query.chainId ?? c.get('chainId');
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1675
|
+
const listed = await Timing.time(c, 'address_activities', async () => {
|
|
1676
|
+
if (!query.include.includes('zones')) {
|
|
1677
|
+
const result = await Store.memoize((signal) => list(tidx({ chainId, signal, tidx: c.get('getTidx')(chainId) }), {
|
|
1678
|
+
address: address,
|
|
1679
|
+
cursor: query.cursor,
|
|
1680
|
+
group: query.group,
|
|
1681
|
+
limit: query.limit,
|
|
1682
|
+
logs: query.logs,
|
|
1683
|
+
store: c.get('store'),
|
|
1684
|
+
}), {
|
|
1685
|
+
key: `activities:v1:${chainId}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
|
|
1686
|
+
store: c.get('store'),
|
|
1687
|
+
ttl: Ttl.seconds(15),
|
|
1688
|
+
});
|
|
1689
|
+
return { chainId, kind: 'chain', ...result };
|
|
1690
|
+
}
|
|
1691
|
+
const zoneChainIds = c.get('zoneChainIds');
|
|
1692
|
+
if (!zoneChainIds)
|
|
1693
|
+
throw new Error('Inferred Zone selection was not resolved.');
|
|
1694
|
+
const chainIds = [chainId, ...zoneChainIds];
|
|
1695
|
+
const result = await Store.memoize((signal) => listChains({
|
|
1696
|
+
address: address,
|
|
1697
|
+
cursor: query.cursor,
|
|
1698
|
+
group: query.group,
|
|
1699
|
+
limit: query.limit,
|
|
1700
|
+
logs: query.logs,
|
|
1701
|
+
providers: chainIds.map((chainId) => ({
|
|
1702
|
+
chainId,
|
|
1703
|
+
provider: tidx({
|
|
1704
|
+
chainId,
|
|
1705
|
+
signal,
|
|
1706
|
+
tidx: c.get('getTidx')(chainId),
|
|
1707
|
+
}),
|
|
1708
|
+
})),
|
|
1709
|
+
store: c.get('store'),
|
|
1710
|
+
}), {
|
|
1711
|
+
key: `activities:zones:v2:${chainIds.join(',')}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
|
|
1712
|
+
store: c.get('store'),
|
|
1713
|
+
ttl: Ttl.seconds(15),
|
|
1714
|
+
});
|
|
1715
|
+
return { kind: 'zones', ...result };
|
|
1645
1716
|
});
|
|
1717
|
+
const { items, rates } = listed.kind === 'zones'
|
|
1718
|
+
? await enrichChainTokens(c, {
|
|
1719
|
+
denomination: query['valuation.currency'],
|
|
1720
|
+
include: query.include,
|
|
1721
|
+
items: listed.items,
|
|
1722
|
+
oracle,
|
|
1723
|
+
})
|
|
1724
|
+
: await enrichTokens(c, {
|
|
1725
|
+
chainId: listed.chainId,
|
|
1726
|
+
denomination: query['valuation.currency'],
|
|
1727
|
+
include: query.include,
|
|
1728
|
+
items: listed.items,
|
|
1729
|
+
oracle,
|
|
1730
|
+
});
|
|
1646
1731
|
// Fold consecutive access-key-signed items into `group` entries only
|
|
1647
1732
|
// when requested; otherwise every item is returned at the root.
|
|
1648
1733
|
const data = query.group ? groupByAccessKey(items) : items;
|
|
1649
1734
|
return c.json(Response.validated(schema.getAddressActivities.Response, {
|
|
1650
1735
|
data,
|
|
1651
1736
|
...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
|
|
1652
|
-
nextCursor,
|
|
1737
|
+
nextCursor: listed.nextCursor,
|
|
1653
1738
|
}), 200);
|
|
1654
1739
|
}
|
|
1655
1740
|
catch (cause) {
|
|
@@ -1666,7 +1751,7 @@ export function addresses(options = {}) {
|
|
|
1666
1751
|
* Runs the same `groupByTx` → `classifyGroup` pipeline the address feed uses
|
|
1667
1752
|
* over a single transaction's logs, anchored on the transaction sender as the
|
|
1668
1753
|
* viewer (a transaction-scoped feed has no subject account). Returns the
|
|
1669
|
-
* classified
|
|
1754
|
+
* classified items (one per independent activity) with the full per-tx
|
|
1670
1755
|
* event set available via `logs=true`.
|
|
1671
1756
|
*/
|
|
1672
1757
|
export function transactions(options = {}) {
|
|
@@ -1682,10 +1767,22 @@ export function transactions(options = {}) {
|
|
|
1682
1767
|
operationId: 'getTransactionActivities',
|
|
1683
1768
|
responses: OpenApi.responses({
|
|
1684
1769
|
errors: {
|
|
1770
|
+
400: {
|
|
1771
|
+
codes: [
|
|
1772
|
+
'chain_id_invalid',
|
|
1773
|
+
'chain_id_unsupported',
|
|
1774
|
+
'query_invalid',
|
|
1775
|
+
'transaction_invalid',
|
|
1776
|
+
],
|
|
1777
|
+
},
|
|
1685
1778
|
404: {
|
|
1686
1779
|
description: 'No mined transaction was found for that hash.',
|
|
1687
1780
|
codes: ['transaction_not_found'],
|
|
1688
1781
|
},
|
|
1782
|
+
409: {
|
|
1783
|
+
description: 'More than one readable chain contains that transaction hash.',
|
|
1784
|
+
codes: ['transaction_ambiguous'],
|
|
1785
|
+
},
|
|
1689
1786
|
502: 'Tempo could not read activity or token data from an upstream service.',
|
|
1690
1787
|
},
|
|
1691
1788
|
success: {
|
|
@@ -1716,36 +1813,110 @@ export function transactions(options = {}) {
|
|
|
1716
1813
|
});
|
|
1717
1814
|
const { transactionHash } = c.req.valid('param');
|
|
1718
1815
|
const query = c.req.valid('query');
|
|
1719
|
-
const chainId = query.chainId ?? c.get('chainId');
|
|
1720
1816
|
try {
|
|
1721
|
-
const
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1817
|
+
const result = await Timing.time(c, 'transaction_activities', async () => {
|
|
1818
|
+
const selected = await (async () => {
|
|
1819
|
+
if (!query.include.includes('zones'))
|
|
1820
|
+
return {
|
|
1821
|
+
chainId: query.chainId ?? c.get('chainId'),
|
|
1822
|
+
kind: 'selected',
|
|
1823
|
+
receipt: null,
|
|
1824
|
+
};
|
|
1825
|
+
const zoneChainIds = c.get('zoneChainIds');
|
|
1826
|
+
if (!zoneChainIds)
|
|
1827
|
+
throw new Error('Inferred Zone selection was not resolved.');
|
|
1828
|
+
const chainIds = [query.chainId ?? c.get('chainId'), ...zoneChainIds];
|
|
1829
|
+
// Cache only positive resolution. Missing transactions must remain
|
|
1830
|
+
// visible as soon as any selected indexer catches up.
|
|
1831
|
+
const match = await Store.memoize(async () => {
|
|
1832
|
+
const probes = await Promise.all(chainIds.map(async (chainId) => {
|
|
1833
|
+
try {
|
|
1834
|
+
return {
|
|
1835
|
+
chainId,
|
|
1836
|
+
value: await getTransactionReceipt({
|
|
1837
|
+
chainId,
|
|
1838
|
+
tidx: c.get('getTidx')(chainId),
|
|
1839
|
+
transactionHash,
|
|
1840
|
+
}),
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
catch (cause) {
|
|
1844
|
+
return { cause, chainId };
|
|
1845
|
+
}
|
|
1846
|
+
}));
|
|
1847
|
+
return ZoneSelection.uniqueMatch(probes);
|
|
1848
|
+
}, {
|
|
1849
|
+
key: `activities:zones:v2:${chainIds.join(',')}:tx:${transactionHash.toLowerCase()}`,
|
|
1850
|
+
shouldCache: (match) => match.kind === 'selected',
|
|
1851
|
+
store: c.get('store'),
|
|
1852
|
+
ttl: Ttl.seconds(15),
|
|
1853
|
+
});
|
|
1854
|
+
if (match.kind === 'failed') {
|
|
1855
|
+
c.set('providerFailures', match.failures.map(({ cause, chainId }) => ({
|
|
1856
|
+
...Tidx.providerFailure(cause),
|
|
1857
|
+
chainId,
|
|
1858
|
+
})));
|
|
1859
|
+
throw match.failures[0].cause;
|
|
1860
|
+
}
|
|
1861
|
+
if (match.kind !== 'selected')
|
|
1862
|
+
return match;
|
|
1863
|
+
return { chainId: match.chainId, kind: 'selected', receipt: match.value };
|
|
1864
|
+
})();
|
|
1865
|
+
if (selected.kind !== 'selected')
|
|
1866
|
+
return selected;
|
|
1867
|
+
const { chainId } = selected;
|
|
1868
|
+
const unenrichedItems = await Store.memoize(() => selected.receipt === null
|
|
1869
|
+
? getTransactionActivities_inner({
|
|
1870
|
+
chainId,
|
|
1871
|
+
logs: query.logs,
|
|
1872
|
+
store: c.get('store'),
|
|
1873
|
+
tidx: c.get('getTidx')(chainId),
|
|
1874
|
+
time: (name, fn) => Timing.time(c, name, fn),
|
|
1875
|
+
transactionHash,
|
|
1876
|
+
})
|
|
1877
|
+
: classifyTransactionActivities({
|
|
1878
|
+
chainId,
|
|
1879
|
+
logs: query.logs,
|
|
1880
|
+
receipt: selected.receipt,
|
|
1881
|
+
store: c.get('store'),
|
|
1882
|
+
tidx: c.get('getTidx')(chainId),
|
|
1883
|
+
time: (name, fn) => Timing.time(c, name, fn),
|
|
1884
|
+
transactionHash,
|
|
1885
|
+
}), {
|
|
1886
|
+
key: `activities:v1:${chainId}:tx:${transactionHash.toLowerCase()}:${query.logs ?? false}`,
|
|
1887
|
+
store: c.get('store'),
|
|
1888
|
+
ttl: Ttl.seconds(15),
|
|
1889
|
+
});
|
|
1890
|
+
return unenrichedItems === null
|
|
1891
|
+
? { kind: 'not_found' }
|
|
1892
|
+
: { chainId, items: unenrichedItems, kind: 'found' };
|
|
1893
|
+
});
|
|
1894
|
+
if (result.kind === 'not_found')
|
|
1733
1895
|
return Response.error(c, {
|
|
1734
1896
|
code: 'transaction_not_found',
|
|
1735
1897
|
message: 'Transaction not found',
|
|
1736
1898
|
status: 404,
|
|
1737
1899
|
});
|
|
1900
|
+
if (result.kind === 'ambiguous')
|
|
1901
|
+
return Response.error(c, {
|
|
1902
|
+
code: 'transaction_ambiguous',
|
|
1903
|
+
message: 'Transaction hash matched more than one readable chain',
|
|
1904
|
+
status: 409,
|
|
1905
|
+
});
|
|
1738
1906
|
// Mined activity can carry current valuations and verification data.
|
|
1739
|
-
|
|
1907
|
+
if (!query.include.includes('zones'))
|
|
1908
|
+
Cache.setPolicy(c, Cache.policies.metadata);
|
|
1909
|
+
c.set('chainId', result.chainId);
|
|
1740
1910
|
// Build immutable token and structured amount copies from RPC metadata.
|
|
1741
1911
|
const { items, rates } = await enrichTokens(c, {
|
|
1742
|
-
chainId,
|
|
1912
|
+
chainId: result.chainId,
|
|
1743
1913
|
denomination: query['valuation.currency'],
|
|
1744
1914
|
include: query.include,
|
|
1745
|
-
items:
|
|
1915
|
+
items: result.items,
|
|
1746
1916
|
oracle,
|
|
1747
1917
|
});
|
|
1748
1918
|
return c.json(Response.validated(schema.getTransactionActivities.Response, {
|
|
1919
|
+
chainId: result.chainId,
|
|
1749
1920
|
data: items,
|
|
1750
1921
|
...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
|
|
1751
1922
|
}), 200);
|
|
@@ -1768,7 +1939,19 @@ export function transactions(options = {}) {
|
|
|
1768
1939
|
* Items carry stub tokens; the caller enriches them via {@link enrichTokens}.
|
|
1769
1940
|
*/
|
|
1770
1941
|
export async function getTransactionActivities(options) {
|
|
1771
|
-
|
|
1942
|
+
return getTransactionActivities_inner({
|
|
1943
|
+
...options,
|
|
1944
|
+
time: async (_name, fn) => fn(),
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
async function getTransactionActivities_inner(options) {
|
|
1948
|
+
const receipt = await options.time('transaction_activity_receipt', () => getTransactionReceipt(options));
|
|
1949
|
+
if (!receipt)
|
|
1950
|
+
return null;
|
|
1951
|
+
return classifyTransactionActivities({ ...options, receipt });
|
|
1952
|
+
}
|
|
1953
|
+
async function getTransactionReceipt(options) {
|
|
1954
|
+
const { chainId, tidx: tidxClient } = options;
|
|
1772
1955
|
const hash = options.transactionHash.toLowerCase();
|
|
1773
1956
|
// The `receipts` table is indexed by `tx_hash`, so this point lookup needs no
|
|
1774
1957
|
// block bound; it also yields the block number the `logs` lookup requires.
|
|
@@ -1781,15 +1964,25 @@ export async function getTransactionActivities(options) {
|
|
|
1781
1964
|
const receipt = receiptResult.rows[0];
|
|
1782
1965
|
if (!receipt)
|
|
1783
1966
|
return null;
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1967
|
+
return {
|
|
1968
|
+
blockNum: Number(Value.toNumber(receipt['block_num'])),
|
|
1969
|
+
feePayer: lowerOrNull(receipt['fee_payer']),
|
|
1970
|
+
txSender: lowerOrNull(receipt['from']),
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
async function classifyTransactionActivities(options) {
|
|
1974
|
+
const { chainId, logs = false, receipt, tidx: tidxClient } = options;
|
|
1975
|
+
const hash = options.transactionHash.toLowerCase();
|
|
1976
|
+
const { blockNum, feePayer, txSender } = receipt;
|
|
1977
|
+
const fingerprintMap_promise = options
|
|
1978
|
+
.time('transaction_activity_mpp', () => Mpp.fingerprintMap({ store: options.store }))
|
|
1979
|
+
.catch(() => ({}));
|
|
1787
1980
|
// `logs` is sorted by block position, so a bare `tx_hash =` scans to genesis;
|
|
1788
1981
|
// pair it with the receipt's block number (see `embedReceipts` in receipts.ts).
|
|
1789
|
-
const logsResult = await tidxClient.fetch({
|
|
1982
|
+
const logsResult = await options.time('transaction_activity_logs', () => tidxClient.fetch({
|
|
1790
1983
|
chainId,
|
|
1791
1984
|
query: `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`,
|
|
1792
|
-
});
|
|
1985
|
+
}));
|
|
1793
1986
|
const rows = logsResult.rows.map((r) => ({
|
|
1794
1987
|
blockNum: Number(Value.toNumber(r['block_num'])),
|
|
1795
1988
|
blockTimestamp: Number(Value.toNumber(r['block_timestamp'])),
|
|
@@ -1812,7 +2005,7 @@ export async function getTransactionActivities(options) {
|
|
|
1812
2005
|
return [];
|
|
1813
2006
|
// Best-effort MPP service directory: a fetch failure yields an empty map, so
|
|
1814
2007
|
// enrichment never blocks classification.
|
|
1815
|
-
const fingerprintMap = await
|
|
2008
|
+
const fingerprintMap = await fingerprintMap_promise;
|
|
1816
2009
|
// Anchor the viewer on the transaction sender: the sender initiated every
|
|
1817
2010
|
// activity in the transaction, so `perspective` is `outgoing` and `signer`
|
|
1818
2011
|
// resolves to `self`. Fall back to the zero address when the sender is absent.
|
|
@@ -1823,15 +2016,15 @@ export async function getTransactionActivities(options) {
|
|
|
1823
2016
|
/**
|
|
1824
2017
|
* Lists classified activity for an address by running the shared pipeline
|
|
1825
2018
|
* (`groupByTx` → `classifyGroup` → `enrichItem`) over rows from a
|
|
1826
|
-
* {@link Provider}. Returns
|
|
1827
|
-
*
|
|
2019
|
+
* {@link Provider}. Returns classified items plus the next cursor; callers run
|
|
2020
|
+
* {@link groupByAccessKey} over the result.
|
|
1828
2021
|
*/
|
|
1829
2022
|
export async function list(provider, options) {
|
|
1830
2023
|
const { address, cursor, group = false, limit = 20, logs = false } = options;
|
|
1831
2024
|
const viewer = address.toLowerCase();
|
|
1832
2025
|
// Best-effort MPP service directory: a fetch failure yields an empty map, so
|
|
1833
2026
|
// enrichment never blocks the feed.
|
|
1834
|
-
const
|
|
2027
|
+
const fingerprintMap_promise = Mpp.fingerprintMap({ store: options.store }).catch(() => ({}));
|
|
1835
2028
|
let currentCursor = cursor;
|
|
1836
2029
|
const allItems = [];
|
|
1837
2030
|
let hasMore = false;
|
|
@@ -1846,6 +2039,7 @@ export async function list(provider, options) {
|
|
|
1846
2039
|
const txGroups = grouped.slice(0, limit + 1);
|
|
1847
2040
|
hasMore = txGroups.length > limit;
|
|
1848
2041
|
const page = txGroups.slice(0, limit);
|
|
2042
|
+
const fingerprintMap = await fingerprintMap_promise;
|
|
1849
2043
|
if (page.length === 0)
|
|
1850
2044
|
break;
|
|
1851
2045
|
allItems.push(...page.flatMap((txGroup) => classifyGroup(txGroup, viewer, fingerprintMap, logs).map(withTitle)));
|
|
@@ -1865,6 +2059,117 @@ export async function list(provider, options) {
|
|
|
1865
2059
|
// public token references and structured amounts via {@link enrichTokens}.
|
|
1866
2060
|
return { items: allItems, nextCursor };
|
|
1867
2061
|
}
|
|
2062
|
+
const chainCursorVersion = 2;
|
|
2063
|
+
/**
|
|
2064
|
+
* Lists address activity across multiple chains in one stable timestamp-ordered
|
|
2065
|
+
* feed. Its opaque cursor retains each provider's native continuation.
|
|
2066
|
+
*/
|
|
2067
|
+
export async function listChains(options) {
|
|
2068
|
+
const { address, cursor, limit = 20, logs = false, providers, store } = options;
|
|
2069
|
+
const group = options.group ?? false;
|
|
2070
|
+
const orderedProviders = [...providers].sort((a, b) => a.chainId - b.chainId);
|
|
2071
|
+
const chainIds = orderedProviders.map(({ chainId }) => chainId);
|
|
2072
|
+
const cursors = decodeChainCursor(cursor, chainIds);
|
|
2073
|
+
const viewer = address.toLowerCase();
|
|
2074
|
+
const fingerprintMap_promise = Mpp.fingerprintMap({ store }).catch(() => ({}));
|
|
2075
|
+
const states = orderedProviders.map(({ chainId, provider }) => ({
|
|
2076
|
+
chainId,
|
|
2077
|
+
exhausted: false,
|
|
2078
|
+
groups: [],
|
|
2079
|
+
provider,
|
|
2080
|
+
}));
|
|
2081
|
+
const allItems = [];
|
|
2082
|
+
let hasMore = false;
|
|
2083
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2084
|
+
await Promise.all(states.map(async (state) => {
|
|
2085
|
+
if (state.exhausted || state.groups.length > 0)
|
|
2086
|
+
return;
|
|
2087
|
+
const { rows } = await state.provider.fetchRows({
|
|
2088
|
+
address: viewer,
|
|
2089
|
+
cursor: cursors.get(state.chainId),
|
|
2090
|
+
limit,
|
|
2091
|
+
});
|
|
2092
|
+
const groups = groupByTx(rows).slice(0, limit + 1);
|
|
2093
|
+
state.exhausted = groups.length <= limit;
|
|
2094
|
+
state.groups.push(...groups);
|
|
2095
|
+
}));
|
|
2096
|
+
const candidates = states
|
|
2097
|
+
.flatMap((state) => state.groups.map((txGroup) => ({ state, txGroup })))
|
|
2098
|
+
.sort((a, b) => {
|
|
2099
|
+
if (a.txGroup.blockTimestamp !== b.txGroup.blockTimestamp)
|
|
2100
|
+
return b.txGroup.blockTimestamp - a.txGroup.blockTimestamp;
|
|
2101
|
+
if (a.state.chainId !== b.state.chainId)
|
|
2102
|
+
return a.state.chainId - b.state.chainId;
|
|
2103
|
+
if (a.txGroup.blockNum !== b.txGroup.blockNum)
|
|
2104
|
+
return b.txGroup.blockNum - a.txGroup.blockNum;
|
|
2105
|
+
return b.txGroup.txIdx - a.txGroup.txIdx;
|
|
2106
|
+
});
|
|
2107
|
+
const page = candidates.slice(0, limit);
|
|
2108
|
+
const fingerprintMap = await fingerprintMap_promise;
|
|
2109
|
+
if (page.length === 0)
|
|
2110
|
+
break;
|
|
2111
|
+
for (const { state, txGroup } of page) {
|
|
2112
|
+
state.groups.splice(state.groups.indexOf(txGroup), 1);
|
|
2113
|
+
cursors.set(state.chainId, state.provider.cursorForGroup(txGroup));
|
|
2114
|
+
allItems.push(...classifyGroup(txGroup, viewer, fingerprintMap, logs).map((item) => ({
|
|
2115
|
+
...withTitle(item),
|
|
2116
|
+
chainId: state.chainId,
|
|
2117
|
+
})));
|
|
2118
|
+
}
|
|
2119
|
+
hasMore = states.some((state) => !state.exhausted || state.groups.length > 0);
|
|
2120
|
+
const enough = group
|
|
2121
|
+
? groupByAccessKeyItems(allItems).length >= minGroupEntries
|
|
2122
|
+
: allItems.length >= limit;
|
|
2123
|
+
if (enough || !hasMore)
|
|
2124
|
+
break;
|
|
2125
|
+
}
|
|
2126
|
+
// A consumed sentinel proves only that the provider may have more rows.
|
|
2127
|
+
// Probe drained buffers so `nextCursor` remains the end-of-list signal.
|
|
2128
|
+
await Promise.all(states.map(async (state) => {
|
|
2129
|
+
if (state.exhausted || state.groups.length > 0)
|
|
2130
|
+
return;
|
|
2131
|
+
const { rows } = await state.provider.fetchRows({
|
|
2132
|
+
address: viewer,
|
|
2133
|
+
cursor: cursors.get(state.chainId),
|
|
2134
|
+
limit: 1,
|
|
2135
|
+
});
|
|
2136
|
+
const group = groupByTx(rows)[0];
|
|
2137
|
+
if (group)
|
|
2138
|
+
state.groups.push(group);
|
|
2139
|
+
else
|
|
2140
|
+
state.exhausted = true;
|
|
2141
|
+
}));
|
|
2142
|
+
hasMore = states.some((state) => !state.exhausted || state.groups.length > 0);
|
|
2143
|
+
return {
|
|
2144
|
+
items: allItems,
|
|
2145
|
+
nextCursor: hasMore ? encodeChainCursor(cursors, chainIds) : null,
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
function decodeChainCursor(cursor, chainIds) {
|
|
2149
|
+
const fields = ['int'];
|
|
2150
|
+
for (const _chainId of chainIds)
|
|
2151
|
+
fields.push('int', 'id');
|
|
2152
|
+
const decoded = cursor ? Cursor.decode(cursor, fields) : undefined;
|
|
2153
|
+
const cursors = new Map();
|
|
2154
|
+
if (!decoded || decoded[0] !== chainCursorVersion)
|
|
2155
|
+
return cursors;
|
|
2156
|
+
for (let index = 0; index < chainIds.length; index++) {
|
|
2157
|
+
const chainId = chainIds[index];
|
|
2158
|
+
const offset = index * 2 + 1;
|
|
2159
|
+
if (decoded[offset] !== chainId)
|
|
2160
|
+
return new Map();
|
|
2161
|
+
const value = decoded[offset + 1];
|
|
2162
|
+
if (value !== 'head')
|
|
2163
|
+
cursors.set(chainId, value);
|
|
2164
|
+
}
|
|
2165
|
+
return cursors;
|
|
2166
|
+
}
|
|
2167
|
+
function encodeChainCursor(cursors, chainIds) {
|
|
2168
|
+
return Cursor.encode([
|
|
2169
|
+
chainCursorVersion,
|
|
2170
|
+
...chainIds.flatMap((chainId) => [chainId, cursors.get(chainId) ?? 'head']),
|
|
2171
|
+
]);
|
|
2172
|
+
}
|
|
1868
2173
|
/**
|
|
1869
2174
|
* Creates a TIDX-backed {@link Provider} that runs three sub-queries in
|
|
1870
2175
|
* parallel and merges them:
|
|
@@ -1875,7 +2180,16 @@ export async function list(provider, options) {
|
|
|
1875
2180
|
* (catches access-key-signed sends where `tx.from` differs from the address).
|
|
1876
2181
|
*/
|
|
1877
2182
|
export function tidx(deps) {
|
|
1878
|
-
const { chainId, tidx: tidxClient } = deps;
|
|
2183
|
+
const { chainId, signal, tidx: tidxClient } = deps;
|
|
2184
|
+
const fetch = (options) => {
|
|
2185
|
+
const requestSignal = signal && options.signal
|
|
2186
|
+
? AbortSignal.any([signal, options.signal])
|
|
2187
|
+
: (signal ?? options.signal);
|
|
2188
|
+
return tidxClient.fetch({
|
|
2189
|
+
...options,
|
|
2190
|
+
...(requestSignal ? { signal: requestSignal } : {}),
|
|
2191
|
+
});
|
|
2192
|
+
};
|
|
1879
2193
|
function parseCursor(cursor) {
|
|
1880
2194
|
// Opaque `[blockNum, txIdx]` keyset cursor, the same encoding every other
|
|
1881
2195
|
// list endpoint uses; a malformed/forged cursor decodes to `undefined` and
|
|
@@ -1895,7 +2209,7 @@ export function tidx(deps) {
|
|
|
1895
2209
|
? `AND block_num <= ${cursor.blockNum} AND (block_num < ${cursor.blockNum} OR tx_idx < ${cursor.txIdx})`
|
|
1896
2210
|
: '';
|
|
1897
2211
|
const queryLimit = limit + 1;
|
|
1898
|
-
const fetchReceipts = (side) =>
|
|
2212
|
+
const fetchReceipts = (side) => fetch({
|
|
1899
2213
|
chainId,
|
|
1900
2214
|
// Raw-table query (no signature-decoded CTE), so the inline SQL is cast
|
|
1901
2215
|
// to `string`.
|
|
@@ -1939,7 +2253,7 @@ export function tidx(deps) {
|
|
|
1939
2253
|
const txHashes = receipts.map((receipt) => `'${receipt.txHash}'`).join(', ');
|
|
1940
2254
|
// `logs` is not keyed by transaction hash, so pair the bounded hash list
|
|
1941
2255
|
// with the selected receipts' block range to avoid a full archive scan.
|
|
1942
|
-
const logs = await
|
|
2256
|
+
const logs = await fetch({
|
|
1943
2257
|
chainId,
|
|
1944
2258
|
// Raw-table query (no signature-decoded CTE), so the inline SQL is cast
|
|
1945
2259
|
// to `string`.
|
|
@@ -1992,7 +2306,7 @@ export function tidx(deps) {
|
|
|
1992
2306
|
`;
|
|
1993
2307
|
const result = await (async () => {
|
|
1994
2308
|
try {
|
|
1995
|
-
return await
|
|
2309
|
+
return await fetch({
|
|
1996
2310
|
chainId,
|
|
1997
2311
|
query: query('block_num'),
|
|
1998
2312
|
signatures: [transferSignature],
|
|
@@ -2003,7 +2317,7 @@ export function tidx(deps) {
|
|
|
2003
2317
|
throw error;
|
|
2004
2318
|
try {
|
|
2005
2319
|
// The expression blocks a mis-selected block-ordered top-N walk.
|
|
2006
|
-
return await
|
|
2320
|
+
return await fetch({
|
|
2007
2321
|
chainId,
|
|
2008
2322
|
query: query('block_num + 0'),
|
|
2009
2323
|
signatures: [transferSignature],
|
|
@@ -2012,7 +2326,7 @@ export function tidx(deps) {
|
|
|
2012
2326
|
catch (fallbackError) {
|
|
2013
2327
|
if (!Tidx.isDeterministicError(fallbackError))
|
|
2014
2328
|
throw fallbackError;
|
|
2015
|
-
const result = await
|
|
2329
|
+
const result = await fetch({
|
|
2016
2330
|
chainId,
|
|
2017
2331
|
query: query(),
|
|
2018
2332
|
signatures: [transferSignature],
|
|
@@ -2070,8 +2384,10 @@ export function mergeAndSort(q1, q2, q3) {
|
|
|
2070
2384
|
const q1Hashes = new Set(q1.map((r) => r.txHash));
|
|
2071
2385
|
const filteredQ2 = q2.filter((r) => !q1Hashes.has(r.txHash));
|
|
2072
2386
|
const filteredQ3 = q3.filter((r) => !q1Hashes.has(r.txHash));
|
|
2073
|
-
|
|
2074
|
-
|
|
2387
|
+
// Dedup by row, not transaction: a tx that both credits and debits the
|
|
2388
|
+
// viewer keeps its rows from both sides, one item each.
|
|
2389
|
+
const q2Keys = new Set(filteredQ2.map((r) => `${r.txHash}:${r.logIdx}`));
|
|
2390
|
+
const dedupedQ3 = filteredQ3.filter((r) => !q2Keys.has(`${r.txHash}:${r.logIdx}`));
|
|
2075
2391
|
return [...q1, ...filteredQ2, ...dedupedQ3].sort((a, b) => {
|
|
2076
2392
|
if (a.blockNum !== b.blockNum)
|
|
2077
2393
|
return b.blockNum - a.blockNum;
|
|
@@ -2136,34 +2452,38 @@ function classifyGroup(group, viewer, fingerprintMap, includeLogs = false) {
|
|
|
2136
2452
|
signer,
|
|
2137
2453
|
viewer,
|
|
2138
2454
|
});
|
|
2139
|
-
// Q2-only
|
|
2140
|
-
//
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2455
|
+
// Q2-only: transfer rows without a joined receipt, one item per row so a
|
|
2456
|
+
// batch paying the viewer several times surfaces every payment. Classify by
|
|
2457
|
+
// comparing `from`/`to` against the viewer (zero-address sides mint/burn).
|
|
2458
|
+
if (q2Rows.length > 0)
|
|
2459
|
+
return [...q2Rows]
|
|
2460
|
+
.sort((a, b) => a.logIdx - b.logIdx)
|
|
2461
|
+
.map((r) => {
|
|
2462
|
+
const base = itemBase(r.logIdx);
|
|
2463
|
+
const from = r.transferFrom;
|
|
2464
|
+
const to = r.transferTo;
|
|
2465
|
+
const sourceToken = token(r.transferAmount, r.transferToken);
|
|
2466
|
+
if (Address.isEqual(from, zeroAddress))
|
|
2467
|
+
return { ...base, data: { recipient: viewer, sourceToken }, type: 'mint' };
|
|
2468
|
+
if (Address.isEqual(to, zeroAddress))
|
|
2469
|
+
return { ...base, data: { sender: from, signer, sourceToken }, type: 'burn' };
|
|
2470
|
+
// Q2-only rows carry no memo, so MPP service attribution is unavailable
|
|
2471
|
+
// here; `attribution` is resolved only on the log-bearing (Q1) path.
|
|
2472
|
+
if (Address.isEqual(from, viewer))
|
|
2473
|
+
return transfer(base, blockNum, {
|
|
2156
2474
|
direction: 'out',
|
|
2157
2475
|
recipient: to,
|
|
2158
2476
|
sender: viewer,
|
|
2159
2477
|
signer,
|
|
2160
2478
|
sourceToken,
|
|
2161
|
-
})
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2479
|
+
});
|
|
2480
|
+
return transfer(base, blockNum, {
|
|
2481
|
+
direction: 'in',
|
|
2482
|
+
recipient: viewer,
|
|
2483
|
+
sender: from,
|
|
2484
|
+
sourceToken,
|
|
2485
|
+
});
|
|
2486
|
+
});
|
|
2167
2487
|
// Fallback — should not happen (a group with neither q1 nor q2 rows).
|
|
2168
2488
|
return [
|
|
2169
2489
|
unknown(itemBase(Math.min(...group.rows.map((r) => r.logIdx))), {
|
|
@@ -2191,7 +2511,7 @@ function classifyFromLogs(rows, ctx) {
|
|
|
2191
2511
|
})();
|
|
2192
2512
|
// When the caller requested raw logs, attach the full per-transaction log set
|
|
2193
2513
|
// (decoded when possible) to every classified item so no co-located log is
|
|
2194
|
-
// hidden
|
|
2514
|
+
// hidden. `unknown` items already carry it.
|
|
2195
2515
|
if (!ctx.includeLogs)
|
|
2196
2516
|
return items;
|
|
2197
2517
|
const events = buildTxEvents(rows, decoded);
|
|
@@ -2208,1214 +2528,1453 @@ function signerForRows(rows, options) {
|
|
|
2208
2528
|
? accessKeySigner
|
|
2209
2529
|
: options.fallback;
|
|
2210
2530
|
}
|
|
2211
|
-
/**
|
|
2531
|
+
/**
|
|
2532
|
+
* Classifies every activity in a Q1 (log-bearing) transaction. Recognizers run
|
|
2533
|
+
* in priority order over a pool of decoded events, each emitting one item per
|
|
2534
|
+
* matched anchor and claiming the exact logs backing it.
|
|
2535
|
+
*/
|
|
2212
2536
|
function classifyFromLogsCore(rows, ctx, events = decodeRows(rows)) {
|
|
2213
2537
|
const { blockNum, fingerprintMap, itemBase, viewer } = ctx;
|
|
2214
2538
|
const signer = signerForRows(rows, { fallback: ctx.signer, viewer });
|
|
2215
2539
|
const minLogIndex = Math.min(...rows.map((r) => r.logIdx));
|
|
2216
|
-
//
|
|
2217
|
-
//
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
'to' in e.args &&
|
|
2223
|
-
Address.isEqual(e.args.to, viewer));
|
|
2224
|
-
const refund = refundTransfer
|
|
2225
|
-
? token(refundTransfer.args.amount, refundTransfer.address)
|
|
2226
|
-
: undefined;
|
|
2227
|
-
return [
|
|
2228
|
-
{
|
|
2229
|
-
...itemBase(closeRequestedLogIndex),
|
|
2230
|
-
data: { ...(refund ? { refund } : {}), signer },
|
|
2231
|
-
type: 'session-closed',
|
|
2232
|
-
},
|
|
2233
|
-
];
|
|
2234
|
-
}
|
|
2235
|
-
// Dedup preference: TransferWithMemo beats Transfer; Mint/Burn beat
|
|
2236
|
-
// zero-address Transfer.
|
|
2237
|
-
const preferenceMap = new Map();
|
|
2540
|
+
// Pair folds are counted: each TransferWithMemo folds one matching plain
|
|
2541
|
+
// Transfer and each Mint/Burn event folds one zero-address transfer, so an
|
|
2542
|
+
// extra identical payment survives as its own item. Keys require the full
|
|
2543
|
+
// event shape; a malformed event must not fold away a classifiable leg.
|
|
2544
|
+
const foldCounts = new Map();
|
|
2545
|
+
const addFold = (key) => foldCounts.set(key, (foldCounts.get(key) ?? 0) + 1);
|
|
2238
2546
|
for (const event of events) {
|
|
2239
2547
|
if (event.eventName === 'TransferWithMemo' && 'from' in event.args && 'to' in event.args) {
|
|
2240
2548
|
const args = event.args;
|
|
2241
|
-
|
|
2549
|
+
addFold(`${event.address}:${args.from}:${args.to}:${args.amount}`);
|
|
2242
2550
|
}
|
|
2243
|
-
if (event.eventName === 'Mint' && 'amount' in event.args)
|
|
2244
|
-
|
|
2245
|
-
if (event.eventName === 'Burn' && 'amount' in event.args)
|
|
2246
|
-
|
|
2551
|
+
if (event.eventName === 'Mint' && 'amount' in event.args && 'to' in event.args)
|
|
2552
|
+
addFold(`mint:${event.address}:${event.args.amount}`);
|
|
2553
|
+
if (event.eventName === 'Burn' && 'amount' in event.args && 'from' in event.args)
|
|
2554
|
+
addFold(`burn:${event.address}:${event.args.amount}`);
|
|
2247
2555
|
}
|
|
2248
|
-
const
|
|
2249
|
-
|
|
2556
|
+
const takeFold = (key) => {
|
|
2557
|
+
const count = foldCounts.get(key) ?? 0;
|
|
2558
|
+
if (count === 0)
|
|
2559
|
+
return false;
|
|
2560
|
+
foldCounts.set(key, count - 1);
|
|
2561
|
+
return true;
|
|
2562
|
+
};
|
|
2563
|
+
const finalEvents = events.filter((event) => {
|
|
2564
|
+
if (event.eventName !== 'Transfer' && event.eventName !== 'TransferWithMemo')
|
|
2565
|
+
return true;
|
|
2566
|
+
if (!('from' in event.args) || !('to' in event.args))
|
|
2250
2567
|
return true;
|
|
2251
2568
|
const args = event.args;
|
|
2252
|
-
if (
|
|
2569
|
+
if (event.eventName === 'Transfer' &&
|
|
2570
|
+
takeFold(`${event.address}:${args.from}:${args.to}:${args.amount}`))
|
|
2253
2571
|
return false;
|
|
2254
2572
|
if (Address.isEqual(args.from, zeroAddress) &&
|
|
2255
|
-
|
|
2573
|
+
takeFold(`mint:${event.address}:${args.amount}`))
|
|
2256
2574
|
return false;
|
|
2257
2575
|
if (Address.isEqual(args.to, zeroAddress) &&
|
|
2258
|
-
|
|
2576
|
+
takeFold(`burn:${event.address}:${args.amount}`))
|
|
2259
2577
|
return false;
|
|
2260
2578
|
return true;
|
|
2261
2579
|
});
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2580
|
+
const pool = [...finalEvents].sort((a, b) => a.logIndex - b.logIndex);
|
|
2581
|
+
const claimed = new Set();
|
|
2582
|
+
const items = [];
|
|
2583
|
+
// Claims made while building an item are journaled, so a build that throws
|
|
2584
|
+
// on a malformed event releases its legs and consumes only the anchor.
|
|
2585
|
+
let journal;
|
|
2586
|
+
const claim = (event) => {
|
|
2587
|
+
claimed.add(event.logIndex);
|
|
2588
|
+
journal?.push(event.logIndex);
|
|
2589
|
+
};
|
|
2590
|
+
// A predicate throwing on a malformed decode must not discard the whole
|
|
2591
|
+
// transaction; treat that event as a non-match instead.
|
|
2592
|
+
const unclaimed = (predicate) => pool.filter((event) => {
|
|
2593
|
+
if (claimed.has(event.logIndex))
|
|
2594
|
+
return false;
|
|
2595
|
+
if (!predicate)
|
|
2264
2596
|
return true;
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2597
|
+
try {
|
|
2598
|
+
return predicate(event);
|
|
2599
|
+
}
|
|
2600
|
+
catch {
|
|
2268
2601
|
return false;
|
|
2269
|
-
|
|
2270
|
-
|
|
2602
|
+
}
|
|
2603
|
+
});
|
|
2604
|
+
const lc = (value) => value.toLowerCase();
|
|
2605
|
+
const hx = (value) => value.toLowerCase();
|
|
2606
|
+
// Non-strict decoding can surface Transfer-shaped events with malformed
|
|
2607
|
+
// args; validating (once per event) keeps predicate comparisons throw-free.
|
|
2608
|
+
const transferIndexes = new Set(pool
|
|
2609
|
+
.filter((event) => {
|
|
2610
|
+
if (event.eventName !== 'Transfer' && event.eventName !== 'TransferWithMemo')
|
|
2271
2611
|
return false;
|
|
2272
|
-
|
|
2612
|
+
const args = event.args;
|
|
2613
|
+
return (typeof args.amount === 'bigint' &&
|
|
2614
|
+
typeof args.from === 'string' &&
|
|
2615
|
+
Address.validate(args.from) &&
|
|
2616
|
+
typeof args.to === 'string' &&
|
|
2617
|
+
Address.validate(args.to));
|
|
2618
|
+
})
|
|
2619
|
+
.map((event) => event.logIndex));
|
|
2620
|
+
const isTransfer = (event) => transferIndexes.has(event.logIndex);
|
|
2621
|
+
const transferArgs = (event) => event.args;
|
|
2622
|
+
// Finds and claims the first unclaimed event matching `predicate`, so a
|
|
2623
|
+
// later identical anchor pairs with the next matching leg instead of
|
|
2624
|
+
// reusing this one.
|
|
2625
|
+
const takeEvent = (predicate) => {
|
|
2626
|
+
for (const event of pool) {
|
|
2627
|
+
if (claimed.has(event.logIndex))
|
|
2628
|
+
continue;
|
|
2629
|
+
const match = (() => {
|
|
2630
|
+
try {
|
|
2631
|
+
return predicate(event);
|
|
2632
|
+
}
|
|
2633
|
+
catch {
|
|
2634
|
+
return false;
|
|
2635
|
+
}
|
|
2636
|
+
})();
|
|
2637
|
+
if (!match)
|
|
2638
|
+
continue;
|
|
2639
|
+
claim(event);
|
|
2640
|
+
return event;
|
|
2641
|
+
}
|
|
2642
|
+
return undefined;
|
|
2643
|
+
};
|
|
2644
|
+
const takeTransfer = (predicate) => takeEvent((event) => isTransfer(event) && predicate(event));
|
|
2645
|
+
// Runs `build` once per unclaimed matching event, in log order; `build`
|
|
2646
|
+
// claims its legs and returns the item(s) to emit, or nothing for
|
|
2647
|
+
// fold-only recognitions. A throwing build consumes only its anchor.
|
|
2648
|
+
const recognize = (predicate, build) => {
|
|
2649
|
+
for (const anchor of unclaimed(predicate)) {
|
|
2650
|
+
// A build may claim a later anchor of the same pass (or a log decoded
|
|
2651
|
+
// into duplicate ABI copies); never process a claimed anchor again.
|
|
2652
|
+
if (claimed.has(anchor.logIndex))
|
|
2653
|
+
continue;
|
|
2654
|
+
claim(anchor);
|
|
2655
|
+
journal = [];
|
|
2656
|
+
try {
|
|
2657
|
+
const built = build(anchor);
|
|
2658
|
+
if (built)
|
|
2659
|
+
items.push(...(Array.isArray(built) ? built : [built]));
|
|
2660
|
+
}
|
|
2661
|
+
catch {
|
|
2662
|
+
// Indexed logs can match a known signature without satisfying that
|
|
2663
|
+
// event's expected shape; release the legs and skip the anchor.
|
|
2664
|
+
for (const index of journal ?? [])
|
|
2665
|
+
claimed.delete(index);
|
|
2666
|
+
}
|
|
2667
|
+
finally {
|
|
2668
|
+
journal = undefined;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
const named = (name) => (event) => event.eventName === name;
|
|
2673
|
+
// Claims and returns a vault operation's token legs, matched by role and
|
|
2674
|
+
// amount: asset legs in or out of the vault, share mint/burn legs on it.
|
|
2675
|
+
const claimVaultLegs = (vault, amounts) => {
|
|
2676
|
+
const legs = [];
|
|
2677
|
+
for (const event of unclaimed()) {
|
|
2678
|
+
const isVaultMintBurn = (event.eventName === 'Mint' || event.eventName === 'Burn') &&
|
|
2679
|
+
Address.isEqual(event.address, vault);
|
|
2680
|
+
const isVaultLeg = (() => {
|
|
2681
|
+
if (!isTransfer(event))
|
|
2682
|
+
return false;
|
|
2683
|
+
const args = transferArgs(event);
|
|
2684
|
+
if (Address.isEqual(args.from, vault) || Address.isEqual(args.to, vault))
|
|
2685
|
+
return true;
|
|
2686
|
+
return (Address.isEqual(event.address, vault) &&
|
|
2687
|
+
(Address.isEqual(args.from, zeroAddress) ||
|
|
2688
|
+
Address.isEqual(args.to, zeroAddress)));
|
|
2689
|
+
})();
|
|
2690
|
+
if (!isVaultMintBurn && !isVaultLeg)
|
|
2691
|
+
continue;
|
|
2692
|
+
const amount = event.args.amount;
|
|
2693
|
+
if (amount === undefined || !amounts.includes(amount))
|
|
2694
|
+
continue;
|
|
2695
|
+
claim(event);
|
|
2696
|
+
legs.push(event);
|
|
2697
|
+
}
|
|
2698
|
+
// The allowance granted for this operation (approve+deposit multicall) is
|
|
2699
|
+
// internal: exact amount, spender is the vault, on a token a leg moved.
|
|
2700
|
+
const legTokens = new Set(legs.map((leg) => leg.address.toLowerCase()));
|
|
2701
|
+
for (const event of unclaimed()) {
|
|
2702
|
+
if (event.eventName !== 'Approval' || !('spender' in event.args))
|
|
2703
|
+
continue;
|
|
2704
|
+
if (!legTokens.has(event.address.toLowerCase()))
|
|
2705
|
+
continue;
|
|
2706
|
+
if (!Address.isEqual(event.args.spender, vault))
|
|
2707
|
+
continue;
|
|
2708
|
+
const amount = event.args.amount;
|
|
2709
|
+
if (amount === undefined || !amounts.includes(amount))
|
|
2710
|
+
continue;
|
|
2711
|
+
claim(event);
|
|
2712
|
+
legs.push(event);
|
|
2713
|
+
}
|
|
2714
|
+
return legs;
|
|
2715
|
+
};
|
|
2716
|
+
// Private Earn router operations. Each wraps a public vault operation on the
|
|
2717
|
+
// vault it names; fold that vault's own event and token legs, leaving vault
|
|
2718
|
+
// activity elsewhere in the batch unclaimed.
|
|
2719
|
+
recognize(named('EarnDeposit'), (event) => {
|
|
2720
|
+
const args = event.args;
|
|
2721
|
+
const vault = lc(args.earnVault);
|
|
2722
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
2723
|
+
const data = {
|
|
2724
|
+
actionId: args.actionId,
|
|
2725
|
+
assets: args.vaultAssets.toString(),
|
|
2726
|
+
inputAmount: args.inputAmount.toString(),
|
|
2727
|
+
inputToken: lc(args.inputToken),
|
|
2728
|
+
shares: args.earnShares.toString(),
|
|
2729
|
+
signer,
|
|
2730
|
+
status: 'completed',
|
|
2731
|
+
vault,
|
|
2732
|
+
zoneDepositHash: args.zoneDepositHash,
|
|
2733
|
+
};
|
|
2734
|
+
// Fold the vault event this router call produced: the nearest preceding
|
|
2735
|
+
// amount match, so a separate same-vault deposit keeps its own.
|
|
2736
|
+
const nested = unclaimed((e) => e.eventName === 'Deposited' &&
|
|
2737
|
+
e.logIndex < event.logIndex &&
|
|
2738
|
+
Address.isEqual(e.address, vault) &&
|
|
2739
|
+
e.args.assets === args.vaultAssets).at(-1);
|
|
2740
|
+
if (nested)
|
|
2741
|
+
claim(nested);
|
|
2742
|
+
else
|
|
2743
|
+
takeEvent((e) => e.eventName === 'Deposited' && Address.isEqual(e.address, vault));
|
|
2744
|
+
claimVaultLegs(vault, [args.inputAmount, args.vaultAssets, args.earnShares]);
|
|
2745
|
+
// The funding leg pays the router (the emitting contract), not the vault.
|
|
2746
|
+
claimVaultLegs(event.address, [args.inputAmount]);
|
|
2747
|
+
return { ...itemBase(event.logIndex), data, type: 'private-assets-deposited' };
|
|
2273
2748
|
});
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2749
|
+
recognize(named('EarnRedeem'), (event) => {
|
|
2750
|
+
const args = event.args;
|
|
2751
|
+
const vault = lc(args.earnVault);
|
|
2752
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
2753
|
+
const data = {
|
|
2754
|
+
actionId: args.actionId,
|
|
2755
|
+
assets: args.vaultAssets.toString(),
|
|
2756
|
+
outputAmount: args.outputAmount.toString(),
|
|
2757
|
+
outputToken: lc(args.outputToken),
|
|
2758
|
+
shares: args.earnShares.toString(),
|
|
2759
|
+
signer,
|
|
2760
|
+
status: 'completed',
|
|
2761
|
+
vault,
|
|
2762
|
+
zoneDepositHash: args.zoneDepositHash,
|
|
2763
|
+
};
|
|
2764
|
+
// Fold the vault event this router call produced: the nearest preceding
|
|
2765
|
+
// amount match, so a separate same-vault redemption keeps its own.
|
|
2766
|
+
const nested = unclaimed((e) => (e.eventName === 'Redeemed' || e.eventName === 'WithdrewExact') &&
|
|
2767
|
+
e.logIndex < event.logIndex &&
|
|
2768
|
+
Address.isEqual(e.address, vault) &&
|
|
2769
|
+
e.args.assets === args.vaultAssets).at(-1);
|
|
2770
|
+
if (nested)
|
|
2771
|
+
claim(nested);
|
|
2772
|
+
else
|
|
2773
|
+
takeEvent((e) => (e.eventName === 'Redeemed' || e.eventName === 'WithdrewExact') &&
|
|
2774
|
+
Address.isEqual(e.address, vault));
|
|
2775
|
+
claimVaultLegs(vault, [args.outputAmount, args.vaultAssets, args.earnShares]);
|
|
2776
|
+
// The payout leg leaves through the router (the emitting contract).
|
|
2777
|
+
claimVaultLegs(event.address, [args.outputAmount]);
|
|
2778
|
+
return { ...itemBase(event.logIndex), data, type: 'private-shares-redeemed' };
|
|
2779
|
+
});
|
|
2780
|
+
// Earn vault operations, each claiming its own token legs.
|
|
2781
|
+
recognize(named('Deposited'), (event) => {
|
|
2782
|
+
const args = event.args;
|
|
2783
|
+
claimVaultLegs(event.address, [args.assets, args.earnShares]);
|
|
2784
|
+
return {
|
|
2785
|
+
...itemBase(event.logIndex),
|
|
2786
|
+
data: {
|
|
2787
|
+
assets: args.assets.toString(),
|
|
2788
|
+
caller: lc(args.caller),
|
|
2789
|
+
receiver: lc(args.receiver),
|
|
2790
|
+
shares: args.earnShares.toString(),
|
|
2791
|
+
signer,
|
|
2792
|
+
status: 'completed',
|
|
2793
|
+
vault: lc(event.address),
|
|
2317
2794
|
},
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
const args =
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2795
|
+
type: 'assets-deposited',
|
|
2796
|
+
};
|
|
2797
|
+
});
|
|
2798
|
+
recognize(named('VenueSharesDeposited'), (event) => {
|
|
2799
|
+
const args = event.args;
|
|
2800
|
+
claimVaultLegs(event.address, [
|
|
2801
|
+
args.earnShares,
|
|
2802
|
+
args.receivedEngineShares,
|
|
2803
|
+
args.requestedVenueShares,
|
|
2804
|
+
]);
|
|
2805
|
+
return {
|
|
2806
|
+
...itemBase(event.logIndex),
|
|
2807
|
+
data: {
|
|
2808
|
+
caller: lc(args.caller),
|
|
2809
|
+
receivedEngineShares: args.receivedEngineShares.toString(),
|
|
2810
|
+
receiver: lc(args.receiver),
|
|
2811
|
+
requestedVenueShares: args.requestedVenueShares.toString(),
|
|
2812
|
+
shares: args.earnShares.toString(),
|
|
2813
|
+
signer,
|
|
2814
|
+
status: 'completed',
|
|
2815
|
+
vault: lc(event.address),
|
|
2336
2816
|
},
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
const args =
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
vault: venueDeposit.address.toLowerCase(),
|
|
2354
|
-
},
|
|
2355
|
-
type: 'shares-deposited',
|
|
2817
|
+
type: 'shares-deposited',
|
|
2818
|
+
};
|
|
2819
|
+
});
|
|
2820
|
+
recognize(named('Redeemed'), (event) => {
|
|
2821
|
+
const args = event.args;
|
|
2822
|
+
claimVaultLegs(event.address, [args.assets, args.earnShares]);
|
|
2823
|
+
return {
|
|
2824
|
+
...itemBase(event.logIndex),
|
|
2825
|
+
data: {
|
|
2826
|
+
assets: args.assets.toString(),
|
|
2827
|
+
caller: lc(args.caller),
|
|
2828
|
+
receiver: lc(args.receiver),
|
|
2829
|
+
shares: args.earnShares.toString(),
|
|
2830
|
+
signer,
|
|
2831
|
+
status: 'completed',
|
|
2832
|
+
vault: lc(event.address),
|
|
2356
2833
|
},
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
const args =
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
},
|
|
2374
|
-
type: 'shares-redeemed',
|
|
2834
|
+
type: 'shares-redeemed',
|
|
2835
|
+
};
|
|
2836
|
+
});
|
|
2837
|
+
recognize(named('WithdrewExact'), (event) => {
|
|
2838
|
+
const args = event.args;
|
|
2839
|
+
claimVaultLegs(event.address, [args.assets, args.earnSharesBurned]);
|
|
2840
|
+
return {
|
|
2841
|
+
...itemBase(event.logIndex),
|
|
2842
|
+
data: {
|
|
2843
|
+
assets: args.assets.toString(),
|
|
2844
|
+
caller: lc(args.caller),
|
|
2845
|
+
receiver: lc(args.receiver),
|
|
2846
|
+
sharesBurned: args.earnSharesBurned.toString(),
|
|
2847
|
+
signer,
|
|
2848
|
+
status: 'completed',
|
|
2849
|
+
vault: lc(event.address),
|
|
2375
2850
|
},
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
const args =
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
},
|
|
2393
|
-
type: 'assets-withdrawn',
|
|
2851
|
+
type: 'assets-withdrawn',
|
|
2852
|
+
};
|
|
2853
|
+
});
|
|
2854
|
+
recognize(named('RedeemRequested'), (event) => {
|
|
2855
|
+
const args = event.args;
|
|
2856
|
+
claimVaultLegs(event.address, [args.earnShares]);
|
|
2857
|
+
return {
|
|
2858
|
+
...itemBase(event.logIndex),
|
|
2859
|
+
data: {
|
|
2860
|
+
receiver: lc(args.receiver),
|
|
2861
|
+
requestId: args.requestId,
|
|
2862
|
+
requester: lc(args.requester),
|
|
2863
|
+
shares: args.earnShares.toString(),
|
|
2864
|
+
signer,
|
|
2865
|
+
status: 'pending',
|
|
2866
|
+
vault: lc(event.address),
|
|
2394
2867
|
},
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
const args =
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
type: 'shares-redemption-requested',
|
|
2868
|
+
type: 'shares-redemption-requested',
|
|
2869
|
+
};
|
|
2870
|
+
});
|
|
2871
|
+
recognize(named('RedeemFinalized'), (event) => {
|
|
2872
|
+
const args = event.args;
|
|
2873
|
+
claimVaultLegs(event.address, [args.assets, args.earnShares]);
|
|
2874
|
+
return {
|
|
2875
|
+
...itemBase(event.logIndex),
|
|
2876
|
+
data: {
|
|
2877
|
+
asset: lc(args.asset),
|
|
2878
|
+
assets: args.assets.toString(),
|
|
2879
|
+
receiver: lc(args.receiver),
|
|
2880
|
+
requestId: args.requestId,
|
|
2881
|
+
shares: args.earnShares.toString(),
|
|
2882
|
+
signer,
|
|
2883
|
+
status: 'completed',
|
|
2884
|
+
vault: lc(event.address),
|
|
2413
2885
|
},
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
const args =
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
status: 'completed',
|
|
2430
|
-
vault: finalized.address.toLowerCase(),
|
|
2431
|
-
},
|
|
2432
|
-
type: 'shares-redemption-finalized',
|
|
2886
|
+
type: 'shares-redemption-finalized',
|
|
2887
|
+
};
|
|
2888
|
+
});
|
|
2889
|
+
recognize(named('RedeemCancelled'), (event) => {
|
|
2890
|
+
const args = event.args;
|
|
2891
|
+
claimVaultLegs(event.address, [args.earnShares]);
|
|
2892
|
+
return {
|
|
2893
|
+
...itemBase(event.logIndex),
|
|
2894
|
+
data: {
|
|
2895
|
+
receiver: lc(args.receiver),
|
|
2896
|
+
requestId: args.requestId,
|
|
2897
|
+
shares: args.earnShares.toString(),
|
|
2898
|
+
signer,
|
|
2899
|
+
status: 'cancelled',
|
|
2900
|
+
vault: lc(event.address),
|
|
2433
2901
|
},
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2902
|
+
type: 'shares-redemption-cancelled',
|
|
2903
|
+
};
|
|
2904
|
+
});
|
|
2905
|
+
// External ERC-4626 vaults. Claim the share and asset legs unconditionally
|
|
2906
|
+
// so nested vault plumbing never resurfaces as burns or transfers, but emit
|
|
2907
|
+
// only operations the viewer is a party to. Fold-only recognitions are
|
|
2908
|
+
// buffered and surface only when nothing else classified.
|
|
2909
|
+
const suppressedVaultItems = [];
|
|
2910
|
+
const viewerInvolved = (parties, legs) => parties.some((party) => Address.isEqual(party, viewer)) ||
|
|
2911
|
+
legs.some((leg) => isTransfer(leg) &&
|
|
2912
|
+
(Address.isEqual(transferArgs(leg).from, viewer) ||
|
|
2913
|
+
Address.isEqual(transferArgs(leg).to, viewer)));
|
|
2914
|
+
recognize(named('Withdraw'), (event) => {
|
|
2915
|
+
const args = event.args;
|
|
2916
|
+
const vault = event.address;
|
|
2917
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
2918
|
+
const data = {
|
|
2919
|
+
assets: args.assets.toString(),
|
|
2920
|
+
caller: lc(args.sender),
|
|
2921
|
+
receiver: lc(args.receiver),
|
|
2922
|
+
sharesBurned: args.shares.toString(),
|
|
2923
|
+
signer,
|
|
2924
|
+
status: 'completed',
|
|
2925
|
+
vault: lc(vault),
|
|
2926
|
+
};
|
|
2927
|
+
const legs = claimVaultLegs(vault, [args.assets, args.shares]);
|
|
2928
|
+
const item = { ...itemBase(event.logIndex), data, type: 'assets-withdrawn' };
|
|
2929
|
+
if (viewerInvolved([args.sender, args.receiver, args.owner], legs))
|
|
2930
|
+
return item;
|
|
2931
|
+
suppressedVaultItems.push(item);
|
|
2932
|
+
return undefined;
|
|
2933
|
+
});
|
|
2934
|
+
recognize(named('Deposit'), (event) => {
|
|
2935
|
+
const args = event.args;
|
|
2936
|
+
const vault = event.address;
|
|
2937
|
+
// Parse before claiming so a malformed event consumes nothing but itself.
|
|
2938
|
+
const data = {
|
|
2939
|
+
assets: args.assets.toString(),
|
|
2940
|
+
caller: lc(args.sender),
|
|
2941
|
+
receiver: lc(args.owner),
|
|
2942
|
+
shares: args.shares.toString(),
|
|
2943
|
+
signer,
|
|
2944
|
+
status: 'completed',
|
|
2945
|
+
vault: lc(vault),
|
|
2946
|
+
};
|
|
2947
|
+
const legs = claimVaultLegs(vault, [args.assets, args.shares]);
|
|
2948
|
+
const item = { ...itemBase(event.logIndex), data, type: 'assets-deposited' };
|
|
2949
|
+
if (viewerInvolved([args.sender, args.owner], legs))
|
|
2950
|
+
return item;
|
|
2951
|
+
suppressedVaultItems.push(item);
|
|
2952
|
+
return undefined;
|
|
2953
|
+
});
|
|
2954
|
+
// DEX order placed: `OrderPlaced` carries the escrow token/amount and side;
|
|
2955
|
+
// claim the escrow transfer that funded it.
|
|
2956
|
+
const orderPlacedItem = (event) => {
|
|
2957
|
+
const args = event.args;
|
|
2958
|
+
const escrow = (leg) => isTransfer(leg) &&
|
|
2959
|
+
Address.isEqual(leg.address, args.token) &&
|
|
2960
|
+
Address.isEqual(transferArgs(leg).to, event.address);
|
|
2961
|
+
// The escrow funds the placement, so it is the nearest preceding match; a
|
|
2962
|
+
// same-token earlier swap input must not be taken instead.
|
|
2963
|
+
const escrowLeg = unclaimed((leg) => escrow(leg) && leg.logIndex < event.logIndex && transferArgs(leg).amount === args.amount).at(-1) ?? unclaimed((leg) => escrow(leg) && leg.logIndex < event.logIndex).at(-1);
|
|
2964
|
+
if (escrowLeg)
|
|
2965
|
+
claim(escrowLeg);
|
|
2966
|
+
return {
|
|
2967
|
+
...itemBase(event.logIndex),
|
|
2968
|
+
data: {
|
|
2969
|
+
orderId: args.orderId.toString(),
|
|
2970
|
+
side: args.isBid ? 'bid' : 'ask',
|
|
2971
|
+
signer,
|
|
2972
|
+
sourceToken: token(args.amount, args.token),
|
|
2973
|
+
tick: Number(args.tick),
|
|
2451
2974
|
},
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
const
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
//
|
|
2464
|
-
//
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2975
|
+
type: 'order-placed',
|
|
2976
|
+
};
|
|
2977
|
+
};
|
|
2978
|
+
const isFlipOrder = (event) => 'isFlipOrder' in event.args && event.args.isFlipOrder === true;
|
|
2979
|
+
// Non-flip order escrow legs claim before swap pairing so they cannot
|
|
2980
|
+
// mispair with an unrelated swap's output leg.
|
|
2981
|
+
recognize((event) => event.eventName === 'OrderPlaced' && !isFlipOrder(event), orderPlacedItem);
|
|
2982
|
+
// Swap: a transfer into a route paired with a different token coming back
|
|
2983
|
+
// out. The DEX matches any swapper; other routes must be viewer-bounded
|
|
2984
|
+
// with route-emitted logs between the legs.
|
|
2985
|
+
{
|
|
2986
|
+
// Outgoing transfers indexed by lowercase sender, for O(candidates)
|
|
2987
|
+
// destination lookups. Claimed entries are skipped at scan time.
|
|
2988
|
+
const transfersByFrom = new Map();
|
|
2989
|
+
for (const event of pool) {
|
|
2990
|
+
if (!isTransfer(event))
|
|
2991
|
+
continue;
|
|
2992
|
+
const key = transferArgs(event).from.toLowerCase();
|
|
2993
|
+
const list = transfersByFrom.get(key);
|
|
2994
|
+
if (list)
|
|
2995
|
+
list.push(event);
|
|
2996
|
+
else
|
|
2997
|
+
transfersByFrom.set(key, [event]);
|
|
2998
|
+
}
|
|
2999
|
+
// One forward pass: claims only shrink the pool, so a source that finds
|
|
3000
|
+
// no destination now never finds one later, and scanning each source once
|
|
3001
|
+
// keeps the cost linear in sources instead of restarting per pair.
|
|
3002
|
+
for (const source of unclaimed(isTransfer)) {
|
|
3003
|
+
if (claimed.has(source.logIndex))
|
|
3004
|
+
continue;
|
|
3005
|
+
const sourceArgs = transferArgs(source);
|
|
3006
|
+
const route = sourceArgs.to;
|
|
3007
|
+
const routeKey = route.toLowerCase();
|
|
3008
|
+
// Tempo system addresses (the reserve, fee manager, outbox, and every
|
|
3009
|
+
// other non-DEX precompile) are never swap routes; new precompiles join
|
|
3010
|
+
// the exclusion automatically.
|
|
3011
|
+
if (nonRouteSystemAddresses.has(routeKey))
|
|
3012
|
+
continue;
|
|
3013
|
+
if (Address.isEqual(route, zeroAddress))
|
|
3014
|
+
continue;
|
|
3015
|
+
if (Address.isEqual(route, viewer))
|
|
3016
|
+
continue;
|
|
3017
|
+
const isDex = Address.isEqual(route, Addresses.stablecoinDex);
|
|
3018
|
+
if (!isDex && !Address.isEqual(sourceArgs.from, viewer))
|
|
3019
|
+
continue;
|
|
3020
|
+
const candidates = (transfersByFrom.get(routeKey) ?? []).filter((event) => {
|
|
3021
|
+
if (claimed.has(event.logIndex) || event.logIndex <= source.logIndex)
|
|
3022
|
+
return false;
|
|
3023
|
+
if (Address.isEqual(event.address, source.address))
|
|
3024
|
+
return false;
|
|
3025
|
+
return !Address.isEqual(transferArgs(event).to, Addresses.feeManager);
|
|
3026
|
+
});
|
|
3027
|
+
// Prefer the output paying the swapper back (so interleaved swaps by
|
|
3028
|
+
// different accounts keep their own outputs); otherwise a DEX pairs
|
|
3029
|
+
// first-in-first-out, and a route's last leg is a direct delivery
|
|
3030
|
+
// (earlier different-token legs are intermediate hops).
|
|
3031
|
+
const destination = candidates.find((event) => Address.isEqual(transferArgs(event).to, sourceArgs.from)) ??
|
|
3032
|
+
(isDex ? candidates[0] : candidates.at(-1));
|
|
3033
|
+
if (!destination)
|
|
3034
|
+
continue;
|
|
3035
|
+
// Route evidence for non-precompile routes: the route itself emitted a
|
|
3036
|
+
// log between the legs (raw rows, so undecoded router events count). A
|
|
3037
|
+
// counterparty that merely received and later paid back is not a route.
|
|
3038
|
+
if (!isDex) {
|
|
3039
|
+
const evidence = rows.some((r) => r.logIdx > source.logIndex &&
|
|
3040
|
+
r.logIdx < destination.logIndex &&
|
|
3041
|
+
r.logAddress.toLowerCase() === routeKey);
|
|
3042
|
+
if (!evidence)
|
|
3043
|
+
continue;
|
|
3044
|
+
}
|
|
3045
|
+
journal = [];
|
|
3046
|
+
try {
|
|
3047
|
+
claim(source);
|
|
3048
|
+
claim(destination);
|
|
3049
|
+
const destinationArgs = transferArgs(destination);
|
|
3050
|
+
const swapper = isDex ? destinationArgs.to : sourceArgs.from;
|
|
3051
|
+
const destTokenAddress = destination.address;
|
|
3052
|
+
const sourceToken = token(sourceArgs.amount, source.address);
|
|
3053
|
+
// Router plumbing between the legs: route-touching or zero-address
|
|
3054
|
+
// token moves and router-side approvals, never viewer-party transfers.
|
|
3055
|
+
// DEX-precompile pairs claim nothing here, so an interleaved swap by
|
|
3056
|
+
// another account keeps its own legs.
|
|
3057
|
+
if (!isDex)
|
|
3058
|
+
for (const event of unclaimed()) {
|
|
3059
|
+
if (event.logIndex <= source.logIndex || event.logIndex >= destination.logIndex)
|
|
3060
|
+
continue;
|
|
3061
|
+
if (isTransfer(event)) {
|
|
3062
|
+
const args = transferArgs(event);
|
|
3063
|
+
if (Address.isEqual(args.from, viewer) || Address.isEqual(args.to, viewer))
|
|
3064
|
+
continue;
|
|
3065
|
+
if (Address.isEqual(args.from, route) ||
|
|
3066
|
+
Address.isEqual(args.to, route) ||
|
|
3067
|
+
Address.isEqual(args.from, zeroAddress) ||
|
|
3068
|
+
Address.isEqual(args.to, zeroAddress))
|
|
3069
|
+
claim(event);
|
|
3070
|
+
}
|
|
3071
|
+
else if (event.eventName === 'Approval' &&
|
|
3072
|
+
'owner' in event.args &&
|
|
3073
|
+
!Address.isEqual(event.args.owner, viewer))
|
|
3074
|
+
claim(event);
|
|
3075
|
+
}
|
|
3076
|
+
// The allowance granted for this swap: same token, owner, and amount,
|
|
3077
|
+
// and causally tied to the input leg (spender is the route, or the
|
|
3078
|
+
// approval immediately precedes it). Unrelated approvals stay.
|
|
3079
|
+
unclaimed((event) => event.eventName === 'Approval' &&
|
|
3080
|
+
'amount' in event.args &&
|
|
3081
|
+
'owner' in event.args &&
|
|
3082
|
+
'spender' in event.args &&
|
|
3083
|
+
Address.isEqual(event.address, source.address) &&
|
|
3084
|
+
Address.isEqual(event.args.owner, sourceArgs.from) &&
|
|
3085
|
+
event.args.amount === sourceArgs.amount &&
|
|
3086
|
+
event.logIndex < source.logIndex &&
|
|
3087
|
+
(Address.isEqual(event.args.spender, route) ||
|
|
3088
|
+
event.logIndex === source.logIndex - 1))
|
|
3089
|
+
.slice(-1)
|
|
3090
|
+
.forEach(claim);
|
|
3091
|
+
// DEX bookkeeping this swap produced: fills against resting orders and
|
|
3092
|
+
// the auto-flip re-placement, acted by the swapper or its route account.
|
|
3093
|
+
// A fill of the viewer's own resting order stays its own item.
|
|
3094
|
+
const isBookkeepingActor = (value) => Address.isEqual(value, swapper) || Address.isEqual(value, route);
|
|
3095
|
+
unclaimed((event) => (Address.isEqual(event.address, route) ||
|
|
3096
|
+
Address.isEqual(event.address, Addresses.stablecoinDex)) &&
|
|
3097
|
+
((event.eventName === 'OrderFilled' &&
|
|
3098
|
+
'taker' in event.args &&
|
|
3099
|
+
isBookkeepingActor(event.args.taker) &&
|
|
3100
|
+
!('maker' in event.args &&
|
|
3101
|
+
Address.isEqual(event.args.maker, viewer))) ||
|
|
3102
|
+
(event.eventName === 'OrderPlaced' &&
|
|
3103
|
+
isFlipOrder(event) &&
|
|
3104
|
+
'maker' in event.args &&
|
|
3105
|
+
isBookkeepingActor(event.args.maker)))).forEach(claim);
|
|
3106
|
+
// Cross-token transfer: the swapped-out token is forwarded whole from
|
|
3107
|
+
// the swapper to a new recipient after the swap (the account server's
|
|
3108
|
+
// approve+swap+transfer multicall), or the route delivered it to the
|
|
3109
|
+
// recipient directly; fold both shapes into one `transfer`. A leg
|
|
3110
|
+
// whose recipient later forwards a different token is the input of a
|
|
3111
|
+
// chained swap, not a delivery.
|
|
3112
|
+
const forwardsOn = (candidate) => {
|
|
3113
|
+
const args = transferArgs(candidate);
|
|
3114
|
+
return (transfersByFrom.get(args.to.toLowerCase()) ?? []).some((onward) => !claimed.has(onward.logIndex) &&
|
|
3115
|
+
onward.logIndex > candidate.logIndex &&
|
|
3116
|
+
!Address.isEqual(onward.address, candidate.address));
|
|
3117
|
+
};
|
|
3118
|
+
const delivery = !isDex && !Address.isEqual(destinationArgs.to, viewer)
|
|
3119
|
+
? destination
|
|
3120
|
+
: takeTransfer((event) => {
|
|
3121
|
+
const args = transferArgs(event);
|
|
3122
|
+
return (event.logIndex > destination.logIndex &&
|
|
3123
|
+
Address.isEqual(event.address, destTokenAddress) &&
|
|
3124
|
+
Address.isEqual(args.from, swapper) &&
|
|
3125
|
+
args.amount === destinationArgs.amount &&
|
|
3126
|
+
!Address.isEqual(args.to, route) &&
|
|
3127
|
+
!Address.isEqual(args.to, swapper) &&
|
|
3128
|
+
!forwardsOn(event));
|
|
3129
|
+
});
|
|
3130
|
+
// A malformed delivery memo degrades to a plain swap item; the claimed
|
|
3131
|
+
// legs must still produce an activity.
|
|
3132
|
+
const item = (() => {
|
|
3133
|
+
if (!delivery)
|
|
3134
|
+
return undefined;
|
|
3135
|
+
try {
|
|
3136
|
+
const args = transferArgs(delivery);
|
|
3137
|
+
const rawMemo = delivery.eventName === 'TransferWithMemo' ? args.memo : undefined;
|
|
3138
|
+
const memo = rawMemo ? Transfers.memoToString(rawMemo) : undefined;
|
|
3139
|
+
const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo });
|
|
3140
|
+
return transfer(itemBase(delivery.logIndex), blockNum, {
|
|
3141
|
+
attribution,
|
|
3142
|
+
destinationToken: token(args.amount, delivery.address),
|
|
3143
|
+
direction: Address.isEqual(args.to, viewer) ? 'in' : 'out',
|
|
3144
|
+
memo,
|
|
3145
|
+
recipient: lc(args.to),
|
|
3146
|
+
sender: lc(swapper),
|
|
3147
|
+
signer,
|
|
3148
|
+
sourceToken,
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
catch {
|
|
3152
|
+
return undefined;
|
|
3153
|
+
}
|
|
3154
|
+
})();
|
|
3155
|
+
items.push(item ?? {
|
|
3156
|
+
...itemBase(source.logIndex),
|
|
3157
|
+
data: {
|
|
3158
|
+
destinationToken: token(destinationArgs.amount, destTokenAddress),
|
|
3159
|
+
signer,
|
|
3160
|
+
sourceToken,
|
|
3161
|
+
},
|
|
3162
|
+
type: 'swap',
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
catch {
|
|
3166
|
+
// A malformed leg costs only this pair; release its claims so the
|
|
3167
|
+
// legs fall through to the later recognizers.
|
|
3168
|
+
for (const index of journal ?? [])
|
|
3169
|
+
claimed.delete(index);
|
|
3170
|
+
}
|
|
3171
|
+
finally {
|
|
3172
|
+
journal = undefined;
|
|
3173
|
+
}
|
|
2488
3174
|
}
|
|
2489
|
-
return [
|
|
2490
|
-
{
|
|
2491
|
-
...itemBase(Math.min(logIndexOfEvent(toDex), logIndexOfEvent(fromDex))),
|
|
2492
|
-
data: {
|
|
2493
|
-
destinationToken: token(fromDex.args.amount, destTokenAddress),
|
|
2494
|
-
signer,
|
|
2495
|
-
sourceToken,
|
|
2496
|
-
},
|
|
2497
|
-
type: 'swap',
|
|
2498
|
-
},
|
|
2499
|
-
];
|
|
2500
|
-
}
|
|
2501
|
-
// Mint: explicit `Mint` event (not the fee manager) or a transfer from zero.
|
|
2502
|
-
const mintEvent = finalEvents.find((e) => e.eventName === 'Mint' &&
|
|
2503
|
-
'amount' in e.args &&
|
|
2504
|
-
!Address.isEqual(e.address, Addresses.feeManager));
|
|
2505
|
-
if (mintEvent) {
|
|
2506
|
-
const args = mintEvent.args;
|
|
2507
|
-
return [
|
|
2508
|
-
{
|
|
2509
|
-
...itemBase(logIndexOfEvent(mintEvent)),
|
|
2510
|
-
data: {
|
|
2511
|
-
recipient: args.to.toLowerCase(),
|
|
2512
|
-
sourceToken: token(args.amount, mintEvent.address),
|
|
2513
|
-
},
|
|
2514
|
-
type: 'mint',
|
|
2515
|
-
},
|
|
2516
|
-
];
|
|
2517
|
-
}
|
|
2518
|
-
const zeroTransferIn = transfers.find((e) => Address.isEqual(e.args.from, zeroAddress));
|
|
2519
|
-
if (zeroTransferIn) {
|
|
2520
|
-
const args = zeroTransferIn.args;
|
|
2521
|
-
return [
|
|
2522
|
-
{
|
|
2523
|
-
...itemBase(logIndexOfEvent(zeroTransferIn)),
|
|
2524
|
-
data: {
|
|
2525
|
-
recipient: args.to.toLowerCase(),
|
|
2526
|
-
sourceToken: token(args.amount, zeroTransferIn.address),
|
|
2527
|
-
},
|
|
2528
|
-
type: 'mint',
|
|
2529
|
-
},
|
|
2530
|
-
];
|
|
2531
3175
|
}
|
|
3176
|
+
// Mint: explicit `Mint` events (not the fee manager) and remaining transfers
|
|
3177
|
+
// from the zero address (the dedup above already folded event/transfer pairs).
|
|
3178
|
+
const mintItem = (event, args) => ({
|
|
3179
|
+
...itemBase(event.logIndex),
|
|
3180
|
+
data: {
|
|
3181
|
+
recipient: lc(args.to),
|
|
3182
|
+
sourceToken: token(args.amount, event.address),
|
|
3183
|
+
},
|
|
3184
|
+
type: 'mint',
|
|
3185
|
+
});
|
|
3186
|
+
recognize((event) => event.eventName === 'Mint' &&
|
|
3187
|
+
'amount' in event.args &&
|
|
3188
|
+
'to' in event.args &&
|
|
3189
|
+
!Address.isEqual(event.address, Addresses.feeManager), (event) => mintItem(event, event.args));
|
|
3190
|
+
recognize((event) => isTransfer(event) &&
|
|
3191
|
+
Address.isEqual(transferArgs(event).from, zeroAddress), (event) => mintItem(event, transferArgs(event)));
|
|
2532
3192
|
// Burn blocked: a compliance burn of receive-policy-blocked funds. It also
|
|
2533
|
-
// emits a zero-address `Transfer
|
|
2534
|
-
//
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
type: 'burn-blocked',
|
|
2547
|
-
},
|
|
2548
|
-
];
|
|
2549
|
-
}
|
|
2550
|
-
// Burn: explicit `Burn` event or a transfer to zero.
|
|
2551
|
-
const burnEvent = finalEvents.find((e) => e.eventName === 'Burn' && 'amount' in e.args);
|
|
2552
|
-
if (burnEvent) {
|
|
2553
|
-
const args = burnEvent.args;
|
|
2554
|
-
return [
|
|
2555
|
-
{
|
|
2556
|
-
...itemBase(logIndexOfEvent(burnEvent)),
|
|
2557
|
-
data: {
|
|
2558
|
-
sender: args.from.toLowerCase(),
|
|
2559
|
-
signer,
|
|
2560
|
-
sourceToken: token(args.amount, burnEvent.address),
|
|
2561
|
-
},
|
|
2562
|
-
type: 'burn',
|
|
2563
|
-
},
|
|
2564
|
-
];
|
|
2565
|
-
}
|
|
2566
|
-
const zeroTransferOut = transfers.find((e) => Address.isEqual(e.args.to, zeroAddress));
|
|
2567
|
-
if (zeroTransferOut) {
|
|
2568
|
-
const args = zeroTransferOut.args;
|
|
2569
|
-
return [
|
|
2570
|
-
{
|
|
2571
|
-
...itemBase(logIndexOfEvent(zeroTransferOut)),
|
|
2572
|
-
data: {
|
|
2573
|
-
sender: args.from.toLowerCase(),
|
|
2574
|
-
signer,
|
|
2575
|
-
sourceToken: token(args.amount, zeroTransferOut.address),
|
|
2576
|
-
},
|
|
2577
|
-
type: 'burn',
|
|
3193
|
+
// emits a zero-address `Transfer`; claim that paired leg so it cannot
|
|
3194
|
+
// classify as a plain `burn`.
|
|
3195
|
+
recognize(named('BurnBlocked'), (event) => {
|
|
3196
|
+
const args = event.args;
|
|
3197
|
+
takeTransfer((leg) => Address.isEqual(leg.address, event.address) &&
|
|
3198
|
+
Address.isEqual(transferArgs(leg).to, zeroAddress) &&
|
|
3199
|
+
transferArgs(leg).amount === args.amount);
|
|
3200
|
+
return {
|
|
3201
|
+
...itemBase(event.logIndex),
|
|
3202
|
+
data: {
|
|
3203
|
+
sender: lc(args.from),
|
|
3204
|
+
signer,
|
|
3205
|
+
sourceToken: token(args.amount, event.address),
|
|
2578
3206
|
},
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
3207
|
+
type: 'burn-blocked',
|
|
3208
|
+
};
|
|
3209
|
+
});
|
|
3210
|
+
// Burn: explicit `Burn` events and remaining transfers to the zero address.
|
|
3211
|
+
const burnItem = (event, args) => ({
|
|
3212
|
+
...itemBase(event.logIndex),
|
|
3213
|
+
data: {
|
|
3214
|
+
sender: lc(args.from),
|
|
3215
|
+
signer,
|
|
3216
|
+
sourceToken: token(args.amount, event.address),
|
|
3217
|
+
},
|
|
3218
|
+
type: 'burn',
|
|
3219
|
+
});
|
|
3220
|
+
recognize((event) => event.eventName === 'Burn' && 'amount' in event.args && 'from' in event.args, (event) => burnItem(event, event.args));
|
|
3221
|
+
recognize((event) => isTransfer(event) && Address.isEqual(transferArgs(event).to, zeroAddress), (event) => burnItem(event, transferArgs(event)));
|
|
3222
|
+
// Approval (excluding zone-outbox spenders): only viewer-party approvals
|
|
3223
|
+
// surface here; the rest stay for the nothing-else-classified fallback.
|
|
3224
|
+
const isErc20Approval = (event) => {
|
|
3225
|
+
if (event.eventName !== 'Approval')
|
|
3226
|
+
return false;
|
|
3227
|
+
const args = event.args;
|
|
3228
|
+
if (typeof args.spender !== 'string' || !Address.validate(args.spender))
|
|
3229
|
+
return false;
|
|
2583
3230
|
// ERC-721 uses the same selector but indexes its token ID as topic3. In
|
|
2584
3231
|
// non-strict mode viem otherwise decodes the missing ERC-20 amount as zero.
|
|
2585
|
-
rows.some((row) => row.logIdx ===
|
|
3232
|
+
return (rows.some((row) => row.logIdx === event.logIndex &&
|
|
2586
3233
|
row.topic3 === null &&
|
|
2587
|
-
/^0x[0-9a-fA-F]{64}$/.test(row.data ?? '')) &&
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
const args =
|
|
2591
|
-
return
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
spender: args.spender.toLowerCase(),
|
|
2598
|
-
},
|
|
2599
|
-
type: 'approval',
|
|
3234
|
+
/^0x[0-9a-fA-F]{64}$/.test(row.data ?? '')) && !Address.isEqual(args.spender, Addresses.zoneOutbox));
|
|
3235
|
+
};
|
|
3236
|
+
const approvalItem = (event) => {
|
|
3237
|
+
const args = event.args;
|
|
3238
|
+
return {
|
|
3239
|
+
...itemBase(event.logIndex),
|
|
3240
|
+
data: {
|
|
3241
|
+
signer,
|
|
3242
|
+
sourceToken: token(args.amount, event.address),
|
|
3243
|
+
spender: lc(args.spender),
|
|
2600
3244
|
},
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
//
|
|
2609
|
-
//
|
|
2610
|
-
//
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
type: 'channel-opened',
|
|
3245
|
+
type: 'approval',
|
|
3246
|
+
};
|
|
3247
|
+
};
|
|
3248
|
+
recognize((event) => isErc20Approval(event) &&
|
|
3249
|
+
'owner' in event.args &&
|
|
3250
|
+
(Address.isEqual(event.args.owner, viewer) ||
|
|
3251
|
+
Address.isEqual(event.args.spender, viewer)), approvalItem);
|
|
3252
|
+
// Payment channels. The reserve doesn't put the token on `TopUp`/`Settled`/
|
|
3253
|
+
// `ChannelClosed`, so each anchor claims its own reserve leg to recover it;
|
|
3254
|
+
// batched channel operations pair with their own legs one at a time.
|
|
3255
|
+
recognize(named('ChannelOpened'), (event) => {
|
|
3256
|
+
const args = event.args;
|
|
3257
|
+
takeTransfer((leg) => Address.isEqual(leg.address, args.token) &&
|
|
3258
|
+
Address.isEqual(transferArgs(leg).to, tip20ChannelReserve) &&
|
|
3259
|
+
transferArgs(leg).amount === args.deposit);
|
|
3260
|
+
return {
|
|
3261
|
+
...itemBase(event.logIndex),
|
|
3262
|
+
data: {
|
|
3263
|
+
channelId: hx(args.channelId),
|
|
3264
|
+
payee: lc(args.payee),
|
|
3265
|
+
payer: lc(args.payer),
|
|
3266
|
+
signer,
|
|
3267
|
+
sourceToken: token(args.deposit, args.token),
|
|
2625
3268
|
},
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
3269
|
+
type: 'channel-opened',
|
|
3270
|
+
};
|
|
3271
|
+
});
|
|
3272
|
+
recognize(named('TopUp'), (event) => {
|
|
3273
|
+
const args = event.args;
|
|
3274
|
+
const inflow = takeTransfer((leg) => Address.isEqual(transferArgs(leg).to, tip20ChannelReserve) &&
|
|
3275
|
+
transferArgs(leg).amount === args.additionalDeposit) ?? takeTransfer((leg) => Address.isEqual(transferArgs(leg).to, tip20ChannelReserve));
|
|
3276
|
+
// A top-up cancels any pending close request on the channel; fold the
|
|
3277
|
+
// cancellation event it bundles.
|
|
3278
|
+
unclaimed((e) => e.eventName === 'CloseRequestCancelled' &&
|
|
3279
|
+
'channelId' in e.args &&
|
|
3280
|
+
e.args.channelId === args.channelId)
|
|
3281
|
+
.slice(0, 1)
|
|
3282
|
+
.forEach(claim);
|
|
3283
|
+
const tokenAddress = inflow
|
|
3284
|
+
? inflow.address
|
|
2640
3285
|
: zeroAddress;
|
|
2641
|
-
return
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
sourceToken: token(args.additionalDeposit, tokenAddress),
|
|
2648
|
-
},
|
|
2649
|
-
type: 'channel-funded',
|
|
3286
|
+
return {
|
|
3287
|
+
...itemBase(event.logIndex),
|
|
3288
|
+
data: {
|
|
3289
|
+
channelId: hx(args.channelId),
|
|
3290
|
+
signer,
|
|
3291
|
+
sourceToken: token(args.additionalDeposit, tokenAddress),
|
|
2650
3292
|
},
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
3293
|
+
type: 'channel-funded',
|
|
3294
|
+
};
|
|
3295
|
+
});
|
|
3296
|
+
recognize(named('Settled'), (event) => {
|
|
3297
|
+
const args = event.args;
|
|
3298
|
+
const payout = (leg) => Address.isEqual(transferArgs(leg).from, tip20ChannelReserve) &&
|
|
3299
|
+
Address.isEqual(transferArgs(leg).to, args.payee);
|
|
3300
|
+
// Prefer the amount-matched payout so batched channel operations sharing
|
|
3301
|
+
// a payee keep their own legs.
|
|
3302
|
+
const payeeLeg = takeTransfer((leg) => payout(leg) && transferArgs(leg).amount === args.deltaPaid) ??
|
|
3303
|
+
takeTransfer(payout);
|
|
2659
3304
|
const tokenAddress = payeeLeg
|
|
2660
3305
|
? payeeLeg.address
|
|
2661
3306
|
: zeroAddress;
|
|
2662
|
-
return
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
sourceToken: token(args.deltaPaid, tokenAddress),
|
|
2671
|
-
},
|
|
2672
|
-
type: 'channel-settled',
|
|
2673
|
-
},
|
|
2674
|
-
];
|
|
2675
|
-
}
|
|
2676
|
-
// Payment channel closed: payout to the payee and refund to the payer arrive
|
|
2677
|
-
// as up to two transfers out of the reserve; attach whichever are present.
|
|
2678
|
-
const channelClosed = finalEvents.find((e) => e.eventName === 'ChannelClosed');
|
|
2679
|
-
if (channelClosed) {
|
|
2680
|
-
const args = channelClosed.args;
|
|
2681
|
-
const payeeLeg = reserveOutflowTo(args.payee.toLowerCase());
|
|
2682
|
-
const payerLeg = reserveOutflowTo(args.payer.toLowerCase());
|
|
2683
|
-
return [
|
|
2684
|
-
{
|
|
2685
|
-
...itemBase(logIndexOfEvent(channelClosed)),
|
|
2686
|
-
data: {
|
|
2687
|
-
channelId: args.channelId.toLowerCase(),
|
|
2688
|
-
payee: args.payee.toLowerCase(),
|
|
2689
|
-
payer: args.payer.toLowerCase(),
|
|
2690
|
-
...(payerLeg && args.refundedToPayer > 0n
|
|
2691
|
-
? { refund: token(args.refundedToPayer, payerLeg.address) }
|
|
2692
|
-
: {}),
|
|
2693
|
-
signer,
|
|
2694
|
-
...(payeeLeg
|
|
2695
|
-
? { sourceToken: token(args.settledToPayee, payeeLeg.address) }
|
|
2696
|
-
: {}),
|
|
2697
|
-
},
|
|
2698
|
-
type: 'channel-closed',
|
|
3307
|
+
return {
|
|
3308
|
+
...itemBase(event.logIndex),
|
|
3309
|
+
data: {
|
|
3310
|
+
channelId: hx(args.channelId),
|
|
3311
|
+
payee: lc(args.payee),
|
|
3312
|
+
payer: lc(args.payer),
|
|
3313
|
+
signer,
|
|
3314
|
+
sourceToken: token(args.deltaPaid, tokenAddress),
|
|
2699
3315
|
},
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
const
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3316
|
+
type: 'channel-settled',
|
|
3317
|
+
};
|
|
3318
|
+
});
|
|
3319
|
+
recognize(named('ChannelClosed'), (event) => {
|
|
3320
|
+
const args = event.args;
|
|
3321
|
+
// A zero settlement or refund has no leg; claiming one would steal it
|
|
3322
|
+
// from a batched sibling operation.
|
|
3323
|
+
const reserveLegTo = (to, amount) => {
|
|
3324
|
+
if (amount === 0n)
|
|
3325
|
+
return undefined;
|
|
3326
|
+
const payout = (leg) => Address.isEqual(transferArgs(leg).from, tip20ChannelReserve) &&
|
|
3327
|
+
Address.isEqual(transferArgs(leg).to, to);
|
|
3328
|
+
return (takeTransfer((leg) => payout(leg) && transferArgs(leg).amount === amount) ??
|
|
3329
|
+
takeTransfer(payout));
|
|
3330
|
+
};
|
|
3331
|
+
const payeeLeg = reserveLegTo(args.payee, args.settledToPayee);
|
|
3332
|
+
const payerLeg = reserveLegTo(args.payer, args.refundedToPayer);
|
|
3333
|
+
return {
|
|
3334
|
+
...itemBase(event.logIndex),
|
|
3335
|
+
data: {
|
|
3336
|
+
channelId: hx(args.channelId),
|
|
3337
|
+
payee: lc(args.payee),
|
|
3338
|
+
payer: lc(args.payer),
|
|
3339
|
+
...(payerLeg && args.refundedToPayer > 0n
|
|
3340
|
+
? { refund: token(args.refundedToPayer, payerLeg.address) }
|
|
3341
|
+
: {}),
|
|
3342
|
+
signer,
|
|
3343
|
+
...(payeeLeg
|
|
3344
|
+
? { sourceToken: token(args.settledToPayee, payeeLeg.address) }
|
|
3345
|
+
: {}),
|
|
2718
3346
|
},
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
//
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
const args =
|
|
2726
|
-
return
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
sourceToken: token(args.amount, args.token),
|
|
2734
|
-
tick: Number(args.tick),
|
|
2735
|
-
},
|
|
2736
|
-
type: 'order-placed',
|
|
3347
|
+
type: 'channel-closed',
|
|
3348
|
+
};
|
|
3349
|
+
});
|
|
3350
|
+
// Payment channel close cancelled: carries no transfer, so the event itself
|
|
3351
|
+
// is the signal. Cancellations bundled into a top-up were claimed above.
|
|
3352
|
+
recognize(named('CloseRequestCancelled'), (event) => {
|
|
3353
|
+
const args = event.args;
|
|
3354
|
+
return {
|
|
3355
|
+
...itemBase(event.logIndex),
|
|
3356
|
+
data: {
|
|
3357
|
+
channelId: hx(args.channelId),
|
|
3358
|
+
payee: lc(args.payee),
|
|
3359
|
+
payer: lc(args.payer),
|
|
3360
|
+
signer,
|
|
2737
3361
|
},
|
|
2738
|
-
|
|
3362
|
+
type: 'channel-close-cancelled',
|
|
3363
|
+
};
|
|
3364
|
+
});
|
|
3365
|
+
// Payment-channel close requested: matched on the raw selector (the event
|
|
3366
|
+
// is not in the merged ABI). Runs after the channel recognizers claimed
|
|
3367
|
+
// their own reserve legs, then takes the leftover refund back to the viewer.
|
|
3368
|
+
for (const row of rows.filter((r) => r.selector === closeRequestedSelector)) {
|
|
3369
|
+
const refundLeg = takeTransfer((event) => {
|
|
3370
|
+
const args = transferArgs(event);
|
|
3371
|
+
if (!Address.isEqual(args.to, viewer))
|
|
3372
|
+
return false;
|
|
3373
|
+
return (Address.isEqual(args.from, tip20ChannelReserve) ||
|
|
3374
|
+
Address.isEqual(args.from, row.logAddress));
|
|
3375
|
+
});
|
|
3376
|
+
const refund = refundLeg
|
|
3377
|
+
? token(transferArgs(refundLeg).amount, refundLeg.address)
|
|
3378
|
+
: undefined;
|
|
3379
|
+
items.push({
|
|
3380
|
+
...itemBase(row.logIdx),
|
|
3381
|
+
data: { ...(refund ? { refund } : {}), signer },
|
|
3382
|
+
type: 'session-closed',
|
|
3383
|
+
});
|
|
2739
3384
|
}
|
|
3385
|
+
// Auto-flip order placements not folded into a swap above.
|
|
3386
|
+
recognize(named('OrderPlaced'), orderPlacedItem);
|
|
2740
3387
|
// DEX order cancelled: refund is credited to the internal DEX balance, so
|
|
2741
|
-
// there is no co-located transfer
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
{
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
3388
|
+
// there is no co-located transfer; the event itself is the signal.
|
|
3389
|
+
recognize(named('OrderCancelled'), (event) => {
|
|
3390
|
+
const args = event.args;
|
|
3391
|
+
return {
|
|
3392
|
+
...itemBase(event.logIndex),
|
|
3393
|
+
data: { orderId: args.orderId.toString(), signer },
|
|
3394
|
+
type: 'order-cancelled',
|
|
3395
|
+
};
|
|
3396
|
+
});
|
|
3397
|
+
// Fee-AMM rebalance swap: `validatorToken` in, `userToken` out; claim both
|
|
3398
|
+
// fee-manager legs so neither resurfaces as a plain transfer.
|
|
3399
|
+
recognize(named('RebalanceSwap'), (event) => {
|
|
3400
|
+
const args = event.args;
|
|
3401
|
+
takeTransfer((leg) => Address.isEqual(leg.address, args.validatorToken) &&
|
|
3402
|
+
Address.isEqual(transferArgs(leg).from, args.swapper) &&
|
|
3403
|
+
Address.isEqual(transferArgs(leg).to, Addresses.feeManager) &&
|
|
3404
|
+
transferArgs(leg).amount === args.amountIn);
|
|
3405
|
+
takeTransfer((leg) => Address.isEqual(leg.address, args.userToken) &&
|
|
3406
|
+
Address.isEqual(transferArgs(leg).from, Addresses.feeManager) &&
|
|
3407
|
+
Address.isEqual(transferArgs(leg).to, args.swapper) &&
|
|
3408
|
+
transferArgs(leg).amount === args.amountOut);
|
|
3409
|
+
return {
|
|
3410
|
+
...itemBase(event.logIndex),
|
|
3411
|
+
data: {
|
|
3412
|
+
destinationToken: token(args.amountOut, args.userToken),
|
|
3413
|
+
signer,
|
|
3414
|
+
sourceToken: token(args.amountIn, args.validatorToken),
|
|
3415
|
+
swapper: lc(args.swapper),
|
|
2767
3416
|
},
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
const args =
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
3417
|
+
type: 'fee-rebalance-swap',
|
|
3418
|
+
};
|
|
3419
|
+
});
|
|
3420
|
+
// Validator fees distributed; claim the fee-manager payout leg.
|
|
3421
|
+
recognize(named('FeesDistributed'), (event) => {
|
|
3422
|
+
const args = event.args;
|
|
3423
|
+
takeTransfer((leg) => Address.isEqual(leg.address, args.token) &&
|
|
3424
|
+
Address.isEqual(transferArgs(leg).from, Addresses.feeManager) &&
|
|
3425
|
+
Address.isEqual(transferArgs(leg).to, args.validator) &&
|
|
3426
|
+
transferArgs(leg).amount === args.amount);
|
|
3427
|
+
return {
|
|
3428
|
+
...itemBase(event.logIndex),
|
|
3429
|
+
data: {
|
|
3430
|
+
signer,
|
|
3431
|
+
sourceToken: token(args.amount, args.token),
|
|
3432
|
+
validator: lc(args.validator),
|
|
2783
3433
|
},
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
3434
|
+
type: 'fees-distributed',
|
|
3435
|
+
};
|
|
3436
|
+
});
|
|
3437
|
+
// Reward pool funded: the funded token is the emitting contract itself;
|
|
3438
|
+
// claim the funding transfer.
|
|
3439
|
+
recognize(named('RewardDistributed'), (event) => {
|
|
3440
|
+
const args = event.args;
|
|
3441
|
+
takeTransfer((leg) => Address.isEqual(leg.address, event.address) &&
|
|
3442
|
+
Address.isEqual(transferArgs(leg).from, args.funder) &&
|
|
3443
|
+
Address.isEqual(transferArgs(leg).to, event.address) &&
|
|
3444
|
+
transferArgs(leg).amount === args.amount);
|
|
3445
|
+
return {
|
|
3446
|
+
...itemBase(event.logIndex),
|
|
3447
|
+
data: {
|
|
3448
|
+
funder: lc(args.funder),
|
|
3449
|
+
signer,
|
|
3450
|
+
sourceToken: token(args.amount, event.address),
|
|
2799
3451
|
},
|
|
2800
|
-
|
|
2801
|
-
|
|
3452
|
+
type: 'reward-distributed',
|
|
3453
|
+
};
|
|
3454
|
+
});
|
|
2802
3455
|
// Reward recipient redirected.
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
{
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
recipient: args.recipient.toLowerCase(),
|
|
2812
|
-
signer,
|
|
2813
|
-
},
|
|
2814
|
-
type: 'reward-recipient-set',
|
|
3456
|
+
recognize(named('RewardRecipientSet'), (event) => {
|
|
3457
|
+
const args = event.args;
|
|
3458
|
+
return {
|
|
3459
|
+
...itemBase(event.logIndex),
|
|
3460
|
+
data: {
|
|
3461
|
+
holder: lc(args.holder),
|
|
3462
|
+
recipient: lc(args.recipient),
|
|
3463
|
+
signer,
|
|
2815
3464
|
},
|
|
2816
|
-
|
|
2817
|
-
|
|
3465
|
+
type: 'reward-recipient-set',
|
|
3466
|
+
};
|
|
3467
|
+
});
|
|
2818
3468
|
// Access-key spending limit updated: `newLimit` is carried as the token
|
|
2819
3469
|
// amount so it enriches with the token's metadata.
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
{
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
signer,
|
|
2830
|
-
sourceToken: token(args.newLimit, args.token),
|
|
2831
|
-
},
|
|
2832
|
-
type: 'spending-limit-updated',
|
|
2833
|
-
},
|
|
2834
|
-
];
|
|
2835
|
-
}
|
|
2836
|
-
// Access-key lifecycle events — emitted alongside any co-located transfer.
|
|
2837
|
-
const accessKeyItems = [];
|
|
2838
|
-
for (const event of finalEvents) {
|
|
2839
|
-
if (event.eventName === 'KeyAuthorized' &&
|
|
2840
|
-
Address.isEqual(event.address, Addresses.accountKeychain)) {
|
|
2841
|
-
const args = event.args;
|
|
2842
|
-
accessKeyItems.push({
|
|
2843
|
-
...itemBase(logIndexOfEvent(event)),
|
|
2844
|
-
data: {
|
|
2845
|
-
account: args.account.toLowerCase(),
|
|
2846
|
-
expiry: Number(args.expiry),
|
|
2847
|
-
publicKey: args.publicKey.toLowerCase(),
|
|
2848
|
-
signatureType: Number(args.signatureType),
|
|
2849
|
-
},
|
|
2850
|
-
type: 'access-key-created',
|
|
2851
|
-
});
|
|
2852
|
-
}
|
|
2853
|
-
else if (event.eventName === 'KeyRevoked' &&
|
|
2854
|
-
Address.isEqual(event.address, Addresses.accountKeychain)) {
|
|
2855
|
-
const args = event.args;
|
|
2856
|
-
accessKeyItems.push({
|
|
2857
|
-
...itemBase(logIndexOfEvent(event)),
|
|
2858
|
-
data: {
|
|
2859
|
-
account: args.account.toLowerCase(),
|
|
2860
|
-
publicKey: args.publicKey.toLowerCase(),
|
|
2861
|
-
},
|
|
2862
|
-
type: 'access-key-revoked',
|
|
2863
|
-
});
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
|
-
// Token creation.
|
|
2867
|
-
const tokenCreated = finalEvents.find((e) => e.eventName === 'TokenCreated' && Address.isEqual(e.address, Addresses.tip20Factory));
|
|
2868
|
-
if (tokenCreated) {
|
|
2869
|
-
const args = tokenCreated.args;
|
|
2870
|
-
return [
|
|
2871
|
-
{
|
|
2872
|
-
...itemBase(logIndexOfEvent(tokenCreated)),
|
|
2873
|
-
data: {
|
|
2874
|
-
address: args.token.toLowerCase(),
|
|
2875
|
-
currency: args.currency,
|
|
2876
|
-
name: args.name,
|
|
2877
|
-
symbol: args.symbol,
|
|
2878
|
-
},
|
|
2879
|
-
type: 'token-created',
|
|
2880
|
-
},
|
|
2881
|
-
...accessKeyItems,
|
|
2882
|
-
];
|
|
2883
|
-
}
|
|
2884
|
-
// Governance / admin / DEX-bookkeeping events. Each maps a single decoded
|
|
2885
|
-
// event to a typed item. Checked after token creation (a token-creation tx
|
|
2886
|
-
// also emits `RoleMembershipUpdated`) and before the plain-transfer fallback;
|
|
2887
|
-
// these carry no token movement, so the event itself is the whole signal.
|
|
2888
|
-
const lc = (value) => value.toLowerCase();
|
|
2889
|
-
const hx = (value) => value.toLowerCase();
|
|
2890
|
-
const findEvent = (name) => finalEvents.find((e) => e.eventName === name);
|
|
2891
|
-
// DEX bookkeeping (taker swaps and `OrderPlaced` already returned above).
|
|
2892
|
-
const orderFilled = findEvent('OrderFilled');
|
|
2893
|
-
if (orderFilled) {
|
|
2894
|
-
const a = orderFilled.args;
|
|
2895
|
-
return [
|
|
2896
|
-
{
|
|
2897
|
-
...itemBase(logIndexOfEvent(orderFilled)),
|
|
2898
|
-
data: {
|
|
2899
|
-
amountFilled: a.amountFilled.toString(),
|
|
2900
|
-
maker: lc(a.maker),
|
|
2901
|
-
orderId: a.orderId.toString(),
|
|
2902
|
-
partialFill: a.partialFill,
|
|
2903
|
-
signer,
|
|
2904
|
-
taker: lc(a.taker),
|
|
2905
|
-
},
|
|
2906
|
-
type: 'order-filled',
|
|
2907
|
-
},
|
|
2908
|
-
];
|
|
2909
|
-
}
|
|
2910
|
-
const orderFlipped = findEvent('OrderFlipped');
|
|
2911
|
-
if (orderFlipped) {
|
|
2912
|
-
const a = orderFlipped.args;
|
|
2913
|
-
return [
|
|
2914
|
-
{
|
|
2915
|
-
...itemBase(logIndexOfEvent(orderFlipped)),
|
|
2916
|
-
data: {
|
|
2917
|
-
flipTick: Number(a.flipTick),
|
|
2918
|
-
maker: lc(a.maker),
|
|
2919
|
-
orderId: a.orderId.toString(),
|
|
2920
|
-
side: a.isBid ? 'bid' : 'ask',
|
|
2921
|
-
signer,
|
|
2922
|
-
sourceToken: token(a.amount, a.token),
|
|
2923
|
-
tick: Number(a.tick),
|
|
2924
|
-
},
|
|
2925
|
-
type: 'order-flipped',
|
|
2926
|
-
},
|
|
2927
|
-
];
|
|
2928
|
-
}
|
|
2929
|
-
const pairCreated = findEvent('PairCreated');
|
|
2930
|
-
if (pairCreated) {
|
|
2931
|
-
const a = pairCreated.args;
|
|
2932
|
-
return [
|
|
2933
|
-
{
|
|
2934
|
-
...itemBase(logIndexOfEvent(pairCreated)),
|
|
2935
|
-
data: { base: lc(a.base), key: hx(a.key), quote: lc(a.quote), signer },
|
|
2936
|
-
type: 'pair-created',
|
|
2937
|
-
},
|
|
2938
|
-
];
|
|
2939
|
-
}
|
|
2940
|
-
// TIP-20 token configuration / admin.
|
|
2941
|
-
const pauseStateUpdate = findEvent('PauseStateUpdate');
|
|
2942
|
-
if (pauseStateUpdate) {
|
|
2943
|
-
const a = pauseStateUpdate.args;
|
|
2944
|
-
return [
|
|
2945
|
-
{
|
|
2946
|
-
...itemBase(logIndexOfEvent(pauseStateUpdate)),
|
|
2947
|
-
data: { paused: a.isPaused, signer, updater: lc(a.updater) },
|
|
2948
|
-
type: 'token-pause-set',
|
|
3470
|
+
recognize(named('SpendingLimitUpdated'), (event) => {
|
|
3471
|
+
const args = event.args;
|
|
3472
|
+
return {
|
|
3473
|
+
...itemBase(event.logIndex),
|
|
3474
|
+
data: {
|
|
3475
|
+
account: lc(args.account),
|
|
3476
|
+
publicKey: lc(args.publicKey),
|
|
3477
|
+
signer,
|
|
3478
|
+
sourceToken: token(args.newLimit, args.token),
|
|
2949
3479
|
},
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3480
|
+
type: 'spending-limit-updated',
|
|
3481
|
+
};
|
|
3482
|
+
});
|
|
3483
|
+
// Access-key lifecycle events.
|
|
3484
|
+
recognize((event) => event.eventName === 'KeyAuthorized' &&
|
|
3485
|
+
Address.isEqual(event.address, Addresses.accountKeychain), (event) => {
|
|
3486
|
+
const args = event.args;
|
|
3487
|
+
return {
|
|
3488
|
+
...itemBase(event.logIndex),
|
|
3489
|
+
data: {
|
|
3490
|
+
account: lc(args.account),
|
|
3491
|
+
expiry: Number(args.expiry),
|
|
3492
|
+
publicKey: lc(args.publicKey),
|
|
3493
|
+
signatureType: Number(args.signatureType),
|
|
2960
3494
|
},
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
const
|
|
2966
|
-
return
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
3495
|
+
type: 'access-key-created',
|
|
3496
|
+
};
|
|
3497
|
+
});
|
|
3498
|
+
recognize((event) => event.eventName === 'KeyRevoked' && Address.isEqual(event.address, Addresses.accountKeychain), (event) => {
|
|
3499
|
+
const args = event.args;
|
|
3500
|
+
return {
|
|
3501
|
+
...itemBase(event.logIndex),
|
|
3502
|
+
data: {
|
|
3503
|
+
account: lc(args.account),
|
|
3504
|
+
publicKey: lc(args.publicKey),
|
|
2971
3505
|
},
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3506
|
+
type: 'access-key-revoked',
|
|
3507
|
+
};
|
|
3508
|
+
});
|
|
3509
|
+
// Token creation. Factory initialization (role grants, config) precedes the
|
|
3510
|
+
// creation event; claim those, leaving later admin actions on the token as
|
|
3511
|
+
// independent activities.
|
|
3512
|
+
recognize((event) => event.eventName === 'TokenCreated' && Address.isEqual(event.address, Addresses.tip20Factory), (event) => {
|
|
3513
|
+
const args = event.args;
|
|
3514
|
+
unclaimed((e) => e.logIndex < event.logIndex && Address.isEqual(e.address, args.token)).forEach(claim);
|
|
3515
|
+
return {
|
|
3516
|
+
...itemBase(event.logIndex),
|
|
3517
|
+
data: {
|
|
3518
|
+
address: lc(args.token),
|
|
3519
|
+
currency: args.currency,
|
|
3520
|
+
name: args.name,
|
|
3521
|
+
symbol: args.symbol,
|
|
2982
3522
|
},
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
3523
|
+
type: 'token-created',
|
|
3524
|
+
};
|
|
3525
|
+
});
|
|
3526
|
+
// Governance / admin / DEX-bookkeeping events. Each maps a single decoded
|
|
3527
|
+
// event to a typed item; every remaining occurrence emits. These carry no
|
|
3528
|
+
// token movement, so the event itself is the whole signal.
|
|
3529
|
+
recognize(named('OrderFilled'), (event) => {
|
|
3530
|
+
const a = event.args;
|
|
3531
|
+
return {
|
|
3532
|
+
...itemBase(event.logIndex),
|
|
3533
|
+
data: {
|
|
3534
|
+
amountFilled: a.amountFilled.toString(),
|
|
3535
|
+
maker: lc(a.maker),
|
|
3536
|
+
orderId: a.orderId.toString(),
|
|
3537
|
+
partialFill: a.partialFill,
|
|
3538
|
+
signer,
|
|
3539
|
+
taker: lc(a.taker),
|
|
2993
3540
|
},
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
const a =
|
|
2999
|
-
return
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3541
|
+
type: 'order-filled',
|
|
3542
|
+
};
|
|
3543
|
+
});
|
|
3544
|
+
recognize(named('OrderFlipped'), (event) => {
|
|
3545
|
+
const a = event.args;
|
|
3546
|
+
return {
|
|
3547
|
+
...itemBase(event.logIndex),
|
|
3548
|
+
data: {
|
|
3549
|
+
flipTick: Number(a.flipTick),
|
|
3550
|
+
maker: lc(a.maker),
|
|
3551
|
+
orderId: a.orderId.toString(),
|
|
3552
|
+
side: a.isBid ? 'bid' : 'ask',
|
|
3553
|
+
signer,
|
|
3554
|
+
sourceToken: token(a.amount, a.token),
|
|
3555
|
+
tick: Number(a.tick),
|
|
3004
3556
|
},
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
const a =
|
|
3010
|
-
return
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3557
|
+
type: 'order-flipped',
|
|
3558
|
+
};
|
|
3559
|
+
});
|
|
3560
|
+
recognize(named('PairCreated'), (event) => {
|
|
3561
|
+
const a = event.args;
|
|
3562
|
+
return {
|
|
3563
|
+
...itemBase(event.logIndex),
|
|
3564
|
+
data: { base: lc(a.base), key: hx(a.key), quote: lc(a.quote), signer },
|
|
3565
|
+
type: 'pair-created',
|
|
3566
|
+
};
|
|
3567
|
+
});
|
|
3568
|
+
// TIP-20 token configuration / admin.
|
|
3569
|
+
recognize(named('PauseStateUpdate'), (event) => {
|
|
3570
|
+
const a = event.args;
|
|
3571
|
+
return {
|
|
3572
|
+
...itemBase(event.logIndex),
|
|
3573
|
+
data: { paused: a.isPaused, signer, updater: lc(a.updater) },
|
|
3574
|
+
type: 'token-pause-set',
|
|
3575
|
+
};
|
|
3576
|
+
});
|
|
3577
|
+
recognize(named('SupplyCapUpdate'), (event) => {
|
|
3578
|
+
const a = event.args;
|
|
3579
|
+
return {
|
|
3580
|
+
...itemBase(event.logIndex),
|
|
3581
|
+
data: { signer, supplyCap: a.newSupplyCap.toString(), updater: lc(a.updater) },
|
|
3582
|
+
type: 'token-supply-cap-set',
|
|
3583
|
+
};
|
|
3584
|
+
});
|
|
3585
|
+
recognize(named('TransferPolicyUpdate'), (event) => {
|
|
3586
|
+
const a = event.args;
|
|
3587
|
+
return {
|
|
3588
|
+
...itemBase(event.logIndex),
|
|
3589
|
+
data: { policyId: a.newPolicyId.toString(), signer, updater: lc(a.updater) },
|
|
3590
|
+
type: 'token-transfer-policy-set',
|
|
3591
|
+
};
|
|
3592
|
+
});
|
|
3593
|
+
recognize(named('QuoteTokenUpdate'), (event) => {
|
|
3594
|
+
const a = event.args;
|
|
3595
|
+
return {
|
|
3596
|
+
...itemBase(event.logIndex),
|
|
3597
|
+
data: { quoteToken: lc(a.newQuoteToken), signer, updater: lc(a.updater) },
|
|
3598
|
+
type: 'token-quote-token-set',
|
|
3599
|
+
};
|
|
3600
|
+
});
|
|
3601
|
+
recognize(named('NextQuoteTokenSet'), (event) => {
|
|
3602
|
+
const a = event.args;
|
|
3603
|
+
return {
|
|
3604
|
+
...itemBase(event.logIndex),
|
|
3605
|
+
data: { nextQuoteToken: lc(a.nextQuoteToken), signer, updater: lc(a.updater) },
|
|
3606
|
+
type: 'token-next-quote-token-set',
|
|
3607
|
+
};
|
|
3608
|
+
});
|
|
3609
|
+
recognize(named('LogoURIUpdated'), (event) => {
|
|
3610
|
+
const a = event.args;
|
|
3611
|
+
return {
|
|
3612
|
+
...itemBase(event.logIndex),
|
|
3613
|
+
data: { logoUri: a.newLogoURI, signer, updater: lc(a.updater) },
|
|
3614
|
+
type: 'token-logo-set',
|
|
3615
|
+
};
|
|
3616
|
+
});
|
|
3617
|
+
recognize(named('RoleMembershipUpdated'), (event) => {
|
|
3618
|
+
const a = event.args;
|
|
3619
|
+
return {
|
|
3620
|
+
...itemBase(event.logIndex),
|
|
3621
|
+
data: {
|
|
3622
|
+
account: lc(a.account),
|
|
3623
|
+
granted: a.hasRole,
|
|
3624
|
+
role: hx(a.role),
|
|
3625
|
+
sender: lc(a.sender),
|
|
3626
|
+
signer,
|
|
3021
3627
|
},
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
const a =
|
|
3027
|
-
return
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
signer,
|
|
3035
|
-
},
|
|
3036
|
-
type: 'role-admin-set',
|
|
3628
|
+
type: 'role-membership-set',
|
|
3629
|
+
};
|
|
3630
|
+
});
|
|
3631
|
+
recognize(named('RoleAdminUpdated'), (event) => {
|
|
3632
|
+
const a = event.args;
|
|
3633
|
+
return {
|
|
3634
|
+
...itemBase(event.logIndex),
|
|
3635
|
+
data: {
|
|
3636
|
+
newAdminRole: hx(a.newAdminRole),
|
|
3637
|
+
role: hx(a.role),
|
|
3638
|
+
sender: lc(a.sender),
|
|
3639
|
+
signer,
|
|
3037
3640
|
},
|
|
3038
|
-
|
|
3039
|
-
|
|
3641
|
+
type: 'role-admin-set',
|
|
3642
|
+
};
|
|
3643
|
+
});
|
|
3040
3644
|
// Fee-manager token configuration.
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
{
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
];
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
return
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
signer,
|
|
3077
|
-
},
|
|
3078
|
-
type: 'compound-policy-created',
|
|
3645
|
+
recognize(named('UserTokenSet'), (event) => {
|
|
3646
|
+
const a = event.args;
|
|
3647
|
+
return {
|
|
3648
|
+
...itemBase(event.logIndex),
|
|
3649
|
+
data: { signer, token: lc(a.token), user: lc(a.user) },
|
|
3650
|
+
type: 'fee-user-token-set',
|
|
3651
|
+
};
|
|
3652
|
+
});
|
|
3653
|
+
recognize(named('ValidatorTokenSet'), (event) => {
|
|
3654
|
+
const a = event.args;
|
|
3655
|
+
return {
|
|
3656
|
+
...itemBase(event.logIndex),
|
|
3657
|
+
data: { signer, token: lc(a.token), validator: lc(a.validator) },
|
|
3658
|
+
type: 'fee-validator-token-set',
|
|
3659
|
+
};
|
|
3660
|
+
});
|
|
3661
|
+
// TIP-403 policy registry. Creation events claim the member events they
|
|
3662
|
+
// bundle (which precede the anchor); later member updates stay independent.
|
|
3663
|
+
recognize(named('CompoundPolicyCreated'), (event) => {
|
|
3664
|
+
const a = event.args;
|
|
3665
|
+
const componentIds = new Set([a.mintRecipientPolicyId, a.recipientPolicyId, a.senderPolicyId].map(String));
|
|
3666
|
+
unclaimed((e) => e.logIndex < event.logIndex &&
|
|
3667
|
+
Address.isEqual(e.address, event.address) &&
|
|
3668
|
+
['BlacklistUpdated', 'PolicyAdminUpdated', 'PolicyCreated', 'WhitelistUpdated'].includes(e.eventName) &&
|
|
3669
|
+
'policyId' in e.args &&
|
|
3670
|
+
componentIds.has(String(e.args.policyId))).forEach(claim);
|
|
3671
|
+
return {
|
|
3672
|
+
...itemBase(event.logIndex),
|
|
3673
|
+
data: {
|
|
3674
|
+
creator: lc(a.creator),
|
|
3675
|
+
mintRecipientPolicyId: a.mintRecipientPolicyId.toString(),
|
|
3676
|
+
policyId: a.policyId.toString(),
|
|
3677
|
+
recipientPolicyId: a.recipientPolicyId.toString(),
|
|
3678
|
+
senderPolicyId: a.senderPolicyId.toString(),
|
|
3679
|
+
signer,
|
|
3079
3680
|
},
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
const a =
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3681
|
+
type: 'compound-policy-created',
|
|
3682
|
+
};
|
|
3683
|
+
});
|
|
3684
|
+
recognize(named('PolicyCreated'), (event) => {
|
|
3685
|
+
const a = event.args;
|
|
3686
|
+
unclaimed((e) => e.logIndex < event.logIndex &&
|
|
3687
|
+
Address.isEqual(e.address, event.address) &&
|
|
3688
|
+
['BlacklistUpdated', 'PolicyAdminUpdated', 'WhitelistUpdated'].includes(e.eventName) &&
|
|
3689
|
+
'policyId' in e.args &&
|
|
3690
|
+
e.args.policyId === a.policyId).forEach(claim);
|
|
3691
|
+
return {
|
|
3692
|
+
...itemBase(event.logIndex),
|
|
3693
|
+
data: {
|
|
3694
|
+
policyId: a.policyId.toString(),
|
|
3695
|
+
policyType: Number(a.policyType),
|
|
3696
|
+
signer,
|
|
3697
|
+
updater: lc(a.updater),
|
|
3095
3698
|
},
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
const a =
|
|
3101
|
-
return
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
updater: lc(a.updater),
|
|
3109
|
-
},
|
|
3110
|
-
type: 'policy-admin-set',
|
|
3699
|
+
type: 'policy-created',
|
|
3700
|
+
};
|
|
3701
|
+
});
|
|
3702
|
+
recognize(named('PolicyAdminUpdated'), (event) => {
|
|
3703
|
+
const a = event.args;
|
|
3704
|
+
return {
|
|
3705
|
+
...itemBase(event.logIndex),
|
|
3706
|
+
data: {
|
|
3707
|
+
admin: lc(a.admin),
|
|
3708
|
+
policyId: a.policyId.toString(),
|
|
3709
|
+
signer,
|
|
3710
|
+
updater: lc(a.updater),
|
|
3111
3711
|
},
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
const a =
|
|
3117
|
-
return
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
updater: lc(a.updater),
|
|
3126
|
-
},
|
|
3127
|
-
type: 'whitelist-updated',
|
|
3712
|
+
type: 'policy-admin-set',
|
|
3713
|
+
};
|
|
3714
|
+
});
|
|
3715
|
+
recognize(named('WhitelistUpdated'), (event) => {
|
|
3716
|
+
const a = event.args;
|
|
3717
|
+
return {
|
|
3718
|
+
...itemBase(event.logIndex),
|
|
3719
|
+
data: {
|
|
3720
|
+
account: lc(a.account),
|
|
3721
|
+
allowed: a.allowed,
|
|
3722
|
+
policyId: a.policyId.toString(),
|
|
3723
|
+
signer,
|
|
3724
|
+
updater: lc(a.updater),
|
|
3128
3725
|
},
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
const a =
|
|
3134
|
-
return
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
updater: lc(a.updater),
|
|
3143
|
-
},
|
|
3144
|
-
type: 'blacklist-updated',
|
|
3726
|
+
type: 'whitelist-updated',
|
|
3727
|
+
};
|
|
3728
|
+
});
|
|
3729
|
+
recognize(named('BlacklistUpdated'), (event) => {
|
|
3730
|
+
const a = event.args;
|
|
3731
|
+
return {
|
|
3732
|
+
...itemBase(event.logIndex),
|
|
3733
|
+
data: {
|
|
3734
|
+
account: lc(a.account),
|
|
3735
|
+
policyId: a.policyId.toString(),
|
|
3736
|
+
restricted: a.restricted,
|
|
3737
|
+
signer,
|
|
3738
|
+
updater: lc(a.updater),
|
|
3145
3739
|
},
|
|
3146
|
-
|
|
3147
|
-
|
|
3740
|
+
type: 'blacklist-updated',
|
|
3741
|
+
};
|
|
3742
|
+
});
|
|
3148
3743
|
// Address / nonce registry.
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
{
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
data: {
|
|
3167
|
-
account: lc(a.account),
|
|
3168
|
-
nonce: a.newNonce.toString(),
|
|
3169
|
-
nonceKey: a.nonceKey.toString(),
|
|
3170
|
-
signer,
|
|
3171
|
-
},
|
|
3172
|
-
type: 'nonce-incremented',
|
|
3744
|
+
recognize(named('MasterRegistered'), (event) => {
|
|
3745
|
+
const a = event.args;
|
|
3746
|
+
return {
|
|
3747
|
+
...itemBase(event.logIndex),
|
|
3748
|
+
data: { masterAddress: lc(a.masterAddress), masterId: hx(a.masterId), signer },
|
|
3749
|
+
type: 'master-registered',
|
|
3750
|
+
};
|
|
3751
|
+
});
|
|
3752
|
+
recognize(named('NonceIncremented'), (event) => {
|
|
3753
|
+
const a = event.args;
|
|
3754
|
+
return {
|
|
3755
|
+
...itemBase(event.logIndex),
|
|
3756
|
+
data: {
|
|
3757
|
+
account: lc(a.account),
|
|
3758
|
+
nonce: a.newNonce.toString(),
|
|
3759
|
+
nonceKey: a.nonceKey.toString(),
|
|
3760
|
+
signer,
|
|
3173
3761
|
},
|
|
3174
|
-
|
|
3175
|
-
|
|
3762
|
+
type: 'nonce-incremented',
|
|
3763
|
+
};
|
|
3764
|
+
});
|
|
3176
3765
|
// Access-key authorization witnesses.
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
{
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
},
|
|
3217
|
-
type: 'validator-rotated',
|
|
3218
|
-
},
|
|
3219
|
-
];
|
|
3220
|
-
}
|
|
3221
|
-
const validatorAdded = findEvent('ValidatorAdded');
|
|
3222
|
-
if (validatorAdded) {
|
|
3223
|
-
const a = validatorAdded.args;
|
|
3224
|
-
return [
|
|
3225
|
-
{
|
|
3226
|
-
...itemBase(logIndexOfEvent(validatorAdded)),
|
|
3227
|
-
data: {
|
|
3228
|
-
egress: a.egress,
|
|
3229
|
-
feeRecipient: lc(a.feeRecipient),
|
|
3230
|
-
index: a.index.toString(),
|
|
3231
|
-
ingress: a.ingress,
|
|
3232
|
-
publicKey: hx(a.publicKey),
|
|
3233
|
-
signer,
|
|
3234
|
-
validator: lc(a.validatorAddress),
|
|
3235
|
-
},
|
|
3236
|
-
type: 'validator-added',
|
|
3237
|
-
},
|
|
3238
|
-
];
|
|
3239
|
-
}
|
|
3240
|
-
const validatorDeactivated = findEvent('ValidatorDeactivated');
|
|
3241
|
-
if (validatorDeactivated) {
|
|
3242
|
-
const a = validatorDeactivated.args;
|
|
3243
|
-
return [
|
|
3244
|
-
{
|
|
3245
|
-
...itemBase(logIndexOfEvent(validatorDeactivated)),
|
|
3246
|
-
data: { index: a.index.toString(), signer, validator: lc(a.validatorAddress) },
|
|
3247
|
-
type: 'validator-deactivated',
|
|
3248
|
-
},
|
|
3249
|
-
];
|
|
3250
|
-
}
|
|
3251
|
-
const validatorMigrated = findEvent('ValidatorMigrated');
|
|
3252
|
-
if (validatorMigrated) {
|
|
3253
|
-
const a = validatorMigrated.args;
|
|
3254
|
-
return [
|
|
3255
|
-
{
|
|
3256
|
-
...itemBase(logIndexOfEvent(validatorMigrated)),
|
|
3257
|
-
data: {
|
|
3258
|
-
index: a.index.toString(),
|
|
3259
|
-
publicKey: hx(a.publicKey),
|
|
3260
|
-
signer,
|
|
3261
|
-
validator: lc(a.validatorAddress),
|
|
3262
|
-
},
|
|
3263
|
-
type: 'validator-migrated',
|
|
3766
|
+
recognize(named('KeyAuthorizationWitness'), (event) => {
|
|
3767
|
+
const a = event.args;
|
|
3768
|
+
return {
|
|
3769
|
+
...itemBase(event.logIndex),
|
|
3770
|
+
data: { account: lc(a.account), signer, witness: hx(a.witness) },
|
|
3771
|
+
type: 'key-authorization-witness',
|
|
3772
|
+
};
|
|
3773
|
+
});
|
|
3774
|
+
recognize(named('KeyAuthorizationWitnessBurned'), (event) => {
|
|
3775
|
+
const a = event.args;
|
|
3776
|
+
return {
|
|
3777
|
+
...itemBase(event.logIndex),
|
|
3778
|
+
data: { account: lc(a.account), signer, witness: hx(a.witness) },
|
|
3779
|
+
type: 'key-authorization-witness-burned',
|
|
3780
|
+
};
|
|
3781
|
+
});
|
|
3782
|
+
// Validator configuration. Rotation claims the add/deactivate pair it
|
|
3783
|
+
// bundles, matched by validator index.
|
|
3784
|
+
recognize(named('ValidatorRotated'), (event) => {
|
|
3785
|
+
const a = event.args;
|
|
3786
|
+
unclaimed((e) => {
|
|
3787
|
+
if (!('index' in e.args))
|
|
3788
|
+
return false;
|
|
3789
|
+
const index = e.args.index;
|
|
3790
|
+
return ((e.eventName === 'ValidatorAdded' && index === a.index) ||
|
|
3791
|
+
(e.eventName === 'ValidatorDeactivated' && index === a.deactivatedIndex));
|
|
3792
|
+
}).forEach(claim);
|
|
3793
|
+
return {
|
|
3794
|
+
...itemBase(event.logIndex),
|
|
3795
|
+
data: {
|
|
3796
|
+
caller: lc(a.caller),
|
|
3797
|
+
deactivatedIndex: a.deactivatedIndex.toString(),
|
|
3798
|
+
egress: a.egress,
|
|
3799
|
+
index: a.index.toString(),
|
|
3800
|
+
ingress: a.ingress,
|
|
3801
|
+
newPublicKey: hx(a.newPublicKey),
|
|
3802
|
+
oldPublicKey: hx(a.oldPublicKey),
|
|
3803
|
+
signer,
|
|
3804
|
+
validator: lc(a.validatorAddress),
|
|
3264
3805
|
},
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
const a =
|
|
3270
|
-
return
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3806
|
+
type: 'validator-rotated',
|
|
3807
|
+
};
|
|
3808
|
+
});
|
|
3809
|
+
recognize(named('ValidatorAdded'), (event) => {
|
|
3810
|
+
const a = event.args;
|
|
3811
|
+
return {
|
|
3812
|
+
...itemBase(event.logIndex),
|
|
3813
|
+
data: {
|
|
3814
|
+
egress: a.egress,
|
|
3815
|
+
feeRecipient: lc(a.feeRecipient),
|
|
3816
|
+
index: a.index.toString(),
|
|
3817
|
+
ingress: a.ingress,
|
|
3818
|
+
publicKey: hx(a.publicKey),
|
|
3819
|
+
signer,
|
|
3820
|
+
validator: lc(a.validatorAddress),
|
|
3280
3821
|
},
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
const a =
|
|
3286
|
-
return
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3822
|
+
type: 'validator-added',
|
|
3823
|
+
};
|
|
3824
|
+
});
|
|
3825
|
+
recognize(named('ValidatorDeactivated'), (event) => {
|
|
3826
|
+
const a = event.args;
|
|
3827
|
+
return {
|
|
3828
|
+
...itemBase(event.logIndex),
|
|
3829
|
+
data: { index: a.index.toString(), signer, validator: lc(a.validatorAddress) },
|
|
3830
|
+
type: 'validator-deactivated',
|
|
3831
|
+
};
|
|
3832
|
+
});
|
|
3833
|
+
recognize(named('ValidatorMigrated'), (event) => {
|
|
3834
|
+
const a = event.args;
|
|
3835
|
+
return {
|
|
3836
|
+
...itemBase(event.logIndex),
|
|
3837
|
+
data: {
|
|
3838
|
+
index: a.index.toString(),
|
|
3839
|
+
publicKey: hx(a.publicKey),
|
|
3840
|
+
signer,
|
|
3841
|
+
validator: lc(a.validatorAddress),
|
|
3296
3842
|
},
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
const a =
|
|
3302
|
-
return
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
ingress: a.ingress,
|
|
3310
|
-
signer,
|
|
3311
|
-
},
|
|
3312
|
-
type: 'validator-ip-set',
|
|
3843
|
+
type: 'validator-migrated',
|
|
3844
|
+
};
|
|
3845
|
+
});
|
|
3846
|
+
recognize(named('SkippedValidatorMigration'), (event) => {
|
|
3847
|
+
const a = event.args;
|
|
3848
|
+
return {
|
|
3849
|
+
...itemBase(event.logIndex),
|
|
3850
|
+
data: {
|
|
3851
|
+
index: a.index.toString(),
|
|
3852
|
+
publicKey: hx(a.publicKey),
|
|
3853
|
+
signer,
|
|
3854
|
+
validator: lc(a.validatorAddress),
|
|
3313
3855
|
},
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
const a =
|
|
3319
|
-
return
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
oldAddress: lc(a.oldAddress),
|
|
3327
|
-
signer,
|
|
3328
|
-
},
|
|
3329
|
-
type: 'validator-ownership-transferred',
|
|
3856
|
+
type: 'validator-migration-skipped',
|
|
3857
|
+
};
|
|
3858
|
+
});
|
|
3859
|
+
recognize(named('FeeRecipientUpdated'), (event) => {
|
|
3860
|
+
const a = event.args;
|
|
3861
|
+
return {
|
|
3862
|
+
...itemBase(event.logIndex),
|
|
3863
|
+
data: {
|
|
3864
|
+
caller: lc(a.caller),
|
|
3865
|
+
feeRecipient: lc(a.feeRecipient),
|
|
3866
|
+
index: a.index.toString(),
|
|
3867
|
+
signer,
|
|
3330
3868
|
},
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
const a =
|
|
3336
|
-
return
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3869
|
+
type: 'validator-fee-recipient-set',
|
|
3870
|
+
};
|
|
3871
|
+
});
|
|
3872
|
+
recognize(named('IpAddressesUpdated'), (event) => {
|
|
3873
|
+
const a = event.args;
|
|
3874
|
+
return {
|
|
3875
|
+
...itemBase(event.logIndex),
|
|
3876
|
+
data: {
|
|
3877
|
+
caller: lc(a.caller),
|
|
3878
|
+
egress: a.egress,
|
|
3879
|
+
index: a.index.toString(),
|
|
3880
|
+
ingress: a.ingress,
|
|
3881
|
+
signer,
|
|
3341
3882
|
},
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
const a =
|
|
3347
|
-
return
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
type: 'network-identity-rotation-epoch-set',
|
|
3883
|
+
type: 'validator-ip-set',
|
|
3884
|
+
};
|
|
3885
|
+
});
|
|
3886
|
+
recognize(named('ValidatorOwnershipTransferred'), (event) => {
|
|
3887
|
+
const a = event.args;
|
|
3888
|
+
return {
|
|
3889
|
+
...itemBase(event.logIndex),
|
|
3890
|
+
data: {
|
|
3891
|
+
caller: lc(a.caller),
|
|
3892
|
+
index: a.index.toString(),
|
|
3893
|
+
newAddress: lc(a.newAddress),
|
|
3894
|
+
oldAddress: lc(a.oldAddress),
|
|
3895
|
+
signer,
|
|
3356
3896
|
},
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
const a =
|
|
3362
|
-
return
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3897
|
+
type: 'validator-ownership-transferred',
|
|
3898
|
+
};
|
|
3899
|
+
});
|
|
3900
|
+
recognize(named('OwnershipTransferred'), (event) => {
|
|
3901
|
+
const a = event.args;
|
|
3902
|
+
return {
|
|
3903
|
+
...itemBase(event.logIndex),
|
|
3904
|
+
data: { newOwner: lc(a.newOwner), oldOwner: lc(a.oldOwner), signer },
|
|
3905
|
+
type: 'ownership-transferred',
|
|
3906
|
+
};
|
|
3907
|
+
});
|
|
3908
|
+
recognize(named('NetworkIdentityRotationEpochSet'), (event) => {
|
|
3909
|
+
const a = event.args;
|
|
3910
|
+
return {
|
|
3911
|
+
...itemBase(event.logIndex),
|
|
3912
|
+
data: {
|
|
3913
|
+
nextEpoch: a.nextEpoch.toString(),
|
|
3914
|
+
previousEpoch: a.previousEpoch.toString(),
|
|
3915
|
+
signer,
|
|
3367
3916
|
},
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3917
|
+
type: 'network-identity-rotation-epoch-set',
|
|
3918
|
+
};
|
|
3919
|
+
});
|
|
3920
|
+
recognize(named('Initialized'), (event) => {
|
|
3921
|
+
const a = event.args;
|
|
3922
|
+
return {
|
|
3923
|
+
...itemBase(event.logIndex),
|
|
3924
|
+
data: { height: a.height.toString(), signer },
|
|
3925
|
+
type: 'initialized',
|
|
3926
|
+
};
|
|
3927
|
+
});
|
|
3928
|
+
// Plain transfers: every remaining viewer-party transfer is its own
|
|
3929
|
+
// activity. Non-viewer transfers are internal legs of what classified
|
|
3930
|
+
// above and surface only via the fallback below.
|
|
3931
|
+
const buildTransfer = (event) => {
|
|
3932
|
+
const args = transferArgs(event);
|
|
3933
|
+
const to = lc(args.to);
|
|
3934
|
+
const from = lc(args.from);
|
|
3935
|
+
const rawMemo = event.eventName === 'TransferWithMemo' ? args.memo : undefined;
|
|
3378
3936
|
// The raw memo (not the suppressed display string) carries the MPP
|
|
3379
3937
|
// attribution fingerprint, so resolve the service name from it.
|
|
3380
3938
|
const memo = rawMemo ? Transfers.memoToString(rawMemo) : undefined;
|
|
3381
3939
|
const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo });
|
|
3382
|
-
const sourceToken = token(args.amount,
|
|
3940
|
+
const sourceToken = token(args.amount, event.address);
|
|
3383
3941
|
if (Address.isEqual(to, viewer))
|
|
3384
|
-
return
|
|
3385
|
-
transfer(itemBase(logIndexOfEvent(t)), blockNum, {
|
|
3386
|
-
attribution,
|
|
3387
|
-
direction: 'in',
|
|
3388
|
-
memo,
|
|
3389
|
-
recipient: viewer,
|
|
3390
|
-
sender: from,
|
|
3391
|
-
sourceToken,
|
|
3392
|
-
}),
|
|
3393
|
-
...accessKeyItems,
|
|
3394
|
-
];
|
|
3395
|
-
return [
|
|
3396
|
-
transfer(itemBase(logIndexOfEvent(t)), blockNum, {
|
|
3942
|
+
return transfer(itemBase(event.logIndex), blockNum, {
|
|
3397
3943
|
attribution,
|
|
3398
|
-
direction: '
|
|
3944
|
+
direction: 'in',
|
|
3399
3945
|
memo,
|
|
3400
|
-
recipient:
|
|
3946
|
+
recipient: viewer,
|
|
3401
3947
|
sender: from,
|
|
3402
|
-
signer,
|
|
3403
3948
|
sourceToken,
|
|
3404
|
-
})
|
|
3405
|
-
|
|
3406
|
-
|
|
3949
|
+
});
|
|
3950
|
+
return transfer(itemBase(event.logIndex), blockNum, {
|
|
3951
|
+
attribution,
|
|
3952
|
+
direction: 'out',
|
|
3953
|
+
memo,
|
|
3954
|
+
recipient: to,
|
|
3955
|
+
sender: from,
|
|
3956
|
+
signer,
|
|
3957
|
+
sourceToken,
|
|
3958
|
+
});
|
|
3959
|
+
};
|
|
3960
|
+
const isUserTransfer = (event) => isTransfer(event) && !Address.isEqual(transferArgs(event).to, Addresses.feeManager);
|
|
3961
|
+
recognize((event) => isUserTransfer(event) &&
|
|
3962
|
+
(Address.isEqual(transferArgs(event).from, viewer) ||
|
|
3963
|
+
Address.isEqual(transferArgs(event).to, viewer)), buildTransfer);
|
|
3964
|
+
// Fallback when nothing viewer-facing classified: surface fold-only vault
|
|
3965
|
+
// items, third-party approvals, and transfers (e.g. a sponsored batch)
|
|
3966
|
+
// instead of reporting `unknown`.
|
|
3967
|
+
if (items.length === 0) {
|
|
3968
|
+
items.push(...suppressedVaultItems);
|
|
3969
|
+
recognize(isErc20Approval, approvalItem);
|
|
3970
|
+
recognize(isUserTransfer, buildTransfer);
|
|
3407
3971
|
}
|
|
3408
|
-
//
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
return [unknown(itemBase(minLogIndex), { events: buildTxEvents(rows, events), signer })];
|
|
3415
|
-
}
|
|
3416
|
-
/** Extracts the block-wide log index from a decoded viem event. */
|
|
3417
|
-
function logIndexOfEvent(event) {
|
|
3418
|
-
return event.logIndex;
|
|
3972
|
+
// Unclassified: a tx the address sent or paid for whose logs matched no
|
|
3973
|
+
// known classifier. Surface it honestly as an `unknown` item carrying every
|
|
3974
|
+
// log (decoded when possible), rather than fabricating a zero transfer.
|
|
3975
|
+
if (items.length === 0)
|
|
3976
|
+
return [unknown(itemBase(minLogIndex), { events: buildTxEvents(rows, events), signer })];
|
|
3977
|
+
return items.sort((a, b) => a.logIndex - b.logIndex);
|
|
3419
3978
|
}
|
|
3420
3979
|
/**
|
|
3421
3980
|
* Builds a `transfer` item whose `data` is the `Transfer` API resource plus the
|
|
@@ -3454,29 +4013,31 @@ function unknown(base, data) {
|
|
|
3454
4013
|
}
|
|
3455
4014
|
/** Decodes a group's raw rows into `viem/tempo` events via `parseEventLogs` (non-strict). */
|
|
3456
4015
|
function decodeRows(rows) {
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
4016
|
+
const logs = rows.map((r) => ({
|
|
4017
|
+
address: r.logAddress,
|
|
4018
|
+
blockHash: '0x0',
|
|
4019
|
+
blockNumber: BigInt(r.blockNum),
|
|
4020
|
+
data: (r.data ?? '0x'),
|
|
4021
|
+
logIndex: r.logIdx,
|
|
4022
|
+
removed: false,
|
|
4023
|
+
topics: [r.selector, r.topic1, r.topic2, r.topic3].filter(Boolean),
|
|
4024
|
+
transactionHash: r.txHash,
|
|
4025
|
+
transactionIndex: r.txIdx,
|
|
4026
|
+
}));
|
|
4027
|
+
try {
|
|
4028
|
+
// One batched call: viem re-processes the merged ABI per invocation, so
|
|
4029
|
+
// per-row calls cost ~55x more. Non-strict decoding skips undecodable
|
|
4030
|
+
// logs per log and can yield `args: undefined`; normalize so `in`-guards
|
|
4031
|
+
// never throw.
|
|
4032
|
+
return parseEventLogs({
|
|
4033
|
+
abi: [...Abis.abis, ...Abis.earnRouter, ...Abis.earnVault, ...erc4626Events],
|
|
4034
|
+
logs,
|
|
4035
|
+
strict: false,
|
|
4036
|
+
}).map((event) => ({ ...event, args: event.args ?? {} }));
|
|
4037
|
+
}
|
|
4038
|
+
catch {
|
|
4039
|
+
return [];
|
|
4040
|
+
}
|
|
3480
4041
|
}
|
|
3481
4042
|
/**
|
|
3482
4043
|
* Builds the full per-transaction `events` capture: one entry per raw log,
|
|
@@ -3578,12 +4139,26 @@ async function enrichTokens(c, options) {
|
|
|
3578
4139
|
collectTokens(item, addresses);
|
|
3579
4140
|
const includeLogoUri = include.includes('token.logoUri');
|
|
3580
4141
|
const includeVerified = include.includes('token.verified');
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
4142
|
+
const resolved_promise = addresses.size > 0
|
|
4143
|
+
? Timing.time(c, 'tokens', () => Promise.all([...addresses].map(async (address) => {
|
|
4144
|
+
const [logoUri, metadata] = await Promise.all([
|
|
4145
|
+
includeLogoUri
|
|
4146
|
+
? Tokens.getTokenLogo(c, { address, chainId }).catch(() => undefined)
|
|
4147
|
+
: undefined,
|
|
4148
|
+
getActivityTokenMetadata(c, { address, chainId }),
|
|
4149
|
+
]);
|
|
4150
|
+
return { address: address.toLowerCase(), logoUri, metadata };
|
|
4151
|
+
})))
|
|
3584
4152
|
: undefined;
|
|
3585
|
-
|
|
3586
|
-
|
|
4153
|
+
// Curated data is needed only for requested valuation or token fields.
|
|
4154
|
+
const snapshot_promise = addresses.size > 0 && (denomination !== undefined || includeLogoUri || includeVerified)
|
|
4155
|
+
? VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
4156
|
+
: Promise.resolve(undefined);
|
|
4157
|
+
const rates_promise = (async () => {
|
|
4158
|
+
if (!denomination)
|
|
4159
|
+
return undefined;
|
|
4160
|
+
const snapshot = await snapshot_promise;
|
|
4161
|
+
return Valuation.ratesFor(c, {
|
|
3587
4162
|
currencies: snapshot
|
|
3588
4163
|
? [...addresses].flatMap((address) => {
|
|
3589
4164
|
const held = snapshot.byAddress.get(address)?.currency;
|
|
@@ -3592,37 +4167,63 @@ async function enrichTokens(c, options) {
|
|
|
3592
4167
|
: [],
|
|
3593
4168
|
denomination,
|
|
3594
4169
|
oracle,
|
|
3595
|
-
})
|
|
3596
|
-
|
|
3597
|
-
if (
|
|
3598
|
-
return { items: items, rates };
|
|
3599
|
-
const
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
4170
|
+
});
|
|
4171
|
+
})();
|
|
4172
|
+
if (!resolved_promise)
|
|
4173
|
+
return { items: items, rates: await rates_promise };
|
|
4174
|
+
const [resolved_raw, rates, snapshot] = await Promise.all([
|
|
4175
|
+
resolved_promise,
|
|
4176
|
+
rates_promise,
|
|
4177
|
+
snapshot_promise,
|
|
4178
|
+
]);
|
|
4179
|
+
const resolved = resolved_raw.map(({ address, logoUri, metadata }) => [
|
|
4180
|
+
address,
|
|
4181
|
+
{
|
|
4182
|
+
currency: metadata.currency,
|
|
4183
|
+
decimals: metadata.decimals,
|
|
4184
|
+
logoUri: includeLogoUri
|
|
4185
|
+
? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
|
|
3603
4186
|
: undefined,
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
currency: metadata.currency,
|
|
3610
|
-
decimals: metadata.decimals,
|
|
3611
|
-
logoUri: includeLogoUri
|
|
3612
|
-
? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
|
|
3613
|
-
: undefined,
|
|
3614
|
-
name: metadata.name,
|
|
3615
|
-
symbol: metadata.symbol,
|
|
3616
|
-
verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
|
|
3617
|
-
},
|
|
3618
|
-
];
|
|
3619
|
-
})));
|
|
4187
|
+
name: metadata.name,
|
|
4188
|
+
symbol: metadata.symbol,
|
|
4189
|
+
verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
|
|
4190
|
+
},
|
|
4191
|
+
]);
|
|
3620
4192
|
const byAddress = new Map(resolved);
|
|
3621
4193
|
return {
|
|
3622
4194
|
items: items.map((item) => enrichItem(item, byAddress, { denomination, rates, snapshot })),
|
|
3623
4195
|
rates,
|
|
3624
4196
|
};
|
|
3625
4197
|
}
|
|
4198
|
+
/** Resolves token metadata on each item's source chain without changing feed order. */
|
|
4199
|
+
async function enrichChainTokens(c, options) {
|
|
4200
|
+
const byChain = new Map();
|
|
4201
|
+
for (const item of options.items) {
|
|
4202
|
+
const items = byChain.get(item.chainId) ?? [];
|
|
4203
|
+
items.push(item);
|
|
4204
|
+
byChain.set(item.chainId, items);
|
|
4205
|
+
}
|
|
4206
|
+
const results = await Promise.all([...byChain].map(async ([chainId, items]) => ({
|
|
4207
|
+
chainId,
|
|
4208
|
+
...(await enrichTokens(c, {
|
|
4209
|
+
chainId,
|
|
4210
|
+
denomination: options.denomination,
|
|
4211
|
+
include: options.include,
|
|
4212
|
+
items,
|
|
4213
|
+
oracle: options.oracle,
|
|
4214
|
+
})),
|
|
4215
|
+
})));
|
|
4216
|
+
const buckets = new Map();
|
|
4217
|
+
for (const result of results)
|
|
4218
|
+
buckets.set(result.chainId, { index: 0, items: result.items });
|
|
4219
|
+
return {
|
|
4220
|
+
items: options.items.map((item) => {
|
|
4221
|
+
const bucket = buckets.get(item.chainId);
|
|
4222
|
+
return bucket.items[bucket.index++];
|
|
4223
|
+
}),
|
|
4224
|
+
rates: results.find((result) => result.rates !== undefined)?.rates,
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
3626
4227
|
/** Returns an enriched copy of an item without mutating its cached token stubs. */
|
|
3627
4228
|
function enrichItem(item, byAddress, valuation) {
|
|
3628
4229
|
const source = item.data;
|
|
@@ -3680,6 +4281,7 @@ function groupByAccessKeyItems(items) {
|
|
|
3680
4281
|
const itemSigner = signerOf(item);
|
|
3681
4282
|
if (itemSigner !== 'self') {
|
|
3682
4283
|
if (currentGroup &&
|
|
4284
|
+
currentGroup.items[0].chainId === item.chainId &&
|
|
3683
4285
|
Address.isEqual(currentGroup.signer, itemSigner) &&
|
|
3684
4286
|
sameCalendarDay(currentGroup.items[0].timestamp, item.timestamp)) {
|
|
3685
4287
|
currentGroup.items.push(item);
|
|
@@ -3717,6 +4319,7 @@ function flushGroup(group) {
|
|
|
3717
4319
|
const firstItem = group.items[0];
|
|
3718
4320
|
const lastItem = group.items[group.items.length - 1];
|
|
3719
4321
|
return {
|
|
4322
|
+
...(firstItem.chainId === undefined ? {} : { chainId: firstItem.chainId }),
|
|
3720
4323
|
data: { items: group.items, signer: group.signer },
|
|
3721
4324
|
perspective: firstItem.perspective,
|
|
3722
4325
|
id: `${firstItem.id}..${lastItem.id}:${group.items.length}`,
|