tapimo 0.7.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 +5 -5
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +95 -95
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +3283 -3283
- package/dist/apps/data/routes/exchanges.d.ts +664 -664
- package/dist/apps/data/routes/fee-amm.d.ts +108 -108
- package/dist/apps/data/routes/indexer.d.ts +34 -34
- package/dist/apps/data/routes/receipts.d.ts +2 -2
- 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 +2 -2
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +4 -0
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +2 -2
- package/dist/apps/data/routes/transfers.d.ts +2 -2
- package/dist/apps/data/routes/valuation.d.ts +32 -32
- 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 +363 -363
- package/dist/apps/data/routes/webhooks.js +14 -6
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +36 -36
- package/dist/apps/management/routes/members.d.ts +78 -78
- package/dist/apps/management/routes/usage.d.ts +80 -80
- package/dist/apps/relay/App.js +1 -1
- 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/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +19 -0
- 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/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/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/package.json +1 -1
- package/src/admin/ui.generated.ts +119 -95
- package/src/apps/data/App.test.ts +13 -1
- 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 +4 -0
- 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/relay/App.test.ts +2 -2
- package/src/apps/relay/App.ts +1 -1
- package/src/apps/relay/Sponsorships.test.ts +2 -0
- package/src/apps/relay/Sponsorships.ts +1 -1
- package/src/db/tables/organizations.test.ts +88 -0
- package/src/db/tables/organizations.ts +26 -1
- package/src/db/tables/sponsoredTransactions.test.ts +41 -2
- package/src/db/tables/sponsoredTransactions.ts +15 -3
- package/src/internal/EdgeCache.test.ts +18 -5
- package/src/internal/EdgeCache.ts +4 -2
- package/src/internal/Schema.test.ts +13 -0
- package/src/internal/Schema.ts +14 -7
|
@@ -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,
|
|
@@ -219,7 +219,7 @@ describe('middleware', () => {
|
|
|
219
219
|
|
|
220
220
|
test('rechecks route eligibility before every cache lookup', async () => {
|
|
221
221
|
let eligible = true
|
|
222
|
-
const { app, downstreamCalls, store } = createApp({ eligibility: () => eligible })
|
|
222
|
+
const { app, downstreamCalls, store } = createApp({ eligibility: [() => eligible] })
|
|
223
223
|
|
|
224
224
|
await app.request('/cached')
|
|
225
225
|
eligible = false
|
|
@@ -229,6 +229,19 @@ describe('middleware', () => {
|
|
|
229
229
|
expect(store.gets).toBe(1)
|
|
230
230
|
expect(store.size).toBe(1)
|
|
231
231
|
})
|
|
232
|
+
|
|
233
|
+
test('requires every route eligibility predicate to permit caching', async () => {
|
|
234
|
+
const { app, downstreamCalls, store } = createApp({
|
|
235
|
+
eligibility: [() => true, () => false],
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
await app.request('/cached')
|
|
239
|
+
await app.request('/cached')
|
|
240
|
+
|
|
241
|
+
expect(downstreamCalls()).toBe(2)
|
|
242
|
+
expect(store.gets).toBe(0)
|
|
243
|
+
expect(store.size).toBe(0)
|
|
244
|
+
})
|
|
232
245
|
})
|
|
233
246
|
|
|
234
247
|
type Environment = { Variables: EdgeCache.Variables }
|
|
@@ -263,7 +276,7 @@ function memoryStore() {
|
|
|
263
276
|
|
|
264
277
|
function createApp(
|
|
265
278
|
options: {
|
|
266
|
-
eligibility?: ((c: Context<Environment>) => boolean) | undefined
|
|
279
|
+
eligibility?: readonly ((c: Context<Environment>) => boolean)[] | undefined
|
|
267
280
|
store?: Store.Store | undefined
|
|
268
281
|
} = {},
|
|
269
282
|
) {
|
|
@@ -282,9 +295,9 @@ function createApp(
|
|
|
282
295
|
const cacheable_marked = EdgeCache.markCacheable(
|
|
283
296
|
createMiddleware<Environment>(async (_c, next) => next()),
|
|
284
297
|
)
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
298
|
+
for (const predicate of options.eligibility ?? [])
|
|
299
|
+
EdgeCache.setEligibility(cacheable_marked, predicate)
|
|
300
|
+
const cacheable = cacheable_marked
|
|
288
301
|
app.get('/cached', cacheable, (c) => {
|
|
289
302
|
c.set('edgeCache', { maxAge: 30, staleWhileRevalidate: 300 })
|
|
290
303
|
c.header('Age', '123')
|
|
@@ -154,12 +154,14 @@ export function markCacheable<const handler extends (...args: never[]) => unknow
|
|
|
154
154
|
return Object.assign(handler, { [cacheableSymbol]: true })
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
/** Adds a request eligibility check
|
|
157
|
+
/** Adds a request eligibility check; every check must permit caching. */
|
|
158
158
|
export function setEligibility<const handler extends (...args: never[]) => unknown>(
|
|
159
159
|
handler: handler,
|
|
160
160
|
predicate: (c: Context) => boolean,
|
|
161
161
|
): handler {
|
|
162
|
-
|
|
162
|
+
const existing = eligibility.get(handler)
|
|
163
|
+
if (cacheableSymbol in handler)
|
|
164
|
+
eligibility.set(handler, existing ? (c) => existing(c) && predicate(c) : predicate)
|
|
163
165
|
return handler
|
|
164
166
|
}
|
|
165
167
|
|
|
@@ -32,3 +32,16 @@ describe('ChainIdQuery', () => {
|
|
|
32
32
|
expect(Schema.ChainIdQuery.parse('42431')).toMatchInlineSnapshot(`42431`)
|
|
33
33
|
})
|
|
34
34
|
})
|
|
35
|
+
|
|
36
|
+
describe('timestampBound', () => {
|
|
37
|
+
test('normalizes RFC 3339 offsets to UTC', () => {
|
|
38
|
+
const bound = Schema.timestampBound('swaps', 'from')
|
|
39
|
+
|
|
40
|
+
expect(bound.parse('2026-06-04T12:00:00+05:30')).toMatchInlineSnapshot(
|
|
41
|
+
`"2026-06-04T06:30:00.000Z"`,
|
|
42
|
+
)
|
|
43
|
+
expect(bound.parse('2026-06-04T12:00:00-05:00')).toMatchInlineSnapshot(
|
|
44
|
+
`"2026-06-04T17:00:00.000Z"`,
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
})
|
package/src/internal/Schema.ts
CHANGED
|
@@ -495,16 +495,23 @@ export function blockNumberBound(noun: string, bound: 'from' | 'to') {
|
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
497
|
* Optional inclusive ISO-8601 timestamp bound query parameter (`timestamp.from`
|
|
498
|
-
* / `timestamp.to`). `noun` is the
|
|
498
|
+
* / `timestamp.to`), normalized to UTC. `noun` is the listed resource.
|
|
499
499
|
*/
|
|
500
500
|
export function timestampBound(noun: string, bound: 'from' | 'to') {
|
|
501
501
|
const direction = bound === 'from' ? 'at or after' : 'at or before'
|
|
502
|
-
return z
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
502
|
+
return z
|
|
503
|
+
.optional(
|
|
504
|
+
z.pipe(
|
|
505
|
+
z.iso.datetime({ offset: true }),
|
|
506
|
+
z.transform((value) => new Date(value).toISOString()),
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
.check(
|
|
510
|
+
z.describe(`Only include ${noun} ${direction} this ISO 8601 timestamp.`),
|
|
511
|
+
z.meta({
|
|
512
|
+
examples: [bound === 'from' ? '2024-01-01T00:00:00Z' : '2024-12-31T23:59:59Z'],
|
|
513
|
+
}),
|
|
514
|
+
)
|
|
508
515
|
}
|
|
509
516
|
|
|
510
517
|
/**
|