tapimo 0.0.2 → 0.2.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/README.md +38 -16
- package/dist/ApiKey.d.ts +14 -11
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +11 -12
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +207 -0
- package/dist/ApiKeys.d.ts.map +1 -0
- package/dist/ApiKeys.js +282 -0
- package/dist/ApiKeys.js.map +1 -0
- package/dist/App.d.ts +6771 -3819
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +145 -317
- package/dist/App.js.map +1 -1
- package/dist/Handler.d.ts +86 -0
- package/dist/Handler.d.ts.map +1 -0
- package/dist/Handler.js +83 -0
- package/dist/Handler.js.map +1 -0
- package/dist/Scope.d.ts +14 -4
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +16 -4
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +5 -7
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +11 -4
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/Access.d.ts +1 -3
- package/dist/admin/Access.d.ts.map +1 -1
- package/dist/admin/Access.js +1 -3
- package/dist/admin/Access.js.map +1 -1
- package/dist/admin/App.d.ts +167 -205
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +2 -4
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +105 -5
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +75 -9
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +6 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +35 -149
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +18 -39
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +1 -1
- package/dist/apps/data/App.d.ts +19176 -0
- package/dist/apps/data/App.d.ts.map +1 -0
- package/dist/apps/data/App.js +236 -0
- package/dist/apps/data/App.js.map +1 -0
- package/dist/apps/data/index.d.ts +3 -0
- package/dist/apps/data/index.d.ts.map +1 -0
- package/dist/apps/data/index.js +3 -0
- package/dist/apps/data/index.js.map +1 -0
- package/dist/apps/{activities.d.ts → data/routes/activities.d.ts} +32 -32
- package/dist/apps/{activities.d.ts.map → data/routes/activities.d.ts.map} +1 -1
- package/dist/apps/{activities.js → data/routes/activities.js} +13 -13
- package/dist/apps/data/routes/activities.js.map +1 -0
- package/dist/apps/{addresses.d.ts → data/routes/addresses.d.ts} +22 -22
- package/dist/apps/{addresses.d.ts.map → data/routes/addresses.d.ts.map} +1 -1
- package/dist/apps/{addresses.js → data/routes/addresses.js} +5 -5
- package/dist/apps/data/routes/addresses.js.map +1 -0
- package/dist/apps/{balances.d.ts → data/routes/balances.d.ts} +49 -49
- package/dist/apps/data/routes/balances.d.ts.map +1 -0
- package/dist/apps/{balances.js → data/routes/balances.js} +42 -11
- package/dist/apps/data/routes/balances.js.map +1 -0
- package/dist/apps/{blocks.d.ts → data/routes/blocks.d.ts} +47 -47
- package/dist/apps/data/routes/blocks.d.ts.map +1 -0
- package/dist/apps/{blocks.js → data/routes/blocks.js} +13 -13
- package/dist/apps/data/routes/blocks.js.map +1 -0
- package/dist/apps/{coingecko.d.ts → data/routes/coingecko.d.ts} +183 -183
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -0
- package/dist/apps/{coingecko.js → data/routes/coingecko.js} +13 -13
- package/dist/apps/data/routes/coingecko.js.map +1 -0
- package/dist/apps/{exchanges.d.ts → data/routes/exchanges.d.ts} +460 -460
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -0
- package/dist/apps/{exchanges.js → data/routes/exchanges.js} +19 -19
- package/dist/apps/data/routes/exchanges.js.map +1 -0
- package/dist/apps/{fee-amm.d.ts → data/routes/fee-amm.d.ts} +94 -94
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -0
- package/dist/apps/{fee-amm.js → data/routes/fee-amm.js} +17 -17
- package/dist/apps/data/routes/fee-amm.js.map +1 -0
- package/dist/apps/{indexer.d.ts → data/routes/indexer.d.ts} +4 -4
- package/dist/apps/data/routes/indexer.d.ts.map +1 -0
- package/dist/apps/{indexer.js → data/routes/indexer.js} +13 -9
- package/dist/apps/data/routes/indexer.js.map +1 -0
- package/dist/apps/{receipts.d.ts → data/routes/receipts.d.ts} +6 -6
- package/dist/apps/data/routes/receipts.d.ts.map +1 -0
- package/dist/apps/{receipts.js → data/routes/receipts.js} +12 -12
- package/dist/apps/data/routes/receipts.js.map +1 -0
- package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +4 -4
- package/dist/apps/data/routes/rpc.d.ts.map +1 -0
- package/dist/apps/{rpc.js → data/routes/rpc.js} +6 -6
- package/dist/apps/data/routes/rpc.js.map +1 -0
- package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +4 -4
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -0
- package/dist/apps/{tokenlist.js → data/routes/tokenlist.js} +8 -8
- package/dist/apps/data/routes/tokenlist.js.map +1 -0
- package/dist/apps/{tokens.d.ts → data/routes/tokens.d.ts} +11 -11
- package/dist/apps/data/routes/tokens.d.ts.map +1 -0
- package/dist/apps/{tokens.js → data/routes/tokens.js} +19 -19
- package/dist/apps/data/routes/tokens.js.map +1 -0
- package/dist/apps/{transactions.d.ts → data/routes/transactions.d.ts} +288 -288
- package/dist/apps/data/routes/transactions.d.ts.map +1 -0
- package/dist/apps/{transactions.js → data/routes/transactions.js} +13 -13
- package/dist/apps/data/routes/transactions.js.map +1 -0
- package/dist/apps/{transfers.d.ts → data/routes/transfers.d.ts} +35 -35
- package/dist/apps/data/routes/transfers.d.ts.map +1 -0
- package/dist/apps/{transfers.js → data/routes/transfers.js} +13 -13
- package/dist/apps/data/routes/transfers.js.map +1 -0
- package/dist/apps/{verified-tokens.d.ts → data/routes/verified-tokens.d.ts} +42 -42
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -0
- package/dist/apps/{verified-tokens.js → data/routes/verified-tokens.js} +9 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -0
- package/dist/apps/{webhooks.d.ts → data/routes/webhooks.d.ts} +66 -63
- package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
- package/dist/apps/{webhooks.js → data/routes/webhooks.js} +67 -59
- package/dist/apps/data/routes/webhooks.js.map +1 -0
- package/dist/apps/docs/App.d.ts +27 -0
- package/dist/apps/docs/App.d.ts.map +1 -0
- package/dist/apps/docs/App.js +122 -0
- package/dist/apps/docs/App.js.map +1 -0
- package/dist/{internal/OpenApi.fonts.d.ts → apps/docs/fonts.d.ts} +1 -1
- package/dist/apps/docs/fonts.d.ts.map +1 -0
- package/dist/{internal/OpenApi.fonts.js → apps/docs/fonts.js} +1 -1
- package/dist/apps/docs/fonts.js.map +1 -0
- package/dist/apps/docs/index.d.ts +2 -0
- package/dist/apps/docs/index.d.ts.map +1 -0
- package/dist/apps/docs/index.js +2 -0
- package/dist/apps/docs/index.js.map +1 -0
- package/dist/{internal/OpenApi.logo.d.ts → apps/docs/logo.d.ts} +1 -1
- package/dist/apps/docs/logo.d.ts.map +1 -0
- package/dist/{internal/OpenApi.logo.js → apps/docs/logo.js} +1 -1
- package/dist/apps/docs/logo.js.map +1 -0
- package/dist/apps/index.d.ts +4 -0
- package/dist/apps/index.d.ts.map +1 -0
- package/dist/apps/index.js +4 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/metadata.d.ts +7 -0
- package/dist/apps/metadata.d.ts.map +1 -0
- package/dist/apps/metadata.js +15 -0
- package/dist/apps/metadata.js.map +1 -0
- package/dist/apps/platform/App.d.ts +2985 -0
- package/dist/apps/platform/App.d.ts.map +1 -0
- package/dist/apps/platform/App.js +74 -0
- package/dist/apps/platform/App.js.map +1 -0
- package/dist/apps/platform/index.d.ts +2 -0
- package/dist/apps/platform/index.d.ts.map +1 -0
- package/dist/apps/platform/index.js +2 -0
- package/dist/apps/platform/index.js.map +1 -0
- package/dist/apps/platform/routes/api-keys.d.ts +681 -0
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/platform/routes/api-keys.js +328 -0
- package/dist/apps/platform/routes/api-keys.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +733 -0
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
- package/dist/apps/platform/routes/invitations.js +413 -0
- package/dist/apps/platform/routes/invitations.js.map +1 -0
- package/dist/apps/platform/routes/me.d.ts +95 -0
- package/dist/apps/platform/routes/me.d.ts.map +1 -0
- package/dist/apps/platform/routes/me.js +81 -0
- package/dist/apps/platform/routes/me.js.map +1 -0
- package/dist/apps/platform/routes/members.d.ts +554 -0
- package/dist/apps/platform/routes/members.d.ts.map +1 -0
- package/dist/apps/platform/routes/members.js +251 -0
- package/dist/apps/platform/routes/members.js.map +1 -0
- package/dist/apps/platform/routes/orgs.d.ts +582 -0
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
- package/dist/apps/platform/routes/orgs.js +265 -0
- package/dist/apps/platform/routes/orgs.js.map +1 -0
- package/dist/apps/platform/routes/projects.d.ts +740 -0
- package/dist/apps/platform/routes/projects.d.ts.map +1 -0
- package/dist/apps/platform/routes/projects.js +286 -0
- package/dist/apps/platform/routes/projects.js.map +1 -0
- package/dist/apps/relay/App.d.ts +41 -0
- package/dist/apps/relay/App.d.ts.map +1 -0
- package/dist/apps/relay/App.js +103 -0
- package/dist/apps/relay/App.js.map +1 -0
- package/dist/apps/relay/Sponsorships.d.ts +107 -0
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -0
- package/dist/apps/relay/Sponsorships.js +193 -0
- package/dist/apps/relay/Sponsorships.js.map +1 -0
- package/dist/apps/relay/index.d.ts +3 -0
- package/dist/apps/relay/index.d.ts.map +1 -0
- package/dist/apps/relay/index.js +3 -0
- package/dist/apps/relay/index.js.map +1 -0
- package/dist/{Cli.d.ts → cli/Cli.d.ts} +13 -2
- package/dist/cli/Cli.d.ts.map +1 -0
- package/dist/cli/Cli.js +19 -0
- package/dist/cli/Cli.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +4 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +15 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +60 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cloudflare.d.ts +7 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +9 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +113 -0
- package/dist/db/Db.d.ts.map +1 -0
- package/dist/db/Db.js +402 -0
- package/dist/db/Db.js.map +1 -0
- package/dist/db/Schema.d.ts +168 -0
- package/dist/db/Schema.d.ts.map +1 -0
- package/dist/db/Schema.js +229 -0
- package/dist/db/Schema.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +84 -0
- package/dist/db/tables/invitations.d.ts.map +1 -0
- package/dist/db/tables/invitations.js +165 -0
- package/dist/db/tables/invitations.js.map +1 -0
- package/dist/db/tables/memberships.d.ts +108 -0
- package/dist/db/tables/memberships.d.ts.map +1 -0
- package/dist/db/tables/memberships.js +153 -0
- package/dist/db/tables/memberships.js.map +1 -0
- package/dist/db/tables/organizations.d.ts +123 -0
- package/dist/db/tables/organizations.d.ts.map +1 -0
- package/dist/db/tables/organizations.js +170 -0
- package/dist/db/tables/organizations.js.map +1 -0
- package/dist/db/tables/projects.d.ts +65 -0
- package/dist/db/tables/projects.d.ts.map +1 -0
- package/dist/db/tables/projects.js +75 -0
- package/dist/db/tables/projects.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +120 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
- package/dist/db/tables/sponsoredTransactions.js +161 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -0
- package/dist/db/tables/users.d.ts +48 -0
- package/dist/db/tables/users.d.ts.map +1 -0
- package/dist/db/tables/users.js +65 -0
- package/dist/db/tables/users.js.map +1 -0
- package/dist/db/tables/verifiedTokens.d.ts +53 -0
- package/dist/db/tables/verifiedTokens.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokens.js +74 -0
- package/dist/db/tables/verifiedTokens.js.map +1 -0
- package/dist/db/tables/webhookDeliveries.d.ts +71 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -0
- package/dist/db/tables/webhookDeliveries.js +119 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +158 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -0
- package/dist/db/tables/webhookSubscriptions.js +272 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -0
- package/dist/handlers/internal/executionError.d.ts +25 -0
- package/dist/handlers/internal/executionError.d.ts.map +1 -0
- package/dist/handlers/internal/executionError.js +186 -0
- package/dist/handlers/internal/executionError.js.map +1 -0
- package/dist/handlers/internal/multisig.d.ts +233 -0
- package/dist/handlers/internal/multisig.d.ts.map +1 -0
- package/dist/handlers/internal/multisig.js +542 -0
- package/dist/handlers/internal/multisig.js.map +1 -0
- package/dist/handlers/internal/requestListener.d.ts +124 -0
- package/dist/handlers/internal/requestListener.d.ts.map +1 -0
- package/dist/handlers/internal/requestListener.js +173 -0
- package/dist/handlers/internal/requestListener.js.map +1 -0
- package/dist/handlers/internal/sponsorship.d.ts +113 -0
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -0
- package/dist/handlers/internal/sponsorship.js +128 -0
- package/dist/handlers/internal/sponsorship.js.map +1 -0
- package/dist/handlers/internal/types.d.ts +284 -0
- package/dist/handlers/internal/types.d.ts.map +1 -0
- package/dist/handlers/internal/types.js +2 -0
- package/dist/handlers/internal/types.js.map +1 -0
- package/dist/handlers/internal/utils.d.ts +34 -0
- package/dist/handlers/internal/utils.d.ts.map +1 -0
- package/dist/handlers/internal/utils.js +131 -0
- package/dist/handlers/internal/utils.js.map +1 -0
- package/dist/handlers/relay.d.ts +123 -0
- package/dist/handlers/relay.d.ts.map +1 -0
- package/dist/handlers/relay.js +1278 -0
- package/dist/handlers/relay.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +224 -16
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +572 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Log.d.ts +7 -3
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +0 -19
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +0 -57
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RpcSchema.d.ts +10 -0
- package/dist/internal/RpcSchema.d.ts.map +1 -0
- package/dist/internal/RpcSchema.js +2 -0
- package/dist/internal/RpcSchema.js.map +1 -0
- package/dist/internal/Store.d.ts +49 -4
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +81 -5
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts +15 -34
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +115 -159
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +84 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Webhooks.d.ts +50 -36
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +92 -205
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +37 -6
- package/src/ApiKey.test.ts +3 -3
- package/src/ApiKey.ts +11 -12
- package/src/ApiKeys.test.ts +278 -0
- package/src/ApiKeys.ts +358 -0
- package/src/App.test-d.ts +1 -1
- package/src/App.test.ts +40508 -19083
- package/src/App.ts +239 -416
- package/src/Client.test-d.ts +307 -16
- package/src/Handler.ts +154 -0
- package/src/Scope.ts +17 -4
- package/src/Webhooks.test.ts +8 -7
- package/src/Webhooks.ts +13 -7
- package/src/admin/Access.ts +1 -3
- package/src/admin/App.ts +19 -24
- package/src/admin/apps/api-keys.test.ts +70 -18
- package/src/admin/apps/api-keys.ts +92 -9
- package/src/admin/apps/scopes.test.ts +8 -0
- package/src/admin/apps/verified-tokens.test.ts +3 -30
- package/src/admin/apps/verified-tokens.ts +18 -37
- package/src/admin/ui.generated.ts +1 -1
- package/src/apps/composition.test.ts +66 -0
- package/src/apps/data/App.ts +295 -0
- package/src/apps/data/index.ts +2 -0
- package/src/apps/{activities.test.ts → data/routes/activities.test.ts} +3 -3
- package/src/apps/{activities.ts → data/routes/activities.ts} +15 -15
- package/src/apps/{addresses.ts → data/routes/addresses.ts} +6 -6
- package/src/apps/{balances.test.ts → data/routes/balances.test.ts} +6 -5
- package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
- package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +4 -4
- package/src/apps/{blocks.ts → data/routes/blocks.ts} +14 -14
- package/src/apps/{coingecko.test.ts → data/routes/coingecko.test.ts} +2 -2
- package/src/apps/{coingecko.ts → data/routes/coingecko.ts} +14 -14
- package/src/apps/{exchanges.test.ts → data/routes/exchanges.test.ts} +2 -2
- package/src/apps/{exchanges.ts → data/routes/exchanges.ts} +20 -20
- package/src/apps/{fee-amm.test.ts → data/routes/fee-amm.test.ts} +3 -3
- package/src/apps/{fee-amm.ts → data/routes/fee-amm.ts} +18 -18
- package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +27 -6
- package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
- package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +4 -4
- package/src/apps/{receipts.ts → data/routes/receipts.ts} +13 -13
- package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +142 -13
- package/src/apps/{rpc.ts → data/routes/rpc.ts} +7 -7
- package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +8 -10
- package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
- package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +6 -5
- package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
- package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +4 -4
- package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
- package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +3 -3
- package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
- package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +27 -15
- package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
- package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +137 -150
- package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +83 -81
- package/src/apps/docs/App.ts +136 -0
- package/src/apps/docs/index.ts +1 -0
- package/src/apps/index.ts +3 -0
- package/src/apps/metadata.ts +23 -0
- package/src/apps/platform/App.ts +130 -0
- package/src/apps/platform/index.ts +1 -0
- package/src/apps/platform/routes/api-keys.test.ts +330 -0
- package/src/apps/platform/routes/api-keys.ts +427 -0
- package/src/apps/platform/routes/invitations.test.ts +361 -0
- package/src/apps/platform/routes/invitations.ts +519 -0
- package/src/apps/platform/routes/me.test.ts +121 -0
- package/src/apps/platform/routes/me.ts +106 -0
- package/src/apps/platform/routes/members.test.ts +195 -0
- package/src/apps/platform/routes/members.ts +313 -0
- package/src/apps/platform/routes/orgs.test.ts +313 -0
- package/src/apps/platform/routes/orgs.ts +343 -0
- package/src/apps/platform/routes/projects.test.ts +181 -0
- package/src/apps/platform/routes/projects.ts +367 -0
- package/src/apps/relay/App.test.ts +484 -0
- package/src/apps/relay/App.ts +128 -0
- package/src/apps/relay/Sponsorships.test.ts +375 -0
- package/src/apps/relay/Sponsorships.ts +305 -0
- package/src/apps/relay/index.ts +2 -0
- package/src/{Cli.test.ts → cli/Cli.test.ts} +10 -9
- package/src/{Cli.ts → cli/Cli.ts} +16 -9
- package/src/cli/bin.ts +4 -0
- package/src/cli/commands/admin.test.ts +71 -0
- package/src/cli/commands/admin.ts +60 -0
- package/src/cloudflare.ts +10 -0
- package/src/db/Db.test.ts +84 -0
- package/src/db/Db.ts +542 -0
- package/src/db/Schema.ts +301 -0
- package/src/db/tables/invitations.ts +198 -0
- package/src/db/tables/memberships.ts +208 -0
- package/src/db/tables/organizations.test.ts +160 -0
- package/src/db/tables/organizations.ts +232 -0
- package/src/db/tables/projects.ts +107 -0
- package/src/db/tables/sponsoredTransactions.test.ts +218 -0
- package/src/db/tables/sponsoredTransactions.ts +236 -0
- package/src/db/tables/users.ts +85 -0
- package/src/db/tables/verifiedTokens.ts +111 -0
- package/src/db/tables/webhookDeliveries.ts +159 -0
- package/src/db/tables/webhookSubscriptions.ts +358 -0
- package/src/handlers/internal/executionError.ts +207 -0
- package/src/handlers/internal/multisig.test.ts +818 -0
- package/src/handlers/internal/multisig.ts +823 -0
- package/src/handlers/internal/requestListener.ts +273 -0
- package/src/handlers/internal/sponsorship.ts +245 -0
- package/src/handlers/internal/types.ts +378 -0
- package/src/handlers/internal/utils.ts +153 -0
- package/src/handlers/relay.test.ts +2035 -0
- package/src/handlers/relay.ts +1623 -0
- package/src/index.ts +4 -2
- package/src/internal/Auth.test.ts +337 -21
- package/src/internal/Auth.ts +783 -44
- package/src/internal/Cursor.test.ts +3 -2
- package/src/internal/Log.test.ts +1 -1
- package/src/internal/Log.ts +11 -3
- package/src/internal/MetricSink.ts +1 -1
- package/src/internal/OpenApi.ts +0 -59
- package/src/internal/RateLimit.test.ts +1 -1
- package/src/internal/RpcSchema.ts +10 -0
- package/src/internal/Store.test.ts +150 -10
- package/src/internal/Store.ts +136 -11
- package/src/internal/Tidx.local.test.ts +1 -1
- package/src/internal/VerifiedTokens.test.ts +99 -115
- package/src/internal/VerifiedTokens.ts +123 -175
- package/src/internal/WebhookDestination.test.ts +1 -1
- package/src/internal/Webhooks.test.ts +111 -111
- package/src/internal/Webhooks.ts +103 -221
- package/dist/ApiKeySource.d.ts +0 -166
- package/dist/ApiKeySource.d.ts.map +0 -1
- package/dist/ApiKeySource.js +0 -234
- package/dist/ApiKeySource.js.map +0 -1
- package/dist/Cli.d.ts.map +0 -1
- package/dist/Cli.js +0 -13
- package/dist/Cli.js.map +0 -1
- package/dist/apps/activities.js.map +0 -1
- package/dist/apps/addresses.js.map +0 -1
- package/dist/apps/balances.d.ts.map +0 -1
- package/dist/apps/balances.js.map +0 -1
- package/dist/apps/blocks.d.ts.map +0 -1
- package/dist/apps/blocks.js.map +0 -1
- package/dist/apps/coingecko.d.ts.map +0 -1
- package/dist/apps/coingecko.js.map +0 -1
- package/dist/apps/exchanges.d.ts.map +0 -1
- package/dist/apps/exchanges.js.map +0 -1
- package/dist/apps/fee-amm.d.ts.map +0 -1
- package/dist/apps/fee-amm.js.map +0 -1
- package/dist/apps/indexer.d.ts.map +0 -1
- package/dist/apps/indexer.js.map +0 -1
- package/dist/apps/receipts.d.ts.map +0 -1
- package/dist/apps/receipts.js.map +0 -1
- package/dist/apps/rpc.d.ts.map +0 -1
- package/dist/apps/rpc.js.map +0 -1
- package/dist/apps/tokenlist.d.ts.map +0 -1
- package/dist/apps/tokenlist.js.map +0 -1
- package/dist/apps/tokens.d.ts.map +0 -1
- package/dist/apps/tokens.js.map +0 -1
- package/dist/apps/transactions.d.ts.map +0 -1
- package/dist/apps/transactions.js.map +0 -1
- package/dist/apps/transfers.d.ts.map +0 -1
- package/dist/apps/transfers.js.map +0 -1
- package/dist/apps/verified-tokens.d.ts.map +0 -1
- package/dist/apps/verified-tokens.js.map +0 -1
- package/dist/apps/webhooks.js.map +0 -1
- package/dist/internal/OpenApi.fonts.d.ts.map +0 -1
- package/dist/internal/OpenApi.fonts.js.map +0 -1
- package/dist/internal/OpenApi.logo.d.ts.map +0 -1
- package/dist/internal/OpenApi.logo.js.map +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/31318/verified-tokens.json +0 -30
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +0 -4
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +0 -30
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +0 -11
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +0 -1
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +0 -5
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +0 -177
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +0 -1
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +0 -1
- package/seeds/42431/verified-tokens.json +0 -44
- package/seeds/openrpc/README.md +0 -27
- package/seeds/openrpc/execution-apis.json +0 -8989
- package/src/ApiKeySource.test-d.ts +0 -11
- package/src/ApiKeySource.test.ts +0 -295
- package/src/ApiKeySource.ts +0 -303
- /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
- /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
|
@@ -0,0 +1,1278 @@
|
|
|
1
|
+
import { AbiEvent, Hex, RpcRequest, RpcResponse } from 'ox';
|
|
2
|
+
import { Transaction as core_Transaction } from 'ox/tempo';
|
|
3
|
+
import { createClient, decodeFunctionData, formatUnits, http, isAddress, parseEventLogs, zeroAddress, } from 'viem';
|
|
4
|
+
import { simulateCalls } from 'viem/actions';
|
|
5
|
+
import { Abis, Actions, Addresses, Capabilities, VirtualAddress } from 'viem/tempo';
|
|
6
|
+
import * as ExecutionError from './internal/executionError.js';
|
|
7
|
+
import { from } from '../Handler.js';
|
|
8
|
+
import * as Store from '../internal/Store.js';
|
|
9
|
+
import * as Multisig from './internal/multisig.js';
|
|
10
|
+
import * as Sponsorship from './internal/sponsorship.js';
|
|
11
|
+
import * as Utils from './internal/utils.js';
|
|
12
|
+
/**
|
|
13
|
+
* Instantiates a relay handler that proxies `eth_fillTransaction`
|
|
14
|
+
* with wallet-aware enrichment (fee token resolution, simulation,
|
|
15
|
+
* sponsorship, AMM resolution).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { Handler } from 'tempo-api/server'
|
|
20
|
+
*
|
|
21
|
+
* const handler = Handler.relay({ getClient })
|
|
22
|
+
*
|
|
23
|
+
* // Plug handler into your server framework of choice:
|
|
24
|
+
* createServer(handler.listener) // Node.js
|
|
25
|
+
* Bun.serve(handler) // Bun
|
|
26
|
+
* Deno.serve(handler) // Deno
|
|
27
|
+
* app.use(handler.listener) // Express
|
|
28
|
+
* app.all('*', c => handler.fetch(c.req.raw)) // Hono
|
|
29
|
+
* export const GET = handler.fetch // Next.js
|
|
30
|
+
* export const POST = handler.fetch // Next.js
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* With sponsorship
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
38
|
+
* import { Handler } from 'tempo-api/server'
|
|
39
|
+
*
|
|
40
|
+
* const handler = Handler.relay({
|
|
41
|
+
* getClient,
|
|
42
|
+
* feePayer: {
|
|
43
|
+
* account: privateKeyToAccount('0x...'),
|
|
44
|
+
* },
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @param options - Options.
|
|
49
|
+
* @returns Request handler.
|
|
50
|
+
*/
|
|
51
|
+
export function relay(options) {
|
|
52
|
+
const { getClient, internal_allowUnsafeUrls = false, onRequest, path = '/', resolveTokens, ...rest } = options;
|
|
53
|
+
const feePayerOptions = options.feePayer;
|
|
54
|
+
// Adapt the state store into the multisig operation store once, up front.
|
|
55
|
+
const multisigOptions = options.multisig
|
|
56
|
+
? { ...options.multisig, store: Multisig.fromStore(options.multisig.store) }
|
|
57
|
+
: undefined;
|
|
58
|
+
// Resolves the chain's fee-token candidates through the caller-provided
|
|
59
|
+
// resolver. An unconfigured resolver (or a chain it has no list for) yields
|
|
60
|
+
// an empty candidate set.
|
|
61
|
+
const getTokens = async (chainId) => {
|
|
62
|
+
if (!resolveTokens || chainId === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
return await resolveTokens(chainId);
|
|
65
|
+
};
|
|
66
|
+
// Memoize cache for TIP-20 metadata and user fee-token preferences. Kept
|
|
67
|
+
// opt-in: an always-on cache would pin a raced null preference (set, then
|
|
68
|
+
// fill before the set mines) for the whole TTL.
|
|
69
|
+
const store = options.cache;
|
|
70
|
+
const features = {
|
|
71
|
+
autoSwap: options.autoSwap ?? options.features === 'all',
|
|
72
|
+
feeTokenResolution: options.features === 'all' || Boolean(resolveTokens),
|
|
73
|
+
simulate: options.features === 'all',
|
|
74
|
+
};
|
|
75
|
+
const autoSwap = (() => {
|
|
76
|
+
if (!features.autoSwap)
|
|
77
|
+
return undefined;
|
|
78
|
+
if (options.autoSwap === false)
|
|
79
|
+
return undefined;
|
|
80
|
+
return {
|
|
81
|
+
slippage: (typeof options.autoSwap === 'object' ? options.autoSwap?.slippage : undefined) ?? 0.05,
|
|
82
|
+
};
|
|
83
|
+
})();
|
|
84
|
+
async function handleRequest(request, options) {
|
|
85
|
+
await onRequest?.(request);
|
|
86
|
+
// Resolve chainId from: 1) explicit option (URL path), 2) first param object, 3) default chain.
|
|
87
|
+
const params = 'params' in request && Array.isArray(request.params) ? request.params : [];
|
|
88
|
+
const first = typeof params[0] === 'object' && params[0]
|
|
89
|
+
? params[0]
|
|
90
|
+
: undefined;
|
|
91
|
+
const chainId = Utils.resolveChainId(first?.['chainId']) ?? options?.chainId ?? getClient().chain?.id;
|
|
92
|
+
const client = getClient(chainId);
|
|
93
|
+
switch (request.method) {
|
|
94
|
+
case 'eth_fillTransaction': {
|
|
95
|
+
const parameters = params[0];
|
|
96
|
+
const capabilities = (parameters['capabilities'] ?? {});
|
|
97
|
+
try {
|
|
98
|
+
const from = typeof parameters['from'] === 'string' ? parameters['from'] : undefined;
|
|
99
|
+
const requestFeeToken = typeof parameters['feeToken'] === 'string'
|
|
100
|
+
? parameters['feeToken']
|
|
101
|
+
: undefined;
|
|
102
|
+
const externalFeePayerUrl = typeof parameters['feePayer'] === 'string'
|
|
103
|
+
? normalizeExternalFeePayerUrl(parameters['feePayer'], {
|
|
104
|
+
allowUnsafe: internal_allowUnsafeUrls,
|
|
105
|
+
})
|
|
106
|
+
: undefined;
|
|
107
|
+
const requestsSponsorship = (!!feePayerOptions || !!externalFeePayerUrl) && parameters['feePayer'] !== false;
|
|
108
|
+
// Default to `true`. Dapps that don't render diffs can pass
|
|
109
|
+
// `capabilities.balanceDiffs: false` to skip the post-fill
|
|
110
|
+
// `tempo_simulateV1` round trip (~250-400ms).
|
|
111
|
+
const requireBalanceDiffs = capabilities['balanceDiffs'] !== false;
|
|
112
|
+
const { feePayer: _feePayer, ...normalized } = Utils.normalizeFillTransactionRequest(parameters);
|
|
113
|
+
const hasMultisigHints = (typeof normalized['multisig'] === 'object' && normalized['multisig'] !== null) ||
|
|
114
|
+
(typeof normalized['multisigInit'] === 'object' &&
|
|
115
|
+
normalized['multisigInit'] !== null) ||
|
|
116
|
+
typeof normalized['multisigSignatureCount'] !== 'undefined';
|
|
117
|
+
const shouldDeferFeePayerSignature = feePayerOptions && multisigOptions && hasMultisigHints;
|
|
118
|
+
// When sponsoring, the sponsor's preferred fee token (if set)
|
|
119
|
+
// overrides whatever the consumer asked for — the sponsor is
|
|
120
|
+
// paying, so the sponsor picks. Falls back to the request's
|
|
121
|
+
// feeToken otherwise.
|
|
122
|
+
const sponsoredFeeToken = requestsSponsorship
|
|
123
|
+
? (feePayerOptions?.feeToken ?? requestFeeToken)
|
|
124
|
+
: requestFeeToken;
|
|
125
|
+
const baseTx = {
|
|
126
|
+
...normalized,
|
|
127
|
+
...(typeof chainId !== 'undefined' ? { chainId } : {}),
|
|
128
|
+
...(sponsoredFeeToken ? { feeToken: sponsoredFeeToken } : {}),
|
|
129
|
+
};
|
|
130
|
+
let filled;
|
|
131
|
+
let sponsored = false;
|
|
132
|
+
let feeToken = sponsoredFeeToken;
|
|
133
|
+
// Lazily resolve a swap source token when autoSwap needs one.
|
|
134
|
+
const resolveFeeTokenForSwap = from
|
|
135
|
+
? async (insufficientToken) => resolveFeeToken(client, {
|
|
136
|
+
account: from,
|
|
137
|
+
feeToken: undefined,
|
|
138
|
+
store,
|
|
139
|
+
tokens: (await getTokens(chainId)).filter((t) => t.toLowerCase() !== insufficientToken.toLowerCase()),
|
|
140
|
+
})
|
|
141
|
+
: undefined;
|
|
142
|
+
// When no sponsor will pay, prefer fee tokens the user actually
|
|
143
|
+
// holds. Extend the configured token list with any TIP20 token
|
|
144
|
+
// the transaction is calling — typically the token being
|
|
145
|
+
// transferred — so a user transferring USDC.e can pay gas in
|
|
146
|
+
// USDC.e even when the configured list defaults to pathUSD.
|
|
147
|
+
const configuredTokens = await getTokens(chainId);
|
|
148
|
+
const unsponsoredTokens = [
|
|
149
|
+
...configuredTokens,
|
|
150
|
+
...callTargetTokens(baseTx).filter((t) => !configuredTokens.some((rt) => rt.toLowerCase() === t.toLowerCase())),
|
|
151
|
+
];
|
|
152
|
+
// When the app provides its own fee payer URL, route the fill
|
|
153
|
+
// through that service so it can sign the transaction.
|
|
154
|
+
const fillClient = externalFeePayerUrl
|
|
155
|
+
? createClient({
|
|
156
|
+
chain: client.chain,
|
|
157
|
+
batch: { multicall: { deployless: true } },
|
|
158
|
+
transport: http(externalFeePayerUrl),
|
|
159
|
+
})
|
|
160
|
+
: client;
|
|
161
|
+
if (requestsSponsorship && (externalFeePayerUrl || !feePayerOptions?.validate)) {
|
|
162
|
+
// Path A: sponsorship guaranteed — skip fee token resolution,
|
|
163
|
+
// fill once with feePayer, then parallelize the rest. Taken when:
|
|
164
|
+
// - no validate is configured (no gating), OR
|
|
165
|
+
// - the app supplied its own external fee payer URL (that
|
|
166
|
+
// relay is the sponsorship authority — the wallet's own
|
|
167
|
+
// `validate` only governs the wallet's own fee payer).
|
|
168
|
+
// Default to the chain's first token so the broadcast envelope
|
|
169
|
+
// always carries a feeToken the chain can charge.
|
|
170
|
+
if (!feeToken)
|
|
171
|
+
feeToken = (await getTokens(chainId))[0];
|
|
172
|
+
const transaction = {
|
|
173
|
+
...baseTx,
|
|
174
|
+
feePayer: true,
|
|
175
|
+
...(feeToken ? { feeToken } : {}),
|
|
176
|
+
};
|
|
177
|
+
if (Sponsorship.isPreparedTransaction(transaction)) {
|
|
178
|
+
filled = {
|
|
179
|
+
transaction: Utils.normalizeTempoTransaction(transaction),
|
|
180
|
+
sponsor: undefined,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
filled = await fill(fillClient, {
|
|
185
|
+
autoSwap,
|
|
186
|
+
feeToken,
|
|
187
|
+
store,
|
|
188
|
+
resolveFeeToken: resolveFeeTokenForSwap,
|
|
189
|
+
transaction,
|
|
190
|
+
});
|
|
191
|
+
// The chain echoes feeToken: null for sponsored fills served
|
|
192
|
+
// by viem clients that strip feeToken pre-sign; re-inject
|
|
193
|
+
// before the spread in mergeCallsFromRequest drops it.
|
|
194
|
+
if (feeToken &&
|
|
195
|
+
filled?.transaction &&
|
|
196
|
+
filled.transaction.feeToken == null)
|
|
197
|
+
filled.transaction.feeToken = feeToken;
|
|
198
|
+
}
|
|
199
|
+
sponsored = true;
|
|
200
|
+
}
|
|
201
|
+
else if (requestsSponsorship && feePayerOptions?.validate) {
|
|
202
|
+
// Path B: sponsorship possible but may be rejected.
|
|
203
|
+
// Speculatively fill the sponsored variant, validate, and only
|
|
204
|
+
// fall back to the unsponsored path on rejection. The accept
|
|
205
|
+
// case (common) avoids the upfront `resolveFeeToken` round
|
|
206
|
+
// trips and the second `eth_fillTransaction`.
|
|
207
|
+
const sponsoredTx = { ...baseTx, feePayer: true };
|
|
208
|
+
if (Sponsorship.isPreparedTransaction(sponsoredTx)) {
|
|
209
|
+
// Already prepared — skip fills, just validate sponsorship.
|
|
210
|
+
const prepared = {
|
|
211
|
+
transaction: Utils.normalizeTempoTransaction(sponsoredTx),
|
|
212
|
+
sponsor: undefined,
|
|
213
|
+
};
|
|
214
|
+
sponsored = await Sponsorship.shouldSponsor({
|
|
215
|
+
sender: from,
|
|
216
|
+
transaction: prepared.transaction,
|
|
217
|
+
validate: feePayerOptions.validate,
|
|
218
|
+
});
|
|
219
|
+
filled = prepared;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const options = {
|
|
223
|
+
autoSwap,
|
|
224
|
+
store,
|
|
225
|
+
resolveFeeToken: resolveFeeTokenForSwap,
|
|
226
|
+
};
|
|
227
|
+
const fill_sponsored = await fill(fillClient, {
|
|
228
|
+
...options,
|
|
229
|
+
feeToken,
|
|
230
|
+
transaction: sponsoredTx,
|
|
231
|
+
});
|
|
232
|
+
sponsored = await Sponsorship.shouldSponsor({
|
|
233
|
+
sender: from,
|
|
234
|
+
transaction: fill_sponsored.transaction,
|
|
235
|
+
validate: feePayerOptions.validate,
|
|
236
|
+
});
|
|
237
|
+
if (sponsored) {
|
|
238
|
+
filled = fill_sponsored;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Sponsor rejected — resolve fee token and fill unsponsored.
|
|
242
|
+
const feeToken_unsponsored = features.feeTokenResolution
|
|
243
|
+
? await resolveFeeToken(client, {
|
|
244
|
+
account: from,
|
|
245
|
+
feeToken: requestFeeToken,
|
|
246
|
+
store,
|
|
247
|
+
tokens: unsponsoredTokens,
|
|
248
|
+
})
|
|
249
|
+
: requestFeeToken;
|
|
250
|
+
const tx_unsponsored = {
|
|
251
|
+
...baseTx,
|
|
252
|
+
...(feeToken_unsponsored ? { feeToken: feeToken_unsponsored } : {}),
|
|
253
|
+
};
|
|
254
|
+
filled = await fill(client, {
|
|
255
|
+
...options,
|
|
256
|
+
feeToken: feeToken_unsponsored,
|
|
257
|
+
transaction: tx_unsponsored,
|
|
258
|
+
});
|
|
259
|
+
feeToken = feeToken_unsponsored;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Path C: no sponsorship configured — resolve fee token, fill once.
|
|
265
|
+
feeToken = features.feeTokenResolution
|
|
266
|
+
? await resolveFeeToken(client, {
|
|
267
|
+
account: from,
|
|
268
|
+
feeToken: requestFeeToken,
|
|
269
|
+
store,
|
|
270
|
+
tokens: unsponsoredTokens,
|
|
271
|
+
})
|
|
272
|
+
: requestFeeToken;
|
|
273
|
+
const transaction = { ...baseTx, ...(feeToken ? { feeToken } : {}) };
|
|
274
|
+
filled = await fill(client, {
|
|
275
|
+
autoSwap,
|
|
276
|
+
feeToken,
|
|
277
|
+
store,
|
|
278
|
+
resolveFeeToken: resolveFeeTokenForSwap,
|
|
279
|
+
transaction,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
const transaction_filled = filled.transaction;
|
|
283
|
+
const swap = 'swap' in filled ? filled.swap : undefined;
|
|
284
|
+
if (!feeToken)
|
|
285
|
+
feeToken =
|
|
286
|
+
transaction_filled.feeToken ?? (await getTokens(chainId))[0];
|
|
287
|
+
// Parallelize: simulate, fee payer signing, and autoSwap metadata.
|
|
288
|
+
const alreadySigned = 'feePayerSignature' in transaction_filled &&
|
|
289
|
+
transaction_filled.feePayerSignature != null;
|
|
290
|
+
const calls = extractCalls(transaction_filled);
|
|
291
|
+
const [simulation, transaction_final, autoSwap_, virtualAddresses] = await Promise.all([
|
|
292
|
+
// Simulate and compute balance diffs + fee.
|
|
293
|
+
// When `capabilities.balanceDiffs` is `false`, skip simulate
|
|
294
|
+
// and compute fee directly from cached metadata.
|
|
295
|
+
(async () => {
|
|
296
|
+
if (!features.simulate)
|
|
297
|
+
return { balanceDiffs: undefined, fee: undefined };
|
|
298
|
+
if (requireBalanceDiffs)
|
|
299
|
+
return simulateAndParseDiffs(client, {
|
|
300
|
+
account: from,
|
|
301
|
+
calls,
|
|
302
|
+
swap,
|
|
303
|
+
feeToken,
|
|
304
|
+
gas: transaction_filled.gas,
|
|
305
|
+
store,
|
|
306
|
+
maxFeePerGas: transaction_filled.maxFeePerGas,
|
|
307
|
+
});
|
|
308
|
+
const fee = await computeFee(client, {
|
|
309
|
+
feeToken,
|
|
310
|
+
gas: transaction_filled.gas,
|
|
311
|
+
store,
|
|
312
|
+
maxFeePerGas: transaction_filled.maxFeePerGas,
|
|
313
|
+
}).catch(() => undefined);
|
|
314
|
+
return { balanceDiffs: undefined, fee };
|
|
315
|
+
})(),
|
|
316
|
+
// Sign as fee payer (if sponsored and not already signed).
|
|
317
|
+
(async () => {
|
|
318
|
+
// Multisig finalization changes the sender signature. Sign after quorum.
|
|
319
|
+
if (shouldDeferFeePayerSignature)
|
|
320
|
+
return transaction_filled;
|
|
321
|
+
// Never sign with the managed fee payer for an app-provided
|
|
322
|
+
// external fee payer URL: that relay is the sponsorship authority,
|
|
323
|
+
// and signing here would bypass this fee payer's `validate` gate.
|
|
324
|
+
if (!(sponsored && feePayerOptions && !externalFeePayerUrl && !alreadySigned))
|
|
325
|
+
return transaction_filled;
|
|
326
|
+
return Sponsorship.sign({
|
|
327
|
+
account: feePayerOptions.account,
|
|
328
|
+
onSponsored: feePayerOptions.onSponsored,
|
|
329
|
+
sender: from,
|
|
330
|
+
transaction: transaction_filled,
|
|
331
|
+
});
|
|
332
|
+
})(),
|
|
333
|
+
// Resolve autoSwap metadata (when AMM path was taken).
|
|
334
|
+
resolveAutoSwapMetadata(client, { autoSwap, store, swap }),
|
|
335
|
+
// Resolve virtual-address recipients so wallets can show the
|
|
336
|
+
// eventual master address before signing.
|
|
337
|
+
resolveVirtualAddresses(client, { calls }),
|
|
338
|
+
]);
|
|
339
|
+
const { balanceDiffs, fee } = simulation;
|
|
340
|
+
const sponsor = (() => {
|
|
341
|
+
if (!sponsored)
|
|
342
|
+
return undefined;
|
|
343
|
+
// App-provided fee payer: relay back the sponsor from the upstream response.
|
|
344
|
+
if (externalFeePayerUrl)
|
|
345
|
+
return filled.sponsor;
|
|
346
|
+
if (feePayerOptions)
|
|
347
|
+
return Sponsorship.getSponsor(feePayerOptions);
|
|
348
|
+
return filled.sponsor;
|
|
349
|
+
})();
|
|
350
|
+
return RpcResponse.from({
|
|
351
|
+
result: {
|
|
352
|
+
...(sponsor ? { sponsor } : {}),
|
|
353
|
+
tx: core_Transaction.toRpc(transaction_final),
|
|
354
|
+
capabilities: {
|
|
355
|
+
balanceDiffs,
|
|
356
|
+
fee,
|
|
357
|
+
sponsored: !!sponsor,
|
|
358
|
+
...(sponsor ? { sponsor } : {}),
|
|
359
|
+
...(autoSwap_ ? { autoSwap: autoSwap_ } : {}),
|
|
360
|
+
...(virtualAddresses ? { virtualAddresses } : {}),
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
}, { request });
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
if (!(error instanceof Error))
|
|
367
|
+
return Utils.rpcErrorJson(request, error);
|
|
368
|
+
if (capabilities['errors'] !== true)
|
|
369
|
+
return Utils.rpcErrorJson(request, error);
|
|
370
|
+
const revert = ExecutionError.parse(error);
|
|
371
|
+
const parameters = request.params?.[0];
|
|
372
|
+
const stub = {
|
|
373
|
+
from: parameters['from'],
|
|
374
|
+
to: parameters['to'] ?? null,
|
|
375
|
+
gas: '0x0',
|
|
376
|
+
nonce: '0x0',
|
|
377
|
+
value: '0x0',
|
|
378
|
+
maxFeePerGas: '0x0',
|
|
379
|
+
maxPriorityFeePerGas: '0x0',
|
|
380
|
+
};
|
|
381
|
+
if (revert?.errorName === 'InsufficientBalance') {
|
|
382
|
+
const args = revert.args;
|
|
383
|
+
const [available, required, token] = args;
|
|
384
|
+
const normalized = Utils.normalizeFillTransactionRequest(parameters);
|
|
385
|
+
// Simulate from zero address for optimistic balance diffs.
|
|
386
|
+
const optimisticCalls = normalized ? extractCalls(normalized) : undefined;
|
|
387
|
+
const [{ balanceDiffs }, virtualAddresses] = optimisticCalls
|
|
388
|
+
? await Promise.all([
|
|
389
|
+
simulateAndParseDiffs(client, {
|
|
390
|
+
account: zeroAddress,
|
|
391
|
+
calls: optimisticCalls,
|
|
392
|
+
store,
|
|
393
|
+
}),
|
|
394
|
+
resolveVirtualAddresses(client, { calls: optimisticCalls }),
|
|
395
|
+
])
|
|
396
|
+
: [{ balanceDiffs: undefined }, undefined];
|
|
397
|
+
// Re-key balance diffs from zero address to the real sender.
|
|
398
|
+
const senderDiffs = parameters['from'] && balanceDiffs
|
|
399
|
+
? { [parameters['from']]: balanceDiffs[zeroAddress] ?? [] }
|
|
400
|
+
: balanceDiffs;
|
|
401
|
+
const metadata = await resolveTokenMetadata(client, { token, store }).catch(() => undefined);
|
|
402
|
+
const deficit = required - available;
|
|
403
|
+
return RpcResponse.from({
|
|
404
|
+
result: {
|
|
405
|
+
tx: stub,
|
|
406
|
+
capabilities: {
|
|
407
|
+
balanceDiffs: senderDiffs,
|
|
408
|
+
error: ExecutionError.serialize(revert),
|
|
409
|
+
requireFunds: metadata
|
|
410
|
+
? {
|
|
411
|
+
amount: Hex.fromNumber(deficit),
|
|
412
|
+
decimals: metadata.decimals,
|
|
413
|
+
formatted: formatUnits(deficit, metadata.decimals),
|
|
414
|
+
token,
|
|
415
|
+
symbol: metadata.symbol,
|
|
416
|
+
}
|
|
417
|
+
: undefined,
|
|
418
|
+
sponsored: false,
|
|
419
|
+
...(virtualAddresses ? { virtualAddresses } : {}),
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
}, { request });
|
|
423
|
+
}
|
|
424
|
+
const normalized = Utils.normalizeFillTransactionRequest(parameters);
|
|
425
|
+
const virtualAddresses = normalized
|
|
426
|
+
? await resolveVirtualAddresses(client, { calls: extractCalls(normalized) }).catch(() => undefined)
|
|
427
|
+
: undefined;
|
|
428
|
+
return RpcResponse.from({
|
|
429
|
+
result: {
|
|
430
|
+
tx: stub,
|
|
431
|
+
capabilities: {
|
|
432
|
+
error: ExecutionError.serialize(revert),
|
|
433
|
+
sponsored: false,
|
|
434
|
+
...(virtualAddresses ? { virtualAddresses } : {}),
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
}, { request });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
case 'eth_signRawTransaction':
|
|
441
|
+
case 'eth_sendRawTransaction':
|
|
442
|
+
case 'eth_sendRawTransactionSync': {
|
|
443
|
+
try {
|
|
444
|
+
const serialized = params[0];
|
|
445
|
+
if (multisigOptions &&
|
|
446
|
+
(request.method === 'eth_sendRawTransaction' ||
|
|
447
|
+
request.method === 'eth_sendRawTransactionSync') &&
|
|
448
|
+
typeof serialized === 'string' &&
|
|
449
|
+
Multisig.isMultisigTransaction(serialized)) {
|
|
450
|
+
const result = await Multisig.handleRawTransaction({
|
|
451
|
+
claimTtl: multisigOptions.claimTtl,
|
|
452
|
+
finalize: multisigOptions.finalize,
|
|
453
|
+
getClient,
|
|
454
|
+
method: request.method,
|
|
455
|
+
request: { params: 'params' in request ? request.params : undefined },
|
|
456
|
+
resolveConfig: multisigOptions.resolveConfig,
|
|
457
|
+
sponsor: feePayerOptions
|
|
458
|
+
? {
|
|
459
|
+
account: feePayerOptions.account,
|
|
460
|
+
feeToken: feePayerOptions.feeToken,
|
|
461
|
+
onSponsored: feePayerOptions.onSponsored,
|
|
462
|
+
resolveFeeToken: async (chainId) => (await getTokens(chainId))[0],
|
|
463
|
+
validate: feePayerOptions.validate,
|
|
464
|
+
}
|
|
465
|
+
: undefined,
|
|
466
|
+
store: multisigOptions.store,
|
|
467
|
+
});
|
|
468
|
+
return RpcResponse.from({ result }, { request });
|
|
469
|
+
}
|
|
470
|
+
if (!feePayerOptions) {
|
|
471
|
+
// eth_signRawTransaction is a signing method that only the
|
|
472
|
+
// relay can fulfill — forwarding it to the RPC node returns
|
|
473
|
+
// an opaque "Method not found". Return an actionable error.
|
|
474
|
+
if (request.method === 'eth_signRawTransaction') {
|
|
475
|
+
return RpcResponse.from({
|
|
476
|
+
error: {
|
|
477
|
+
code: -32601,
|
|
478
|
+
message: 'eth_signRawTransaction requires a fee payer to be configured on the relay. ' +
|
|
479
|
+
'Set the `feePayer` option in `Handler.relay()` to enable transaction sponsorship.',
|
|
480
|
+
},
|
|
481
|
+
}, { request });
|
|
482
|
+
}
|
|
483
|
+
const result = await client.request(request);
|
|
484
|
+
return RpcResponse.from({ result }, { request });
|
|
485
|
+
}
|
|
486
|
+
if (typeof serialized !== 'string' ||
|
|
487
|
+
!Sponsorship.requestsRawSponsorship(serialized)) {
|
|
488
|
+
const result = await client.request(request);
|
|
489
|
+
return RpcResponse.from({ result }, { request });
|
|
490
|
+
}
|
|
491
|
+
const result = await Sponsorship.handleRawTransaction({
|
|
492
|
+
account: feePayerOptions.account,
|
|
493
|
+
feeToken: feePayerOptions.feeToken,
|
|
494
|
+
getFeeToken: async (chainId) => (await getTokens(chainId))[0],
|
|
495
|
+
getClient,
|
|
496
|
+
method: request.method,
|
|
497
|
+
onSponsored: feePayerOptions.onSponsored,
|
|
498
|
+
request: { params: 'params' in request ? request.params : undefined },
|
|
499
|
+
validate: feePayerOptions.validate,
|
|
500
|
+
});
|
|
501
|
+
return RpcResponse.from({ result }, { request });
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
return Utils.rpcErrorJson(request, error);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
case 'eth_getTransactionByHash':
|
|
508
|
+
case 'eth_getTransactionReceipt': {
|
|
509
|
+
try {
|
|
510
|
+
if (multisigOptions) {
|
|
511
|
+
const aliased = await Multisig.handleGetTransaction({
|
|
512
|
+
getClient,
|
|
513
|
+
method: request.method,
|
|
514
|
+
request: { params: 'params' in request ? request.params : undefined },
|
|
515
|
+
store: multisigOptions.store,
|
|
516
|
+
});
|
|
517
|
+
if (aliased)
|
|
518
|
+
return RpcResponse.from({ result: aliased.result }, { request });
|
|
519
|
+
}
|
|
520
|
+
const result = await client.request(request);
|
|
521
|
+
return RpcResponse.from({ result }, { request });
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
return Utils.rpcErrorJson(request, error);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
default: {
|
|
528
|
+
try {
|
|
529
|
+
const result = await client.request(request);
|
|
530
|
+
return RpcResponse.from({ result }, { request });
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
return Utils.rpcErrorJson(request, error);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const router = from(rest);
|
|
539
|
+
async function handlePost(c) {
|
|
540
|
+
const chainId = Utils.resolveChainId(c.req.param('chainId'));
|
|
541
|
+
const body = await c.req.raw.json();
|
|
542
|
+
const isBatch = Array.isArray(body);
|
|
543
|
+
if (!isBatch) {
|
|
544
|
+
const request = RpcRequest.from(body);
|
|
545
|
+
return Response.json(await handleRequest(request, { chainId }));
|
|
546
|
+
}
|
|
547
|
+
const responses = await Promise.all(body.map((item) => handleRequest(RpcRequest.from(item), {
|
|
548
|
+
chainId,
|
|
549
|
+
})));
|
|
550
|
+
return Response.json(responses);
|
|
551
|
+
}
|
|
552
|
+
router.post(path, handlePost);
|
|
553
|
+
router.post(`${path === '/' ? '' : path}/:chainId`, handlePost);
|
|
554
|
+
return router;
|
|
555
|
+
}
|
|
556
|
+
// TODO: cleanup
|
|
557
|
+
async function fill(client, options) {
|
|
558
|
+
const { autoSwap, feeToken, store, transaction: request } = options;
|
|
559
|
+
// Skip re-formatting if already in RPC format (e.g. from viem's fillTransaction).
|
|
560
|
+
const format = (value) => value['type'] === '0x76' ? value : Utils.formatFillTransactionRequest(client, value);
|
|
561
|
+
// Re-fill the transaction with prepended swap calls so the user can
|
|
562
|
+
// mint the missing `insufficientToken` (typically the fee token) from
|
|
563
|
+
// a token they already hold. Returns null if no source token is
|
|
564
|
+
// available or autoSwap is disabled.
|
|
565
|
+
async function fillWithSwap(insufficientToken, deficit) {
|
|
566
|
+
if (!autoSwap)
|
|
567
|
+
return null;
|
|
568
|
+
const sourceToken = feeToken && feeToken.toLowerCase() !== insufficientToken.toLowerCase()
|
|
569
|
+
? feeToken
|
|
570
|
+
: await options.resolveFeeToken?.(insufficientToken);
|
|
571
|
+
if (!sourceToken || sourceToken.toLowerCase() === insufficientToken.toLowerCase())
|
|
572
|
+
return null;
|
|
573
|
+
const maxAmountIn = deficit + (deficit * BigInt(Math.round(autoSwap.slippage * 1000))) / 1000n;
|
|
574
|
+
const originalCalls = request['calls'] ?? [];
|
|
575
|
+
const swapCalls = buildSwapCalls(client, sourceToken, insufficientToken, deficit, maxAmountIn);
|
|
576
|
+
const result = await client.request({
|
|
577
|
+
method: 'eth_fillTransaction',
|
|
578
|
+
params: [
|
|
579
|
+
format({
|
|
580
|
+
...request,
|
|
581
|
+
calls: [...swapCalls, ...originalCalls],
|
|
582
|
+
}),
|
|
583
|
+
],
|
|
584
|
+
});
|
|
585
|
+
const sponsor = result['capabilities']?.sponsor;
|
|
586
|
+
const mergedTx = mergeCallsFromRequest(result.tx, {
|
|
587
|
+
...request,
|
|
588
|
+
calls: [...swapCalls, ...originalCalls],
|
|
589
|
+
});
|
|
590
|
+
return {
|
|
591
|
+
transaction: Utils.normalizeTempoTransaction(mergedTx),
|
|
592
|
+
sponsor,
|
|
593
|
+
swap: {
|
|
594
|
+
calls: swapCalls,
|
|
595
|
+
tokenIn: sourceToken,
|
|
596
|
+
tokenOut: insufficientToken,
|
|
597
|
+
amountOut: deficit,
|
|
598
|
+
maxAmountIn,
|
|
599
|
+
},
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
const formatted = format(request);
|
|
604
|
+
const result = await client.request({
|
|
605
|
+
method: 'eth_fillTransaction',
|
|
606
|
+
params: [formatted],
|
|
607
|
+
});
|
|
608
|
+
// FIXME: node estimates gas with secp256k1 dummy sig + null feePayerSignature.
|
|
609
|
+
// Actual tx has larger keychain/webAuthn sigs + real fee payer sig, costing
|
|
610
|
+
// more intrinsic gas. Mirror the bump from viem's tempo chainConfig.
|
|
611
|
+
// Skip if another relay already bumped (indicated by feePayerSignature).
|
|
612
|
+
// @ts-expect-error
|
|
613
|
+
if (result.tx.gas && request.feePayer && !result.tx.feePayerSignature)
|
|
614
|
+
result.tx.gas = Hex.fromNumber(BigInt(result.tx.gas) + 20000n);
|
|
615
|
+
const upstreamCapabilities = result.capabilities;
|
|
616
|
+
const sponsor = upstreamCapabilities?.['sponsor'];
|
|
617
|
+
// External fee-payer relays surface chain reverts (e.g. InsufficientBalance,
|
|
618
|
+
// InsufficientAllowance) inside `capabilities.error` with a stub `tx`
|
|
619
|
+
// instead of throwing. Re-throw all upstream errors so the outer handler
|
|
620
|
+
// either recovers via autoSwap (InsufficientBalance) or propagates the
|
|
621
|
+
// error to the caller. Without this, the wallet would otherwise sign the
|
|
622
|
+
// zero-stub tx with its own fee payer and broadcast garbage.
|
|
623
|
+
const upstreamError = upstreamCapabilities?.['error'];
|
|
624
|
+
if (upstreamError) {
|
|
625
|
+
const synthetic = new Error(upstreamError.message ?? upstreamError.errorName ?? 'UpstreamRevert');
|
|
626
|
+
synthetic.name = 'UpstreamRevertError';
|
|
627
|
+
synthetic.data = upstreamError.data;
|
|
628
|
+
throw synthetic;
|
|
629
|
+
}
|
|
630
|
+
// Reconstruct a `swap` shape from upstream's `capabilities.autoSwap` so the
|
|
631
|
+
// wallet relay's outer code can re-resolve autoSwap metadata locally —
|
|
632
|
+
// otherwise upstream-driven swaps are silently dropped from the response.
|
|
633
|
+
const swap = extractSwapFromCapabilities(upstreamCapabilities?.['autoSwap']);
|
|
634
|
+
// The chain's `eth_fillTransaction` doesn't echo back `calls`, so merge
|
|
635
|
+
// them in from the original request before normalizing — otherwise the
|
|
636
|
+
// typed envelope built for sponsorship signing throws CallsEmptyError.
|
|
637
|
+
const mergedTx = mergeCallsFromRequest(result.tx, request);
|
|
638
|
+
// The node's `eth_fillTransaction` may pick a fee token the user
|
|
639
|
+
// doesn't yet hold (e.g. one this transaction will mint via a
|
|
640
|
+
// swap). Tempo's gas check runs before the calls execute, so the
|
|
641
|
+
// tx would revert at broadcast with `have 0 want N`. Validate the
|
|
642
|
+
// sender's pre-tx balance against the computed gas cost and, if
|
|
643
|
+
// short, autoSwap a token they do hold into the fee token.
|
|
644
|
+
if (autoSwap && !swap) {
|
|
645
|
+
const fromAddress = request['from'];
|
|
646
|
+
const resolvedFeeToken = (mergedTx['feeToken'] ?? feeToken);
|
|
647
|
+
const gas = mergedTx['gas'] ? BigInt(mergedTx['gas']) : 0n;
|
|
648
|
+
const maxFeePerGas = mergedTx['maxFeePerGas']
|
|
649
|
+
? BigInt(mergedTx['maxFeePerGas'])
|
|
650
|
+
: 0n;
|
|
651
|
+
if (fromAddress && resolvedFeeToken && gas > 0n && maxFeePerGas > 0n) {
|
|
652
|
+
const [balance, metadata] = await Promise.all([
|
|
653
|
+
Actions.token
|
|
654
|
+
.getBalance(client, { account: fromAddress, token: resolvedFeeToken })
|
|
655
|
+
.then((balance) => balance.amount)
|
|
656
|
+
.catch(() => 0n),
|
|
657
|
+
resolveTokenMetadata(client, { token: resolvedFeeToken, store }).catch(() => undefined),
|
|
658
|
+
]);
|
|
659
|
+
if (metadata) {
|
|
660
|
+
const requiredFee = (gas * maxFeePerGas) / 10n ** BigInt(Math.max(0, 18 - metadata.decimals));
|
|
661
|
+
if (balance < requiredFee) {
|
|
662
|
+
// Best-effort: if the swap itself can't be filled (e.g. the
|
|
663
|
+
// source token also has insufficient balance), fall through
|
|
664
|
+
// and return the original tx with the resolved feeToken
|
|
665
|
+
// rather than failing the whole request.
|
|
666
|
+
const swapResult = await fillWithSwap(resolvedFeeToken, requiredFee - balance).catch(() => null);
|
|
667
|
+
if (swapResult)
|
|
668
|
+
return swapResult;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
transaction: Utils.normalizeTempoTransaction(mergedTx),
|
|
675
|
+
sponsor,
|
|
676
|
+
...(swap ? { swap } : {}),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
if (!(error instanceof Error))
|
|
681
|
+
throw error;
|
|
682
|
+
if (!autoSwap)
|
|
683
|
+
throw error;
|
|
684
|
+
const revert = ExecutionError.parse(error);
|
|
685
|
+
if (revert?.errorName !== 'InsufficientBalance')
|
|
686
|
+
throw error;
|
|
687
|
+
const [available, required, token] = revert.args;
|
|
688
|
+
if (typeof available === 'undefined' || typeof required === 'undefined' || !token)
|
|
689
|
+
throw error;
|
|
690
|
+
const swapResult = await fillWithSwap(token, required - available);
|
|
691
|
+
if (!swapResult)
|
|
692
|
+
throw error;
|
|
693
|
+
return swapResult;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async function resolveFeeToken(client, options) {
|
|
697
|
+
const { feeToken, account, store, tokens } = options;
|
|
698
|
+
if (feeToken)
|
|
699
|
+
return feeToken;
|
|
700
|
+
if (!account)
|
|
701
|
+
return undefined;
|
|
702
|
+
// Cache the user's preferred fee token for `userTokenCacheTtl` seconds.
|
|
703
|
+
// The on-chain preference rarely changes; a short TTL avoids the
|
|
704
|
+
// ~150-250ms `userTokens` multicall on every unsponsored fill while
|
|
705
|
+
// still picking up updates within a minute. The cached value is just a
|
|
706
|
+
// hint — if the user's balance is 0 we fall back to the highest-balance
|
|
707
|
+
// token from the configured list.
|
|
708
|
+
const getUserToken = () => Actions.fee.getUserToken(client, { account }).catch(() => null);
|
|
709
|
+
const userTokenPromise = store
|
|
710
|
+
? Store.memoize(async () => {
|
|
711
|
+
const result = await getUserToken();
|
|
712
|
+
return result ? { address: result.address } : null;
|
|
713
|
+
}, {
|
|
714
|
+
key: `fee.userToken:${client.chain?.id ?? 0}:${account.toLowerCase()}`,
|
|
715
|
+
store,
|
|
716
|
+
ttl: (options.userTokenCacheTtl ?? 60) * 1000,
|
|
717
|
+
})
|
|
718
|
+
: getUserToken();
|
|
719
|
+
const [userToken, balances] = await Promise.all([
|
|
720
|
+
userTokenPromise,
|
|
721
|
+
tokens
|
|
722
|
+
? Promise.all(tokens.map(async (token) => ({
|
|
723
|
+
address: token,
|
|
724
|
+
balance: await Actions.token
|
|
725
|
+
.getBalance(client, { account, token })
|
|
726
|
+
.then((balance) => balance.amount)
|
|
727
|
+
.catch(() => 0n),
|
|
728
|
+
})))
|
|
729
|
+
: [],
|
|
730
|
+
]);
|
|
731
|
+
// If on-chain preference is set and user has balance, use it.
|
|
732
|
+
if (userToken) {
|
|
733
|
+
const match = balances.find((b) => b.address.toLowerCase() === userToken.address.toLowerCase() && b.balance > 0n);
|
|
734
|
+
if (match)
|
|
735
|
+
return userToken.address;
|
|
736
|
+
// Token list may not include the preference — check on-chain directly.
|
|
737
|
+
if (!match) {
|
|
738
|
+
try {
|
|
739
|
+
const { amount: balance } = await Actions.token.getBalance(client, {
|
|
740
|
+
account,
|
|
741
|
+
token: userToken.address,
|
|
742
|
+
});
|
|
743
|
+
if (balance > 0n)
|
|
744
|
+
return userToken.address;
|
|
745
|
+
}
|
|
746
|
+
catch { }
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
// Pick the token with the highest balance.
|
|
750
|
+
let best;
|
|
751
|
+
for (const asset of balances) {
|
|
752
|
+
if (asset.balance <= 0n)
|
|
753
|
+
continue;
|
|
754
|
+
if (!best || asset.balance > best.balance)
|
|
755
|
+
best = asset;
|
|
756
|
+
}
|
|
757
|
+
if (best)
|
|
758
|
+
return best.address;
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Extracts unique TIP20 token addresses (Tempo `0x20c0…` prefix) that the
|
|
763
|
+
* transaction's calls target. Used as fallback fee-token candidates so a
|
|
764
|
+
* user transferring a token they hold can pay gas in that token even when
|
|
765
|
+
* the configured fee-token list defaults to one they don't hold.
|
|
766
|
+
*/
|
|
767
|
+
function callTargetTokens(transaction) {
|
|
768
|
+
const calls = transaction['calls'];
|
|
769
|
+
if (!calls)
|
|
770
|
+
return [];
|
|
771
|
+
const out = [];
|
|
772
|
+
const seen = new Set();
|
|
773
|
+
for (const c of calls) {
|
|
774
|
+
if (!c.to)
|
|
775
|
+
continue;
|
|
776
|
+
const lower = c.to.toLowerCase();
|
|
777
|
+
if (!lower.startsWith('0x20c0'))
|
|
778
|
+
continue;
|
|
779
|
+
if (seen.has(lower))
|
|
780
|
+
continue;
|
|
781
|
+
seen.add(lower);
|
|
782
|
+
out.push(c.to);
|
|
783
|
+
}
|
|
784
|
+
return out;
|
|
785
|
+
}
|
|
786
|
+
async function resolveVirtualAddresses(client, options) {
|
|
787
|
+
const targets = getVirtualAddressTargets(options.calls);
|
|
788
|
+
if (targets.length === 0)
|
|
789
|
+
return undefined;
|
|
790
|
+
const masters = new Map();
|
|
791
|
+
for (const address of targets) {
|
|
792
|
+
const { masterId } = VirtualAddress.parse(address);
|
|
793
|
+
const lower = masterId.toLowerCase();
|
|
794
|
+
const entry = masters.get(lower) ?? { addresses: [], masterId };
|
|
795
|
+
entry.addresses.push(address);
|
|
796
|
+
masters.set(lower, entry);
|
|
797
|
+
}
|
|
798
|
+
const entries = await Promise.all([...masters.values()].map(async ({ addresses, masterId }) => {
|
|
799
|
+
const master = await Actions.virtualAddress.getMasterAddress(client, { masterId });
|
|
800
|
+
return addresses.map((address) => [address, master]);
|
|
801
|
+
}));
|
|
802
|
+
return Object.fromEntries(entries.flat());
|
|
803
|
+
}
|
|
804
|
+
function getVirtualAddressTargets(calls) {
|
|
805
|
+
const targets = new Set();
|
|
806
|
+
for (const call of calls) {
|
|
807
|
+
for (const address of [call.to, decodeTransferRecipient(call.data)]) {
|
|
808
|
+
if (!address || !isAddress(address) || !VirtualAddress.isVirtual(address))
|
|
809
|
+
continue;
|
|
810
|
+
targets.add(address.toLowerCase());
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return [...targets];
|
|
814
|
+
}
|
|
815
|
+
function decodeTransferRecipient(data) {
|
|
816
|
+
if (!data)
|
|
817
|
+
return undefined;
|
|
818
|
+
const selector = data.slice(0, 10).toLowerCase();
|
|
819
|
+
if (!transferSelectors.has(selector))
|
|
820
|
+
return undefined;
|
|
821
|
+
try {
|
|
822
|
+
const { args, functionName } = decodeFunctionData({
|
|
823
|
+
abi: Abis.tip20,
|
|
824
|
+
data: data,
|
|
825
|
+
});
|
|
826
|
+
if ((functionName === 'transfer' || functionName === 'transferWithMemo') &&
|
|
827
|
+
typeof args[0] === 'string')
|
|
828
|
+
return args[0];
|
|
829
|
+
if ((functionName === 'transferFrom' || functionName === 'transferFromWithMemo') &&
|
|
830
|
+
typeof args[1] === 'string')
|
|
831
|
+
return args[1];
|
|
832
|
+
}
|
|
833
|
+
catch { }
|
|
834
|
+
return undefined;
|
|
835
|
+
}
|
|
836
|
+
const transferSelectors = new Set(['0xa9059cbb', '0x95777d59', '0x23b872dd', '0x929c2539']);
|
|
837
|
+
// TODO: cleanup/remove
|
|
838
|
+
function extractCalls(transaction) {
|
|
839
|
+
const calls = transaction['calls'];
|
|
840
|
+
if (calls && calls.length > 0)
|
|
841
|
+
return calls.map((c) => ({
|
|
842
|
+
...(c.to ? { to: c.to } : {}),
|
|
843
|
+
...(c.data ? { data: c.data } : {}),
|
|
844
|
+
...(c.value ? { value: c.value } : {}),
|
|
845
|
+
}));
|
|
846
|
+
return [
|
|
847
|
+
{
|
|
848
|
+
...(transaction['to'] ? { to: transaction['to'] } : {}),
|
|
849
|
+
...(transaction['data'] ? { data: transaction['data'] } : {}),
|
|
850
|
+
...(transaction['value'] ? { value: transaction['value'] } : {}),
|
|
851
|
+
},
|
|
852
|
+
];
|
|
853
|
+
}
|
|
854
|
+
// TODO: cleanup/remove
|
|
855
|
+
async function simulate(client, options) {
|
|
856
|
+
const { account, calls } = options;
|
|
857
|
+
try {
|
|
858
|
+
return await Actions.simulate.simulateCalls(client, {
|
|
859
|
+
...(account ? { account } : {}),
|
|
860
|
+
calls: calls,
|
|
861
|
+
traceTransfers: true,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
// TODO: Remove fallback once all nodes support tempo_simulateV1.
|
|
866
|
+
// Fall back to viem's simulateCalls (eth_simulateV1) if the Tempo
|
|
867
|
+
// method (tempo_simulateV1) is not supported.
|
|
868
|
+
const code = error.code ??
|
|
869
|
+
error.cause?.code;
|
|
870
|
+
if (code !== -32601)
|
|
871
|
+
throw error;
|
|
872
|
+
const { results } = await simulateCalls(client, {
|
|
873
|
+
...(account ? { account } : {}),
|
|
874
|
+
calls: calls,
|
|
875
|
+
});
|
|
876
|
+
return { results, tokenMetadata: undefined };
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
async function simulateAndParseDiffs(client, options) {
|
|
880
|
+
const { account, calls, swap, feeToken, gas, store, maxFeePerGas } = options;
|
|
881
|
+
try {
|
|
882
|
+
const { results, tokenMetadata } = await simulate(client, {
|
|
883
|
+
account: account === zeroAddress ? undefined : account,
|
|
884
|
+
calls,
|
|
885
|
+
});
|
|
886
|
+
// Collect all logs across all call results.
|
|
887
|
+
const logs = [];
|
|
888
|
+
for (const result of results)
|
|
889
|
+
if (result.logs)
|
|
890
|
+
logs.push(...result.logs);
|
|
891
|
+
// Build per-token balance diffs relative to the sender.
|
|
892
|
+
const balanceDiffs = account
|
|
893
|
+
? await buildBalanceDiffs(client, {
|
|
894
|
+
account,
|
|
895
|
+
store,
|
|
896
|
+
logs,
|
|
897
|
+
swap,
|
|
898
|
+
tokenMetadata: tokenMetadata,
|
|
899
|
+
})
|
|
900
|
+
: {};
|
|
901
|
+
// Compute fee breakdown.
|
|
902
|
+
const fee = await computeFee(client, {
|
|
903
|
+
feeToken,
|
|
904
|
+
gas,
|
|
905
|
+
store,
|
|
906
|
+
maxFeePerGas,
|
|
907
|
+
tokenMetadata: tokenMetadata,
|
|
908
|
+
}).catch(() => undefined);
|
|
909
|
+
return { balanceDiffs, fee };
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
// Simulation failures should not block the fill response —
|
|
913
|
+
// return empty diffs with fee computed from transaction fields.
|
|
914
|
+
const fee = await computeFee(client, { feeToken, gas, store, maxFeePerGas });
|
|
915
|
+
return { balanceDiffs: undefined, fee };
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function buildBalanceDiffs(client, options) {
|
|
919
|
+
const { account, store, logs, swap, tokenMetadata } = options;
|
|
920
|
+
const accountLower = account.toLowerCase();
|
|
921
|
+
const dexLower = Addresses.stablecoinDex.toLowerCase();
|
|
922
|
+
const swapTokenIn = swap?.tokenIn.toLowerCase();
|
|
923
|
+
const swapTokenOut = swap?.tokenOut.toLowerCase();
|
|
924
|
+
const transferLogs = parseEventLogs({
|
|
925
|
+
abi: [AbiEvent.fromAbi(Abis.tip20, 'Transfer')],
|
|
926
|
+
eventName: 'Transfer',
|
|
927
|
+
logs,
|
|
928
|
+
});
|
|
929
|
+
const approvalLogs = parseEventLogs({
|
|
930
|
+
abi: [AbiEvent.fromAbi(Abis.tip20, 'Approval')],
|
|
931
|
+
eventName: 'Approval',
|
|
932
|
+
logs,
|
|
933
|
+
});
|
|
934
|
+
// Track net movement per token: incoming vs outgoing.
|
|
935
|
+
const tokenMap = new Map();
|
|
936
|
+
// Track total transferred per (token, spender) so we can suppress covered approvals.
|
|
937
|
+
const transferredBySpender = new Map();
|
|
938
|
+
for (const log of transferLogs) {
|
|
939
|
+
const token = log.address.toLowerCase();
|
|
940
|
+
const fromLower = log.args.from.toLowerCase();
|
|
941
|
+
const toLower = log.args.to.toLowerCase();
|
|
942
|
+
// Skip swap-related transfers (reported in capabilities.autoSwap instead).
|
|
943
|
+
if (swap) {
|
|
944
|
+
if (token === swapTokenIn && fromLower === accountLower && toLower === dexLower)
|
|
945
|
+
continue;
|
|
946
|
+
if (token === swapTokenOut && fromLower === dexLower && toLower === accountLower)
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
const entry = tokenMap.get(token) ?? {
|
|
950
|
+
incoming: 0n,
|
|
951
|
+
outgoing: 0n,
|
|
952
|
+
recipients: new Set(),
|
|
953
|
+
token: log.address,
|
|
954
|
+
};
|
|
955
|
+
if (fromLower === accountLower) {
|
|
956
|
+
entry.outgoing += log.args.amount;
|
|
957
|
+
entry.recipients.add(log.args.to);
|
|
958
|
+
const key = `${token}:${toLower}`;
|
|
959
|
+
transferredBySpender.set(key, (transferredBySpender.get(key) ?? 0n) + log.args.amount);
|
|
960
|
+
}
|
|
961
|
+
if (toLower === accountLower)
|
|
962
|
+
entry.incoming += log.args.amount;
|
|
963
|
+
tokenMap.set(token, entry);
|
|
964
|
+
}
|
|
965
|
+
// Treat approvals as outgoing unless the spender already transferred >= approval amount.
|
|
966
|
+
for (const log of approvalLogs) {
|
|
967
|
+
if (log.args.owner.toLowerCase() !== accountLower)
|
|
968
|
+
continue;
|
|
969
|
+
const token = log.address.toLowerCase();
|
|
970
|
+
// Skip swap-related approvals (reported in capabilities.autoSwap instead).
|
|
971
|
+
if (swap && token === swapTokenIn && log.args.spender.toLowerCase() === dexLower)
|
|
972
|
+
continue;
|
|
973
|
+
const spenderKey = `${token}:${log.args.spender.toLowerCase()}`;
|
|
974
|
+
const transferred = transferredBySpender.get(spenderKey) ?? 0n;
|
|
975
|
+
if (log.args.amount <= transferred)
|
|
976
|
+
continue;
|
|
977
|
+
const entry = tokenMap.get(token) ?? {
|
|
978
|
+
incoming: 0n,
|
|
979
|
+
outgoing: 0n,
|
|
980
|
+
recipients: new Set(),
|
|
981
|
+
token: log.address,
|
|
982
|
+
};
|
|
983
|
+
entry.outgoing += log.args.amount - transferred;
|
|
984
|
+
entry.recipients.add(log.args.spender);
|
|
985
|
+
tokenMap.set(token, entry);
|
|
986
|
+
}
|
|
987
|
+
// Collect unique tokens that need decimals.
|
|
988
|
+
const entries = [...tokenMap.values()].filter((e) => {
|
|
989
|
+
const net = e.outgoing > e.incoming ? e.outgoing - e.incoming : e.incoming - e.outgoing;
|
|
990
|
+
return net > 0n;
|
|
991
|
+
});
|
|
992
|
+
if (entries.length === 0)
|
|
993
|
+
return {};
|
|
994
|
+
// Resolve metadata for all tokens in parallel (simulation metadata first, RPC fallback).
|
|
995
|
+
const metadataMap = new Map();
|
|
996
|
+
await Promise.all(entries.map(async (entry) => {
|
|
997
|
+
try {
|
|
998
|
+
const metadata = await resolveTokenMetadata(client, {
|
|
999
|
+
token: entry.token,
|
|
1000
|
+
tokenMetadata,
|
|
1001
|
+
store,
|
|
1002
|
+
});
|
|
1003
|
+
metadataMap.set(entry.token.toLowerCase(), metadata);
|
|
1004
|
+
}
|
|
1005
|
+
catch { }
|
|
1006
|
+
}));
|
|
1007
|
+
// Build the diff array for this account.
|
|
1008
|
+
const diffs = [];
|
|
1009
|
+
for (const entry of entries) {
|
|
1010
|
+
const net = entry.outgoing > entry.incoming
|
|
1011
|
+
? entry.outgoing - entry.incoming
|
|
1012
|
+
: entry.incoming - entry.outgoing;
|
|
1013
|
+
const direction = entry.outgoing > entry.incoming ? 'outgoing' : 'incoming';
|
|
1014
|
+
const meta = metadataMap.get(entry.token.toLowerCase());
|
|
1015
|
+
const decimals = meta?.decimals ?? 0;
|
|
1016
|
+
diffs.push({
|
|
1017
|
+
address: entry.token,
|
|
1018
|
+
decimals,
|
|
1019
|
+
direction,
|
|
1020
|
+
formatted: formatUnits(net, decimals),
|
|
1021
|
+
name: meta?.name ?? '',
|
|
1022
|
+
symbol: meta?.symbol ?? '',
|
|
1023
|
+
recipients: [...entry.recipients],
|
|
1024
|
+
value: Hex.fromNumber(net),
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
return { [account]: diffs };
|
|
1028
|
+
}
|
|
1029
|
+
async function resolveTokenMetadata(client, options) {
|
|
1030
|
+
const { token, tokenMetadata, store } = options;
|
|
1031
|
+
const meta = tokenMetadata?.[token] ?? tokenMetadata?.[token.toLowerCase()];
|
|
1032
|
+
// TIP-20 metadata (decimals/symbol/name) is immutable per token, so cache
|
|
1033
|
+
// long-term. Skips the multicall RPC on cache hits.
|
|
1034
|
+
const fetcher = () => Actions.token.getMetadata(client, { token });
|
|
1035
|
+
const fallback = store
|
|
1036
|
+
? await Store.memoize(fetcher, {
|
|
1037
|
+
key: `tokenMetadata:${client.chain?.id ?? 0}:${token.toLowerCase()}`,
|
|
1038
|
+
store,
|
|
1039
|
+
ttl: 24 * 60 * 60 * 1000,
|
|
1040
|
+
})
|
|
1041
|
+
: await fetcher();
|
|
1042
|
+
return {
|
|
1043
|
+
decimals: fallback.decimals ?? 6,
|
|
1044
|
+
symbol: meta?.symbol || fallback.symbol,
|
|
1045
|
+
name: meta?.name || fallback.name,
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
async function resolveAutoSwapMetadata(client, options) {
|
|
1049
|
+
const { autoSwap, store, swap } = options;
|
|
1050
|
+
if (!autoSwap || !swap)
|
|
1051
|
+
return undefined;
|
|
1052
|
+
const [inMeta, outMeta] = await Promise.all([
|
|
1053
|
+
resolveTokenMetadata(client, { token: swap.tokenIn, store }).catch(() => undefined),
|
|
1054
|
+
resolveTokenMetadata(client, { token: swap.tokenOut, store }).catch(() => undefined),
|
|
1055
|
+
]);
|
|
1056
|
+
if (!inMeta || !outMeta)
|
|
1057
|
+
return undefined;
|
|
1058
|
+
return {
|
|
1059
|
+
calls: swap.calls.map((c) => ({ to: c.to, data: c.data })),
|
|
1060
|
+
slippage: autoSwap.slippage,
|
|
1061
|
+
maxIn: {
|
|
1062
|
+
token: swap.tokenIn,
|
|
1063
|
+
value: Hex.fromNumber(swap.maxAmountIn),
|
|
1064
|
+
formatted: formatUnits(swap.maxAmountIn, inMeta.decimals),
|
|
1065
|
+
decimals: inMeta.decimals,
|
|
1066
|
+
symbol: inMeta.symbol,
|
|
1067
|
+
name: inMeta.name,
|
|
1068
|
+
},
|
|
1069
|
+
minOut: {
|
|
1070
|
+
token: swap.tokenOut,
|
|
1071
|
+
value: Hex.fromNumber(swap.amountOut),
|
|
1072
|
+
formatted: formatUnits(swap.amountOut, outMeta.decimals),
|
|
1073
|
+
decimals: outMeta.decimals,
|
|
1074
|
+
symbol: outMeta.symbol,
|
|
1075
|
+
name: outMeta.name,
|
|
1076
|
+
},
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
async function computeFee(client, options) {
|
|
1080
|
+
const { feeToken, gas, store, maxFeePerGas, tokenMetadata } = options;
|
|
1081
|
+
if (!feeToken || !gas || !maxFeePerGas)
|
|
1082
|
+
return undefined;
|
|
1083
|
+
try {
|
|
1084
|
+
const metadata = await resolveTokenMetadata(client, { token: feeToken, tokenMetadata, store });
|
|
1085
|
+
const raw = gas * maxFeePerGas;
|
|
1086
|
+
const amount = raw / 10n ** BigInt(18 - metadata.decimals);
|
|
1087
|
+
return {
|
|
1088
|
+
amount: Hex.fromNumber(amount),
|
|
1089
|
+
decimals: metadata.decimals,
|
|
1090
|
+
formatted: formatUnits(amount, metadata.decimals),
|
|
1091
|
+
symbol: metadata.symbol,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
catch {
|
|
1095
|
+
return undefined;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function buildSwapCalls(client, sourceToken, targetToken, deficit, maxAmountIn) {
|
|
1099
|
+
const approve = Actions.token.approve.call(client, {
|
|
1100
|
+
token: sourceToken,
|
|
1101
|
+
spender: Addresses.stablecoinDex,
|
|
1102
|
+
amount: maxAmountIn,
|
|
1103
|
+
});
|
|
1104
|
+
const buy = Actions.dex.buy.call({
|
|
1105
|
+
tokenIn: sourceToken,
|
|
1106
|
+
tokenOut: targetToken,
|
|
1107
|
+
amountOut: deficit,
|
|
1108
|
+
maxAmountIn,
|
|
1109
|
+
});
|
|
1110
|
+
return [
|
|
1111
|
+
{ to: approve.to, data: approve.data, value: 0n },
|
|
1112
|
+
{ to: buy.to, data: buy.data, value: 0n },
|
|
1113
|
+
];
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Merges the original fill request into the result tx. The chain's
|
|
1117
|
+
* `eth_fillTransaction` returns only the "filled" gas/nonce/fee fields and
|
|
1118
|
+
* omits envelope inputs like `calls`, `chainId`, `validBefore`, `nonceKey`,
|
|
1119
|
+
* `keyData`, `keyType`, `feePayer`. Without these the typed Tempo envelope
|
|
1120
|
+
* built for sponsorship signing throws `CallsEmptyError` or
|
|
1121
|
+
* `Cannot convert undefined to a BigInt` when serializing.
|
|
1122
|
+
*
|
|
1123
|
+
* Result fields take precedence (they are the chain's authoritative filled
|
|
1124
|
+
* values); request fields fill in everything else. Calls are normalized
|
|
1125
|
+
* separately so legacy `to`/`data`/`value` requests are also supported.
|
|
1126
|
+
*/
|
|
1127
|
+
function mergeCallsFromRequest(resultTx, request) {
|
|
1128
|
+
const merged = { ...request, ...resultTx };
|
|
1129
|
+
const resultCalls = resultTx['calls'];
|
|
1130
|
+
if (Array.isArray(resultCalls) && resultCalls.length > 0)
|
|
1131
|
+
return merged;
|
|
1132
|
+
const reqCalls = request['calls'];
|
|
1133
|
+
if (Array.isArray(reqCalls) && reqCalls.length > 0) {
|
|
1134
|
+
merged['calls'] = reqCalls;
|
|
1135
|
+
return merged;
|
|
1136
|
+
}
|
|
1137
|
+
const { to, data, value } = request;
|
|
1138
|
+
if (typeof to === 'undefined' && typeof data === 'undefined' && typeof value === 'undefined')
|
|
1139
|
+
return merged;
|
|
1140
|
+
merged['calls'] = [
|
|
1141
|
+
{
|
|
1142
|
+
...(typeof to !== 'undefined' ? { to } : {}),
|
|
1143
|
+
...(typeof data !== 'undefined' ? { data } : {}),
|
|
1144
|
+
...(typeof value !== 'undefined' ? { value } : {}),
|
|
1145
|
+
},
|
|
1146
|
+
];
|
|
1147
|
+
return merged;
|
|
1148
|
+
}
|
|
1149
|
+
/** Normalizes external fee-payer relay URLs before forwarding fill payloads. */
|
|
1150
|
+
function normalizeExternalFeePayerUrl(value, options = {}) {
|
|
1151
|
+
let url;
|
|
1152
|
+
try {
|
|
1153
|
+
url = new URL(value);
|
|
1154
|
+
}
|
|
1155
|
+
catch {
|
|
1156
|
+
throw new RpcResponse.InvalidParamsError({ message: 'Invalid fee payer URL.' });
|
|
1157
|
+
}
|
|
1158
|
+
const allowUnsafe = options.allowUnsafe === true;
|
|
1159
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
1160
|
+
throw new RpcResponse.InvalidParamsError({ message: 'Invalid fee payer URL protocol.' });
|
|
1161
|
+
if (!allowUnsafe && url.protocol !== 'https:')
|
|
1162
|
+
throw new RpcResponse.InvalidParamsError({ message: 'Invalid fee payer URL protocol.' });
|
|
1163
|
+
if (!allowUnsafe && isUnsafeHostname(url.hostname))
|
|
1164
|
+
throw new RpcResponse.InvalidParamsError({ message: 'Invalid fee payer URL host.' });
|
|
1165
|
+
url.username = '';
|
|
1166
|
+
url.password = '';
|
|
1167
|
+
url.hash = '';
|
|
1168
|
+
return url.href;
|
|
1169
|
+
}
|
|
1170
|
+
function isUnsafeHostname(hostname) {
|
|
1171
|
+
const host = normalizeHostname(hostname);
|
|
1172
|
+
if (host === 'localhost' || host.endsWith('.localhost'))
|
|
1173
|
+
return true;
|
|
1174
|
+
const ipv4 = parseIpv4(host);
|
|
1175
|
+
if (ipv4)
|
|
1176
|
+
return isUnsafeIpv4(ipv4);
|
|
1177
|
+
if (!host.includes(':'))
|
|
1178
|
+
return false;
|
|
1179
|
+
const ipv6 = parseIpv6(host);
|
|
1180
|
+
if (!ipv6)
|
|
1181
|
+
return true;
|
|
1182
|
+
return isUnsafeIpv6(ipv6);
|
|
1183
|
+
}
|
|
1184
|
+
function normalizeHostname(hostname) {
|
|
1185
|
+
return hostname
|
|
1186
|
+
.toLowerCase()
|
|
1187
|
+
.replace(/^\[|\]$/g, '')
|
|
1188
|
+
.split('%')[0];
|
|
1189
|
+
}
|
|
1190
|
+
function parseIpv4(value) {
|
|
1191
|
+
const parts = value.split('.');
|
|
1192
|
+
if (parts.length !== 4 || parts.some((part) => !/^\d+$/.test(part)))
|
|
1193
|
+
return undefined;
|
|
1194
|
+
const numbers = parts.map((part) => Number(part));
|
|
1195
|
+
if (numbers.some((part) => !Number.isInteger(part) || part < 0 || part > 255))
|
|
1196
|
+
return undefined;
|
|
1197
|
+
return numbers;
|
|
1198
|
+
}
|
|
1199
|
+
function isUnsafeIpv4(parts) {
|
|
1200
|
+
const [a, b] = parts;
|
|
1201
|
+
if (a === 0 || a === 10 || a === 127)
|
|
1202
|
+
return true;
|
|
1203
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
1204
|
+
return true;
|
|
1205
|
+
if (a === 169 && b === 254)
|
|
1206
|
+
return true;
|
|
1207
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
1208
|
+
return true;
|
|
1209
|
+
if (a === 192 && b === 168)
|
|
1210
|
+
return true;
|
|
1211
|
+
if (a >= 224)
|
|
1212
|
+
return true;
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
function parseIpv6(value) {
|
|
1216
|
+
const halves = value.split('::');
|
|
1217
|
+
if (halves.length > 2)
|
|
1218
|
+
return undefined;
|
|
1219
|
+
const left = halves[0] ? halves[0].split(':') : [];
|
|
1220
|
+
const right = halves[1] ? halves[1].split(':') : [];
|
|
1221
|
+
const missing = halves.length === 2 ? 8 - left.length - right.length : 0;
|
|
1222
|
+
if (missing < 0)
|
|
1223
|
+
return undefined;
|
|
1224
|
+
const parts = [...left, ...Array(missing).fill('0'), ...right];
|
|
1225
|
+
if (parts.length !== 8)
|
|
1226
|
+
return undefined;
|
|
1227
|
+
const numbers = parts.map((part) => /^[\da-f]{1,4}$/i.test(part) ? Number.parseInt(part, 16) : NaN);
|
|
1228
|
+
if (numbers.some((part) => !Number.isInteger(part) || part < 0 || part > 0xffff))
|
|
1229
|
+
return undefined;
|
|
1230
|
+
return numbers;
|
|
1231
|
+
}
|
|
1232
|
+
function isUnsafeIpv6(parts) {
|
|
1233
|
+
const [a] = parts;
|
|
1234
|
+
if (parts.every((part) => part === 0))
|
|
1235
|
+
return true;
|
|
1236
|
+
if (parts.slice(0, 7).every((part) => part === 0) && parts[7] === 1)
|
|
1237
|
+
return true;
|
|
1238
|
+
if ((a & 0xfe00) === 0xfc00)
|
|
1239
|
+
return true;
|
|
1240
|
+
if ((a & 0xffc0) === 0xfe80)
|
|
1241
|
+
return true;
|
|
1242
|
+
if ((a & 0xffc0) === 0xfec0)
|
|
1243
|
+
return true;
|
|
1244
|
+
if ((a & 0xff00) === 0xff00)
|
|
1245
|
+
return true;
|
|
1246
|
+
const ipv4 = ipv4FromIpv6(parts);
|
|
1247
|
+
if (ipv4)
|
|
1248
|
+
return isUnsafeIpv4(ipv4);
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
function ipv4FromIpv6(parts) {
|
|
1252
|
+
const mapped = parts.slice(0, 5).every((part) => part === 0) && parts[5] === 0xffff;
|
|
1253
|
+
const compatible = parts.slice(0, 6).every((part) => part === 0);
|
|
1254
|
+
if (!mapped && !compatible)
|
|
1255
|
+
return undefined;
|
|
1256
|
+
return [parts[6] >> 8, parts[6] & 0xff, parts[7] >> 8, parts[7] & 0xff];
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Reconstructs a `swap` shape (matching the inner autoSwap branch's return
|
|
1260
|
+
* value) from an upstream relay's `capabilities.autoSwap`. Used so a wallet
|
|
1261
|
+
* relay forwarding to an external feePayer URL can re-emit autoSwap metadata
|
|
1262
|
+
* locally without losing track of the upstream's swap.
|
|
1263
|
+
*/
|
|
1264
|
+
function extractSwapFromCapabilities(autoSwap) {
|
|
1265
|
+
if (!autoSwap || typeof autoSwap !== 'object')
|
|
1266
|
+
return undefined;
|
|
1267
|
+
const a = autoSwap;
|
|
1268
|
+
if (!a.calls || !a.maxIn?.token || !a.maxIn.value || !a.minOut?.token || !a.minOut.value)
|
|
1269
|
+
return undefined;
|
|
1270
|
+
return {
|
|
1271
|
+
calls: a.calls,
|
|
1272
|
+
tokenIn: a.maxIn.token,
|
|
1273
|
+
tokenOut: a.minOut.token,
|
|
1274
|
+
amountOut: BigInt(a.minOut.value),
|
|
1275
|
+
maxAmountIn: BigInt(a.maxIn.value),
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
//# sourceMappingURL=relay.js.map
|