tapimo 0.4.4 → 0.5.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 (229) hide show
  1. package/dist/App.d.ts +4701 -3416
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +24 -2
  4. package/dist/App.js.map +1 -1
  5. package/dist/Assets.js +3 -3
  6. package/dist/Assets.js.map +1 -1
  7. package/dist/Scope.d.ts +18 -1
  8. package/dist/Scope.d.ts.map +1 -1
  9. package/dist/Scope.js +23 -3
  10. package/dist/Scope.js.map +1 -1
  11. package/dist/Webhooks.d.ts +1 -1
  12. package/dist/Webhooks.d.ts.map +1 -1
  13. package/dist/Webhooks.js +5 -10
  14. package/dist/Webhooks.js.map +1 -1
  15. package/dist/admin/App.d.ts +6 -34
  16. package/dist/admin/App.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.d.ts +3 -3
  18. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  19. package/dist/admin/apps/api-keys.js +4 -1
  20. package/dist/admin/apps/api-keys.js.map +1 -1
  21. package/dist/admin/apps/scopes.d.ts +6 -34
  22. package/dist/admin/apps/scopes.d.ts.map +1 -1
  23. package/dist/admin/apps/scopes.js +2 -2
  24. package/dist/admin/apps/scopes.js.map +1 -1
  25. package/dist/admin/apps/verified-tokens.d.ts +57 -57
  26. package/dist/admin/ui.generated.d.ts +1 -1
  27. package/dist/admin/ui.generated.js +33 -33
  28. package/dist/admin/ui.generated.js.map +1 -1
  29. package/dist/apps/data/App.d.ts +4574 -4277
  30. package/dist/apps/data/App.d.ts.map +1 -1
  31. package/dist/apps/data/App.js +155 -77
  32. package/dist/apps/data/App.js.map +1 -1
  33. package/dist/apps/data/FundingQuoteProviders.d.ts +109 -0
  34. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -0
  35. package/dist/apps/data/FundingQuoteProviders.js +464 -0
  36. package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
  37. package/dist/apps/data/index.d.ts +1 -1
  38. package/dist/apps/data/index.js +1 -1
  39. package/dist/apps/data/routes/activities.d.ts +26 -26
  40. package/dist/apps/data/routes/addresses.d.ts +38 -38
  41. package/dist/apps/data/routes/balances.d.ts +45 -45
  42. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  43. package/dist/apps/data/routes/balances.js +67 -149
  44. package/dist/apps/data/routes/balances.js.map +1 -1
  45. package/dist/apps/data/routes/blocks.d.ts +2 -3
  46. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  47. package/dist/apps/data/routes/blocks.js +3 -5
  48. package/dist/apps/data/routes/blocks.js.map +1 -1
  49. package/dist/apps/data/routes/coingecko.d.ts +177 -176
  50. package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
  51. package/dist/apps/data/routes/coingecko.js +3 -8
  52. package/dist/apps/data/routes/coingecko.js.map +1 -1
  53. package/dist/apps/data/routes/funding.d.ts +131 -228
  54. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  55. package/dist/apps/data/routes/funding.js +152 -219
  56. package/dist/apps/data/routes/funding.js.map +1 -1
  57. package/dist/apps/data/routes/indexer.d.ts +34 -34
  58. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  59. package/dist/apps/data/routes/indexer.js +9 -2
  60. package/dist/apps/data/routes/indexer.js.map +1 -1
  61. package/dist/apps/data/routes/receipts.d.ts +228 -228
  62. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  63. package/dist/apps/data/routes/rpc.js +86 -16
  64. package/dist/apps/data/routes/rpc.js.map +1 -1
  65. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  66. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  67. package/dist/apps/data/routes/webhooks.d.ts +340 -0
  68. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  69. package/dist/apps/data/routes/webhooks.js +13 -1
  70. package/dist/apps/data/routes/webhooks.js.map +1 -1
  71. package/dist/apps/index.d.ts +1 -1
  72. package/dist/apps/index.js +1 -1
  73. package/dist/apps/management/App.d.ts +1833 -851
  74. package/dist/apps/management/App.d.ts.map +1 -1
  75. package/dist/apps/management/App.js +4 -0
  76. package/dist/apps/management/App.js.map +1 -1
  77. package/dist/apps/management/routes/api-keys.d.ts +41 -41
  78. package/dist/apps/management/routes/api-keys.js +2 -2
  79. package/dist/apps/management/routes/api-keys.js.map +1 -1
  80. package/dist/apps/management/routes/billing.d.ts +48 -48
  81. package/dist/apps/management/routes/invitations.d.ts +132 -132
  82. package/dist/apps/management/routes/invite-links.d.ts +1100 -0
  83. package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
  84. package/dist/apps/management/routes/invite-links.js +411 -0
  85. package/dist/apps/management/routes/invite-links.js.map +1 -0
  86. package/dist/apps/management/routes/members.d.ts +41 -41
  87. package/dist/apps/management/routes/orgs.d.ts +92 -92
  88. package/dist/apps/management/routes/projects.d.ts +9 -9
  89. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  90. package/dist/apps/management/routes/scopes.js +2 -2
  91. package/dist/apps/management/routes/scopes.js.map +1 -1
  92. package/dist/apps/management/routes/usage.d.ts +24 -24
  93. package/dist/apps/mcp.js +2 -2
  94. package/dist/apps/mcp.js.map +1 -1
  95. package/dist/apps/relay/App.d.ts +2 -2
  96. package/dist/apps/relay/App.d.ts.map +1 -1
  97. package/dist/apps/relay/App.js +44 -6
  98. package/dist/apps/relay/App.js.map +1 -1
  99. package/dist/db/Db.d.ts +3 -0
  100. package/dist/db/Db.d.ts.map +1 -1
  101. package/dist/db/Db.js +57 -0
  102. package/dist/db/Db.js.map +1 -1
  103. package/dist/db/Schema.d.ts +31 -0
  104. package/dist/db/Schema.d.ts.map +1 -1
  105. package/dist/db/Schema.js +31 -0
  106. package/dist/db/Schema.js.map +1 -1
  107. package/dist/db/tables/inviteLinks.d.ts +42 -0
  108. package/dist/db/tables/inviteLinks.d.ts.map +1 -0
  109. package/dist/db/tables/inviteLinks.js +148 -0
  110. package/dist/db/tables/inviteLinks.js.map +1 -0
  111. package/dist/db/tables/organizations.d.ts.map +1 -1
  112. package/dist/db/tables/organizations.js +2 -0
  113. package/dist/db/tables/organizations.js.map +1 -1
  114. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  115. package/dist/db/tables/sponsoredTransactions.js +6 -2
  116. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  117. package/dist/handlers/relay.d.ts.map +1 -1
  118. package/dist/handlers/relay.js +13 -3
  119. package/dist/handlers/relay.js.map +1 -1
  120. package/dist/internal/Auth.d.ts +9 -1
  121. package/dist/internal/Auth.d.ts.map +1 -1
  122. package/dist/internal/Auth.js +138 -10
  123. package/dist/internal/Auth.js.map +1 -1
  124. package/dist/internal/EdgeCache.d.ts +3 -1
  125. package/dist/internal/EdgeCache.d.ts.map +1 -1
  126. package/dist/internal/EdgeCache.js +17 -2
  127. package/dist/internal/EdgeCache.js.map +1 -1
  128. package/dist/internal/{FundingRoutes.d.ts → FundingQuotes.d.ts} +240 -369
  129. package/dist/internal/FundingQuotes.d.ts.map +1 -0
  130. package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +144 -183
  131. package/dist/internal/FundingQuotes.js.map +1 -0
  132. package/dist/internal/Log.d.ts +18 -12
  133. package/dist/internal/Log.d.ts.map +1 -1
  134. package/dist/internal/Log.js +2 -0
  135. package/dist/internal/Log.js.map +1 -1
  136. package/dist/internal/Tidx.d.ts +9 -3
  137. package/dist/internal/Tidx.d.ts.map +1 -1
  138. package/dist/internal/Tidx.js +13 -7
  139. package/dist/internal/Tidx.js.map +1 -1
  140. package/dist/internal/Viem.d.ts +17 -2
  141. package/dist/internal/Viem.d.ts.map +1 -1
  142. package/dist/internal/Viem.js +30 -3
  143. package/dist/internal/Viem.js.map +1 -1
  144. package/dist/internal/index.d.ts +1 -1
  145. package/dist/internal/index.js +1 -1
  146. package/package.json +2 -2
  147. package/src/ApiKeys.test.ts +25 -0
  148. package/src/App.test-d.ts +2 -0
  149. package/src/App.test.ts +13 -2
  150. package/src/App.ts +37 -5
  151. package/src/Assets.ts +3 -3
  152. package/src/Client.test-d.ts +21 -11
  153. package/src/Scope.test.ts +1 -0
  154. package/src/Scope.ts +32 -4
  155. package/src/Webhooks.ts +5 -10
  156. package/src/admin/apps/api-keys.test.ts +27 -0
  157. package/src/admin/apps/api-keys.ts +8 -1
  158. package/src/admin/apps/scopes.test.ts +7 -2
  159. package/src/admin/apps/scopes.ts +2 -2
  160. package/src/admin/ui/src/components/Dialog.tsx +8 -2
  161. package/src/admin/ui/src/lib/scopes.test.ts +19 -0
  162. package/src/admin/ui/src/lib/scopes.ts +12 -0
  163. package/src/admin/ui/src/routes/api-keys.tsx +128 -27
  164. package/src/admin/ui.generated.ts +33 -33
  165. package/src/apps/composition.test.ts +77 -0
  166. package/src/apps/data/App.test.ts +354 -0
  167. package/src/apps/data/App.ts +178 -91
  168. package/src/apps/data/FundingQuoteProviders.test.ts +577 -0
  169. package/src/apps/data/FundingQuoteProviders.ts +633 -0
  170. package/src/apps/data/index.ts +1 -1
  171. package/src/apps/data/routes/balances.test.ts +64 -18
  172. package/src/apps/data/routes/balances.ts +76 -177
  173. package/src/apps/data/routes/blocks.ts +3 -5
  174. package/src/apps/data/routes/coingecko.ts +3 -8
  175. package/src/apps/data/routes/funding.test.ts +304 -192
  176. package/src/apps/data/routes/funding.ts +166 -286
  177. package/src/apps/data/routes/indexer.test.ts +173 -15
  178. package/src/apps/data/routes/indexer.ts +8 -2
  179. package/src/apps/data/routes/rpc.test.ts +219 -0
  180. package/src/apps/data/routes/rpc.ts +92 -17
  181. package/src/apps/data/routes/webhooks.test.ts +21 -0
  182. package/src/apps/data/routes/webhooks.ts +13 -1
  183. package/src/apps/index.ts +1 -1
  184. package/src/apps/management/App.ts +4 -0
  185. package/src/apps/management/Billing.test.ts +2 -2
  186. package/src/apps/management/routes/api-keys.test.ts +47 -3
  187. package/src/apps/management/routes/api-keys.ts +2 -2
  188. package/src/apps/management/routes/invite-links.test.ts +385 -0
  189. package/src/apps/management/routes/invite-links.ts +570 -0
  190. package/src/apps/management/routes/scopes.test.ts +3 -2
  191. package/src/apps/management/routes/scopes.ts +4 -2
  192. package/src/apps/mcp.test.ts +18 -0
  193. package/src/apps/mcp.ts +2 -2
  194. package/src/apps/relay/App.test.ts +328 -7
  195. package/src/apps/relay/App.ts +42 -6
  196. package/src/apps/relay/Sponsorships.test.ts +2 -2
  197. package/src/db/Db.test.ts +5 -5
  198. package/src/db/Db.ts +65 -0
  199. package/src/db/Schema.ts +39 -0
  200. package/src/db/tables/billingSettings.test.ts +2 -2
  201. package/src/db/tables/inviteLinks.ts +187 -0
  202. package/src/db/tables/organizations.test.ts +2 -2
  203. package/src/db/tables/organizations.ts +2 -0
  204. package/src/db/tables/sponsoredTransactions.test.ts +19 -2
  205. package/src/db/tables/sponsoredTransactions.ts +21 -14
  206. package/src/db/tables/stripeCustomers.test.ts +2 -2
  207. package/src/handlers/relay.ts +16 -4
  208. package/src/internal/Auth.test.ts +132 -22
  209. package/src/internal/Auth.ts +181 -14
  210. package/src/internal/EdgeCache.test.ts +24 -3
  211. package/src/internal/EdgeCache.ts +18 -2
  212. package/src/internal/FundingQuotes.test.ts +268 -0
  213. package/src/internal/{FundingRoutes.ts → FundingQuotes.ts} +216 -277
  214. package/src/internal/Log.ts +21 -14
  215. package/src/internal/Tidx.test.ts +48 -0
  216. package/src/internal/Tidx.ts +21 -7
  217. package/src/internal/Viem.test.ts +46 -0
  218. package/src/internal/Viem.ts +48 -6
  219. package/src/internal/index.test.ts +1 -1
  220. package/src/internal/index.ts +1 -1
  221. package/dist/apps/data/FundingRouteProviders.d.ts +0 -36
  222. package/dist/apps/data/FundingRouteProviders.d.ts.map +0 -1
  223. package/dist/apps/data/FundingRouteProviders.js +0 -245
  224. package/dist/apps/data/FundingRouteProviders.js.map +0 -1
  225. package/dist/internal/FundingRoutes.d.ts.map +0 -1
  226. package/dist/internal/FundingRoutes.js.map +0 -1
  227. package/src/apps/data/FundingRouteProviders.test.ts +0 -225
  228. package/src/apps/data/FundingRouteProviders.ts +0 -312
  229. package/src/internal/FundingRoutes.test.ts +0 -213
