tapimo 0.5.0 → 0.5.2

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 (197) hide show
  1. package/dist/App.d.ts +4854 -4829
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +20 -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.map +1 -1
  8. package/dist/Scope.js +1 -2
  9. package/dist/Scope.js.map +1 -1
  10. package/dist/Webhooks.d.ts +1 -1
  11. package/dist/Webhooks.d.ts.map +1 -1
  12. package/dist/Webhooks.js +5 -10
  13. package/dist/Webhooks.js.map +1 -1
  14. package/dist/admin/App.d.ts +6 -34
  15. package/dist/admin/App.d.ts.map +1 -1
  16. package/dist/admin/apps/api-keys.d.ts +3 -3
  17. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  18. package/dist/admin/apps/api-keys.js +4 -1
  19. package/dist/admin/apps/api-keys.js.map +1 -1
  20. package/dist/admin/apps/scopes.d.ts +6 -34
  21. package/dist/admin/apps/scopes.d.ts.map +1 -1
  22. package/dist/admin/apps/scopes.js +2 -2
  23. package/dist/admin/apps/scopes.js.map +1 -1
  24. package/dist/admin/apps/verified-tokens.d.ts +2 -2
  25. package/dist/admin/ui.generated.d.ts +1 -1
  26. package/dist/admin/ui.generated.js +33 -33
  27. package/dist/admin/ui.generated.js.map +1 -1
  28. package/dist/apps/data/App.d.ts +1398 -1405
  29. package/dist/apps/data/App.d.ts.map +1 -1
  30. package/dist/apps/data/App.js +25 -38
  31. package/dist/apps/data/App.js.map +1 -1
  32. package/dist/apps/data/FundingQuoteProviders.d.ts +108 -3
  33. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  34. package/dist/apps/data/FundingQuoteProviders.js +409 -2
  35. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  36. package/dist/apps/data/routes/activities.d.ts +245 -245
  37. package/dist/apps/data/routes/blocks.d.ts +123 -123
  38. package/dist/apps/data/routes/funding.d.ts +236 -146
  39. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  40. package/dist/apps/data/routes/funding.js +23 -12
  41. package/dist/apps/data/routes/funding.js.map +1 -1
  42. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  43. package/dist/apps/data/routes/indexer.js +9 -2
  44. package/dist/apps/data/routes/indexer.js.map +1 -1
  45. package/dist/apps/data/routes/rpc.d.ts +31 -31
  46. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  47. package/dist/apps/data/routes/transactions.d.ts +281 -281
  48. package/dist/apps/data/routes/transfers.d.ts +66 -73
  49. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  50. package/dist/apps/data/routes/transfers.js +21 -27
  51. package/dist/apps/data/routes/transfers.js.map +1 -1
  52. package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
  53. package/dist/apps/data/routes/webhooks.d.ts +35 -35
  54. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  55. package/dist/apps/data/routes/webhooks.js +176 -42
  56. package/dist/apps/data/routes/webhooks.js.map +1 -1
  57. package/dist/apps/management/App.d.ts +520 -504
  58. package/dist/apps/management/App.d.ts.map +1 -1
  59. package/dist/apps/management/Billing.d.ts.map +1 -1
  60. package/dist/apps/management/Billing.js +5 -4
  61. package/dist/apps/management/Billing.js.map +1 -1
  62. package/dist/apps/management/routes/api-keys.d.ts +120 -120
  63. package/dist/apps/management/routes/billing.d.ts +211 -211
  64. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  65. package/dist/apps/management/routes/billing.js +1 -1
  66. package/dist/apps/management/routes/billing.js.map +1 -1
  67. package/dist/apps/management/routes/invitations.d.ts +72 -72
  68. package/dist/apps/management/routes/invite-links.d.ts +159 -159
  69. package/dist/apps/management/routes/me.d.ts +17 -17
  70. package/dist/apps/management/routes/members.d.ts +80 -80
  71. package/dist/apps/management/routes/orgs.d.ts +59 -43
  72. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  73. package/dist/apps/management/routes/orgs.js +18 -1
  74. package/dist/apps/management/routes/orgs.js.map +1 -1
  75. package/dist/apps/management/routes/projects.d.ts +109 -109
  76. package/dist/apps/management/routes/scopes.d.ts +11 -11
  77. package/dist/apps/management/routes/usage.d.ts +79 -79
  78. package/dist/apps/mcp.js +2 -2
  79. package/dist/apps/mcp.js.map +1 -1
  80. package/dist/apps/relay/App.d.ts +2 -2
  81. package/dist/apps/relay/App.js +2 -2
  82. package/dist/apps/relay/Sponsorships.d.ts +3 -0
  83. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  84. package/dist/apps/relay/Sponsorships.js +15 -1
  85. package/dist/apps/relay/Sponsorships.js.map +1 -1
  86. package/dist/db/Db.d.ts.map +1 -1
  87. package/dist/db/Db.js +4 -0
  88. package/dist/db/Db.js.map +1 -1
  89. package/dist/db/Schema.js +1 -1
  90. package/dist/db/Schema.js.map +1 -1
  91. package/dist/db/tables/organizations.d.ts +6 -0
  92. package/dist/db/tables/organizations.d.ts.map +1 -1
  93. package/dist/db/tables/organizations.js +26 -0
  94. package/dist/db/tables/organizations.js.map +1 -1
  95. package/dist/db/tables/requestUsage.d.ts +2 -11
  96. package/dist/db/tables/requestUsage.d.ts.map +1 -1
  97. package/dist/db/tables/requestUsage.js +1 -20
  98. package/dist/db/tables/requestUsage.js.map +1 -1
  99. package/dist/db/tables/sponsoredTransactions.d.ts +2 -1
  100. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  101. package/dist/db/tables/sponsoredTransactions.js +52 -10
  102. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  103. package/dist/internal/Auth.d.ts +4 -0
  104. package/dist/internal/Auth.d.ts.map +1 -1
  105. package/dist/internal/Auth.js +15 -8
  106. package/dist/internal/Auth.js.map +1 -1
  107. package/dist/internal/Cache.d.ts.map +1 -1
  108. package/dist/internal/Cache.js +9 -1
  109. package/dist/internal/Cache.js.map +1 -1
  110. package/dist/internal/EdgeCache.d.ts.map +1 -1
  111. package/dist/internal/EdgeCache.js +12 -3
  112. package/dist/internal/EdgeCache.js.map +1 -1
  113. package/dist/internal/FundingQuotes.d.ts +176 -56
  114. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  115. package/dist/internal/FundingQuotes.js +100 -5
  116. package/dist/internal/FundingQuotes.js.map +1 -1
  117. package/dist/internal/Log.d.ts +18 -12
  118. package/dist/internal/Log.d.ts.map +1 -1
  119. package/dist/internal/Log.js +2 -0
  120. package/dist/internal/Log.js.map +1 -1
  121. package/dist/internal/RateLimit.d.ts +2 -0
  122. package/dist/internal/RateLimit.d.ts.map +1 -1
  123. package/dist/internal/RateLimit.js +4 -4
  124. package/dist/internal/RateLimit.js.map +1 -1
  125. package/dist/internal/Tidx.d.ts +9 -3
  126. package/dist/internal/Tidx.d.ts.map +1 -1
  127. package/dist/internal/Tidx.js +13 -7
  128. package/dist/internal/Tidx.js.map +1 -1
  129. package/dist/internal/Viem.d.ts +14 -1
  130. package/dist/internal/Viem.d.ts.map +1 -1
  131. package/dist/internal/Viem.js +19 -1
  132. package/dist/internal/Viem.js.map +1 -1
  133. package/package.json +7 -2
  134. package/src/ApiKeys.test.ts +2 -0
  135. package/src/App.test.ts +15 -4
  136. package/src/App.ts +31 -5
  137. package/src/Assets.ts +3 -3
  138. package/src/Client.test-d.ts +30 -3
  139. package/src/Scope.ts +4 -2
  140. package/src/Webhooks.ts +5 -10
  141. package/src/admin/apps/api-keys.test.ts +27 -0
  142. package/src/admin/apps/api-keys.ts +8 -1
  143. package/src/admin/apps/scopes.test.ts +7 -2
  144. package/src/admin/apps/scopes.ts +2 -2
  145. package/src/admin/ui/src/components/Dialog.tsx +8 -2
  146. package/src/admin/ui/src/lib/scopes.test.ts +19 -0
  147. package/src/admin/ui/src/lib/scopes.ts +12 -0
  148. package/src/admin/ui/src/routes/api-keys.tsx +128 -27
  149. package/src/admin/ui.generated.ts +33 -33
  150. package/src/apps/composition.test.ts +77 -0
  151. package/src/apps/data/App.ts +25 -45
  152. package/src/apps/data/FundingQuoteProviders.test.ts +615 -5
  153. package/src/apps/data/FundingQuoteProviders.ts +570 -4
  154. package/src/apps/data/routes/funding.test.ts +181 -7
  155. package/src/apps/data/routes/funding.ts +27 -12
  156. package/src/apps/data/routes/indexer.test.ts +82 -20
  157. package/src/apps/data/routes/indexer.ts +8 -2
  158. package/src/apps/data/routes/tokens.test.ts +54 -0
  159. package/src/apps/data/routes/transfers.test.ts +73 -1
  160. package/src/apps/data/routes/transfers.ts +24 -30
  161. package/src/apps/data/routes/webhooks.test.ts +203 -0
  162. package/src/apps/data/routes/webhooks.ts +191 -54
  163. package/src/apps/management/Billing.test.ts +52 -3
  164. package/src/apps/management/Billing.ts +5 -4
  165. package/src/apps/management/routes/billing.ts +6 -1
  166. package/src/apps/management/routes/orgs.ts +19 -1
  167. package/src/apps/mcp.test.ts +18 -0
  168. package/src/apps/mcp.ts +2 -2
  169. package/src/apps/relay/App.test.ts +6 -5
  170. package/src/apps/relay/App.ts +2 -2
  171. package/src/apps/relay/Sponsorships.test.ts +2 -2
  172. package/src/apps/relay/Sponsorships.ts +24 -1
  173. package/src/db/Db.test.ts +5 -5
  174. package/src/db/Db.ts +4 -0
  175. package/src/db/Schema.ts +1 -1
  176. package/src/db/tables/billingSettings.test.ts +2 -2
  177. package/src/db/tables/organizations.test.ts +32 -2
  178. package/src/db/tables/organizations.ts +28 -0
  179. package/src/db/tables/requestUsage.ts +3 -23
  180. package/src/db/tables/sponsoredTransactions.test.ts +60 -2
  181. package/src/db/tables/sponsoredTransactions.ts +67 -21
  182. package/src/db/tables/stripeCustomers.test.ts +2 -2
  183. package/src/internal/Auth.test.ts +63 -10
  184. package/src/internal/Auth.ts +21 -9
  185. package/src/internal/Cache.test.ts +19 -0
  186. package/src/internal/Cache.ts +13 -1
  187. package/src/internal/EdgeCache.test.ts +40 -0
  188. package/src/internal/EdgeCache.ts +11 -3
  189. package/src/internal/FundingQuotes.test.ts +130 -3
  190. package/src/internal/FundingQuotes.ts +113 -6
  191. package/src/internal/Log.ts +21 -14
  192. package/src/internal/RateLimit.test.ts +12 -0
  193. package/src/internal/RateLimit.ts +4 -4
  194. package/src/internal/Tidx.test.ts +48 -0
  195. package/src/internal/Tidx.ts +21 -7
  196. package/src/internal/Viem.test.ts +46 -0
  197. package/src/internal/Viem.ts +34 -1
