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.
Files changed (69) hide show
  1. package/dist/App.d.ts +5 -5
  2. package/dist/admin/ui.generated.d.ts.map +1 -1
  3. package/dist/admin/ui.generated.js +95 -95
  4. package/dist/admin/ui.generated.js.map +1 -1
  5. package/dist/apps/data/App.d.ts +3283 -3283
  6. package/dist/apps/data/routes/exchanges.d.ts +664 -664
  7. package/dist/apps/data/routes/fee-amm.d.ts +108 -108
  8. package/dist/apps/data/routes/indexer.d.ts +34 -34
  9. package/dist/apps/data/routes/receipts.d.ts +2 -2
  10. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  11. package/dist/apps/data/routes/tokenlist.js +12 -3
  12. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  13. package/dist/apps/data/routes/tokens.d.ts +2 -2
  14. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  15. package/dist/apps/data/routes/tokens.js +4 -0
  16. package/dist/apps/data/routes/tokens.js.map +1 -1
  17. package/dist/apps/data/routes/transactions.d.ts +2 -2
  18. package/dist/apps/data/routes/transfers.d.ts +2 -2
  19. package/dist/apps/data/routes/valuation.d.ts +32 -32
  20. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  21. package/dist/apps/data/routes/verified-tokens.js +34 -9
  22. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  23. package/dist/apps/data/routes/webhooks.d.ts +363 -363
  24. package/dist/apps/data/routes/webhooks.js +14 -6
  25. package/dist/apps/data/routes/webhooks.js.map +1 -1
  26. package/dist/apps/management/routes/faucet.d.ts +36 -36
  27. package/dist/apps/management/routes/members.d.ts +78 -78
  28. package/dist/apps/management/routes/usage.d.ts +80 -80
  29. package/dist/apps/relay/App.js +1 -1
  30. package/dist/apps/relay/App.js.map +1 -1
  31. package/dist/apps/relay/Sponsorships.js +1 -1
  32. package/dist/apps/relay/Sponsorships.js.map +1 -1
  33. package/dist/db/tables/organizations.d.ts.map +1 -1
  34. package/dist/db/tables/organizations.js +19 -0
  35. package/dist/db/tables/organizations.js.map +1 -1
  36. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  37. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  38. package/dist/db/tables/sponsoredTransactions.js +13 -3
  39. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  40. package/dist/internal/EdgeCache.d.ts +1 -1
  41. package/dist/internal/EdgeCache.d.ts.map +1 -1
  42. package/dist/internal/EdgeCache.js +3 -2
  43. package/dist/internal/EdgeCache.js.map +1 -1
  44. package/dist/internal/Schema.d.ts +2 -2
  45. package/dist/internal/Schema.d.ts.map +1 -1
  46. package/dist/internal/Schema.js +4 -2
  47. package/dist/internal/Schema.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/admin/ui.generated.ts +119 -95
  50. package/src/apps/data/App.test.ts +13 -1
  51. package/src/apps/data/routes/tokenlist.test.ts +15 -0
  52. package/src/apps/data/routes/tokenlist.ts +18 -5
  53. package/src/apps/data/routes/tokens.ts +4 -0
  54. package/src/apps/data/routes/verified-tokens.test.ts +51 -0
  55. package/src/apps/data/routes/verified-tokens.ts +52 -15
  56. package/src/apps/data/routes/webhooks.test.ts +56 -0
  57. package/src/apps/data/routes/webhooks.ts +17 -8
  58. package/src/apps/relay/App.test.ts +2 -2
  59. package/src/apps/relay/App.ts +1 -1
  60. package/src/apps/relay/Sponsorships.test.ts +2 -0
  61. package/src/apps/relay/Sponsorships.ts +1 -1
  62. package/src/db/tables/organizations.test.ts +88 -0
  63. package/src/db/tables/organizations.ts +26 -1
  64. package/src/db/tables/sponsoredTransactions.test.ts +41 -2
  65. package/src/db/tables/sponsoredTransactions.ts +15 -3
  66. package/src/internal/EdgeCache.test.ts +18 -5
  67. package/src/internal/EdgeCache.ts +4 -2
  68. package/src/internal/Schema.test.ts +13 -0
  69. 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()).toMatchInlineSnapshot(`
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, '2026-01-01T00:00:00.000Z')
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
- await tx.kysely
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
- .execute()
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 cacheable = options.eligibility
286
- ? EdgeCache.setEligibility(cacheable_marked, options.eligibility)
287
- : cacheable_marked
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 to a cacheable route handler. */
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
- if (cacheableSymbol in handler) eligibility.set(handler, predicate)
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
+ })
@@ -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 resource being listed (e.g. `transfers`).
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.optional(z.iso.datetime({ offset: true })).check(
503
- z.describe(`Only include ${noun} ${direction} this ISO 8601 timestamp.`),
504
- z.meta({
505
- examples: [bound === 'from' ? '2024-01-01T00:00:00Z' : '2024-12-31T23:59:59Z'],
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
  /**