@@ -12,6 +12,8 @@ export type Variables = {
12
12
  errorCode?: string | undefined
13
13
  /** Redacted provider failure details recorded by an upstream adapter. */
14
14
  providerFailure?: Entry['provider'] | undefined
15
+ /** Redacted provider failures when multiple upstream adapters fail. */
16
+ providerFailures?: Entry['providerFailures'] | undefined
15
17
  }
16
18
 
17
19
  /** Hono environment shape used by the request logger. */
@@ -28,6 +30,20 @@ export type Environment = {
28
30
  /** Log level, derived from the response status. */
29
31
  export type Level = 'error' | 'info' | 'warn'
30
32
 
33
+ /** Redacted upstream provider failure details. */
34
+ export type ProviderFailure = {
35
+ /** Stable provider error code, when one is safe to retain. */
36
+ code?: string | undefined
37
+ /** Failure boundary that rejected the provider request. */
38
+ failure: 'http' | 'network' | 'payload' | 'timeout' | 'unknown'
39
+ /** Provider identifier. */
40
+ id: string
41
+ /** Provider operation identifier. */
42
+ operation: string
43
+ /** Upstream HTTP status, when a response was received. */
44
+ status?: number | undefined
45
+ }
46
+
31
47
  /** One canonical request log entry. */
32
48
  export type Entry = {
33
49
  /** `'hit'` when the edge response cache served the request without reaching the origin handlers. */
@@ -64,20 +80,9 @@ export type Entry = {
64
80
  }
65
81
  | undefined
66
82
  /** Redacted upstream provider failure details, when an adapter request fails. */
67
- provider?:
68
- | {
69
- /** Stable provider error code, when one is safe to retain. */
70
- code?: string | undefined
71
- /** Failure boundary that rejected the provider request. */
72
- failure: 'http' | 'network' | 'payload' | 'unknown'
73
- /** Provider identifier. */
74
- id: string
75
- /** Provider operation identifier. */
76
- operation: string
77
- /** Upstream HTTP status, when a response was received. */
78
- status?: number | undefined
79
- }
80
- | undefined
83
+ provider?: ProviderFailure | undefined
84
+ /** Redacted failures for every provider when multiple adapters fail. */
85
+ providerFailures?: readonly ProviderFailure[] | undefined
81
86
  /** Raw query string (no leading `?`) with API-key tokens redacted, when present. */
82
87
  query?: string | undefined
83
88
  /** Rate limit state from the `RateLimit-*` response headers, when enforced. */
@@ -136,6 +141,7 @@ export function middleware<environment extends Environment = Environment>(
136
141
  const principal = c.get('principal' as never) as Auth.Principal | undefined
137
142
  const errorCode = c.get('errorCode' as never) as string | undefined
138
143
  const provider = c.get('providerFailure' as never) as Entry['provider']
144
+ const providerFailures = c.get('providerFailures' as never) as Entry['providerFailures']
139
145
 
140
146
  // The `request` total metric duplicates `duration` (measured here, one
141
147
  // layer further out), so only operation timings are kept.
@@ -186,6 +192,7 @@ export function middleware<environment extends Environment = Environment>(
186
192
  },
187
193
  }),
188
194
  ...(provider === undefined ? {} : { provider }),
195
+ ...(providerFailures === undefined ? {} : { providerFailures }),
189
196
  ...(query ? { query } : {}),
190
197
  ...(rateLimit === undefined ? {} : { rateLimit }),
191
198
  requestId: c.get('requestId' as never) as string,
@@ -203,6 +203,21 @@ describe('resolve', () => {
203
203
  `)
204
204
  })
205
205
 
206
+ test('resolves Bearer auth', () => {
207
+ expect(
208
+ Tidx.resolve(
209
+ { baseUrl: 'https://internal.indexer', bearerAuth: 'zone-secret' },
210
+ { chainId: 4217, principal: apiKeyPrincipal },
211
+ ),
212
+ ).toMatchInlineSnapshot(`
213
+ {
214
+ "baseUrl": "https://internal.indexer",
215
+ "basicAuth": undefined,
216
+ "bearerAuth": "zone-secret",
217
+ }
218
+ `)
219
+ })
220
+
206
221
  test('the resolver form routes anonymous callers to a separate upstream', () => {
207
222
  const tidx = ({ principal }: Viem.resolve.Context) =>
208
223
  principal?.type === 'public'
@@ -243,6 +258,28 @@ describe('resolve', () => {
243
258
  })
244
259
  })
245
260
 
261
+ describe('getClient', () => {
262
+ test('sends Bearer auth', async () => {
263
+ const fetch = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
264
+ new Response(JSON.stringify({ columns: [], ok: true, row_count: 0, rows: [] }), {
265
+ headers: { 'content-type': 'application/json' },
266
+ }),
267
+ )
268
+ try {
269
+ const client = Tidx.getClient({
270
+ chainId: 4217,
271
+ tidx: { baseUrl: 'https://internal.indexer', bearerAuth: 'zone-secret' },
272
+ })
273
+ await client.fetch({ query: 'select 1' })
274
+
275
+ const upstream = fetch.mock.calls[0]![0] as Request
276
+ expect(upstream.headers.get('authorization')).toBe('Bearer zone-secret')
277
+ } finally {
278
+ fetch.mockRestore()
279
+ }
280
+ })
281
+ })
282
+
246
283
  describe('createGetClient', () => {
247
284
  test('memoizes one client per resolved upstream', () => {
248
285
  const getTidx = Tidx.createGetClient({
@@ -267,6 +304,17 @@ describe('createGetClient', () => {
267
304
  expect(getTidx(4217, null)).toBe(getTidx(4217, publicPrincipal))
268
305
  })
269
306
 
307
+ test('does not share clients across Bearer credentials', () => {
308
+ const getTidx = Tidx.createGetClient({
309
+ tidx: ({ principal }) => ({
310
+ baseUrl: 'https://internal.indexer',
311
+ bearerAuth: principal?.type === 'public' ? 'public-secret' : 'private-secret',
312
+ }),
313
+ })
314
+
315
+ expect(getTidx(4217, publicPrincipal)).not.toBe(getTidx(4217, apiKeyPrincipal))
316
+ })
317
+
270
318
  test('consults the resolver on every call but builds one client per upstream', () => {
271
319
  const seen: (string | undefined)[] = []
272
320
  const getTidx = Tidx.createGetClient({
@@ -29,8 +29,8 @@ export type UrlResolver = (chainId: Viem.ChainId) => string | undefined
29
29
  * receives a {@link Viem.resolve.Context} (`chainId` plus the request
30
30
  * `principal`), so callers can route traffic per caller — e.g. send anonymous
31
31
  * (`principal.type === 'public'`) traffic to a separate, no-auth indexer. A
32
- * missing `baseUrl` falls back to the built-in public host map ({@link url}); a
33
- * missing `basicAuth` means no Basic auth is sent.
32
+ * missing `baseUrl` falls back to the built-in public host map ({@link url});
33
+ * missing auth fields send no Authorization header.
34
34
  */
35
35
  export function resolve(
36
36
  tidx: getClient.Tidx | undefined,
@@ -42,6 +42,7 @@ export function resolve(
42
42
  (typeof config?.baseUrl === 'function' ? config.baseUrl(context.chainId) : config?.baseUrl) ??
43
43
  url[context.chainId],
44
44
  basicAuth: config?.basicAuth,
45
+ ...(config?.bearerAuth ? { bearerAuth: config.bearerAuth } : {}),
45
46
  }
46
47
  }
47
48
 
@@ -56,13 +57,22 @@ export declare namespace resolve {
56
57
  baseUrl: string | undefined
57
58
  /** Basic auth credentials, or undefined to send no Basic auth. */
58
59
  basicAuth?: string | undefined
60
+ /** Bearer auth credential, or undefined to send no Bearer auth. */
61
+ bearerAuth?: string | undefined
59
62
  }
60
63
  }
61
64
 
62
65
  export function getClient(options: getClient.Options): Client {
63
66
  const { chainId = Viem.defaultChainId, principal = null, tidx } = options
64
- const { baseUrl, basicAuth } = resolve(tidx, { chainId, principal })
65
- return withTransientRetry(Tidx.create({ baseUrl, basicAuth, chainId }))
67
+ const { baseUrl, basicAuth, bearerAuth } = resolve(tidx, { chainId, principal })
68
+ return withTransientRetry(
69
+ Tidx.create({
70
+ baseUrl,
71
+ basicAuth,
72
+ bearerAuth,
73
+ chainId,
74
+ }),
75
+ )
66
76
  }
67
77
 
68
78
  /**
@@ -72,7 +82,7 @@ export function getClient(options: getClient.Options): Client {
72
82
  * output (`baseUrl` + `basicAuth` + `chainId`), not the principal, so a resolver
73
83
  * `tidx` may branch on any principal field while client cardinality stays
74
84
  * bounded by the number of distinct upstreams — callers that resolve to the same
75
- * upstream share a client. Shared by `App.create` (per-request reads) and
85
+ * upstream share a client. Shared by `data()` (per-request reads) and
76
86
  * `Webhooks.createPoller` (the scheduled poller) so the resolver logic lives in
77
87
  * one place.
78
88
  */
@@ -83,8 +93,8 @@ export function createGetClient(options: createGetClient.Options = {}): GetClien
83
93
  return (chainId = defaultChainId, principal = null) => {
84
94
  // Resolve to derive the cache key; `getClient` re-resolves to build the
85
95
  // client on a miss. `resolve` is pure and cheap, so the extra call is free.
86
- const { baseUrl, basicAuth } = resolve(tidx, { chainId, principal })
87
- const cacheKey = `${baseUrl}|${basicAuth ?? ''}|${chainId}`
96
+ const { baseUrl, basicAuth, bearerAuth } = resolve(tidx, { chainId, principal })
97
+ const cacheKey = `${baseUrl}|${basicAuth ?? ''}|${bearerAuth ?? ''}|${chainId}`
88
98
  const existing = clients.get(cacheKey)
89
99
  if (existing) return existing
90
100
 
@@ -218,12 +228,16 @@ export declare namespace getClient {
218
228
  baseUrl?: UrlResolver | string | undefined
219
229
  /** Basic auth credentials for TIDX requests. */
220
230
  basicAuth?: string | undefined
231
+ /** Bearer auth credential for TIDX requests. */
232
+ bearerAuth?: string | undefined
221
233
  }
222
234
  | ((context: Viem.resolve.Context) => {
223
235
  /** TIDX base URL to use; falls back to the built-in public host when omitted. */
224
236
  baseUrl?: string | undefined
225
237
  /** Basic auth credentials for TIDX requests, or undefined for none. */
226
238
  basicAuth?: string | undefined
239
+ /** Bearer auth credential for TIDX requests, or undefined for none. */
240
+ bearerAuth?: string | undefined
227
241
  })
228
242
  }
229
243
 
@@ -113,3 +113,49 @@ describe('resolve', () => {
113
113
  `)
114
114
  })