@@ -31,7 +31,7 @@ export const url = {
31
31
  * webhook poller). A missing `url` falls back to the built-in public host map
32
32
  * ({@link url}); a missing `basicAuth` means no Basic auth is sent.
33
33
  *
34
- * Memoization is keyed on the resolved output (see `App.create` and
34
+ * Memoization is keyed on the resolved output (see {@link createGetClient} and
35
35
  * {@link Tidx.createGetClient}), not the context, so a resolver may branch on
36
36
  * any principal field without unbounded client cardinality: callers that
37
37
  * resolve to the same upstream share a client. Pass the result to
@@ -69,4 +69,22 @@ export function getClient(options) {
69
69
  .extend(publicActions)
70
70
  .extend(tempoActions());
71
71
  }
72
+ /** Builds a memoized Tempo client resolver. */
73
+ export function createGetClient(options = {}) {
74
+ const { defaultChainId: defaultChainId_option = defaultChainId, rpc } = options;
75
+ const clients = new Map();
76
+ return (chainId = defaultChainId_option, principal = null) => {
77
+ const { basicAuth, url } = resolve(rpc, { chainId, principal });
78
+ const cacheKey = JSON.stringify([url, basicAuth, chainId]);
79
+ const existing = clients.get(cacheKey);
80
+ if (existing)
81
+ return existing;
82
+ const client = getClient({
83
+ chainId,
84
+ rpc: { basicAuth, url: () => url },
85
+ });
86
+ clients.set(cacheKey, client);
87
+ return client;
88
+ };
89
+ }
72
90
  //# sourceMappingURL=Viem.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Viem.js","sourceRoot":"","sources":["../../src/internal/Viem.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Viem.js","sourceRoot":"","sources":["../../src/internal/Viem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAM9D,4DAA4D;AAC5D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC,CAAA;AAElD,wCAAwC;AACxC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,OAAO,EAAE,YAAY,CAAC,EAAE;IACxB,OAAO,EAAE,YAAY,CAAC,EAAE;CAChB,CAAA;AAEV,8BAA8B;AAC9B,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAA;AAE7C,2CAA2C;AAC3C,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAA;AAC/B,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,MAAM,GAAG,GAA2B;IACzC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,uBAAuB;IAC1C,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,+BAA+B;CACnD,CAAA;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,OAAO,CACrB,GAA8B,EAC9B,OAAwB;IAExB,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC7D,OAAO;QACL,SAAS,EAAE,MAAM,EAAE,SAAS;QAC5B,GAAG,EACD,CAAC,OAAO,MAAM,EAAE,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC;YAC/E,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;KACvB,CAAA;AACH,CAAC;AA4BD,MAAM,UAAU,SAAS,CAAC,OAA0B;IAClD,MAAM,EAAE,OAAO,GAAG,cAAc,EAAE,SAAS,GAAG,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAC9E,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IACvE,MAAM,YAAY,GAChB,SAAS,IAAI,SAAS;QACpB,CAAC,CAAC;YACE,OAAO,EAAE;gBACP,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,qBAAqB,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE;SACF;QACH,CAAC,CAAC,SAAS,CAAA;IAEf,OAAO,YAAY,CAAC;QAClB,iEAAiE;QACjE,uEAAuE;QACvE,oEAAoE;QACpE,sEAAsE;QACtE,wEAAwE;QACxE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE;QAC1C,KAAK,EAAE,EAAE,GAAG,YAAY,EAAE,EAAE,EAAE,OAAO,EAAE;QACvC,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9D,CAAC;SACC,MAAM,CAAC,aAAa,CAAC;SACrB,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;AAC3B,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,eAAe,CAAC,OAAO,GAA4B,EAAE;IACnE,MAAM,EAAE,cAAc,EAAE,qBAAqB,GAAG,cAAc,EAAE,GAAG,EAAE,GAAG,OAAO,CAAA;IAC/E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgC,CAAA;IAEvD,OAAO,CAAC,OAAO,GAAG,qBAAqB,EAAE,SAAS,GAAG,IAAI,EAAE,EAAE;QAC3D,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;QAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACtC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAE7B,MAAM,MAAM,GAAG,SAAS,CAAC;YACvB,OAAO;YACP,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE;SACnC,CAAC,CAAA;QACF,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC7B,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tapimo",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.5.2",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -26,7 +26,7 @@
26
26
  "ox": "0.14.30",
27
27
  "pg": "^8.22.0",
28
28
  "stripe": "^19.1.0",
29
- "tidx.ts": "^0.1.1",
29
+ "tidx.ts": "^0.1.2",
30
30
  "viem": "2.54.6",
31
31
  "vocs": "^2.3.3",
32
32
  "zod": "^4.4.3"
@@ -50,6 +50,11 @@
50
50
  "types": "./dist/admin/index.d.ts",
51
51
  "default": "./dist/admin/index.js"
52
52
  },
