tapimo 0.6.0 → 0.7.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.
Files changed (250) hide show
  1. package/dist/App.d.ts +8641 -8263
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +112 -11
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +2 -1
  12. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  13. package/dist/admin/apps/early-access.d.ts +2 -2
  14. package/dist/admin/apps/early-access.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.js +5 -5
  16. package/dist/admin/apps/early-access.js.map +1 -1
  17. package/dist/admin/apps/organizations.d.ts +124 -1
  18. package/dist/admin/apps/organizations.d.ts.map +1 -1
  19. package/dist/admin/apps/organizations.js +97 -6
  20. package/dist/admin/apps/organizations.js.map +1 -1
  21. package/dist/admin/ui.generated.d.ts +1 -1
  22. package/dist/admin/ui.generated.d.ts.map +1 -1
  23. package/dist/admin/ui.generated.js +95 -87
  24. package/dist/admin/ui.generated.js.map +1 -1
  25. package/dist/analytics/Analytics.d.ts.map +1 -1
  26. package/dist/analytics/Analytics.js +20 -0
  27. package/dist/analytics/Analytics.js.map +1 -1
  28. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  29. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  30. package/dist/analytics/tables/requestEvents.js +36 -7
  31. package/dist/analytics/tables/requestEvents.js.map +1 -1
  32. package/dist/apps/data/App.d.ts +5016 -4985
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/FundingQuoteProviders.js +2 -15
  35. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +1 -1
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +1 -1
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +27 -27
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +21 -1
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +58 -58
  45. package/dist/apps/data/routes/blocks.d.ts +39 -39
  46. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  47. package/dist/apps/data/routes/exchanges.d.ts +113 -113
  48. package/dist/apps/data/routes/fee-amm.d.ts +105 -91
  49. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  50. package/dist/apps/data/routes/fee-amm.js +76 -91
  51. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  52. package/dist/apps/data/routes/funding.d.ts +72 -34
  53. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  54. package/dist/apps/data/routes/funding.js +34 -26
  55. package/dist/apps/data/routes/funding.js.map +1 -1
  56. package/dist/apps/data/routes/indexer.d.ts +5 -5
  57. package/dist/apps/data/routes/receipts.d.ts +6 -6
  58. package/dist/apps/data/routes/rpc.d.ts +31 -31
  59. package/dist/apps/data/routes/tokens.d.ts +2 -2
  60. package/dist/apps/data/routes/tokens.js +1 -1
  61. package/dist/apps/data/routes/transactions.d.ts +4 -4
  62. package/dist/apps/data/routes/transfers.d.ts +99 -88
  63. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  64. package/dist/apps/data/routes/transfers.js +226 -141
  65. package/dist/apps/data/routes/transfers.js.map +1 -1
  66. package/dist/apps/data/routes/valuation.d.ts +34 -34
  67. package/dist/apps/data/routes/valuation.js +3 -3
  68. package/dist/apps/data/routes/valuation.js.map +1 -1
  69. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  70. package/dist/apps/data/routes/webhooks.d.ts +366 -366
  71. package/dist/apps/index.d.ts +1 -0
  72. package/dist/apps/index.d.ts.map +1 -1
  73. package/dist/apps/index.js +1 -0
  74. package/dist/apps/index.js.map +1 -1
  75. package/dist/apps/management/App.d.ts +460 -523
  76. package/dist/apps/management/App.d.ts.map +1 -1
  77. package/dist/apps/management/routes/api-keys.d.ts +7 -23
  78. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  79. package/dist/apps/management/routes/api-keys.js +20 -8
  80. package/dist/apps/management/routes/api-keys.js.map +1 -1
  81. package/dist/apps/management/routes/billing.d.ts +106 -151
  82. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  83. package/dist/apps/management/routes/billing.js +28 -28
  84. package/dist/apps/management/routes/billing.js.map +1 -1
  85. package/dist/apps/management/routes/faucet.d.ts +40 -26
  86. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  87. package/dist/apps/management/routes/faucet.js +33 -18
  88. package/dist/apps/management/routes/faucet.js.map +1 -1
  89. package/dist/apps/management/routes/invitations.d.ts +131 -131
  90. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  91. package/dist/apps/management/routes/invitations.js +0 -3
  92. package/dist/apps/management/routes/invitations.js.map +1 -1
  93. package/dist/apps/management/routes/me.d.ts +6 -8
  94. package/dist/apps/management/routes/me.d.ts.map +1 -1
  95. package/dist/apps/management/routes/me.js +0 -4
  96. package/dist/apps/management/routes/me.js.map +1 -1
  97. package/dist/apps/management/routes/members.d.ts +45 -45
  98. package/dist/apps/management/routes/orgs.d.ts +94 -94
  99. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  100. package/dist/apps/management/routes/orgs.js +5 -0
  101. package/dist/apps/management/routes/orgs.js.map +1 -1
  102. package/dist/apps/management/routes/usage.d.ts +32 -32
  103. package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
  104. package/dist/apps/management/routes/webhooks.d.ts +1 -1
  105. package/dist/apps/mpp/App.d.ts +363 -0
  106. package/dist/apps/mpp/App.d.ts.map +1 -0
  107. package/dist/apps/mpp/App.js +72 -0
  108. package/dist/apps/mpp/App.js.map +1 -0
  109. package/dist/apps/mpp/Mpp.d.ts +178 -0
  110. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  111. package/dist/apps/mpp/Mpp.js +183 -0
  112. package/dist/apps/mpp/Mpp.js.map +1 -0
  113. package/dist/apps/mpp/index.d.ts +3 -0
  114. package/dist/apps/mpp/index.d.ts.map +1 -0
  115. package/dist/apps/mpp/index.js +3 -0
  116. package/dist/apps/mpp/index.js.map +1 -0
  117. package/dist/apps/relay/App.d.ts.map +1 -1
  118. package/dist/apps/relay/App.js +56 -9
  119. package/dist/apps/relay/App.js.map +1 -1
  120. package/dist/db/Db.d.ts +2 -0
  121. package/dist/db/Db.d.ts.map +1 -1
  122. package/dist/db/Db.js +27 -0
  123. package/dist/db/Db.js.map +1 -1
  124. package/dist/db/Schema.d.ts +12 -0
  125. package/dist/db/Schema.d.ts.map +1 -1
  126. package/dist/db/Schema.js +11 -0
  127. package/dist/db/Schema.js.map +1 -1
  128. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  129. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  130. package/dist/db/tables/enabledBillingSources.js +69 -0
  131. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  132. package/dist/db/tables/invitations.d.ts +1 -12
  133. package/dist/db/tables/invitations.d.ts.map +1 -1
  134. package/dist/db/tables/invitations.js +3 -23
  135. package/dist/db/tables/invitations.js.map +1 -1
  136. package/dist/db/tables/organizations.d.ts +5 -2
  137. package/dist/db/tables/organizations.d.ts.map +1 -1
  138. package/dist/db/tables/organizations.js +13 -2
  139. package/dist/db/tables/organizations.js.map +1 -1
  140. package/dist/handlers/internal/utils.d.ts +1 -16
  141. package/dist/handlers/internal/utils.d.ts.map +1 -1
  142. package/dist/handlers/internal/utils.js +17 -6
  143. package/dist/handlers/internal/utils.js.map +1 -1
  144. package/dist/handlers/relay.d.ts.map +1 -1
  145. package/dist/handlers/relay.js +37 -5
  146. package/dist/handlers/relay.js.map +1 -1
  147. package/dist/internal/Auth.d.ts +0 -32
  148. package/dist/internal/Auth.d.ts.map +1 -1
  149. package/dist/internal/Auth.js +3 -51
  150. package/dist/internal/Auth.js.map +1 -1
  151. package/dist/internal/FundingQuotes.d.ts +78 -32
  152. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  153. package/dist/internal/FundingQuotes.js +45 -8
  154. package/dist/internal/FundingQuotes.js.map +1 -1
  155. package/dist/internal/Log.d.ts +18 -4
  156. package/dist/internal/Log.d.ts.map +1 -1
  157. package/dist/internal/Log.js +50 -3
  158. package/dist/internal/Log.js.map +1 -1
  159. package/dist/internal/MetricSink.d.ts.map +1 -1
  160. package/dist/internal/MetricSink.js +15 -0
  161. package/dist/internal/MetricSink.js.map +1 -1
  162. package/dist/internal/Response.d.ts +2 -2
  163. package/dist/internal/Response.d.ts.map +1 -1
  164. package/dist/internal/Response.js +12 -4
  165. package/dist/internal/Response.js.map +1 -1
  166. package/dist/internal/Viem.d.ts +1 -1
  167. package/package.json +7 -2
  168. package/src/App.test.ts +9 -4
  169. package/src/App.ts +14 -2
  170. package/src/Client.test-d.ts +109 -28
  171. package/src/Scope.ts +5 -0
  172. package/src/admin/App.test.ts +4 -0
  173. package/src/admin/apps/early-access.ts +6 -5
  174. package/src/admin/apps/organizations.test.ts +86 -2
  175. package/src/admin/apps/organizations.ts +155 -31
  176. package/src/admin/apps/scopes.test.ts +5 -0
  177. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  178. package/src/admin/ui/src/lib/organizations.ts +92 -0
  179. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  180. package/src/admin/ui/src/routes/early-access.tsx +8 -3
  181. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  182. package/src/admin/ui.generated.ts +95 -109
  183. package/src/analytics/Analytics.test.ts +83 -0
  184. package/src/analytics/Analytics.ts +20 -0
  185. package/src/analytics/tables/requestEvents.ts +60 -7
  186. package/src/apps/composition.test.ts +11 -2
  187. package/src/apps/data/App.test.ts +1 -1
  188. package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
  189. package/src/apps/data/FundingQuoteProviders.ts +2 -29
  190. package/src/apps/data/FxOracle.test.ts +2 -2
  191. package/src/apps/data/FxOracle.ts +2 -2
  192. package/src/apps/data/routes/activities.test.ts +6 -2
  193. package/src/apps/data/routes/activities.ts +39 -1
  194. package/src/apps/data/routes/balances.test.ts +6 -2
  195. package/src/apps/data/routes/exchanges.test.ts +7 -3
  196. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  197. package/src/apps/data/routes/fee-amm.ts +98 -119
  198. package/src/apps/data/routes/funding.test.ts +29 -8
  199. package/src/apps/data/routes/funding.ts +42 -48
  200. package/src/apps/data/routes/receipts.test.ts +6 -2
  201. package/src/apps/data/routes/tokens.ts +1 -1
  202. package/src/apps/data/routes/transfers.test.ts +203 -35
  203. package/src/apps/data/routes/transfers.ts +294 -172
  204. package/src/apps/data/routes/valuation.test.ts +18 -3
  205. package/src/apps/data/routes/valuation.ts +4 -4
  206. package/src/apps/index.ts +1 -0
  207. package/src/apps/management/routes/api-keys.test.ts +2 -75
  208. package/src/apps/management/routes/api-keys.ts +30 -7
  209. package/src/apps/management/routes/billing.test.ts +106 -85
  210. package/src/apps/management/routes/billing.ts +32 -21
  211. package/src/apps/management/routes/faucet.test.ts +11 -3
  212. package/src/apps/management/routes/faucet.ts +46 -30
  213. package/src/apps/management/routes/invitations.test.ts +0 -172
  214. package/src/apps/management/routes/invitations.ts +0 -3
  215. package/src/apps/management/routes/me.test.ts +3 -145
  216. package/src/apps/management/routes/me.ts +0 -6
  217. package/src/apps/management/routes/orgs.test.ts +45 -2
  218. package/src/apps/management/routes/orgs.ts +6 -0
  219. package/src/apps/management/routes/scopes.test.ts +1 -0
  220. package/src/apps/mpp/App.ts +86 -0
  221. package/src/apps/mpp/Mpp.test.ts +193 -0
  222. package/src/apps/mpp/Mpp.ts +353 -0
  223. package/src/apps/mpp/index.ts +2 -0
  224. package/src/apps/mpp/internal/Adapter.ts +11 -0
  225. package/src/apps/mpp/internal/Error.ts +9 -0
  226. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  227. package/src/apps/mpp/internal/Screening.ts +10 -0
  228. package/src/apps/relay/App.test.ts +88 -6
  229. package/src/apps/relay/App.ts +61 -9
  230. package/src/db/Db.test.ts +2 -2
  231. package/src/db/Db.ts +30 -0
  232. package/src/db/Schema.ts +15 -0
  233. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  234. package/src/db/tables/enabledBillingSources.ts +118 -0
  235. package/src/db/tables/invitations.ts +4 -26
  236. package/src/db/tables/organizations.test.ts +103 -0
  237. package/src/db/tables/organizations.ts +18 -2
  238. package/src/handlers/internal/utils.ts +22 -6
  239. package/src/handlers/relay.errors.test.ts +153 -0
  240. package/src/handlers/relay.ts +42 -7
  241. package/src/internal/Auth.test.ts +3 -3
  242. package/src/internal/Auth.ts +3 -63
  243. package/src/internal/FundingQuotes.test.ts +18 -5
  244. package/src/internal/FundingQuotes.ts +46 -11
  245. package/src/internal/Log.test.ts +91 -0
  246. package/src/internal/Log.ts +69 -5
  247. package/src/internal/MetricSink.test.ts +55 -0
  248. package/src/internal/MetricSink.ts +14 -0
  249. package/src/internal/Response.test.ts +28 -0
  250. package/src/internal/Response.ts +12 -4