115
115
  })
116
+
117
+ describe('createGetClient', () => {
118
+ test('memoizes one client per resolved upstream', () => {
119
+ const getClient = Viem.createGetClient({
120
+ rpc: ({ principal }) =>
121
+ principal?.type === 'public'
122
+ ? { url: 'https://rpc.tempo.xyz' }
123
+ : { basicAuth: 'user:pass', url: 'https://internal.rpc' },
124
+ })
125
+
126
+ expect(getClient(4217, apiKeyPrincipal)).toBe(getClient(4217, apiKeyPrincipal))
127
+ expect(getClient(4217, publicPrincipal)).toBe(getClient(4217, publicPrincipal))
128
+ expect(getClient(4217, publicPrincipal)).not.toBe(getClient(4217, apiKeyPrincipal))
129
+ expect(getClient(4217, apiKeyPrincipal)).not.toBe(getClient(42431, apiKeyPrincipal))
130
+ })
131
+
132
+ test('shares a client across principals when they resolve identically', () => {
133
+ const getClient = Viem.createGetClient()
134
+
135
+ expect(getClient(4217, publicPrincipal)).toBe(getClient(4217, apiKeyPrincipal))
136
+ expect(getClient(4217, null)).toBe(getClient(4217, publicPrincipal))
137
+ })
138
+
139
+ test('consults the resolver on cache hits', () => {
140
+ const seen: (string | undefined)[] = []
141
+ const getClient = Viem.createGetClient({
142
+ rpc: ({ principal }) => {
143
+ seen.push(principal?.type)
144
+ return { url: 'https://internal.rpc' }
145
+ },
146
+ })
147
+
148
+ const first = getClient(4217, apiKeyPrincipal)
149
+ const second = getClient(4217, apiKeyPrincipal)
150
+ getClient(4217, publicPrincipal)
151
+
152
+ expect(seen).toMatchInlineSnapshot(`
153
+ [
154
+ "api_key",
155
+ "api_key",
156
+ "public",
157
+ ]
158
+ `)
159
+ expect(first).toBe(second)
160
+ })
161
+ })
@@ -1,3 +1,4 @@
1
+ import { ZoneRpcAuthentication } from 'ox/tempo'
1
2
  import { createClient, http, publicActions } from 'viem'