53
+ "./client": {
54
+ "src": "./src/Client.ts",
55
+ "types": "./dist/Client.d.ts",
56
+ "default": "./dist/Client.js"
57
+ },
53
58
  "./cloudflare": {
54
59
  "src": "./src/cloudflare.ts",
55
60
  "types": "./dist/cloudflare.d.ts",
@@ -114,6 +114,8 @@ describe('mint', () => {
114
114
  'zone:abc:read',
115
115
  'zone:1.5:read',
116
116
  'zone:-1:read',
117
+ 'zone:0:read',
118
+ 'zone:000421700001:read',
117
119
  'zone:1:write',
118
120
  ])
119
121
  await expect(
package/src/App.test.ts CHANGED
@@ -74,6 +74,18 @@ describe('create', () => {
74
74
  `)
75
75
  })
76
76
 
77
+ test('ignores client-supplied request ids', async () => {
78
+ const entries: Log.Entry[] = []
79
+ const app = TestApp.create({ logger: (entry) => void entries.push(entry) })
80
+ const response = await app.request('/health', {
81
+ headers: { 'tempo-request-id': 'client-controlled' },
82
+ })
83
+ const requestId = response.headers.get('tempo-request-id')
84
+
85
+ expect(requestId === 'client-controlled').toMatchInlineSnapshot(`false`)
86
+ expect(entries[0]?.requestId === requestId).toMatchInlineSnapshot(`true`)
87
+ })
88
+
77
89
  test('documents supported API key transports', async () => {
78
90
  const app = TestApp.create()
79
91
  const spec = await (await app.request('/openapi.json')).json()
@@ -222,7 +234,7 @@ describe('create', () => {
222
234
  const response = await app.request(`/v1/tokens/${TestApp.token}`, {
223
235
  headers: {
224
236
  authorization: 'Payment malformed',
225
- 'x-forwarded-for': '203.0.113.10',
237
+ 'cf-connecting-ip': '203.0.113.10',
226
238
  },
227
239
  })
228
240
 
@@ -248,7 +260,7 @@ describe('create', () => {
248
260
  const response = await app.request(`/v1/tokens/${TestApp.token}`, {
249
261
  headers: {
250
262
  authorization: 'Payment malformed',
251
- 'x-forwarded-for': '203.0.113.10',
263
+ 'cf-connecting-ip': '203.0.113.10',
252
264
  },
253
265
  })
254
266
 
@@ -473,7 +485,7 @@ describe('create', () => {
473
485
  // key matches, so a `:chainId//icons` regression would drop every logo.
474
486
  const db = TestApp.database()
475
487
  await TestApp.verifiedSeed(db, 42431)
476
- const app = App.create({ db }).route(
488
+ const app = App.create({ db, defaultChainId: 42431 }).route(
477
489
  '/',
478
490
  App.data({
479
491
  assets: {
@@ -485,7 +497,6 @@ describe('create', () => {
485
497
  }
486
498
  : undefined,
487
499
  },
488
- defaultChainId: 42431,
489
500
  }),
490
501
  )
491
502
  const response = await app.request('/v1/tokens?verified=true')
package/src/App.ts CHANGED
@@ -149,6 +149,13 @@ export function create<const path extends string = '/'>(options: create.Options<
149
149
  // `options.db`, API-key records in `options.kv`.
150
150
  const store = options.cache?.store ?? Store.memory()
151
151
  const scopeCatalog = Scope.extend(options.scopes)
152
+ const defaultChainId = options.defaultChainId ?? Viem.defaultChainId
153
+ const getClient = Viem.createGetClient({ defaultChainId, rpc: options.rpc })
154
+ const supportedChainIds = new Set<number>([
155
+ ...Object.keys(Viem.url).map(Number),
156
+ defaultChainId,
157
+ ...(options.supportedChainIds ?? []),
158
+ ])
152
159
 
153
160
  // Build the app in two phases: register middleware imperatively, then chain
154
161
  // route mounts so the merged Hono schema flows through the return type. The
@@ -165,7 +172,11 @@ export function create<const path extends string = '/'>(options: create.Options<
165
172
  '*',
166
173
  hono_compress(typeof options.compress === 'object' ? options.compress : undefined),
167
174
  )
168
- base.use('*', requestId({ headerName: 'tempo-request-id' }))
175
+ base.use('*', requestId())
176
+ base.use('*', async (c, next) => {
177
+ await next()
178
+ c.header('tempo-request-id', c.get('requestId'))
179
+ })
169
180
  // One canonical structured log line per request, plus request metrics.
170
181
  // Registered after `requestId` (the entry embeds it) and before the timing and
171
182
  // edge-cache layers so the entry covers edge-cache hits and includes the
@@ -233,11 +244,15 @@ export function create<const path extends string = '/'>(options: create.Options<
233
244
  base.use('*', async (c, next) => {
234
245
  c.set('analytics', analytics)
235
246
  c.set('basePath', basePath)
247
+ c.set('chainId', defaultChainId)
236
248
  c.set('db', db.source)
237
249
  c.set('dbCached', db.cached ?? db.source)
250
+ c.set('getClient', (chainId) => getClient(chainId, Auth.getPrincipal(c)))
238
251
  c.set('kv', options.kv)
252
+ c.set('rpc', options.rpc)
239
253
  c.set('scopeCatalog', scopeCatalog)
240
254
  c.set('store', store)
255
+ c.set('supportedChainIds', supportedChainIds)
241
256
  await next()
242
257
  })
243
258
 
@@ -271,7 +286,10 @@ export function create<const path extends string = '/'>(options: create.Options<
271
286
  const route = app.route.bind(app)
272
287
  ;(app as unknown as { route: typeof route }).route = ((path, sub) => {
273
288
  const entry = Metadata.read(sub)
274
- if (entry) metadata.push(entry)
289
+ if (entry) {
290
+ metadata.push(entry)
291
+ for (const chainId of entry.supportedChainIds ?? []) supportedChainIds.add(chainId)
292
+ }
275
293
  return route(path, sub)
276
294
  }) as typeof route
277
295
 
@@ -442,9 +460,9 @@ export function listener(app: { fetch: NodeFetch }): Listener {
442
460
 
443
461
  export declare namespace create {
444
462
  /**
445
- * Options for creating the Tempo API Hono app the shared core:
446
- * db/cache/kv/auth capabilities and cross-cutting middleware. Chain-data
447
- * configuration belongs to {@link data}; email to {@link management}.
463
+ * Options for creating the Tempo API Hono app: shared chain, RPC, database,
464
+ * cache, authentication, and cross-cutting configuration. Email belongs to
465
+ * {@link management}.
448
466
  */
449
467
  type Options<path extends string = '/'> = {
450
468
  /** Analytics store for usage reads (e.g. `Analytics.clickhouse(...)`), or a per-request factory. Accepts `{ source }` to mirror `db`. Omit to leave usage analytics unconfigured — usage routes answer 501. Ingest happens outside this app (queue consumer via `Analytics.handleQueue`). */
@@ -459,6 +477,8 @@ export declare namespace create {
459
477
  cors?: boolean | Cors | undefined
460
478
  /** Authoritative Postgres store backing stateful features (webhooks, verified tokens). Pass a long-lived `Db.postgres(...)` singleton on Node, or a factory (`() => Db.postgres(...)`) on Workers so each request builds a fresh Hyperdrive-pooled connection. Pass `{ source, cached }` to route staleness-tolerant reads (verified tokens) through a second, query-caching Hyperdrive config; `source` stays uncached for read-after-write correctness. Apply migrations out of band via `tapimo admin migrate`. */
461
479
  db: Db.Source | { cached?: Db.Source | undefined; source: Db.Source }
480
+ /** Default Tempo chain id for requests that omit `chainId`. */
481
+ defaultChainId?: Viem.ChainId | undefined
462
482
  /** KV state store backing API-key records (e.g. `Store.cloudflareKv(...)`). Omit to leave API-key auth closed — every token resolves to `null`. */
463
483
  kv?: Kv | undefined
464
484
  /** Request logging. Disabled by default. Pass `true` to emit one canonical structured log entry per request through the level-matched console method (indexed by Workers Logs); a level (e.g. `'warn'`) to emit only that level and above (dropping successful-request info lines); or an emit function for a custom sink. */
@@ -467,10 +487,14 @@ export declare namespace create {
467
487
  metrics?: Metrics.Metrics | undefined
468
488
  /** Base path to mount API handlers under. Provided as a literal via the `path` generic so the resulting Hono schema keeps typed route paths for `testClient`. */
469
489
  path?: path | undefined
490
+ /** Tempo RPC options, including per-request routing by request principal. */
491
+ rpc?: Viem.getClient.Rpc | undefined
470
492
  /** OpenAPI `servers` for the generated document. Defaults to a single relative server (the host serving the doc), so the reference targets the deployment it is served from. Set the canonical public URL(s) here, e.g. `[{ url: 'https://api.tempo.xyz' }]`. */
471
493
  servers?: readonly Server[] | undefined
472
494
  /** Host-defined API-key scopes appended to the built-in {@link Scope.catalog}. */
473
495
  scopes?: readonly Scope.Entry[] | undefined
496
+ /** Extra chain ids served beyond the built-in chains and `defaultChainId`. */
497
+ supportedChainIds?: readonly number[] | undefined
474
498
  }
475
499
 
476
500
  /** KV state-store configuration. */
@@ -532,6 +556,8 @@ export declare namespace create {
532
556
  docs?: Docs | undefined
533
557
  /** Security scheme definitions this group's routes reference (e.g. the session cookie). */
534
558
  securitySchemes?: Record<string, unknown> | undefined
559
+ /** Chain ids served by this route group, merged before auth handles the first request. */
560
+ supportedChainIds?: readonly number[] | undefined
535
561
  /** Root-level OpenAPI tag definitions for this group's operations. */
536
562
  tags?: readonly Tag[] | undefined
537
563
  /** Builds the OAS 3.1 `webhooks` block documenting outbound deliveries, emitted only when the group's webhook routes are mounted. Called during spec generation only, keeping schema conversion off isolate startup. */
package/src/Assets.ts CHANGED
@@ -26,9 +26,9 @@ export type Assets = {
26
26
  export function cloudflareR2(bucket: cloudflareR2.Bucket): Assets {
27
27
  return {
28
28
  async get(key) {
29
- // Exact keys sort first, so expected misses avoid `NoSuchKey` spans.
30
- const { objects } = await bucket.list({ limit: 1, prefix: key })
31
- if (objects[0]?.key !== key) return undefined
29
+ // Legacy source-extension aliases may sort before the extensionless key.
30
+ const { objects } = await bucket.list({ limit: 1_000, prefix: key })
31
+ if (!objects.some((object) => object.key === key)) return undefined
32
32
  const object = await bucket.get(key)
33
33
  if (!object) return undefined
34
34
  return {
@@ -364,7 +364,7 @@ describe('narrows responses by status', () => {
364
364
  param: { orgId: 'org_test' },
365
365
  })
366
366
 
367
- expectTypeOf(res.status).toEqualTypeOf<200 | 404 | 502 | PublicAccessStatus>()
367
+ expectTypeOf(res.status).toEqualTypeOf<200 | 404 | 409 | 502 | PublicAccessStatus>()
368
368
 
369
369
  switch (res.status) {
370
370
  case 200: {
@@ -379,6 +379,12 @@ describe('narrows responses by status', () => {
379
379
  void json
380
380
  return
381
381
  }
382
+ case 409: {
383
+ const json = await res.json()
384
+ expectTypeOf(json.error.code).toEqualTypeOf<'sponsorships_unreported'>()
385
+ void json
386
+ return
387
+ }
382
388
  }
383
389
  })
384
390
 
@@ -1193,7 +1199,7 @@ describe('narrows responses by status', () => {
1193
1199
  const client = Client.create()
1194
1200
  const res = await client.v1.funding.quotes.$get({
1195
1201
  query: {
1196
- provider: 'relay',
1202
+ provider: 'across',
1197
1203
  sourceAmount: '1000000',
1198
1204
  sourceChain: 'base',
1199
1205
  sourceToken: 'USDC',
@@ -1208,7 +1214,10 @@ describe('narrows responses by status', () => {
1208
1214
  expectTypeOf(json.data[0]!.sourceAmount).toEqualTypeOf<
1209
1215
  z.output<typeof Funding.schema.quoteFunding.Response>['data'][number]['sourceAmount']
1210
1216
  >()
1211
- expectTypeOf(json.data[0]!.provider.id).toEqualTypeOf<'relay'>()
1217
+ expectTypeOf(json.data[0]!.provider.id).toEqualTypeOf<
1218
+ 'across' | 'relay' | 'rhino' | 'squid'
1219
+ >()
1220
+ expectTypeOf(json.data[0]!.quote.expiresAt).toEqualTypeOf<string | undefined>()
1212
1221
  expectTypeOf(json.data[0]!.quote.outputAmount).toEqualTypeOf<string>()
1213
1222
  void json
1214
1223
  return
@@ -1952,6 +1961,11 @@ describe('narrows responses by status', () => {
1952
1961
 
1953
1962
  if (res.status === 200) {
1954
1963
  const json = await res.json()
1964
+ expectTypeOf(json.destination.type).toEqualTypeOf<'betterstack' | 'slack' | 'url'>()
1965
+ if (json.destination.type === 'betterstack')
1966
+ expectTypeOf(json.destination.token).toEqualTypeOf<string>()
1967
+ if (json.destination.type === 'slack')
1968
+ expectTypeOf(json.destination.url).toEqualTypeOf<string>()
1955
1969
  expectTypeOf(json.secret).toEqualTypeOf<
1956
1970
  z.output<typeof Webhooks.schema.createWebhook.Response>['secret']
1957
1971
  >()
@@ -1967,6 +1981,11 @@ describe('narrows responses by status', () => {
1967
1981
 
1968
1982
  if (res.status === 200) {
1969
1983
  const json = await res.json()
1984
+ const destination = json.data[0]!.destination
1985
+ if (destination.type === 'betterstack')
1986
+ expectTypeOf(destination.token).toEqualTypeOf<'[redacted]'>()
1987
+ if (destination.type === 'slack')
1988
+ expectTypeOf(destination.url).toEqualTypeOf<'https://hooks.slack.com/…'>()
1970
1989
  expectTypeOf(json.data[0]!.id).toEqualTypeOf<
1971
1990
  z.output<typeof Webhooks.schema.listWebhooks.Response>['data'][number]['id']
1972
1991
  >()
@@ -1988,6 +2007,10 @@ describe('narrows responses by status', () => {
1988
2007
 
1989
2008
  if (res.status === 200) {
1990
2009
  const json = await res.json()
2010
+ if (json.destination.type === 'betterstack')
2011
+ expectTypeOf(json.destination.token).toEqualTypeOf<'[redacted]'>()
2012
+ if (json.destination.type === 'slack')
2013
+ expectTypeOf(json.destination.url).toEqualTypeOf<'https://hooks.slack.com/…'>()
1991
2014
  expectTypeOf(json.id).toEqualTypeOf<
1992
2015
  z.output<typeof Webhooks.schema.getWebhook.Response>['id']
1993
2016
  >()
@@ -2063,6 +2086,10 @@ describe('narrows responses by status', () => {
2063
2086
 
2064
2087
  if (res.status === 200) {
2065
2088
  const json = await res.json()
2089
+ if (json.destination.type === 'betterstack')
2090
+ expectTypeOf(json.destination.token).toEqualTypeOf<'[redacted]'>()
2091
+ if (json.destination.type === 'slack')
2092
+ expectTypeOf(json.destination.url).toEqualTypeOf<'https://hooks.slack.com/…'>()
2066
2093
  expectTypeOf(json.status).toEqualTypeOf<
2067
2094
  z.output<typeof Webhooks.schema.updateWebhook.Response>['status']
2068
2095
  >()
package/src/Scope.ts CHANGED
@@ -86,8 +86,10 @@ export namespace schema {
86
86
  /** A concrete per-zone read scope (`zone:421700001:read`). */
87
87
  export const Zone = z
88
88
  .templateLiteral(['zone:', z.int(), ':read'])
89
- // `z.int()` also matches a sign; zone chain ids are positive.
90
- .check(z.regex(/^zone:\d+:read$/), z.describe('Per-zone read scope (`zone:<chainId>:read`).'))
89
+ .check(
90
+ z.regex(/^zone:[1-9]\d*:read$/),
91
+ z.describe('Per-zone read scope (`zone:<chainId>:read`).'),
92
+ )
91
93
  }
92
94
 
93
95
  /** Per-zone read scope, granting access to one zone chain id. */
package/src/Webhooks.ts CHANGED
@@ -28,7 +28,7 @@ export { eventTypes, schema } from './apps/data/routes/webhooks.js'
28
28
  *
29
29
  * The monitoring event types (`log:emitted`, `block:created`,
30
30
  * `transaction:included`) scan a confirmed window directly over JSON-RPC, so the
31
- * poller builds memoized viem clients (via {@link Viem.getClient}); the legacy
31
+ * poller builds memoized viem clients (via {@link Viem.createGetClient}); the legacy
32
32
  * `token:transfer` type still reads from TIDX, so it
33
33
  * also builds memoized TIDX clients (via {@link Tidx.createGetClient}) when a
34
34
  * `tidx` config is supplied. The poller keeps `App.create` a pure Hono app: it
@@ -42,15 +42,10 @@ export function createPoller(options: createPoller.Options): createPoller.Poller
42
42
  const getTidx = options.tidx
43
43
  ? Tidx.createGetClient({ defaultChainId: options.defaultChainId, tidx: options.tidx })
44
44
  : undefined
45
- const clients = new Map<number, Viem.getClient.ReturnType>()
46
- const getClient = (chainId: Viem.ChainId) => {
47
- let client = clients.get(chainId)
48
- if (!client) {
49
- client = Viem.getClient({ chainId, rpc: options.rpc })
50
- clients.set(chainId, client)
51
- }
52
- return client
53
- }
45
+ const getClient = Viem.createGetClient({
46
+ defaultChainId: options.defaultChainId,
47
+ rpc: options.rpc,
48
+ })
54
49
 
55
50
  const metrics = options.metrics ? MetricSink.webhooks(options.metrics) : undefined
56
51
 
@@ -68,6 +68,19 @@ describe('POST /api-keys', () => {
68
68
  expect(resolved?.environment).toBe('sandbox')
69
69
  })
70
70
 
71
+ test('mints a key with a concrete Zone read scope', async () => {
72
+ const { client, store } = TestAdmin.setup()
73
+ const scope = 'zone:421700001:read' as const
74
+ const response = await client['api-keys'].$post({
75
+ json: { ...validBody, scopes: [scope] },
76
+ })
77
+ const body = await TestApp.json(response, ApiKeys.schema.createApiKey.Response)
78
+
79
+ expect(response.status).toBe(200)
80
+ expect(body.scopes).toEqual([scope])
81
+ expect((await core_ApiKeys.resolve(store, body.token))?.scopes).toEqual([scope])
82
+ })
83
+
71
84
  test('records the expiry when provided', async () => {
72
85
  const { client, store } = TestAdmin.setup()
73
86
  const expiresAt = '2100-01-01T00:00:00.000Z'
@@ -91,6 +104,20 @@ describe('POST /api-keys', () => {
91
104
  expect(body.error?.code).toMatchInlineSnapshot(`"body_invalid"`)
92
105
  })
93
106
 
107
+ test('rejects non-concrete or non-canonical Zone scopes with 400', async () => {
108
+ for (const scope of ['zone:<chainId>:read', 'zone:0:read', 'zone:000421700001:read']) {
109
+ const { client } = TestAdmin.setup()
110
+ const response = await client['api-keys'].$post({
111
+ // Cast past the typed client to exercise runtime validation.
112
+ json: { ...validBody, scopes: [scope] } as never,
113
+ })
114
+ const body = (await response.json()) as { error?: { code?: string } }
115
+
116
+ expect(response.status).toBe(400)
117
+ expect(body.error?.code).toBe('body_invalid')
118
+ }
119
+ })
120
+
94
121
  test('ignores a client-supplied createdBy (audit trail is server-set)', async () => {
95
122
  const { client } = TestAdmin.setup()
96
123
  const response = await client['api-keys'].$post({
@@ -19,7 +19,14 @@ export namespace schema {
19
19
  export const Body = Schema.describe(
20
20
  z.extend(z.omit(ApiKeys.schema.MintInput, { createdBy: true }), {
21
21
  scopes: z
22
- .readonly(z.array(z.enum([...Scope.catalog.map((entry) => entry.scope), Scope.wildcard])))
22
+ .readonly(
23
+ z.array(
24
+ z.union([
25
+ z.enum([...Scope.catalog.map((entry) => entry.scope), Scope.wildcard]),
26
+ Scope.schema.Zone,
27
+ ]),
28
+ ),
29
+ )
23
30
  .check(z.describe("Granted scopes. '*' grants all scopes.")),
24
31
  }),
25
32
  'Fields accepted when minting an API key.',
@@ -54,17 +54,22 @@ describe('GET /scopes', () => {
54
54
  "scope": "webhooks:write",
55
55
  "selfServe": false,
56
56
  },
57
+ {
58
+ "description": "Read RPC and indexed data for one zone chain id.",
59
+ "scope": "zone:<chainId>:read",
60
+ "selfServe": false,
61
+ },
57
62
  ],
58
63
  }
59
64
  `)
60
65
  })
61
66
 
62
- test('serves the Scope.catalog source of truth verbatim', async () => {
67
+ test('serves the extended Scope catalog source of truth verbatim', async () => {
63
68
  const { client } = TestAdmin.setup()
64
69
  const response = await client.scopes.$get()
65
70
  const body = await TestApp.json(response, Scopes.schema.getScopes.Response)
66
71
 
67
- expect(body.data).toEqual(Scope.catalog)
72
+ expect(body.data).toEqual(Scope.extend())
68
73
  })
69
74
 
70
75
  test('is never cached (admin responses are per-principal)', async () => {
@@ -33,7 +33,7 @@ export namespace schema {
33
33
  }
34
34
  }
35
35
 
36
- /** Catalog of issuable API-key scopes, served from {@link Scope.catalog}. */
36
+ /** Catalog of API-key scopes, including dynamic scope templates. */
37
37
  export function scopes() {
38
38
  return new Hono<App.Environment>().get(
39
39
  '/',
@@ -45,6 +45,6 @@ export function scopes() {
45
45
  summary: 'List scopes',
46
46
  tags: ['Scopes'],
47
47
  }),
48
- (c) => c.json({ data: Scope.catalog }),
48
+ (c) => c.json({ data: Scope.extend() }),
49
49
  )
50
50
  }
@@ -1,12 +1,18 @@
1
1
  import { Dialog as Base } from '@base-ui/react/dialog'
2
2
  import type { ComponentProps, ReactNode } from 'react'
3
+ import { cx } from 'regen-ui'
3
4
 
4
5
  /** Backdrop + centered popup shell. Children render inside; pair with Title/Description. */
5
- function Popup(props: { children: ReactNode }) {
6
+ function Popup(props: { children: ReactNode; className?: string | undefined }) {
6
7
  return (
7
8
  <Base.Portal>
8
9
  <Base.Backdrop className="fixed inset-0 z-40 bg-black/60 transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" />
9
- <Base.Popup className="fixed left-1/2 top-1/2 z-50 w-[calc(100vw-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 border border-border bg-surface p-6 shadow-lg outline-none transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0">
10
+ <Base.Popup
11
+ className={cx(
12
+ 'fixed left-1/2 top-1/2 z-50 w-[calc(100vw-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 border border-border bg-surface p-6 shadow-lg outline-none transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0',
13
+ props.className,
14
+ )}
15
+ >
10
16
  {props.children}
11
17
  </Base.Popup>
12
18
  </Base.Portal>
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from 'vite-plus/test'
2
+
3
+ import { fromZoneChainId } from './scopes'
4
+
5
+ describe('fromZoneChainId', () => {
6
+ it('builds a canonical Zone read scope', () => {
7
+ expect(fromZoneChainId(' 000421700001 ')).toBe('zone:421700001:read')
8
+ })
9
+
10
+ it('rejects non-positive and malformed chain ids', () => {
11
+ expect(['', '0', '-1', '1.5', 'mainnet'].map(fromZoneChainId)).toEqual([
12
+ undefined,
13
+ undefined,
14
+ undefined,
15
+ undefined,
16
+ undefined,
17
+ ])
18
+ })
19
+ })
@@ -6,6 +6,18 @@ import { client } from './client'
6
6
  /** A single issuable API-key scope from the catalog endpoint. */
7
7
  export type Scope = InferResponseType<(typeof client)['scopes']['$get'], 200>['data'][number]
8
8
 
9
+ /** Dynamic Zone scope template returned by the scope catalog. */
10
+ export const zoneTemplate = 'zone:<chainId>:read'
11
+
12
+ /** Builds a canonical Zone read scope from a positive integer chain id. */
13
+ export function fromZoneChainId(value: string): `zone:${string}:read` | undefined {
14
+ const chainId = value.trim()
15
+ if (!/^\d+$/.test(chainId)) return undefined
16
+ const normalized = BigInt(chainId)
17
+ if (normalized === 0n) return undefined
18
+ return `zone:${normalized}:read`
19
+ }
20
+
9
21
  /** List the issuable API-key scope catalog. */
10
22
  export namespace get {
11
23
  export const key = ['scopes'] as const