@@ -3,6 +3,7 @@ import { sql, type Selectable } from 'kysely'
3
3
  import * as Id from '../../internal/Id.js'
4
4
  import type * as Db from '../Db.js'
5
5
  import type * as db_Schema from '../Schema.js'
6
+ import type * as EnabledBillingSources from './enabledBillingSources.js'
6
7
  import type * as Memberships from './memberships.js'
7
8
 
8
9
  /** Columns of the `organizations` table, derived from `Schema.Organization`. */
@@ -50,8 +51,8 @@ export declare namespace create {
50
51
  }
51
52
 
52
53
  /**
53
- * Inserts an organization owned by `userId` the org row plus its `owner`
54
- * membership, atomically.
54
+ * Inserts an organization, its owner membership, and initial billing sources
55
+ * atomically.
55
56
  *
56
57
  * @param db - The database.
57
58
  * @param input - The organization to insert.
@@ -82,6 +83,18 @@ export function createOwned(db: Db.Db, input: createOwned.Input): Promise<Record
82
83
  userId: input.userId,
83
84
  })
84
85
  .execute()
86
+ if (input.enabledBillingSources?.length)
87
+ await trx
88
+ .insertInto('enabled_billing_sources')
89
+ .values(
90
+ input.enabledBillingSources.map((source) => ({
91
+ createdAt: now,
92
+ createdBy: input.userId,
93
+ orgId: record.id,
94
+ source,
95
+ })),
96
+ )
97
+ .execute()
85
98
  return record
86
99
  })
87
100
  }
@@ -89,6 +102,8 @@ export function createOwned(db: Db.Db, input: createOwned.Input): Promise<Record
89
102
  export declare namespace createOwned {
90
103
  /** Fields accepted when inserting a user-owned organization. */
