tapimo 0.12.0 → 0.14.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 +11333 -9563
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +39 -3
- package/dist/App.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/Webhooks.js +2 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +1064 -924
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +80 -0
- 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 +63 -15
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +2 -1
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +92 -3
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +69 -7
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +7 -7
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +151 -151
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/Zones.d.ts +28 -28
- package/dist/apps/Zones.js +7 -7
- package/dist/apps/data/App.d.ts +2909 -2523
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +26 -21
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/ZoneSelection.d.ts +8 -8
- package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
- package/dist/apps/data/ZoneSelection.js +1 -1
- package/dist/apps/data/ZoneSelection.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +588 -584
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +1561 -1239
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +22 -22
- package/dist/apps/data/routes/balances.d.ts +26 -26
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +43 -16
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +44 -44
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +21 -11
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +3 -3
- package/dist/apps/data/routes/earn.d.ts +201 -25
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +434 -75
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +160 -145
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +180 -114
- 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 +18 -19
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +155 -55
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +28 -10
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +13 -13
- package/dist/apps/data/routes/tokens.d.ts +5 -5
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +51 -20
- 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 +37 -17
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +13 -13
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +18 -12
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +31 -31
- 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 +8 -8
- package/dist/apps/data/routes/webhooks.d.ts +816 -113
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +47 -5
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +14 -14
- package/dist/apps/data/routes/zones.js +1 -1
- package/dist/apps/funding/App.d.ts +466 -44
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +1 -1
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +17 -12
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +650 -57
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
- package/dist/apps/funding/routes/transfers.js +238 -24
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +5120 -4152
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/Billing.d.ts +1 -1
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +17 -1
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +632 -81
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +236 -4
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +76 -76
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invitations.d.ts +24 -24
- package/dist/apps/management/routes/invite-links.d.ts +4 -4
- package/dist/apps/management/routes/members.d.ts +16 -16
- package/dist/apps/management/routes/orgs.d.ts +20 -20
- package/dist/apps/management/routes/projects.d.ts +4 -4
- package/dist/apps/management/routes/usage.d.ts +8 -8
- package/dist/apps/management/routes/verified-token-requests.d.ts +17 -17
- package/dist/apps/management/routes/webhooks.d.ts +675 -25
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +21 -21
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +115 -23
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +27 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +34 -1
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +11 -5
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +21 -3
- package/dist/db/Schema.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/fundingTransferTransactions.d.ts +38 -0
- package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
- package/dist/db/tables/fundingTransferTransactions.js +44 -3
- package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
- package/dist/db/tables/fundingTransfers.d.ts +17 -0
- package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
- package/dist/db/tables/fundingTransfers.js +21 -0
- package/dist/db/tables/fundingTransfers.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +24 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +27 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -1
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +26 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +146 -13
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +9 -6
- 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 +3 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +6 -3
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EarnVaults.d.ts +3 -3
- package/dist/internal/EarnVaults.js +5 -5
- 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 +4 -2
- 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 +5 -3
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +13 -3
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +6 -3
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +8 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +16 -3
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +82 -16
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +7 -2
- 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/Viem.d.ts +16 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -2
- package/dist/internal/Viem.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/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/DepositWebhook.d.ts.map +1 -1
- package/dist/internal/funding/DepositWebhook.js +2 -1
- package/dist/internal/funding/DepositWebhook.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +2 -10
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +108 -13
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +34 -8
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
- package/dist/internal/funding/SourceObservation.js +5 -7
- package/dist/internal/funding/SourceObservation.js.map +1 -1
- package/dist/internal/funding/Transfer.d.ts +114 -18
- package/dist/internal/funding/Transfer.d.ts.map +1 -1
- package/dist/internal/funding/Transfer.js +185 -55
- package/dist/internal/funding/Transfer.js.map +1 -1
- package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
- package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
- package/dist/internal/funding/TransferReconciliation.js +192 -0
- package/dist/internal/funding/TransferReconciliation.js.map +1 -0
- package/dist/internal/funding/TransferWebhook.d.ts +21 -0
- package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
- package/dist/internal/funding/TransferWebhook.js +43 -0
- package/dist/internal/funding/TransferWebhook.js.map +1 -0
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +3 -12
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/dist/internal/funding/providers/stargate.d.ts +127 -9
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
- package/dist/internal/funding/providers/stargate.js +548 -112
- package/dist/internal/funding/providers/stargate.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
- package/dist/internal/webhookDestinations/slack.js +16 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -1
- package/package.json +2 -2
- package/src/App.test.ts +100 -0
- package/src/App.ts +43 -3
- package/src/Client.test-d.ts +129 -2
- package/src/Scope.test.ts +20 -0
- package/src/Scope.ts +16 -0
- package/src/Webhooks.ts +2 -2
- 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 +1 -1
- package/src/admin/apps/earn-vaults.ts +2 -1
- package/src/admin/apps/organizations.test.ts +53 -1
- package/src/admin/apps/organizations.ts +90 -5
- package/src/admin/ui/src/lib/api-keys.ts +19 -0
- package/src/admin/ui/src/lib/organizations.test.ts +112 -1
- package/src/admin/ui/src/lib/organizations.ts +55 -0
- package/src/admin/ui/src/routes/api-keys.tsx +89 -2
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
- package/src/admin/ui.generated.ts +189 -151
- package/src/apps/Zones.test.ts +7 -7
- package/src/apps/Zones.ts +7 -7
- package/src/apps/composition.test.ts +2 -1
- package/src/apps/data/App.test.ts +41 -0
- package/src/apps/data/App.ts +30 -21
- package/src/apps/data/ZoneSelection.test.ts +2 -2
- package/src/apps/data/ZoneSelection.ts +8 -8
- package/src/apps/data/routes/activities.test.ts +1526 -12
- package/src/apps/data/routes/activities.ts +1774 -1356
- package/src/apps/data/routes/balances.ts +133 -97
- package/src/apps/data/routes/blocks.test.ts +533 -0
- package/src/apps/data/routes/blocks.ts +35 -16
- package/src/apps/data/routes/earn.test.ts +373 -16
- package/src/apps/data/routes/earn.ts +548 -79
- package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
- package/src/apps/data/routes/exchanges.test.ts +740 -15
- package/src/apps/data/routes/exchanges.ts +954 -753
- package/src/apps/data/routes/fee-amm.ts +17 -18
- package/src/apps/data/routes/receipts.ts +47 -16
- package/src/apps/data/routes/tokens.test.ts +578 -0
- package/src/apps/data/routes/tokens.ts +283 -225
- package/src/apps/data/routes/transactions.ts +68 -29
- package/src/apps/data/routes/transfers.test.ts +421 -0
- package/src/apps/data/routes/transfers.ts +45 -27
- package/src/apps/data/routes/valuation.ts +94 -78
- package/src/apps/data/routes/webhooks.test.ts +21 -5
- package/src/apps/data/routes/webhooks.ts +63 -8
- package/src/apps/data/routes/zones.test.ts +2 -2
- package/src/apps/data/routes/zones.ts +1 -1
- package/src/apps/funding/App.ts +12 -2
- package/src/apps/funding/routes/deposit-addresses.test.ts +29 -7
- package/src/apps/funding/routes/deposit-addresses.ts +19 -14
- package/src/apps/funding/routes/quotes.test.ts +5 -9
- package/src/apps/funding/routes/transfers.test.ts +901 -23
- package/src/apps/funding/routes/transfers.ts +347 -64
- package/src/apps/management/Billing.ts +18 -5
- package/src/apps/management/routes/api-keys.test.ts +220 -0
- package/src/apps/management/routes/api-keys.ts +280 -3
- package/src/apps/management/routes/webhooks.test.ts +3 -3
- package/src/apps/management/routes/webhooks.ts +24 -25
- package/src/apps/relay/App.test.ts +212 -1
- package/src/apps/relay/App.ts +158 -29
- package/src/apps/relay/Sponsorships.ts +24 -1
- package/src/db/Db.ts +48 -1
- package/src/db/Schema.ts +22 -4
- package/src/db/tables/earnVaults.test.ts +9 -9
- package/src/db/tables/fundingCatalog.test.ts +16 -2
- package/src/db/tables/fundingCatalog.ts +9 -3
- package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
- package/src/db/tables/fundingTransferTransactions.ts +79 -3
- package/src/db/tables/fundingTransfers.test.ts +26 -0
- package/src/db/tables/fundingTransfers.ts +36 -0
- package/src/db/tables/organizations.test.ts +41 -0
- package/src/db/tables/organizations.ts +49 -0
- package/src/db/tables/projects.test.ts +55 -0
- package/src/db/tables/projects.ts +46 -1
- package/src/db/tables/sponsoredTransactions.test.ts +338 -0
- package/src/db/tables/sponsoredTransactions.ts +197 -13
- package/src/db/tables/webhookSubscriptions.ts +18 -11
- package/src/handlers/internal/utils.test.ts +53 -0
- package/src/handlers/internal/utils.ts +15 -0
- package/src/internal/Auth.ts +12 -4
- package/src/internal/EarnVaults.capabilities.test.ts +2 -2
- package/src/internal/EarnVaults.test.ts +1 -1
- package/src/internal/EarnVaults.ts +5 -5
- package/src/internal/Fetch.test.ts +46 -0
- package/src/internal/Fetch.ts +56 -0
- package/src/internal/Log.test-d.ts +2 -0
- package/src/internal/Log.test.ts +21 -0
- package/src/internal/Log.ts +22 -5
- package/src/internal/OpenApi.ts +14 -4
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.test.ts +2 -0
- package/src/internal/Schema.ts +12 -2
- package/src/internal/Store.test.ts +178 -0
- package/src/internal/Store.ts +104 -18
- package/src/internal/Tidx.test.ts +2 -1
- package/src/internal/Tidx.ts +7 -2
- 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/Viem.ts +39 -3
- package/src/internal/WebhookDestination.ts +3 -0
- package/src/internal/Webhooks.test.ts +45 -0
- 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/DepositWebhook.ts +2 -1
- package/src/internal/funding/Evidence.ts +2 -13
- package/src/internal/funding/Provider.test.ts +73 -0
- package/src/internal/funding/Provider.ts +125 -28
- package/src/internal/funding/SourceObservation.ts +5 -9
- package/src/internal/funding/Transfer.test.ts +155 -0
- package/src/internal/funding/Transfer.ts +387 -147
- package/src/internal/funding/TransferReconciliation.test.ts +147 -0
- package/src/internal/funding/TransferReconciliation.ts +264 -0
- package/src/internal/funding/TransferWebhook.test.ts +128 -0
- package/src/internal/funding/TransferWebhook.ts +67 -0
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/index.test.ts +4 -167
- package/src/internal/funding/providers/relay.test.ts +2 -2
- package/src/internal/funding/providers/relay.ts +3 -15
- package/src/internal/funding/providers/stargate.test.ts +328 -0
- package/src/internal/funding/providers/stargate.ts +807 -135
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
- package/src/internal/webhookDestinations/slack.ts +15 -0
|
@@ -3,6 +3,7 @@ import { nanoid } from 'nanoid'
|
|
|
3
3
|
import * as Runtime from '../../../test/runtime.js'
|
|
4
4
|
import * as Db from '../Db.js'
|
|
5
5
|
import * as Organizations from './organizations.js'
|
|
6
|
+
import * as Projects from './projects.js'
|
|
6
7
|
import * as SponsoredTransactions from './sponsoredTransactions.js'
|
|
7
8
|
|
|
8
9
|
const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` })
|
|
@@ -124,6 +125,20 @@ describe('upsert', () => {
|
|
|
124
125
|
await db.close()
|
|
125
126
|
})
|
|
126
127
|
|
|
128
|
+
test('behavior: rejects a sign payload already attributed to another project', async () => {
|
|
129
|
+
const db = create()
|
|
130
|
+
await db.migrate()
|
|
131
|
+
const { transactionHash: _, ...intent } = input
|
|
132
|
+
const first = await SponsoredTransactions.upsert(db, intent)
|
|
133
|
+
|
|
134
|
+
await expect(
|
|
135
|
+
SponsoredTransactions.upsert(db, { ...input, projectId: 'prj_2' }),
|
|
136
|
+
).rejects.toBeInstanceOf(SponsoredTransactions.AttributionConflictError)
|
|
137
|
+
|
|
138
|
+
expect(await SponsoredTransactions.get(db, first.id)).toStrictEqual(first)
|
|
139
|
+
await db.close()
|
|
140
|
+
})
|
|
141
|
+
|
|
127
142
|
test('behavior: a raw submission reopens a failed fill intent', async () => {
|
|
128
143
|
const db = create()
|
|
129
144
|
await db.migrate()
|
|
@@ -393,6 +408,320 @@ describe('spend', () => {
|
|
|
393
408
|
expect(await SponsoredTransactions.spend(db, { chainIds: [input.chainId], orgId: 'org_1', since: future })).toBe(0n) // prettier-ignore
|
|
394
409
|
await db.close()
|
|
395
410
|
})
|
|
411
|
+
|
|
412
|
+
test('behavior: promotional spend is isolated by project', async () => {
|
|
413
|
+
const db = create()
|
|
414
|
+
await db.migrate()
|
|
415
|
+
await SponsoredTransactions.upsert(db, {
|
|
416
|
+
...input,
|
|
417
|
+
billable: false,
|
|
418
|
+
feeMax: '100',
|
|
419
|
+
projectId: 'prj_1',
|
|
420
|
+
signPayload: `0x${'09'.repeat(32)}`,
|
|
421
|
+
})
|
|
422
|
+
await SponsoredTransactions.upsert(db, {
|
|
423
|
+
...input,
|
|
424
|
+
billable: false,
|
|
425
|
+
feeMax: '200',
|
|
426
|
+
projectId: 'prj_2',
|
|
427
|
+
signPayload: `0x${'0a'.repeat(32)}`,
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
expect(
|
|
431
|
+
await SponsoredTransactions.spend(db, {
|
|
432
|
+
billable: false,
|
|
433
|
+
chainIds: [input.chainId],
|
|
434
|
+
orgId: 'org_1',
|
|
435
|
+
projectId: 'prj_1',
|
|
436
|
+
since: '2000-01-01T00:00:00.000Z',
|
|
437
|
+
}),
|
|
438
|
+
).toBe(100n)
|
|
439
|
+
await db.close()
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
describe('reserve', () => {
|
|
444
|
+
test('behavior: enforces promotional limits independently per project', async () => {
|
|
445
|
+
const db = create()
|
|
446
|
+
await db.migrate()
|
|
447
|
+
const limit = {
|
|
448
|
+
billable: false,
|
|
449
|
+
chainIds: [input.chainId],
|
|
450
|
+
max: 100n,
|
|
451
|
+
projectId: 'prj_1',
|
|
452
|
+
since: '2000-01-01T00:00:00.000Z',
|
|
453
|
+
} as const
|
|
454
|
+
await SponsoredTransactions.reserve(
|
|
455
|
+
db,
|
|
456
|
+
{
|
|
457
|
+
...input,
|
|
458
|
+
billable: false,
|
|
459
|
+
feeMax: '100',
|
|
460
|
+
signPayload: `0x${'0b'.repeat(32)}`,
|
|
461
|
+
},
|
|
462
|
+
limit,
|
|
463
|
+
)
|
|
464
|
+
await SponsoredTransactions.reserve(
|
|
465
|
+
db,
|
|
466
|
+
{
|
|
467
|
+
...input,
|
|
468
|
+
billable: false,
|
|
469
|
+
feeMax: '100',
|
|
470
|
+
projectId: 'prj_2',
|
|
471
|
+
signPayload: `0x${'0c'.repeat(32)}`,
|
|
472
|
+
},
|
|
473
|
+
{ ...limit, projectId: 'prj_2' },
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
await expect(
|
|
477
|
+
SponsoredTransactions.reserve(
|
|
478
|
+
db,
|
|
479
|
+
{
|
|
480
|
+
...input,
|
|
481
|
+
billable: false,
|
|
482
|
+
feeMax: '1',
|
|
483
|
+
signPayload: `0x${'0d'.repeat(32)}`,
|
|
484
|
+
},
|
|
485
|
+
limit,
|
|
486
|
+
),
|
|
487
|
+
).rejects.toBeInstanceOf(SponsoredTransactions.PeriodSpendLimitError)
|
|
488
|
+
await db.close()
|
|
489
|
+
})
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
describe('reservePromotion', () => {
|
|
493
|
+
async function configure(db: Db.Db, projectSpendLimit: string | null) {
|
|
494
|
+
await Organizations.create(db, { id: 'org_1', name: 'Privy' })
|
|
495
|
+
await Organizations.setSponsorshipSubsidy(db, 'org_1', {
|
|
496
|
+
durationDays: 90,
|
|
497
|
+
projectSpendLimit,
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
test('behavior: atomically records the first sponsorship and immutable window', async () => {
|
|
502
|
+
const db = create()
|
|
503
|
+
await db.migrate()
|
|
504
|
+
await configure(db, '0.000001')
|
|
505
|
+
const project = await Projects.create(db, { name: 'App', orgId: 'org_1' })
|
|
506
|
+
const result = await SponsoredTransactions.reservePromotion(
|
|
507
|
+
db,
|
|
508
|
+
{ ...input, billable: false, projectId: project.id },
|
|
509
|
+
{
|
|
510
|
+
at: '2026-01-01T00:00:00.000Z',
|
|
511
|
+
chainIds: [input.chainId],
|
|
512
|
+
projectId: project.id,
|
|
513
|
+
},
|
|
514
|
+
)
|
|
515
|
+
const updated = await Projects.get(db, project.id)
|
|
516
|
+
expect({
|
|
517
|
+
endsAt: updated?.sponsorshipSubsidyEndsAt,
|
|
518
|
+
startsAt: updated?.sponsorshipSubsidyStartsAt,
|
|
519
|
+
}).toMatchInlineSnapshot(`
|
|
520
|
+
{
|
|
521
|
+
"endsAt": "2026-04-01T00:00:00.000Z",
|
|
522
|
+
"startsAt": "2026-01-01T00:00:00.000Z",
|
|
523
|
+
}
|
|
524
|
+
`)
|
|
525
|
+
if (result.status === 'ineligible') throw new Error('Expected a promotion reservation.')
|
|
526
|
+
expect(result.record.billable).toBe(false)
|
|
527
|
+
expect((await Projects.get(db, project.id))?.sponsorshipSpendLimit).toBe('1')
|
|
528
|
+
await db.close()
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
test('behavior: records an uncapped promotion', async () => {
|
|
532
|
+
const db = create()
|
|
533
|
+
await db.migrate()
|
|
534
|
+
await configure(db, null)
|
|
535
|
+
const project = await Projects.create(db, { name: 'App', orgId: 'org_1' })
|
|
536
|
+
const result = await SponsoredTransactions.reservePromotion(
|
|
537
|
+
db,
|
|
538
|
+
{ ...input, billable: false, feeMax: '100', projectId: project.id },
|
|
539
|
+
{
|
|
540
|
+
at: '2026-01-01T00:00:00.000Z',
|
|
541
|
+
chainIds: [input.chainId],
|
|
542
|
+
projectId: project.id,
|
|
543
|
+
},
|
|
544
|
+
)
|
|
545
|
+
if (result.status === 'ineligible') throw new Error('Expected a promotion reservation.')
|
|
546
|
+
expect(result.record.billable).toBe(false)
|
|
547
|
+
expect(await Projects.get(db, project.id)).toMatchObject({
|
|
548
|
+
sponsorshipSubsidyEndsAt: '2026-04-01T00:00:00.000Z',
|
|
549
|
+
sponsorshipSubsidyStartsAt: '2026-01-01T00:00:00.000Z',
|
|
550
|
+
sponsorshipSpendLimit: null,
|
|
551
|
+
})
|
|
552
|
+
await SponsoredTransactions.reservePromotion(
|
|
553
|
+
db,
|
|
554
|
+
{
|
|
555
|
+
...input,
|
|
556
|
+
billable: false,
|
|
557
|
+
feeMax: '100',
|
|
558
|
+
projectId: project.id,
|
|
559
|
+
signPayload: `0x${'24'.repeat(32)}`,
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
at: '2026-01-02T00:00:00.000Z',
|
|
563
|
+
chainIds: [input.chainId],
|
|
564
|
+
projectId: project.id,
|
|
565
|
+
},
|
|
566
|
+
)
|
|
567
|
+
expect((await Projects.get(db, project.id))?.sponsorshipSpendLimit).toBeNull()
|
|
568
|
+
await db.close()
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
test('behavior: reads the current organization policy when activating', async () => {
|
|
572
|
+
const db = create()
|
|
573
|
+
await db.migrate()
|
|
574
|
+
await configure(db, '0.000001')
|
|
575
|
+
const project = await Projects.create(db, { name: 'App', orgId: 'org_1' })
|
|
576
|
+
await Organizations.setSponsorshipSubsidy(db, 'org_1', {
|
|
577
|
+
durationDays: 90,
|
|
578
|
+
projectSpendLimit: '0.000002',
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
const result = await SponsoredTransactions.reservePromotion(
|
|
582
|
+
db,
|
|
583
|
+
{ ...input, billable: false, feeMax: '2', projectId: project.id },
|
|
584
|
+
{
|
|
585
|
+
at: '2026-01-01T00:00:00.000Z',
|
|
586
|
+
chainIds: [input.chainId],
|
|
587
|
+
projectId: project.id,
|
|
588
|
+
},
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
expect(result.status).toBe('subsidized')
|
|
592
|
+
expect((await Projects.get(db, project.id))?.sponsorshipSpendLimit).toBe('2')
|
|
593
|
+
await db.close()
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
test('behavior: conflicting envelopes do not activate another project', async () => {
|
|
597
|
+
const db = create()
|
|
598
|
+
await db.migrate()
|
|
599
|
+
await configure(db, null)
|
|
600
|
+
const first = await Projects.create(db, { name: 'First', orgId: 'org_1' })
|
|
601
|
+
const second = await Projects.create(db, { name: 'Second', orgId: 'org_1' })
|
|
602
|
+
await SponsoredTransactions.upsert(db, { ...input, projectId: first.id })
|
|
603
|
+
|
|
604
|
+
await expect(
|
|
605
|
+
SponsoredTransactions.reservePromotion(
|
|
606
|
+
db,
|
|
607
|
+
{ ...input, billable: false, projectId: second.id },
|
|
608
|
+
{
|
|
609
|
+
at: '2026-01-01T00:00:00.000Z',
|
|
610
|
+
chainIds: [input.chainId],
|
|
611
|
+
projectId: second.id,
|
|
612
|
+
},
|
|
613
|
+
),
|
|
614
|
+
).rejects.toBeInstanceOf(SponsoredTransactions.AttributionConflictError)
|
|
615
|
+
|
|
616
|
+
expect(await Projects.get(db, second.id)).toMatchObject({
|
|
617
|
+
sponsorshipSubsidyEndsAt: null,
|
|
618
|
+
sponsorshipSubsidyStartsAt: null,
|
|
619
|
+
})
|
|
620
|
+
await db.close()
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
test('behavior: later policy changes cannot raise an activated project cap', async () => {
|
|
624
|
+
const db = create()
|
|
625
|
+
await db.migrate()
|
|
626
|
+
await configure(db, '0.000001')
|
|
627
|
+
const project = await Projects.create(db, { name: 'App', orgId: 'org_1' })
|
|
628
|
+
const promotion = {
|
|
629
|
+
at: '2026-01-01T00:00:00.000Z',
|
|
630
|
+
chainIds: [input.chainId],
|
|
631
|
+
projectId: project.id,
|
|
632
|
+
} as const
|
|
633
|
+
await SponsoredTransactions.reservePromotion(
|
|
634
|
+
db,
|
|
635
|
+
{
|
|
636
|
+
...input,
|
|
637
|
+
billable: false,
|
|
638
|
+
feeMax: '1',
|
|
639
|
+
projectId: project.id,
|
|
640
|
+
},
|
|
641
|
+
promotion,
|
|
642
|
+
)
|
|
643
|
+
await Organizations.setSponsorshipSubsidy(db, 'org_1', {
|
|
644
|
+
durationDays: 90,
|
|
645
|
+
projectSpendLimit: '0.000002',
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
const result = await SponsoredTransactions.reservePromotion(
|
|
649
|
+
db,
|
|
650
|
+
{
|
|
651
|
+
...input,
|
|
652
|
+
billable: false,
|
|
653
|
+
feeMax: '1',
|
|
654
|
+
projectId: project.id,
|
|
655
|
+
signPayload: `0x${'23'.repeat(32)}`,
|
|
656
|
+
},
|
|
657
|
+
promotion,
|
|
658
|
+
)
|
|
659
|
+
if (result.status === 'ineligible') throw new Error('Expected a promotion reservation.')
|
|
660
|
+
expect(result.record.billable).toBe(true)
|
|
661
|
+
expect((await Projects.get(db, project.id))?.sponsorshipSpendLimit).toBe('1')
|
|
662
|
+
await db.close()
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
test('behavior: preserves activation when the first sponsorship exceeds the cap', async () => {
|
|
666
|
+
const db = create()
|
|
667
|
+
await db.migrate()
|
|
668
|
+
await configure(db, '0')
|
|
669
|
+
const project = await Projects.create(db, { name: 'App', orgId: 'org_1' })
|
|
670
|
+
const result = await SponsoredTransactions.reservePromotion(
|
|
671
|
+
db,
|
|
672
|
+
{ ...input, billable: false, feeMax: '1', projectId: project.id },
|
|
673
|
+
{
|
|
674
|
+
at: '2026-01-01T00:00:00.000Z',
|
|
675
|
+
chainIds: [input.chainId],
|
|
676
|
+
projectId: project.id,
|
|
677
|
+
},
|
|
678
|
+
)
|
|
679
|
+
if (result.status === 'ineligible') throw new Error('Expected a promotion reservation.')
|
|
680
|
+
expect(result.record.billable).toBe(true)
|
|
681
|
+
expect(await Projects.get(db, project.id)).toMatchObject({
|
|
682
|
+
sponsorshipSubsidyEndsAt: '2026-04-01T00:00:00.000Z',
|
|
683
|
+
sponsorshipSubsidyStartsAt: '2026-01-01T00:00:00.000Z',
|
|
684
|
+
})
|
|
685
|
+
expect(await SponsoredTransactions.listPending(db)).toMatchObject([{ billable: true }])
|
|
686
|
+
await db.close()
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
test('behavior: concurrent first sponsorships share one promotion window', async () => {
|
|
690
|
+
const schema = `t_${nanoid()}`
|
|
691
|
+
const firstDb = Db.postgres({ connectionString: Runtime.postgresUrl, schema })
|
|
692
|
+
const secondDb = Db.postgres({ connectionString: Runtime.postgresUrl, schema })
|
|
693
|
+
await firstDb.migrate()
|
|
694
|
+
await configure(firstDb, '0.000002')
|
|
695
|
+
const project = await Projects.create(firstDb, { name: 'App', orgId: 'org_1' })
|
|
696
|
+
const reserve = (db: Db.Db, at: string, signPayload: string) =>
|
|
697
|
+
SponsoredTransactions.reservePromotion(
|
|
698
|
+
db,
|
|
699
|
+
{
|
|
700
|
+
...input,
|
|
701
|
+
billable: false,
|
|
702
|
+
projectId: project.id,
|
|
703
|
+
signPayload,
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
at,
|
|
707
|
+
chainIds: [input.chainId],
|
|
708
|
+
projectId: project.id,
|
|
709
|
+
},
|
|
710
|
+
)
|
|
711
|
+
const [first, second] = await Promise.all([
|
|
712
|
+
reserve(firstDb, '2026-01-01T00:00:00.000Z', `0x${'21'.repeat(32)}`),
|
|
713
|
+
reserve(secondDb, '2026-01-02T00:00:00.000Z', `0x${'22'.repeat(32)}`),
|
|
714
|
+
])
|
|
715
|
+
expect(first.status).toBe('subsidized')
|
|
716
|
+
expect(second.status).toBe('subsidized')
|
|
717
|
+
const updated = await Projects.get(firstDb, project.id)
|
|
718
|
+
if (!updated?.sponsorshipSubsidyEndsAt || !updated.sponsorshipSubsidyStartsAt)
|
|
719
|
+
throw new Error('Expected a promotion window.')
|
|
720
|
+
expect(
|
|
721
|
+
Date.parse(updated.sponsorshipSubsidyEndsAt) - Date.parse(updated.sponsorshipSubsidyStartsAt),
|
|
722
|
+
).toBe(90 * 86_400_000)
|
|
723
|
+
await Promise.all([firstDb.close(), secondDb.close()])
|
|
724
|
+
})
|
|
396
725
|
})
|
|
397
726
|
|
|
398
727
|
describe('listUnreported', () => {
|
|
@@ -407,6 +736,15 @@ describe('listUnreported', () => {
|
|
|
407
736
|
// Pending, sandbox, and foreign-chain rows never report.
|
|
408
737
|
await SponsoredTransactions.upsert(db, { ...input, signPayload: `0x${'13'.repeat(32)}` })
|
|
409
738
|
await SponsoredTransactions.upsert(db, { ...input, billable: false, environment: 'sandbox', signPayload: `0x${'14'.repeat(32)}` }) // prettier-ignore
|
|
739
|
+
const promotion = await SponsoredTransactions.upsert(db, {
|
|
740
|
+
...input,
|
|
741
|
+
billable: false,
|
|
742
|
+
signPayload: `0x${'16'.repeat(32)}`,
|
|
743
|
+
})
|
|
744
|
+
await SponsoredTransactions.finalize(db, promotion.id, {
|
|
745
|
+
feeAmount: '300',
|
|
746
|
+
finalizedAt: '2026-01-03T00:00:00.000Z',
|
|
747
|
+
})
|
|
410
748
|
|
|
411
749
|
const rows = await SponsoredTransactions.listUnreported(db, { chainIds })
|
|
412
750
|
expect(rows.map((row) => row.id)).toStrictEqual([first.id, second.id])
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { ExpressionBuilder, Selectable } from 'kysely'
|
|
2
2
|
import { sql } from 'kysely'
|
|
3
|
+
import { Value } from 'ox'
|
|
3
4
|
|
|
5
|
+
import * as Fees from '../../internal/Fees.js'
|
|
4
6
|
import * as Id from '../../internal/Id.js'
|
|
5
7
|
import type * as Db from '../Db.js'
|
|
6
8
|
import type * as db_Schema from '../Schema.js'
|
|
9
|
+
import * as Projects from './projects.js'
|
|
7
10
|
|
|
8
11
|
/** Columns of the `sponsored_transactions` table, derived from `Schema.SponsoredTransaction`. */
|
|
9
12
|
export type Table = db_Schema.SponsoredTransaction
|
|
@@ -46,7 +49,7 @@ export async function upsert(db: Db.Db, input: upsert.Input): Promise<Record> {
|
|
|
46
49
|
transaction: input.transaction,
|
|
47
50
|
transactionHash: input.transactionHash ?? null,
|
|
48
51
|
}
|
|
49
|
-
|
|
52
|
+
const stored = await db.kysely
|
|
50
53
|
.insertInto('sponsored_transactions')
|
|
51
54
|
.values(record)
|
|
52
55
|
.onConflict((oc) =>
|
|
@@ -69,11 +72,16 @@ export async function upsert(db: Db.Db, input: upsert.Input): Promise<Record> {
|
|
|
69
72
|
finalizedAt: sql`CASE WHEN ${eb.ref('sponsored_transactions.status')} = 'failed' THEN NULL ELSE ${eb.ref('sponsored_transactions.finalizedAt')} END`,
|
|
70
73
|
status: sql`CASE WHEN ${eb.ref('sponsored_transactions.status')} = 'failed' THEN 'pending' ELSE ${eb.ref('sponsored_transactions.status')} END`,
|
|
71
74
|
}))
|
|
72
|
-
// A shared envelope cannot transfer billing attribution
|
|
73
|
-
.where('sponsored_transactions.orgId', '=', input.orgId)
|
|
75
|
+
// A shared envelope cannot transfer billing attribution between owners.
|
|
76
|
+
.where('sponsored_transactions.orgId', '=', input.orgId)
|
|
77
|
+
.where(
|
|
78
|
+
sql<boolean>`sponsored_transactions.project_id IS NOT DISTINCT FROM ${record.projectId}`,
|
|
79
|
+
),
|
|
74
80
|
)
|
|
75
81
|
.returningAll()
|
|
76
|
-
.
|
|
82
|
+
.executeTakeFirst()
|
|
83
|
+
if (!stored) throw new AttributionConflictError()
|
|
84
|
+
return stored
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
export declare namespace upsert {
|
|
@@ -208,8 +216,13 @@ export async function assignTransactionHash(
|
|
|
208
216
|
(record.finalizedAt === null || record.finalizedAt < failedSince)
|
|
209
217
|
)
|
|
210
218
|
return
|
|
211
|
-
|
|
212
|
-
|
|
219
|
+
const limited =
|
|
220
|
+
limit !== undefined &&
|
|
221
|
+
(limit.billable === undefined ? record.billable : record.billable === limit.billable)
|
|
222
|
+
if (record.status === 'failed' && limited) {
|
|
223
|
+
const scope = limit.projectId ? `${record.orgId}:${limit.projectId}` : record.orgId
|
|
224
|
+
await sql`select pg_advisory_xact_lock(${billingLockClass}, hashtext(${scope}))`.execute(tx.kysely) // prettier-ignore
|
|
225
|
+
}
|
|
213
226
|
let query = tx.kysely
|
|
214
227
|
.updateTable('sponsored_transactions')
|
|
215
228
|
.set({
|
|
@@ -232,10 +245,12 @@ export async function assignTransactionHash(
|
|
|
232
245
|
)
|
|
233
246
|
const updated = await query.executeTakeFirst()
|
|
234
247
|
if (updated.numUpdatedRows === 0n) return
|
|
235
|
-
if (record.status === 'failed' &&
|
|
248
|
+
if (record.status === 'failed' && limited) {
|
|
236
249
|
const committed = await spend(tx, {
|
|
250
|
+
...(limit.billable === undefined ? {} : { billable: limit.billable }),
|
|
237
251
|
chainIds: limit.chainIds,
|
|
238
252
|
orgId: record.orgId,
|
|
253
|
+
...(limit.projectId === undefined ? {} : { projectId: limit.projectId }),
|
|
239
254
|
since: limit.since,
|
|
240
255
|
})
|
|
241
256
|
if (committed > limit.max) throw new PeriodSpendLimitError()
|
|
@@ -319,12 +334,17 @@ export async function reserve(
|
|
|
319
334
|
return db.transaction(async (tx) => {
|
|
320
335
|
// `hashtext` maps the org id into the advisory-lock keyspace; the class
|
|
321
336
|
// namespaces it against other advisory-lock users. Released at tx end.
|
|
322
|
-
|
|
337
|
+
const scope = limit.projectId ? `${input.orgId}:${limit.projectId}` : input.orgId
|
|
338
|
+
await sql`select pg_advisory_xact_lock(${billingLockClass}, hashtext(${scope}))`.execute(tx.kysely) // prettier-ignore
|
|
323
339
|
const record = await upsert(tx, input)
|
|
324
|
-
|
|
340
|
+
const limited =
|
|
341
|
+
limit.billable === undefined ? record.billable : record.billable === limit.billable
|
|
342
|
+
if (limited) {
|
|
325
343
|
const committed = await spend(tx, {
|
|
344
|
+
...(limit.billable === undefined ? {} : { billable: limit.billable }),
|
|
326
345
|
chainIds: limit.chainIds,
|
|
327
346
|
orgId: input.orgId,
|
|
347
|
+
...(limit.projectId === undefined ? {} : { projectId: limit.projectId }),
|
|
328
348
|
since: limit.since,
|
|
329
349
|
})
|
|
330
350
|
if (committed > limit.max) throw new PeriodSpendLimitError()
|
|
@@ -339,15 +359,160 @@ const billingLockClass = 1112294220
|
|
|
339
359
|
export declare namespace reserve {
|
|
340
360
|
/** The period spend limit enforced by {@link reserve}. */
|
|
341
361
|
type Limit = {
|
|
362
|
+
/** Billing snapshot whose rows consume this limit; omit for customer-billable spend. */
|
|
363
|
+
billable?: boolean | undefined
|
|
342
364
|
/** Chains whose spend counts (mainnet chain ids). */
|
|
343
365
|
chainIds: readonly number[]
|
|
344
366
|
/** Limit in fee-token base units. */
|
|
345
367
|
max: bigint
|
|
368
|
+
/** Project scope for independent limits; omit for the organization-wide budget. */
|
|
369
|
+
projectId?: string | undefined
|
|
346
370
|
/** Window start (ISO 8601). */
|
|
347
371
|
since: string
|
|
348
372
|
}
|
|
349
373
|
}
|
|
350
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Applies the current promotion policy and records the sponsorship atomically.
|
|
377
|
+
* Exhausted promotions retain their window and fall back to customer billing.
|
|
378
|
+
*/
|
|
379
|
+
export function reservePromotion(
|
|
380
|
+
db: Db.Db,
|
|
381
|
+
input: upsert.Input,
|
|
382
|
+
options: reservePromotion.Options,
|
|
383
|
+
): Promise<reservePromotion.Result> {
|
|
384
|
+
return db.transaction(async (tx) => {
|
|
385
|
+
const scope = `${input.orgId}:${options.projectId}`
|
|
386
|
+
await sql`select pg_advisory_xact_lock(${billingLockClass}, hashtext(${scope}))`.execute(tx.kysely) // prettier-ignore
|
|
387
|
+
const existing = await tx.kysely
|
|
388
|
+
.selectFrom('sponsored_transactions')
|
|
389
|
+
.selectAll()
|
|
390
|
+
.where('signPayload', '=', input.signPayload)
|
|
391
|
+
.forUpdate()
|
|
392
|
+
.executeTakeFirst()
|
|
393
|
+
if (existing) {
|
|
394
|
+
if (existing.orgId !== input.orgId || existing.projectId !== options.projectId)
|
|
395
|
+
throw new AttributionConflictError()
|
|
396
|
+
const stored = await upsert(tx, input)
|
|
397
|
+
if (stored.billable && options.billingLimit) {
|
|
398
|
+
await sql`select pg_advisory_xact_lock(${billingLockClass}, hashtext(${input.orgId}))`.execute(tx.kysely) // prettier-ignore
|
|
399
|
+
const billed = await spend(tx, {
|
|
400
|
+
chainIds: options.billingLimit.chainIds,
|
|
401
|
+
orgId: input.orgId,
|
|
402
|
+
since: options.billingLimit.since,
|
|
403
|
+
})
|
|
404
|
+
if (billed > options.billingLimit.max) throw new PeriodSpendLimitError()
|
|
405
|
+
}
|
|
406
|
+
if (!stored.billable) {
|
|
407
|
+
const project = await Projects.get(tx, options.projectId)
|
|
408
|
+
if (!project?.sponsorshipSubsidyStartsAt) throw new PromotionActivationError()
|
|
409
|
+
if (project.sponsorshipSpendLimit !== null) {
|
|
410
|
+
const committed = await spend(tx, {
|
|
411
|
+
billable: false,
|
|
412
|
+
chainIds: options.chainIds,
|
|
413
|
+
orgId: input.orgId,
|
|
414
|
+
projectId: options.projectId,
|
|
415
|
+
since: project.sponsorshipSubsidyStartsAt,
|
|
416
|
+
})
|
|
417
|
+
if (committed > BigInt(project.sponsorshipSpendLimit)) throw new PeriodSpendLimitError()
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return { record: stored, status: stored.billable ? 'billed' : 'subsidized' }
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const organization = await tx.kysely
|
|
424
|
+
.selectFrom('organizations')
|
|
425
|
+
.select(['sponsorshipSubsidyDurationDays', 'sponsorshipSubsidyProjectSpendLimit'])
|
|
426
|
+
.where('id', '=', input.orgId)
|
|
427
|
+
.forShare()
|
|
428
|
+
.executeTakeFirst()
|
|
429
|
+
if (!organization || organization.sponsorshipSubsidyDurationDays === null)
|
|
430
|
+
return { status: 'ineligible' }
|
|
431
|
+
|
|
432
|
+
const current = await Projects.get(tx, options.projectId)
|
|
433
|
+
if (!current || current.orgId !== input.orgId) throw new PromotionActivationError()
|
|
434
|
+
if (
|
|
435
|
+
current.sponsorshipSubsidyEndsAt !== null &&
|
|
436
|
+
Date.parse(options.at) >= Date.parse(current.sponsorshipSubsidyEndsAt)
|
|
437
|
+
)
|
|
438
|
+
return { status: 'ineligible' }
|
|
439
|
+
|
|
440
|
+
const endsAt = new Date(
|
|
441
|
+
Date.parse(options.at) + organization.sponsorshipSubsidyDurationDays * 86_400_000,
|
|
442
|
+
).toISOString()
|
|
443
|
+
const project = await Projects.activateSponsorship(tx, {
|
|
444
|
+
at: options.at,
|
|
445
|
+
endsAt,
|
|
446
|
+
id: options.projectId,
|
|
447
|
+
orgId: input.orgId,
|
|
448
|
+
spendLimit:
|
|
449
|
+
organization.sponsorshipSubsidyProjectSpendLimit === null
|
|
450
|
+
? null
|
|
451
|
+
: Value.from(
|
|
452
|
+
organization.sponsorshipSubsidyProjectSpendLimit,
|
|
453
|
+
Fees.tokenDecimals,
|
|
454
|
+
).toString(),
|
|
455
|
+
})
|
|
456
|
+
if (!project?.sponsorshipSubsidyStartsAt || !project.sponsorshipSubsidyEndsAt)
|
|
457
|
+
throw new PromotionActivationError()
|
|
458
|
+
let record = await upsert(tx, input)
|
|
459
|
+
if (project.sponsorshipSpendLimit !== null) {
|
|
460
|
+
const committed = await spend(tx, {
|
|
461
|
+
billable: false,
|
|
462
|
+
chainIds: options.chainIds,
|
|
463
|
+
orgId: input.orgId,
|
|
464
|
+
projectId: options.projectId,
|
|
465
|
+
since: project.sponsorshipSubsidyStartsAt,
|
|
466
|
+
})
|
|
467
|
+
if (committed > BigInt(project.sponsorshipSpendLimit)) {
|
|
468
|
+
record = await tx.kysely
|
|
469
|
+
.updateTable('sponsored_transactions')
|
|
470
|
+
.set({ billable: true })
|
|
471
|
+
.where('id', '=', record.id)
|
|
472
|
+
.returningAll()
|
|
473
|
+
.executeTakeFirstOrThrow()
|
|
474
|
+
if (options.billingLimit) {
|
|
475
|
+
await sql`select pg_advisory_xact_lock(${billingLockClass}, hashtext(${input.orgId}))`.execute(tx.kysely) // prettier-ignore
|
|
476
|
+
const billed = await spend(tx, {
|
|
477
|
+
chainIds: options.billingLimit.chainIds,
|
|
478
|
+
orgId: input.orgId,
|
|
479
|
+
since: options.billingLimit.since,
|
|
480
|
+
})
|
|
481
|
+
if (billed > options.billingLimit.max) throw new PeriodSpendLimitError()
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return { record, status: record.billable ? 'billed' : 'subsidized' }
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export declare namespace reservePromotion {
|
|
490
|
+
/** Project promotion window and cap. */
|
|
491
|
+
type Options = {
|
|
492
|
+
/** Timestamp of the recorded sponsorship that may activate the window. */
|
|
493
|
+
at: string
|
|
494
|
+
/** Customer billing limit used when the project promotion is exhausted. */
|
|
495
|
+
billingLimit?: reserve.Limit | undefined
|
|
496
|
+
/** Mainnet chains whose promotional spend counts. */
|
|
497
|
+
chainIds: readonly number[]
|
|
498
|
+
/** Project receiving the independent promotion. */
|
|
499
|
+
projectId: string
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Promotion reservation outcome. */
|
|
503
|
+
type Result =
|
|
504
|
+
| {
|
|
505
|
+
/** Stored sponsorship. */
|
|
506
|
+
record: Record
|
|
507
|
+
/** Whether Tempo or the customer pays for the stored sponsorship. */
|
|
508
|
+
status: 'billed' | 'subsidized'
|
|
509
|
+
}
|
|
510
|
+
| {
|
|
511
|
+
/** The current organization or project policy does not grant a promotion. */
|
|
512
|
+
status: 'ineligible'
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
351
516
|
/**
|
|
352
517
|
* Billable spend committed since a cutoff, in fee-token base units: finalized
|
|
353
518
|
* rows at their actual fee plus pending rows at their signed fee cap, so
|
|
@@ -358,8 +523,8 @@ export declare namespace reserve {
|
|
|
358
523
|
* @returns The committed spend in base units.
|
|
359
524
|
*/
|
|
360
525
|
export async function spend(db: Db.Db, options: spend.Options): Promise<bigint> {
|
|
361
|
-
const { chainIds, environment = 'production', orgId, since } = options
|
|
362
|
-
|
|
526
|
+
const { billable, chainIds, environment = 'production', orgId, projectId, since } = options
|
|
527
|
+
let query = db.kysely
|
|
363
528
|
.selectFrom('sponsored_transactions')
|
|
364
529
|
.select([
|
|
365
530
|
sql<string>`coalesce(sum(case when status = 'finalized' then fee_amount::numeric else 0 end), 0)`.as('finalized'), // prettier-ignore
|
|
@@ -372,22 +537,31 @@ export async function spend(db: Db.Db, options: spend.Options): Promise<bigint>
|
|
|
372
537
|
.where('status', 'in', ['finalized', 'pending'])
|
|
373
538
|
.where('chainId', 'in', [...chainIds])
|
|
374
539
|
.where('createdAt', '>=', since)
|
|
540
|
+
if (billable !== undefined) query = query.where('billable', '=', billable)
|
|
541
|
+
if (projectId !== undefined) query = query.where('projectId', '=', projectId)
|
|
375
542
|
// Production mirrors the sponsorship gate — only billable rows count. Sandbox
|
|
376
543
|
// rows are non-billable by construction, so the figure is display-only.
|
|
377
|
-
const row = await (
|
|
378
|
-
|
|
544
|
+
const row = await (
|
|
545
|
+
environment === 'production' && billable === undefined
|
|
546
|
+
? query.where('billable', '=', true)
|
|
547
|
+
: query
|
|
548
|
+
).executeTakeFirstOrThrow()
|
|
379
549
|
return BigInt(row.finalized) + BigInt(row.pending)
|
|
380
550
|
}
|
|
381
551
|
|
|
382
552
|
export declare namespace spend {
|
|
383
553
|
/** Options for {@link spend}. */
|
|
384
554
|
type Options = {
|
|
555
|
+
/** Billing snapshot to count; omit for the existing production-billable behavior. */
|
|
556
|
+
billable?: boolean | undefined
|
|
385
557
|
/** Chains whose spend counts (mainnet chain ids). */
|
|
386
558
|
chainIds: readonly number[]
|
|
387
559
|
/** Environment whose spend counts; production mirrors the billing gate, sandbox is display-only. Defaults to `production`. */
|
|
388
560
|
environment?: Table['environment'] | undefined
|
|
389
561
|
/** Organization id (`org_…`) the spend attributes to. */
|
|
390
562
|
orgId: string
|
|
563
|
+
/** Project whose spend counts; omit for organization-wide spend. */
|
|
564
|
+
projectId?: string | undefined
|
|
391
565
|
/** Window start (ISO 8601); rows created earlier are out of scope. */
|
|
392
566
|
since: string
|
|
393
567
|
}
|
|
@@ -531,3 +705,13 @@ function intentStatus(failedSince: string | undefined) {
|
|
|
531
705
|
export class PeriodSpendLimitError extends Error {
|
|
532
706
|
override name = 'SponsoredTransactions.PeriodSpendLimitError'
|
|
533
707
|
}
|
|
708
|
+
|
|
709
|
+
/** Thrown when a promotional window cannot be activated consistently. */
|
|
710
|
+
export class PromotionActivationError extends Error {
|
|
711
|
+
override name = 'SponsoredTransactions.PromotionActivationError'
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** Thrown when one signed envelope is presented with conflicting project attribution. */
|
|
715
|
+
export class AttributionConflictError extends Error {
|
|
716
|
+
override name = 'SponsoredTransactions.AttributionConflictError'
|
|
717
|
+
}
|