tapimo 0.6.0 → 0.7.1
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 +8641 -8263
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +112 -11
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +2 -2
- package/dist/admin/apps/early-access.d.ts.map +1 -1
- package/dist/admin/apps/early-access.js +5 -5
- package/dist/admin/apps/early-access.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.js.map +1 -1
- 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 +47 -39
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +8050 -8019
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +2 -15
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +1 -1
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +1 -1
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +27 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +21 -1
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +58 -58
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +669 -669
- package/dist/apps/data/routes/fee-amm.d.ts +157 -143
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +72 -34
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +34 -26
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +30 -30
- package/dist/apps/data/routes/receipts.d.ts +8 -8
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +12 -3
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +5 -1
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +6 -6
- package/dist/apps/data/routes/transfers.d.ts +101 -90
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +226 -141
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +2 -2
- package/dist/apps/data/routes/valuation.js +3 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +34 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +2 -2
- package/dist/apps/data/routes/webhooks.js +14 -6
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +460 -523
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -23
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +20 -8
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +106 -151
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +28 -28
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +48 -34
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +33 -18
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +131 -131
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +0 -3
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -8
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +0 -4
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +73 -73
- package/dist/apps/management/routes/orgs.d.ts +94 -94
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
- package/dist/apps/management/routes/webhooks.d.ts +1 -1
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +57 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.js +1 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +11 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -12
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -23
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +32 -2
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +13 -3
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -32
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +3 -51
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +3 -2
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +78 -32
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +45 -8
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +50 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +15 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +2 -2
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +14 -2
- package/src/Client.test-d.ts +109 -28
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/apps/early-access.ts +6 -5
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +8 -3
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui.generated.ts +49 -39
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +14 -2
- package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
- package/src/apps/data/FundingQuoteProviders.ts +2 -29
- package/src/apps/data/FxOracle.test.ts +2 -2
- package/src/apps/data/FxOracle.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +6 -2
- package/src/apps/data/routes/activities.ts +39 -1
- package/src/apps/data/routes/balances.test.ts +6 -2
- package/src/apps/data/routes/exchanges.test.ts +7 -3
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +29 -8
- package/src/apps/data/routes/funding.ts +42 -48
- package/src/apps/data/routes/receipts.test.ts +6 -2
- package/src/apps/data/routes/tokenlist.test.ts +15 -0
- package/src/apps/data/routes/tokenlist.ts +18 -5
- package/src/apps/data/routes/tokens.ts +5 -1
- package/src/apps/data/routes/transfers.test.ts +203 -35
- package/src/apps/data/routes/transfers.ts +294 -172
- package/src/apps/data/routes/valuation.test.ts +18 -3
- package/src/apps/data/routes/valuation.ts +4 -4
- package/src/apps/data/routes/verified-tokens.test.ts +51 -0
- package/src/apps/data/routes/verified-tokens.ts +52 -15
- package/src/apps/data/routes/webhooks.test.ts +56 -0
- package/src/apps/data/routes/webhooks.ts +17 -8
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +2 -75
- package/src/apps/management/routes/api-keys.ts +30 -7
- package/src/apps/management/routes/billing.test.ts +106 -85
- package/src/apps/management/routes/billing.ts +32 -21
- package/src/apps/management/routes/faucet.test.ts +11 -3
- package/src/apps/management/routes/faucet.ts +46 -30
- package/src/apps/management/routes/invitations.test.ts +0 -172
- package/src/apps/management/routes/invitations.ts +0 -3
- package/src/apps/management/routes/me.test.ts +3 -145
- package/src/apps/management/routes/me.ts +0 -6
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +90 -8
- package/src/apps/relay/App.ts +62 -10
- package/src/apps/relay/Sponsorships.test.ts +2 -0
- package/src/apps/relay/Sponsorships.ts +1 -1
- package/src/db/Db.test.ts +2 -2
- package/src/db/Db.ts +30 -0
- package/src/db/Schema.ts +15 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +4 -26
- package/src/db/tables/organizations.test.ts +191 -0
- package/src/db/tables/organizations.ts +44 -3
- package/src/db/tables/sponsoredTransactions.test.ts +41 -2
- package/src/db/tables/sponsoredTransactions.ts +15 -3
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +3 -63
- package/src/internal/EdgeCache.test.ts +18 -5
- package/src/internal/EdgeCache.ts +4 -2
- package/src/internal/FundingQuotes.test.ts +18 -5
- package/src/internal/FundingQuotes.ts +46 -11
- package/src/internal/Log.test.ts +91 -0
- package/src/internal/Log.ts +69 -5
- package/src/internal/MetricSink.test.ts +55 -0
- package/src/internal/MetricSink.ts +14 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
- package/src/internal/Schema.test.ts +13 -0
- package/src/internal/Schema.ts +14 -7
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { sql, type Selectable } from 'kysely'
|
|
1
|
+
import { sql, type ExpressionBuilder, type Selectable } from 'kysely'
|
|
2
2
|
|
|
3
3
|
import * as Id from '../../internal/Id.js'
|
|
4
|
+
import * as Viem from '../../internal/Viem.js'
|
|
5
|
+
import * as SponsoredTransactions from './sponsoredTransactions.js'
|
|
4
6
|
import type * as Db from '../Db.js'
|
|
5
7
|
import type * as db_Schema from '../Schema.js'
|
|
8
|
+
import type * as EnabledBillingSources from './enabledBillingSources.js'
|
|
6
9
|
import type * as Memberships from './memberships.js'
|
|
7
10
|
|
|
8
11
|
/** Columns of the `organizations` table, derived from `Schema.Organization`. */
|
|
@@ -50,8 +53,8 @@ export declare namespace create {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
/**
|
|
53
|
-
* Inserts an organization
|
|
54
|
-
*
|
|
56
|
+
* Inserts an organization, its owner membership, and initial billing sources
|
|
57
|
+
* atomically.
|
|
55
58
|
*
|
|
56
59
|
* @param db - The database.
|
|
57
60
|
* @param input - The organization to insert.
|
|
@@ -82,6 +85,18 @@ export function createOwned(db: Db.Db, input: createOwned.Input): Promise<Record
|
|
|
82
85
|
userId: input.userId,
|
|
83
86
|
})
|
|
84
87
|
.execute()
|
|
88
|
+
if (input.enabledBillingSources?.length)
|
|
89
|
+
await trx
|
|
90
|
+
.insertInto('enabled_billing_sources')
|
|
91
|
+
.values(
|
|
92
|
+
input.enabledBillingSources.map((source) => ({
|
|
93
|
+
createdAt: now,
|
|
94
|
+
createdBy: input.userId,
|
|
95
|
+
orgId: record.id,
|
|
96
|
+
source,
|
|
97
|
+
})),
|
|
98
|
+
)
|
|
99
|
+
.execute()
|
|
85
100
|
return record
|
|
86
101
|
})
|
|
87
102
|
}
|
|
@@ -89,6 +104,8 @@ export function createOwned(db: Db.Db, input: createOwned.Input): Promise<Record
|
|
|
89
104
|
export declare namespace createOwned {
|
|
90
105
|
/** Fields accepted when inserting a user-owned organization. */
|
|
91
106
|
type Input = {
|
|
107
|
+
/** Billing sources enabled when the organization is created. */
|
|
108
|
+
enabledBillingSources?: readonly EnabledBillingSources.Source[] | undefined
|
|
92
109
|
/** Human-readable organization name. */
|
|
93
110
|
name: string
|
|
94
111
|
/** Owning user id (`usr_…`), granted the `owner` membership. */
|
|
@@ -105,12 +122,17 @@ export declare namespace createOwned {
|
|
|
105
122
|
*/
|
|
106
123
|
export function deleteOrganization(db: Db.Db, id: string): Promise<boolean> {
|
|
107
124
|
return db.kysely.transaction().execute(async (trx) => {
|
|
125
|
+
const failedSince = new Date(
|
|
126
|
+
Date.now() - SponsoredTransactions.failedIntentRecoveryTtlMs,
|
|
127
|
+
).toISOString()
|
|
108
128
|
const unreported = await trx
|
|
109
129
|
.selectFrom('sponsored_transactions')
|
|
110
130
|
.select('id')
|
|
111
131
|
.where('orgId', '=', id)
|
|
112
132
|
.where('billable', '=', true)
|
|
133
|
+
.where('chainId', '=', Viem.chainId.mainnet)
|
|
113
134
|
.where('meterReportedAt', 'is', null)
|
|
135
|
+
.where(unreportedSponsorshipStatus(failedSince))
|
|
114
136
|
.limit(1)
|
|
115
137
|
.executeTakeFirst()
|
|
116
138
|
if (unreported) throw new UnreportedSponsorshipsError()
|
|
@@ -121,6 +143,7 @@ export function deleteOrganization(db: Db.Db, id: string): Promise<boolean> {
|
|
|
121
143
|
.where('ownerId', '=', id)
|
|
122
144
|
.execute()
|
|
123
145
|
await trx.deleteFrom('billing_settings').where('orgId', '=', id).execute()
|
|
146
|
+
await trx.deleteFrom('enabled_billing_sources').where('orgId', '=', id).execute()
|
|
124
147
|
await trx.deleteFrom('invitations').where('orgId', '=', id).execute()
|
|
125
148
|
await trx.deleteFrom('invite_link_redemptions').where('orgId', '=', id).execute()
|
|
126
149
|
await trx.deleteFrom('invite_links').where('orgId', '=', id).execute()
|
|
@@ -136,12 +159,17 @@ export function deleteOrganization(db: Db.Db, id: string): Promise<boolean> {
|
|
|
136
159
|
|
|
137
160
|
/** Whether an organization has billable sponsorships awaiting settlement. */
|
|
138
161
|
export async function hasUnreportedSponsorships(db: Db.Db, id: string): Promise<boolean> {
|
|
162
|
+
const failedSince = new Date(
|
|
163
|
+
Date.now() - SponsoredTransactions.failedIntentRecoveryTtlMs,
|
|
164
|
+
).toISOString()
|
|
139
165
|
const record = await db.kysely
|
|
140
166
|
.selectFrom('sponsored_transactions')
|
|
141
167
|
.select('id')
|
|
142
168
|
.where('orgId', '=', id)
|
|
143
169
|
.where('billable', '=', true)
|
|
170
|
+
.where('chainId', '=', Viem.chainId.mainnet)
|
|
144
171
|
.where('meterReportedAt', 'is', null)
|
|
172
|
+
.where(unreportedSponsorshipStatus(failedSince))
|
|
145
173
|
.limit(1)
|
|
146
174
|
.executeTakeFirst()
|
|
147
175
|
return record !== undefined
|
|
@@ -413,6 +441,19 @@ export declare namespace upsert {
|
|
|
413
441
|
}
|
|
414
442
|
}
|
|
415
443
|
|
|
444
|
+
/** Blocks rows that can still settle or recover into billable mainnet spend. */
|
|
445
|
+
function unreportedSponsorshipStatus(failedSince: string) {
|
|
446
|
+
return (eb: ExpressionBuilder<Db.Database, 'sponsored_transactions'>) =>
|
|
447
|
+
eb.or([
|
|
448
|
+
eb('status', '!=', 'failed'),
|
|
449
|
+
eb.and([
|
|
450
|
+
eb('status', '=', 'failed'),
|
|
451
|
+
eb('transactionHash', 'is', null),
|
|
452
|
+
eb('finalizedAt', '>=', failedSince),
|
|
453
|
+
]),
|
|
454
|
+
])
|
|
455
|
+
}
|
|
456
|
+
|
|
416
457
|
/** Organization deletion is blocked until billable sponsorships settle. */
|
|
417
458
|
export class UnreportedSponsorshipsError extends Error {
|
|
418
459
|
override name = 'Organizations.UnreportedSponsorshipsError'
|
|
@@ -2,6 +2,7 @@ import { nanoid } from 'nanoid'
|
|
|
2
2
|
|
|
3
3
|
import * as Runtime from '../../../test/runtime.js'
|
|
4
4
|
import * as Db from '../Db.js'
|
|
5
|
+
import * as Organizations from './organizations.js'
|
|
5
6
|
import * as SponsoredTransactions from './sponsoredTransactions.js'
|
|
6
7
|
|
|
7
8
|
const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` })
|
|
@@ -160,7 +161,8 @@ describe('listPending', () => {
|
|
|
160
161
|
|
|
161
162
|
const pending = await SponsoredTransactions.listPending(db)
|
|
162
163
|
// Inserts can share a millisecond, so assert membership, not order.
|
|
163
|
-
expect(pending.map((row) => row.transactionHash).sort()).
|
|
164
|
+
expect(pending.map((row) => row.transactionHash).sort((left, right) => String(left).localeCompare(String(right)))) // prettier-ignore
|
|
165
|
+
.toMatchInlineSnapshot(`
|
|
164
166
|
[
|
|
165
167
|
"0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
|
|
166
168
|
"0xa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3",
|
|
@@ -175,9 +177,10 @@ describe('assignTransactionHash', () => {
|
|
|
175
177
|
test('behavior: reopens a failed intent when reconciliation finds its hash', async () => {
|
|
176
178
|
const db = create()
|
|
177
179
|
await db.migrate()
|
|
180
|
+
await Organizations.create(db, { id: 'org_1', name: 'One' })
|
|
178
181
|
const { transactionHash: _, ...intent } = input
|
|
179
182
|
const record = await SponsoredTransactions.upsert(db, intent)
|
|
180
|
-
await SponsoredTransactions.fail(db, record.id,
|
|
183
|
+
await SponsoredTransactions.fail(db, record.id, new Date().toISOString())
|
|
181
184
|
|
|
182
185
|
await SponsoredTransactions.assignTransactionHash(db, record.id, input.transactionHash)
|
|
183
186
|
|
|
@@ -190,9 +193,45 @@ describe('assignTransactionHash', () => {
|
|
|
190
193
|
await db.close()
|
|
191
194
|
})
|
|
192
195
|
|
|
196
|
+
test('behavior: does not reopen failed intents after the recovery window', async () => {
|
|
197
|
+
const db = create()
|
|
198
|
+
await db.migrate()
|
|
199
|
+
await Organizations.create(db, { id: 'org_1', name: 'One' })
|
|
200
|
+
const { transactionHash: _, ...intent } = input
|
|
201
|
+
const record = await SponsoredTransactions.upsert(db, intent)
|
|
202
|
+
await SponsoredTransactions.fail(
|
|
203
|
+
db,
|
|
204
|
+
record.id,
|
|
205
|
+
new Date(Date.now() - SponsoredTransactions.failedIntentRecoveryTtlMs - 1).toISOString(),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
await SponsoredTransactions.assignTransactionHash(db, record.id, input.transactionHash)
|
|
209
|
+
|
|
210
|
+
const assigned = await SponsoredTransactions.get(db, record.id)
|
|
211
|
+
expect(assigned?.status).toMatchInlineSnapshot(`"failed"`)
|
|
212
|
+
expect(assigned?.transactionHash).toBeNull()
|
|
213
|
+
await db.close()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('behavior: does not reopen failed intents for deleted orgs', async () => {
|
|
217
|
+
const db = create()
|
|
218
|
+
await db.migrate()
|
|
219
|
+
const { transactionHash: _, ...intent } = input
|
|
220
|
+
const record = await SponsoredTransactions.upsert(db, intent)
|
|
221
|
+
await SponsoredTransactions.fail(db, record.id, new Date().toISOString())
|
|
222
|
+
|
|
223
|
+
await SponsoredTransactions.assignTransactionHash(db, record.id, input.transactionHash)
|
|
224
|
+
|
|
225
|
+
const assigned = await SponsoredTransactions.get(db, record.id)
|
|
226
|
+
expect(assigned?.status).toMatchInlineSnapshot(`"failed"`)
|
|
227
|
+
expect(assigned?.transactionHash).toBeNull()
|
|
228
|
+
await db.close()
|
|
229
|
+
})
|
|
230
|
+
|
|
193
231
|
test('behavior: keeps an over-limit failed intent retryable across a period rollover', async () => {
|
|
194
232
|
const db = create()
|
|
195
233
|
await db.migrate()
|
|
234
|
+
await Organizations.create(db, { id: 'org_1', name: 'One' })
|
|
196
235
|
const { transactionHash: _, ...intent } = input
|
|
197
236
|
vi.useFakeTimers({ now: new Date('2026-01-31T23:59:00.000Z') })
|
|
198
237
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExpressionBuilder, Selectable } from 'kysely'
|
|
1
|
+
import type { ExpressionBuilder, Selectable, SqlBool } from 'kysely'
|
|
2
2
|
import { sql } from 'kysely'
|
|
3
3
|
|
|
4
4
|
import * as Id from '../../internal/Id.js'
|
|
@@ -11,6 +11,9 @@ export type Table = db_Schema.SponsoredTransaction
|
|
|
11
11
|
/** A stored sponsored-transaction row. */
|
|
12
12
|
export type Record = Selectable<Table>
|
|
13
13
|
|
|
14
|
+
/** How long failed fill intents remain eligible for on-chain reconciliation. */
|
|
15
|
+
export const failedIntentRecoveryTtlMs = 86_400_000
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* Records one sponsorship commitment as `pending`, keyed by its fee-payer
|
|
16
19
|
* sign payload. Written after fee-payer signing and before any broadcast, so
|
|
@@ -199,9 +202,15 @@ export async function assignTransactionHash(
|
|
|
199
202
|
!['failed', 'pending'].includes(record.status)
|
|
200
203
|
)
|
|
201
204
|
return
|
|
205
|
+
const failedSince = new Date(Date.now() - failedIntentRecoveryTtlMs).toISOString()
|
|
206
|
+
if (
|
|
207
|
+
record.status === 'failed' &&
|
|
208
|
+
(record.finalizedAt === null || record.finalizedAt < failedSince)
|
|
209
|
+
)
|
|
210
|
+
return
|
|
202
211
|
if (record.status === 'failed' && record.billable && limit)
|
|
203
212
|
await sql`select pg_advisory_xact_lock(${billingLockClass}, hashtext(${record.orgId}))`.execute(tx.kysely) // prettier-ignore
|
|
204
|
-
|
|
213
|
+
let query = tx.kysely
|
|
205
214
|
.updateTable('sponsored_transactions')
|
|
206
215
|
.set({
|
|
207
216
|
...(record.status === 'failed' ? { createdAt: new Date().toISOString() } : {}),
|
|
@@ -212,7 +221,10 @@ export async function assignTransactionHash(
|
|
|
212
221
|
.where('id', '=', id)
|
|
213
222
|
.where('status', '=', record.status)
|
|
214
223
|
.where('transactionHash', 'is', null)
|
|
215
|
-
|
|
224
|
+
if (record.status === 'failed')
|
|
225
|
+
query = query.where(sql<SqlBool>`exists (select 1 from organizations where organizations.id = sponsored_transactions.org_id)`) // prettier-ignore
|
|
226
|
+
const updated = await query.executeTakeFirst()
|
|
227
|
+
if (updated.numUpdatedRows === 0n) return
|
|
216
228
|
if (record.status === 'failed' && record.billable && limit) {
|
|
217
229
|
const committed = await spend(tx, {
|
|
218
230
|
chainIds: limit.chainIds,
|
|
@@ -3,6 +3,8 @@ import { KeyAuthorization, Transaction as core_Transaction, TxEnvelopeTempo } fr
|
|
|
3
3
|
import { type Client, BaseError } from 'viem'
|
|
4
4
|
import * as z from 'zod/mini'
|
|
5
5
|
|
|
6
|
+
import * as ApiResponse from '../../internal/Response.js'
|
|
7
|
+
|
|
6
8
|
export function resolveChainId(value: unknown) {
|
|
7
9
|
if (typeof value === 'number') return value
|
|
8
10
|
if (typeof value === 'bigint') return Number(value)
|
|
@@ -97,24 +99,38 @@ export function rpcErrorJson(request: RpcRequest.RpcRequest, error: unknown) {
|
|
|
97
99
|
{ request },
|
|
98
100
|
)
|
|
99
101
|
|
|
100
|
-
if (
|
|
102
|
+
if (error instanceof z.core.$ZodError)
|
|
101
103
|
return RpcResponse.from(
|
|
102
104
|
{
|
|
103
105
|
error: {
|
|
104
106
|
code: RpcResponse.InvalidParamsError.code,
|
|
105
|
-
|
|
107
|
+
data: {
|
|
108
|
+
code: 'invalid_params',
|
|
109
|
+
issues: ApiResponse.validationDetails(error.issues.slice(0, 10)),
|
|
110
|
+
},
|
|
111
|
+
message: 'Invalid params.',
|
|
106
112
|
},
|
|
107
113
|
},
|
|
108
114
|
{ request },
|
|
109
115
|
)
|
|
110
116
|
|
|
111
117
|
const inner = resolveError(error)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
if (inner.code === undefined || inner.message === undefined)
|
|
119
|
+
return RpcResponse.from(
|
|
120
|
+
{
|
|
121
|
+
error: {
|
|
122
|
+
code: RpcResponse.InternalError.code,
|
|
123
|
+
data: { code: 'internal_error' },
|
|
124
|
+
message: 'Internal error',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{ request },
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const { code, data, message } = inner
|
|
115
131
|
return RpcResponse.from(
|
|
116
132
|
{
|
|
117
|
-
error: { code, message, ...(data ? {
|
|
133
|
+
error: { code, message, ...(data === undefined ? {} : { data }) },
|
|
118
134
|
},
|
|
119
135
|
{ request },
|
|
120
136
|
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as z from 'zod/mini'
|
|
2
|
+
|
|
3
|
+
import * as Relay from '../../test/Relay.js'
|
|
4
|
+
|
|
5
|
+
describe('request errors', () => {
|
|
6
|
+
test('returns Parse error for invalid JSON', async () => {
|
|
7
|
+
const server = await Relay.createServer((await Relay.relay()).listener)
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch(server.url, {
|
|
11
|
+
body: '{',
|
|
12
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13
|
+
method: 'POST',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
17
|
+
{
|
|
18
|
+
"error": {
|
|
19
|
+
"code": -32700,
|
|
20
|
+
"message": "Parse error",
|
|
21
|
+
},
|
|
22
|
+
"id": null,
|
|
23
|
+
"jsonrpc": "2.0",
|
|
24
|
+
}
|
|
25
|
+
`)
|
|
26
|
+
} finally {
|
|
27
|
+
await server.closeAsync()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test.each([
|
|
32
|
+
['non-object request', 'null'],
|
|
33
|
+
['invalid version', JSON.stringify({ id: 1, jsonrpc: '1.0', method: 'eth_chainId' })],
|
|
34
|
+
['empty batch', '[]'],
|
|
35
|
+
])('returns Invalid Request for %s', async (_name, body) => {
|
|
36
|
+
const server = await Relay.createServer((await Relay.relay()).listener)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(server.url, {
|
|
40
|
+
body,
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
method: 'POST',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
46
|
+
{
|
|
47
|
+
"error": {
|
|
48
|
+
"code": -32600,
|
|
49
|
+
"message": "Invalid Request",
|
|
50
|
+
},
|
|
51
|
+
"id": null,
|
|
52
|
+
"jsonrpc": "2.0",
|
|
53
|
+
}
|
|
54
|
+
`)
|
|
55
|
+
} finally {
|
|
56
|
+
await server.closeAsync()
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('returns one Invalid Request error for a malformed batch member', async () => {
|
|
61
|
+
const server = await Relay.createServer(
|
|
62
|
+
(
|
|
63
|
+
await Relay.relay({
|
|
64
|
+
getClient: () => {
|
|
65
|
+
throw new Error('transport credential must not leak')
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
).listener,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(server.url, {
|
|
73
|
+
body: JSON.stringify([
|
|
74
|
+
{ id: 1, jsonrpc: '2.0', method: 'eth_chainId' },
|
|
75
|
+
{ id: 2, jsonrpc: '1.0', method: 'eth_chainId' },
|
|
76
|
+
]),
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
method: 'POST',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
82
|
+
[
|
|
83
|
+
{
|
|
84
|
+
"error": {
|
|
85
|
+
"code": -32603,
|
|
86
|
+
"data": {
|
|
87
|
+
"code": "internal_error",
|
|
88
|
+
},
|
|
89
|
+
"message": "Internal error",
|
|
90
|
+
},
|
|
91
|
+
"id": 1,
|
|
92
|
+
"jsonrpc": "2.0",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"error": {
|
|
96
|
+
"code": -32600,
|
|
97
|
+
"message": "Invalid Request",
|
|
98
|
+
},
|
|
99
|
+
"id": null,
|
|
100
|
+
"jsonrpc": "2.0",
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
`)
|
|
104
|
+
} finally {
|
|
105
|
+
await server.closeAsync()
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('returns bounded structured issues for invalid params', async () => {
|
|
110
|
+
const schema = z.object({ value: z.string() })
|
|
111
|
+
const server = await Relay.createServer(
|
|
112
|
+
(
|
|
113
|
+
await Relay.relay({
|
|
114
|
+
onRequest: async () => {
|
|
115
|
+
schema.parse({ value: 1 })
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
).listener,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(server.url, {
|
|
123
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
124
|
+
headers: { 'Content-Type': 'application/json' },
|
|
125
|
+
method: 'POST',
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
129
|
+
{
|
|
130
|
+
"error": {
|
|
131
|
+
"code": -32602,
|
|
132
|
+
"data": {
|
|
133
|
+
"code": "invalid_params",
|
|
134
|
+
"issues": [
|
|
135
|
+
{
|
|
136
|
+
"message": "Invalid input",
|
|
137
|
+
"path": [
|
|
138
|
+
"value",
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
"message": "Invalid params.",
|
|
144
|
+
},
|
|
145
|
+
"id": 1,
|
|
146
|
+
"jsonrpc": "2.0",
|
|
147
|
+
}
|
|
148
|
+
`)
|
|
149
|
+
} finally {
|
|
150
|
+
await server.closeAsync()
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
})
|
package/src/handlers/relay.ts
CHANGED
|
@@ -639,20 +639,30 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
639
639
|
|
|
640
640
|
async function handlePost(c: { req: { raw: Request; param: (key: string) => string } }) {
|
|
641
641
|
const chainId = Utils.resolveChainId(c.req.param('chainId'))
|
|
642
|
-
const body = await c.req.raw.json()
|
|
642
|
+
const body = await c.req.raw.json().catch(() => undefined)
|
|
643
|
+
if (body === undefined) return Response.json(rpcProtocolError('parse'))
|
|
644
|
+
|
|
643
645
|
const isBatch = Array.isArray(body)
|
|
644
646
|
|
|
645
647
|
if (!isBatch) {
|
|
648
|
+
if (!isRpcRequest(body)) return Response.json(rpcProtocolError('invalid_request'))
|
|
646
649
|
const request = RpcRequest.from(body as never) as RpcRequest.RpcRequest<RpcSchema.Ox>
|
|
647
|
-
|
|
650
|
+
const response = await handleRequest(request, { chainId }).catch((error) =>
|
|
651
|
+
Utils.rpcErrorJson(request, error),
|
|
652
|
+
)
|
|
653
|
+
return Response.json(response)
|
|
648
654
|
}
|
|
649
655
|
|
|
656
|
+
if (body.length === 0) return Response.json(rpcProtocolError('invalid_request'))
|
|
657
|
+
|
|
650
658
|
const responses = await Promise.all(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
659
|
+
body.map((item) => {
|
|
660
|
+
if (!isRpcRequest(item)) return Promise.resolve(rpcProtocolError('invalid_request'))
|
|
661
|
+
return handleRequest(
|
|
662
|
+
RpcRequest.from(item as never) as RpcRequest.RpcRequest<RpcSchema.Ox>,
|
|
663
|
+
{ chainId },
|
|
664
|
+
).catch((error) => Utils.rpcErrorJson(item as RpcRequest.RpcRequest, error))
|
|
665
|
+
}),
|
|
656
666
|
)
|
|
657
667
|
return Response.json(responses)
|
|
658
668
|
}
|
|
@@ -663,6 +673,31 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
663
673
|
return router
|
|
664
674
|
}
|
|
665
675
|
|
|
676
|
+
function isRpcRequest(value: unknown): value is Record<string, unknown> {
|
|
677
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
|
|
678
|
+
const request = value as Record<string, unknown>
|
|
679
|
+
if (request['jsonrpc'] !== '2.0' || typeof request['method'] !== 'string') return false
|
|
680
|
+
|
|
681
|
+
const id = request['id']
|
|
682
|
+
if (id !== undefined && id !== null && typeof id !== 'number' && typeof id !== 'string')
|
|
683
|
+
return false
|
|
684
|
+
|
|
685
|
+
const params = request['params']
|
|
686
|
+
if (params === undefined || Array.isArray(params)) return true
|
|
687
|
+
return Boolean(params) && typeof params === 'object'
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function rpcProtocolError(type: 'invalid_request' | 'parse') {
|
|
691
|
+
return {
|
|
692
|
+
error:
|
|
693
|
+
type === 'parse'
|
|
694
|
+
? { code: RpcResponse.ParseError.code, message: 'Parse error' }
|
|
695
|
+
: { code: RpcResponse.InvalidRequestError.code, message: 'Invalid Request' },
|
|
696
|
+
id: null,
|
|
697
|
+
jsonrpc: '2.0',
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
666
701
|
function defaultGetClient(options: { apiKey?: string | undefined }) {
|
|
667
702
|
const clients = new Map<number, Client>()
|
|
668
703
|
for (const chain of defaultChains) {
|
|
@@ -671,7 +671,7 @@ describe('require', () => {
|
|
|
671
671
|
headers: { 'tempo-api-key': sandboxToken },
|
|
672
672
|
})
|
|
673
673
|
expect(response.status).toBe(200)
|
|
674
|
-
// The public ceiling (60), not the API-key default (
|
|
674
|
+
// The public ceiling (60), not the API-key default (10,000).
|
|
675
675
|
expect(response.headers.get('RateLimit-Limit')).toMatchInlineSnapshot(`"60"`)
|
|
676
676
|
})
|
|
677
677
|
|
|
@@ -684,8 +684,8 @@ describe('require', () => {
|
|
|
684
684
|
headers: { 'tempo-api-key': sandboxToken },
|
|
685
685
|
})
|
|
686
686
|
expect(response.status).toBe(200)
|
|
687
|
-
// The API-key default (
|
|
688
|
-
expect(response.headers.get('RateLimit-Limit')).toMatchInlineSnapshot(`"
|
|
687
|
+
// The API-key default (10,000), not the public ceiling (60).
|
|
688
|
+
expect(response.headers.get('RateLimit-Limit')).toMatchInlineSnapshot(`"10000"`)
|
|
689
689
|
})
|
|
690
690
|
|
|
691
691
|
test('throttles a sandbox key to the public quota when billing is unknown', async () => {
|