91
104
  type Input = {
105
+ /** Billing sources enabled when the organization is created. */
106
+ enabledBillingSources?: readonly EnabledBillingSources.Source[] | undefined
92
107
  /** Human-readable organization name. */
93
108
  name: string
94
109
  /** Owning user id (`usr_…`), granted the `owner` membership. */
@@ -121,6 +136,7 @@ export function deleteOrganization(db: Db.Db, id: string): Promise<boolean> {
121
136
  .where('ownerId', '=', id)
122
137
  .execute()
123
138
  await trx.deleteFrom('billing_settings').where('orgId', '=', id).execute()
139
+ await trx.deleteFrom('enabled_billing_sources').where('orgId', '=', id).execute()
124
140
  await trx.deleteFrom('invitations').where('orgId', '=', id).execute()
125
141
  await trx.deleteFrom('invite_link_redemptions').where('orgId', '=', id).execute()
126
142
  await trx.deleteFrom('invite_links').where('orgId', '=', id).execute()
@@ -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 ((error as { name?: string | undefined }).name === 'ZodError')
102
+ if (error instanceof z.core.$ZodError)
101
103
  return RpcResponse.from(
102
104
  {
103
105
  error: {
104
106
  code: RpcResponse.InvalidParamsError.code,
105
- message: (error as Error).message,
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
- const message = inner.message ?? (error as Error).message
113
- const code = inner.code ?? -32603
114
- const data = inner.data
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 ? { 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
+ })
@@ -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
- return Response.json(await handleRequest(request, { chainId }))
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
- (body as unknown[]).map((item) =>
652
- handleRequest(RpcRequest.from(item as never) as RpcRequest.RpcRequest<RpcSchema.Ox>, {
653
- chainId,
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 (100).
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 (100), not the public ceiling (60).
688
- expect(response.headers.get('RateLimit-Limit')).toMatchInlineSnapshot(`"100"`)
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 () => {
@@ -16,8 +16,6 @@ import * as ApiKey from '../ApiKey.js'
16
16
  import * as ApiKeys from '../ApiKeys.js'
17
17
  import * as Db from '../db/Db.js'
18
18
  import type * as Store from './Store.js'
19
- import * as EarlyAccess from '../db/tables/earlyAccess.js'
20
- import * as Invitations from '../db/tables/invitations.js'
21
19
  import * as Memberships from '../db/tables/memberships.js'
22
20
  import * as Metadata from '../apps/metadata.js'
23
21
  import * as OpenApi from './OpenApi.js'
@@ -32,6 +30,7 @@ import * as Viem from './Viem.js'
32
30
 
33
31
  const policySymbol = Symbol('tempo-api.auth.policy')
34
32
  const mppManagementSymbol = Symbol('tempo-api.auth.mpp-management')
33
+ const defaultApiKeyRateLimit = { perMinute: 10_000 } satisfies RateLimit.Limit
35
34
  const defaultMppRateLimit = { perMinute: 100 } satisfies RateLimit.Limit
36
35
 
37
36
  /** Auth context consumed by Tempo API handlers. */
@@ -159,8 +158,6 @@ export type PolicyOverride = {
159
158
  | undefined
160
159
  /** Session lane: `true` allows signed-in wallet sessions, `false` disables. */
161
160
  session?: boolean | undefined
162
- /** Temporary early-access gate: `true` rejects sessions without early access ({@link tmp_earlyAccess}). TODO: remove after GA. */
163
- tmp_earlyAccess?: boolean | undefined
164
161
  }
165
162
 
166
163
  /** Authenticated or anonymous caller. */
@@ -229,8 +226,6 @@ export type Variables = {
229
226
  principal?: Principal | undefined
230
227
  /** Project resolved by {@link ensureProject} for the current request. */
231
228
  project?: Projects.Record | undefined
232
- /** Temporary early-access gate toggle; unset or false grants everyone early access. TODO: remove after GA. */
233
- tmp_earlyAccess?: boolean | undefined
234
229
  }
235
230
 
236
231
  /** Reads the authenticated principal from a Hono context. */
@@ -385,42 +380,6 @@ export function ensureProjectError(c: hono_Context) {
385
380
  })
386
381
  }
387
382
 
388
- /** Whether the caller can grant early access, independent of the temporary route gate. TODO: remove after GA. */
389
- export async function tmp_earlyAccessGrant(c: hono_Context): Promise<boolean> {
390
- const principal = getPrincipal(c)
391
- // Trust chain: `management:write` keys are only mintable by early-access owners.
392
- if (principal?.type === 'api_key' || principal?.type === 'super_admin') return true
393
- if (principal?.type !== 'session' || !principal.email) return false
394
- const email = principal.email.trim().toLowerCase()
395
- const db = Db.get(contextDb(c))
396
- if (await EarlyAccess.matches(db, email)) return true
397
- return Invitations.hasEarlyAccessGrant(db, email)
398
- }
399
-
400
- /** Whether the caller has effective early access, including a dormant temporary gate. TODO: remove after GA. */
401
- export async function tmp_earlyAccess(c: hono_Context): Promise<boolean> {
402
- const enabled = (c.get as (key: string) => unknown)('tmp_earlyAccess') as boolean | undefined
403
- if (!enabled) return true
404
- return tmp_earlyAccessGrant(c)
405
- }
406
-
407
- /**
408
- * Always false at runtime, but typed as `boolean` so handlers can include a
409
- * never-reached early-access-error branch in Hono's inferred response union
410
- * (the `tmp_earlyAccess` policy flag produces this `403` before the handler
411
- * runs). TODO: remove after GA.
412
- */
413
- export const tmp_narrowEarlyAccess = false as boolean
414
-
415
- /** Typed early-access error response for Hono client inference; also the live gate rejection. TODO: remove after GA. */
416
- export function tmp_ensureEarlyAccessError(c: hono_Context) {
417
- return Response.error(c, {
418
- code: 'early_access_required',
419
- message: 'Requires early access. Ask a teammate with access to invite you by email, or contact Tempo.', // prettier-ignore
420
- status: 403,
421
- })
422
- }
423
-
424
383
  /** Reads the caller's membership resolved by the organization or project guard; `undefined` for API keys and `super_admin`. */
425
384
  export function membership<environment extends Environment>(
426
385
  c: hono_Context<environment>,
@@ -629,9 +588,6 @@ export function middleware<
629
588
  if (management && (!auth.mpp || !endpointPolicy.mpp)) return next()
630
589
 
631
590
  c.set('auth', auth)
632
- // Consumed by `tmp_earlyAccess`; absent (or false) leaves the gate dormant.
633
- // TODO: remove after GA.
634
- c.set('tmp_earlyAccess', options.tmp_earlyAccess)
635
591
  return require<environment>(endpointPolicy)(c, next)
636
592
  })
637
593
  }
@@ -687,12 +643,6 @@ export declare namespace middleware {
687
643
  * checks — before the key lookup runs. Omit to disable the entry entirely.
688
644
  */
689
645
  superAdmin?: { secret: string } | undefined
690
- /**
691
- * Temporary invite-only gate for routes flagged via {@link policy}.
692
- * Allowlist entries live in the `early_access` table (admin dash); unset
693
- * grants everyone early access. TODO: remove after GA.
694
- */
695
- tmp_earlyAccess?: boolean | undefined
696
646
  }
697
647
 
698
648
  /** Resolve-cache tuning for API-key resolution. */
@@ -1196,7 +1146,6 @@ function resolvePolicy(
1196
1146
  let mpp = base.mpp
1197
1147
  let public_ = base.public
1198
1148
  let session = base.session
1199
- let tmp_earlyAccess = base.tmp_earlyAccess
1200
1149
 
1201
1150
  for (const override of overrides) {
1202
1151
  if (override === false) return undefined
@@ -1205,7 +1154,6 @@ function resolvePolicy(
1205
1154
  mpp = resolveMppLane(mpp, override.mpp, defaults.mpp)
1206
1155
  public_ = resolvePublicLane(public_, override.public, defaults.public)
1207
1156
  session = resolveSessionLane(session, override.session, defaults.session)
1208
- tmp_earlyAccess = override.tmp_earlyAccess ?? tmp_earlyAccess
1209
1157
  }
1210
1158
 
1211
1159
  // The OpenAPI summary/description rides along as the payment-challenge
@@ -1221,7 +1169,6 @@ function resolvePolicy(
1221
1169
  ...(mpp ? { mpp } : {}),
1222
1170
  ...(public_ ? { public: public_ } : {}),
1223
1171
  ...(session ? { session } : {}),
1224
- ...(tmp_earlyAccess ? { tmp_earlyAccess } : {}),
1225
1172
  }
1226
1173
  }
1227
1174
 
@@ -1444,12 +1391,6 @@ export function require<environment extends Environment = Environment>(
1444
1391
  )
1445
1392
  return Response.unsupportedChainId(c, chainId, supportedChainIds)
1446
1393
  const proceed = async () => {
1447
- // Runs after principal resolution, so api_key and super_admin pass by
1448
- // type; only gated sessions are rejected. TODO: remove after GA.
1449
- if (policy.tmp_earlyAccess && !(await tmp_earlyAccess(c))) {
1450
- c.res = tmp_ensureEarlyAccessError(c)
1451
- return
1452
- }
1453
1394
  await next()
1454
1395
  // Query credential responses must not enter shared caches.
1455
1396
  const cacheControl = c.res.headers.get('Cache-Control')?.toLowerCase()
@@ -1685,8 +1626,6 @@ export declare namespace require {
1685
1626
  | undefined
1686
1627
  /** Session access policy: `true` allows signed-in wallet sessions. */
1687
1628
  session?: boolean | undefined
1688
- /** Temporary early-access gate for this route; see {@link tmp_earlyAccess}. TODO: remove after GA. */
1689
- tmp_earlyAccess?: boolean | undefined
1690
1629
  }
1691
1630
  }
1692
1631
 
@@ -1782,7 +1721,8 @@ function resolveApiKeyLimit(options: resolveApiKeyLimit.Options): RateLimit.Limi
1782
1721
  policy?.rateLimit ?? // 2. route override (protective cap)
1783
1722
  apiKey.rateLimits?.[Scope.wildcard] ?? // 3. per-key default
1784
1723
  auth.apiKey?.rateLimits?.[scope] ?? // 4. config per-scope
1785
- auth.apiKey?.rateLimits?.[Scope.wildcard] ?? { perMinute: 100 } // 5. config default // 6. hardcoded fallback
1724
+ auth.apiKey?.rateLimits?.[Scope.wildcard] ?? // 5. config default
1725
+ defaultApiKeyRateLimit // 6. hardcoded fallback
1786
1726
  )
1787
1727
  }
1788
1728
 
@@ -40,16 +40,29 @@ describe('listQuotes', () => {
40
40
  expect(seen?.candidate.id).toBe('base-usdc-tempo-usdce-relay')
41
41
  expect(quotes).toHaveLength(1)
42
42
  expect(quotes[0]).toMatchObject({
43
+ destinationAmount: {
44
+ baseUnits: '990000',
45
+ currency: 'USD',
46
+ decimals: 6,
47
+ formatted: '0.99',
48
+ },
49
+ destinationAmountMin: {
50
+ baseUnits: '980000',
51
+ currency: 'USD',
52
+ decimals: 6,
53
+ formatted: '0.98',
54
+ },
43
55
  id: 'base-usdc-tempo-usdce-relay',
44
56
  quality: {
45
57
  liquiditySource: 'providerQuote',
46
58
  tier: 'liquid',
47
59
  },
48
- quote: {
49
- inputAmount: '1000000',
50
- minimumOutputAmount: '980000',
51
- outputAmount: '990000',
52
- outputAmountFormatted: '0.99',
60
+ quote: { sampledAt: '2026-01-01T00:00:00Z' },
61
+ sourceAmount: {
62
+ baseUnits: '1000000',
63
+ currency: 'USD',
64
+ decimals: 6,
65
+ formatted: '1',
53
66
  },
54
67
  })
55
68
  expect(attempts.map(stableAttempt)).toEqual([{ outcome: 'available', provider: 'relay' }])