2
3
  import type { Client as core_Client, HttpTransport, PublicActions } from 'viem'
3
4
  import { tempoActions } from 'viem/tempo'
@@ -57,7 +58,7 @@ export type UrlResolver = (chainId: ChainId) => string | undefined
57
58
  * webhook poller). A missing `url` falls back to the built-in public host map
58
59
  * ({@link url}); a missing `basicAuth` means no Basic auth is sent.
59
60
  *
60
- * Memoization is keyed on the resolved output (see `App.create` and
61
+ * Memoization is keyed on the resolved output (see {@link createGetClient} and
61
62
  * {@link Tidx.createGetClient}), not the context, so a resolver may branch on
62
63
  * any principal field without unbounded client cardinality: callers that
63
64
  * resolve to the same upstream share a client. Pass the result to
@@ -103,8 +104,17 @@ export declare namespace resolve {
103
104
  }
104
105
 
105
106
  export function getClient(options: getClient.Options): getClient.ReturnType {
106
- const { chainId = defaultChainId, principal = null, rpc } = options
107
+ const { chainId = defaultChainId, principal = null, rpc, zoneToken } = options
107
108
  const { basicAuth, url: rpcUrl } = resolve(rpc, { chainId, principal })
109
+ const fetchOptions =
110
+ basicAuth || zoneToken
111
+ ? {
112
+ headers: {
113
+ ...(basicAuth ? { Authorization: `Basic ${btoa(basicAuth)}` } : {}),
114
+ ...(zoneToken ? { [ZoneRpcAuthentication.headerName]: zoneToken } : {}),
115
+ },
116
+ }
117
+ : undefined
108
118
 
109
119
  return createClient({
110
120
  // Auto-batch `eth_call`s within the same macrotask into a single
@@ -114,15 +124,45 @@ export function getClient(options: getClient.Options): getClient.ReturnType {
114
124
  // configure a deployed multicall3, can still serve the aggregated call.
115
125
  batch: { multicall: { deployless: true } },
116
126
  chain: { ...tempoMainnet, id: chainId },
117
- transport: http(
118
- rpcUrl,
119
- basicAuth ? { fetchOptions: { headers: { Authorization: `Basic ${btoa(basicAuth)}` } } } : {},
120
- ),
127
+ transport: http(rpcUrl, fetchOptions ? { fetchOptions } : {}),
121
128
  })
122
129
  .extend(publicActions)
123
130
  .extend(tempoActions())
124
131
  }
125
132
 
133
+ /** Builds a memoized Tempo client resolver. */
134
+ export function createGetClient(options: createGetClient.Options = {}): createGetClient.ReturnType {
135
+ const { defaultChainId: defaultChainId_option = defaultChainId, rpc } = options
136
+ const clients = new Map<string, getClient.ReturnType>()
137
+
138
+ return (chainId = defaultChainId_option, principal = null) => {
139
+ const { basicAuth, url } = resolve(rpc, { chainId, principal })
140
+ const cacheKey = JSON.stringify([url, basicAuth, chainId])
141
+ const existing = clients.get(cacheKey)
142
+ if (existing) return existing
143
+
144
+ const client = getClient({
145
+ chainId,
146
+ rpc: { basicAuth, url: () => url },
147
+ })
148
+ clients.set(cacheKey, client)
149
+ return client
150
+ }
151
+ }
152
+
153
+ export declare namespace createGetClient {
154
+ /** Options for building a memoized Tempo client resolver. */
155
+ type Options = {
156
+ /** Tempo chain id used when a caller omits one. */
157
+ defaultChainId?: ChainId | undefined
158
+ /** Tempo RPC options applied to every constructed client. */
159
+ rpc?: getClient.Rpc | undefined
160
+ }
161
+
162
+ /** Memoized Tempo client resolver. */
163
+ type ReturnType = (chainId?: ChainId, principal?: Auth.Principal | null) => getClient.ReturnType
164
+ }
165
+
126
166
  export declare namespace getClient {
127
167
  /** Tempo viem client with public and Tempo action helpers. */
128
168
  type ReturnType = core_Client<
@@ -142,6 +182,8 @@ export declare namespace getClient {
142
182
  principal?: Auth.Principal | null | undefined
143
183
  /** Tempo RPC options. */
144
184
  rpc?: Rpc | undefined
185
+ /** Zone authorization token forwarded to the RPC node. */
186
+ zoneToken?: string | undefined
145
187
  }
146
188
 
147
189
  /**
@@ -9,7 +9,7 @@ test('exports internal modules', () => {
9
9
  "EdgeCache",
10
10
  "Errors",
11
11
  "Fees",
12
- "FundingRoutes",
12
+ "FundingQuotes",
13
13
  "Id",
14
14
  "Log",
15
15
  "MetricSink",
@@ -4,7 +4,7 @@ export * as Cursor from './Cursor.js'
4
4
  export * as EdgeCache from './EdgeCache.js'
5
5
  export * as Errors from './Errors.js'
6
6
  export * as Fees from './Fees.js'
7
- export * as FundingRoutes from './FundingRoutes.js'
7
+ export * as FundingQuotes from './FundingQuotes.js'
8
8
  export * as Id from './Id.js'
9
9
  export * as Log from './Log.js'
10
10
  export * as MetricSink from './MetricSink.js'
@@ -1,36 +0,0 @@
1
- import * as FundingRoutes from '../../internal/FundingRoutes.js';
2
- type Fetch = typeof globalThis.fetch;
3
- /** Creates the live funding route adapters configured for this deployment. */
4
- export declare function createAdapters(options?: createAdapters.Options): {
5
- relay: FundingRoutes.ProviderAdapter;
6
- } | {
7
- relay?: never;
8
- };
9
- export declare namespace createAdapters {
10
- /** Provider adapter factory options. Omit a provider to keep its routes static. */
11
- type Options = {
12
- /** Relay quote adapter options. */
13
- relay?: relay.Options | false | undefined;
14
- };
15
- }
16
- /** Creates a Relay quote adapter backed by `/chains/liquidity` and `/quote/v2`. */
17
- export declare function relay(options: relay.Options): FundingRoutes.ProviderAdapter;
18
- export declare namespace relay {
19
- /** Relay quote adapter options. */
20
- type Options = {
21
- /** Probe wallet address required by Relay's quote API. */
22
- userAddress: string;
23
- /** Optional recipient override; defaults to `userAddress`. */
24
- recipientAddress?: string | undefined;
25
- /** Optional Relay API key for higher rate limits. */
26
- apiKey?: string | undefined;
27
- /** Override for tests or Relay testnet environments. */
28
- baseUrl?: string | undefined;
29
- /** Fetch implementation override for tests. */
30
- fetch?: Fetch | undefined;
31
- /** Clock override for deterministic tests. */
32
- now?: (() => Date) | undefined;
33
- };
34
- }
35
- export {};
36
- //# sourceMappingURL=FundingRouteProviders.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FundingRouteProviders.d.ts","sourceRoot":"","sources":["../../../src/apps/data/FundingRouteProviders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,iCAAiC,CAAA;AAEhE,KAAK,KAAK,GAAG,OAAO,UAAU,CAAC,KAAK,CAAA;AAMpC,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,GAAE,cAAc,CAAC,OAAY;;;;EAIlE;AAED,MAAM,CAAC,OAAO,WAAW,cAAc,CAAC;IACtC,mFAAmF;IACnF,KAAK,OAAO,GAAG;QACb,mCAAmC;QACnC,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,GAAG,KAAK,GAAG,SAAS,CAAA;KAC1C,CAAA;CACF;AAED,mFAAmF;AACnF,wBAAgB,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,aAAa,CAAC,eAAe,CAkD3E;AAED,MAAM,CAAC,OAAO,WAAW,KAAK,CAAC;IAC7B,mCAAmC;IACnC,KAAK,OAAO,GAAG;QACb,0DAA0D;QAC1D,WAAW,EAAE,MAAM,CAAA;QACnB,8DAA8D;QAC9D,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QACrC,qDAAqD;QACrD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QAC3B,wDAAwD;QACxD,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QAC5B,+CAA+C;QAC/C,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAA;QACzB,8CAA8C;QAC9C,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;KAC/B,CAAA;CACF"}
@@ -1,245 +0,0 @@
1
- import * as FundingRoutes from '../../internal/FundingRoutes.js';
2
- const defaultFetch = (input, init) => globalThis.fetch(input, init);
3
- const defaultRelayBaseUrl = 'https://api.relay.link';
4
- /** Creates the live funding route adapters configured for this deployment. */
5
- export function createAdapters(options = {}) {
6
- return (options.relay ? { relay: relay(options.relay) } : {});
7
- }
8
- /** Creates a Relay quote adapter backed by `/chains/liquidity` and `/quote/v2`. */
9
- export function relay(options) {
10
- const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRelayBaseUrl);
11
- const fetcher = options.fetch ?? defaultFetch;
12
- const now = options.now ?? (() => new Date());
13
- return {
14
- async probeQuote({ route }, signal) {
15
- const originChainId = providerChainId(route.sourceChain, 'relay');
16
- const destinationChainId = eip155ChainId(route.destinationChain);
17
- const headers = relayHeaders(options.apiKey);
18
- try {
19
- const quote = await requestJson(new URL(`${baseUrl}/quote/v2`), {
20
- body: {
21
- amount: route.sourceAmount.amount,
22
- destinationChainId: Number(destinationChainId),
23
- destinationCurrency: route.destinationToken.address,
24
- originChainId: Number(originChainId),
25
- originCurrency: route.sourceToken.address,
26
- recipient: options.recipientAddress ?? options.userAddress,
27
- tradeType: 'EXACT_INPUT',
28
- user: options.userAddress,
29
- },
30
- fetcher,
31
- headers,
32
- method: 'POST',
33
- signal,
34
- });
35
- const liquidityUrl = new URL(`${baseUrl}/chains/liquidity`);
36
- liquidityUrl.searchParams.set('chainId', destinationChainId);
37
- const liquidity = await requestJson(liquidityUrl, { fetcher, headers, signal }).catch(() => undefined);
38
- return formatRelayResult(route, quote, liquidity, now());
39
- }
40
- catch (cause) {
41
- const unavailable = providerUnavailable(cause, [
42
- 'NO_QUOTES',
43
- 'NO_ROUTE',
44
- 'UNSUPPORTED_ROUTE',
45
- ]);
46
- if (!unavailable)
47
- throw cause;
48
- return unavailableResult({
49
- detail: 'relay:quote-v2',
50
- message: unavailable,
51
- now: now(),
52
- source: 'providerQuote',
53
- });
54
- }
55
- },
56
- };
57
- }
58
- function formatRelayResult(route, quote, liquidity, now) {
59
- const details = record(record(quote)['details']);
60
- const currencyOut = record(details['currencyOut']);
61
- const outputAmount = decimalString(currencyOut['amount']);
62
- const liquidityRow = relayLiquidity(liquidity, route);
63
- const destinationLiquidity = decimalString(liquidityRow?.['balance']);
64
- const availableVolumeUsd = decimalString(liquidityRow?.['amountUsd']) ?? null;
65
- const liquidityStatus = relayLiquidityStatus({ destinationLiquidity, outputAmount });
66
- return {
67
- limits: null,
68
- minimumOutputAmount: decimalString(currencyOut['minimumAmount']),
69
- outputAmount,
70
- outputAmountFormatted: outputAmount
71
- ? decimalFromBaseUnits(outputAmount, route.destinationToken.decimals)
72
- : null,
73
- quality: {
74
- availableVolumeUsd: availableVolumeUsd,
75
- estimatedSeconds: nonnegativeInteger(details['timeEstimate']),
76
- feeBps: null,
77
- historicalVolumeUsd24h: null,
78
- instantVolumeUsd: availableVolumeUsd,
79
- liquiditySource: 'providerQuote',
80
- score: scoreFor(liquidityStatus.tier),
81
- sourceDetail: 'relay:quote-v2',
82
- tier: liquidityStatus.tier,
83
- },
84
- sampledAt: now.toISOString(),
85
- status: liquidityStatus.status,
86
- };
87
- }
88
- function relayLiquidityStatus(options) {
89
- if (!options.outputAmount)
90
- return { status: 'unknown', tier: 'unknown' };
91
- if (!options.destinationLiquidity)
92
- return { status: 'unknown', tier: 'unknown' };
93
- if (compareIntegerStrings(options.destinationLiquidity, options.outputAmount) < 0)
94
- return { status: 'thin', tier: 'thin' };
95
- return { status: 'available', tier: 'liquid' };
96
- }
97
- function unavailableResult(options) {
98
- return {
99
- quality: {
100
- availableVolumeUsd: null,
101
- estimatedSeconds: null,
102
- feeBps: null,
103
- historicalVolumeUsd24h: null,
104
- instantVolumeUsd: null,
105
- liquiditySource: options.source,
106
- score: 0,
107
- sourceDetail: `${options.detail}:${options.message}`,
108
- tier: 'unavailable',
109
- },
110
- sampledAt: options.now.toISOString(),
111
- status: 'unavailable',
112
- };
113
- }
114
- function relayLiquidity(input, route) {
115
- const response = record(input);
116
- const rows = Array.isArray(response['liquidity']) ? response['liquidity'] : [];
117
- const destinationChainId = eip155ChainId(route.destinationChain);
118
- return rows.map(record).find((row) => {
119
- const rowChainId = integerString(row['chainId']);
120
- const rowAddress = typeof row['address'] === 'string' ? row['address'].toLowerCase() : undefined;
121
- return (rowChainId === destinationChainId &&
122
- rowAddress === route.destinationToken.address.toLowerCase());
123
- });
124
- }
125
- async function requestJson(url, options) {
126
- const headers = { accept: 'application/json', ...options.headers };
127
- const init = {
128
- headers: options.body === undefined ? headers : { ...headers, 'content-type': 'application/json' },
129
- method: options.method ?? 'GET',
130
- signal: options.signal,
131
- };
132
- if (options.body !== undefined)
133
- init.body = JSON.stringify(options.body);
134
- const response = await options.fetcher(url, init);
135
- const text = await response.text();
136
- const body = parseJson(text);
137
- if (!response.ok)
138
- throw new ProviderResponseError(response.status, body);
139
- return body;
140
- }
141
- function relayHeaders(apiKey) {
142
- return apiKey ? { 'x-api-key': apiKey } : undefined;
143
- }
144
- function providerUnavailable(cause, codes) {
145
- if (!(cause instanceof ProviderResponseError) || cause.status < 400 || cause.status >= 500)
146
- return undefined;
147
- const code = providerErrorCode(cause.body);
148
- if (code && codes.includes(code))
149
- return code;
150
- return undefined;
151
- }
152
- function providerErrorCode(input) {
153
- const body = record(input);
154
- const code = stringValue(body['code']) ?? stringValue(body['errorCode']);
155
- return code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined;
156
- }
157
- class ProviderResponseError extends Error {
158
- status;
159
- body;
160
- name = 'FundingRouteProviders.ProviderResponseError';
161
- constructor(status, body) {
162
- super(`Provider request failed with status ${status}`);
163
- this.status = status;
164
- this.body = body;
165
- }
166
- }
167
- function providerChainId(routeChain, provider) {
168
- const providerIds = routeChain.providerIds;
169
- const id = providerIds?.[provider];
170
- if (id && /^\d+$/.test(id))
171
- return id;
172
- return eip155ChainId(routeChain);
173
- }
174
- function eip155ChainId(chain) {
175
- if (chain.kind !== 'evm')
176
- throw new UnsupportedProviderRouteError();
177
- const match = /^eip155:(\d+)$/.exec(chain.id);
178
- if (!match?.[1])
179
- throw new UnsupportedProviderRouteError();
180
- return match[1];
181
- }
182
- class UnsupportedProviderRouteError extends Error {
183
- name = 'FundingRouteProviders.UnsupportedProviderRouteError';
184
- }
185
- function normalizeBaseUrl(input) {
186
- return input.replace(/\/+$/, '');
187
- }
188
- function parseJson(input) {
189
- if (!input)
190
- return null;
191
- try {
192
- return JSON.parse(input);
193
- }
194
- catch {
195
- return input;
196
- }
197
- }
198
- function record(input) {
199
- return input && typeof input === 'object' && !Array.isArray(input)
200
- ? input
201
- : {};
202
- }
203
- function stringValue(input) {
204
- return typeof input === 'string' && input.length > 0 ? input : undefined;
205
- }
206
- function decimalString(input) {
207
- if (typeof input === 'string' && /^\d+(\.\d+)?$/.test(input))
208
- return input;
209
- if (typeof input === 'number' && Number.isFinite(input) && input >= 0)
210
- return String(input);
211
- return undefined;
212
- }
213
- function integerString(input) {
214
- if (typeof input === 'string' && /^\d+$/.test(input))
215
- return input;
216
- if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0)
217
- return String(input);
218
- return undefined;
219
- }
220
- function nonnegativeInteger(input) {
221
- if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0)
222
- return input;
223
- if (typeof input === 'string' && /^\d+$/.test(input))
224
- return Number(input);
225
- return null;
226
- }
227
- function decimalFromBaseUnits(baseUnits, decimals) {
228
- const padded = baseUnits.padStart(decimals + 1, '0');
229
- const whole = decimals === 0 ? padded : padded.slice(0, -decimals);
230
- const fraction = decimals === 0 ? '' : padded.slice(-decimals).replace(/0+$/, '');
231
- return fraction ? `${whole}.${fraction}` : whole;
232
- }
233
- function compareIntegerStrings(left, right) {
234
- return BigInt(left) === BigInt(right) ? 0 : BigInt(left) > BigInt(right) ? 1 : -1;
235
- }
236
- function scoreFor(tier) {
237
- if (tier === 'liquid')
238
- return 90;
239
- if (tier === 'thin')
240
- return 65;
241
- if (tier === 'unavailable')
242
- return 0;
243
- return null;
244
- }
245
- //# sourceMappingURL=FundingRouteProviders.js.map