tapimo 0.10.0 → 0.10.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 (295) hide show
  1. package/dist/ApiKey.d.ts +2 -1
  2. package/dist/ApiKey.d.ts.map +1 -1
  3. package/dist/ApiKeys.d.ts +4 -2
  4. package/dist/ApiKeys.d.ts.map +1 -1
  5. package/dist/ApiKeys.js +26 -1
  6. package/dist/ApiKeys.js.map +1 -1
  7. package/dist/App.d.ts +6007 -5003
  8. package/dist/App.d.ts.map +1 -1
  9. package/dist/App.js +1 -0
  10. package/dist/App.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 +6 -6
  14. package/dist/Webhooks.js.map +1 -1
  15. package/dist/admin/App.d.ts +91 -84
  16. package/dist/admin/App.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.d.ts +22 -13
  18. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  19. package/dist/admin/apps/api-keys.js +6 -0
  20. package/dist/admin/apps/api-keys.js.map +1 -1
  21. package/dist/admin/apps/organizations.d.ts +4 -2
  22. package/dist/admin/apps/organizations.d.ts.map +1 -1
  23. package/dist/admin/ui.generated.d.ts +1 -1
  24. package/dist/admin/ui.generated.js +69 -69
  25. package/dist/admin/ui.generated.js.map +1 -1
  26. package/dist/analytics/Analytics.d.ts.map +1 -1
  27. package/dist/analytics/Analytics.js +232 -0
  28. package/dist/analytics/Analytics.js.map +1 -1
  29. package/dist/analytics/tables/requestEvents.d.ts +4 -0
  30. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  31. package/dist/analytics/tables/requestEvents.js +2 -0
  32. package/dist/analytics/tables/requestEvents.js.map +1 -1
  33. package/dist/apps/Zones.d.ts +4266 -82
  34. package/dist/apps/Zones.d.ts.map +1 -1
  35. package/dist/apps/Zones.js +27 -8
  36. package/dist/apps/Zones.js.map +1 -1
  37. package/dist/apps/data/App.d.ts +3467 -2696
  38. package/dist/apps/data/App.d.ts.map +1 -1
  39. package/dist/apps/data/App.js +2 -0
  40. package/dist/apps/data/App.js.map +1 -1
  41. package/dist/apps/data/routes/activities.d.ts +2999 -100
  42. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  43. package/dist/apps/data/routes/activities.js +340 -2
  44. package/dist/apps/data/routes/activities.js.map +1 -1
  45. package/dist/apps/data/routes/addresses.d.ts +514 -0
  46. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  47. package/dist/apps/data/routes/balances.d.ts +26 -26
  48. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  49. package/dist/apps/data/routes/balances.js +7 -3
  50. package/dist/apps/data/routes/balances.js.map +1 -1
  51. package/dist/apps/data/routes/blocks.d.ts +4 -4
  52. package/dist/apps/data/routes/earn.d.ts +75 -75
  53. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  54. package/dist/apps/data/routes/earn.js +65 -36
  55. package/dist/apps/data/routes/earn.js.map +1 -1
  56. package/dist/apps/data/routes/exchanges.d.ts +124 -120
  57. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  58. package/dist/apps/data/routes/exchanges.js +115 -77
  59. package/dist/apps/data/routes/exchanges.js.map +1 -1
  60. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  61. package/dist/apps/data/routes/indexer.d.ts +2 -1
  62. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  63. package/dist/apps/data/routes/indexer.js +3 -2
  64. package/dist/apps/data/routes/indexer.js.map +1 -1
  65. package/dist/apps/data/routes/receipts.d.ts +4 -3
  66. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  67. package/dist/apps/data/routes/receipts.js +15 -14
  68. package/dist/apps/data/routes/receipts.js.map +1 -1
  69. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  70. package/dist/apps/data/routes/rpc.js +5 -1
  71. package/dist/apps/data/routes/rpc.js.map +1 -1
  72. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  73. package/dist/apps/data/routes/tokens.d.ts +1 -1
  74. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  75. package/dist/apps/data/routes/tokens.js +4 -3
  76. package/dist/apps/data/routes/tokens.js.map +1 -1
  77. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  78. package/dist/apps/data/routes/transfers.js +25 -7
  79. package/dist/apps/data/routes/transfers.js.map +1 -1
  80. package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
  81. package/dist/apps/data/routes/webhooks.d.ts +71 -1
  82. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  83. package/dist/apps/data/routes/webhooks.js +177 -23
  84. package/dist/apps/data/routes/webhooks.js.map +1 -1
  85. package/dist/apps/data/routes/zones.js +1 -1
  86. package/dist/apps/data/routes/zones.js.map +1 -1
  87. package/dist/apps/funding/App.d.ts +322 -89
  88. package/dist/apps/funding/App.d.ts.map +1 -1
  89. package/dist/apps/funding/routes/deposit-addresses.d.ts +375 -45
  90. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  91. package/dist/apps/funding/routes/deposit-addresses.js +208 -12
  92. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  93. package/dist/apps/funding/routes/transfers.d.ts +32 -32
  94. package/dist/apps/funding/routes/transfers.js +1 -1
  95. package/dist/apps/funding/routes/transfers.js.map +1 -1
  96. package/dist/apps/management/App.d.ts +418 -418
  97. package/dist/apps/management/routes/api-keys.d.ts +50 -50
  98. package/dist/apps/management/routes/billing.d.ts +72 -72
  99. package/dist/apps/management/routes/billing.js +1 -1
  100. package/dist/apps/management/routes/billing.js.map +1 -1
  101. package/dist/apps/management/routes/faucet.d.ts +8 -8
  102. package/dist/apps/management/routes/faucet.js +2 -2
  103. package/dist/apps/management/routes/faucet.js.map +1 -1
  104. package/dist/apps/management/routes/invite-links.d.ts +42 -42
  105. package/dist/apps/management/routes/me.d.ts +6 -6
  106. package/dist/apps/management/routes/members.d.ts +14 -14
  107. package/dist/apps/management/routes/projects.d.ts +40 -40
  108. package/dist/apps/management/routes/usage.d.ts +8 -8
  109. package/dist/apps/management/routes/webhooks.d.ts +49 -49
  110. package/dist/apps/mcp.d.ts.map +1 -1
  111. package/dist/apps/mcp.js +4 -1
  112. package/dist/apps/mcp.js.map +1 -1
  113. package/dist/db/Db.d.ts +2 -0
  114. package/dist/db/Db.d.ts.map +1 -1
  115. package/dist/db/Db.js +90 -0
  116. package/dist/db/Db.js.map +1 -1
  117. package/dist/db/Schema.d.ts +19 -0
  118. package/dist/db/Schema.d.ts.map +1 -1
  119. package/dist/db/Schema.js +24 -0
  120. package/dist/db/Schema.js.map +1 -1
  121. package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
  122. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  123. package/dist/db/tables/fundingDepositAddresses.js +17 -0
  124. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  125. package/dist/db/tables/fundingDeposits.d.ts +41 -0
  126. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  127. package/dist/db/tables/fundingDeposits.js +69 -0
  128. package/dist/db/tables/fundingDeposits.js.map +1 -1
  129. package/dist/db/tables/webhookQueueEvents.d.ts +122 -0
  130. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -0
  131. package/dist/db/tables/webhookQueueEvents.js +160 -0
  132. package/dist/db/tables/webhookQueueEvents.js.map +1 -0
  133. package/dist/db/tables/webhookSubscriptions.d.ts +2 -0
  134. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  135. package/dist/db/tables/webhookSubscriptions.js +10 -0
  136. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  137. package/dist/internal/Auth.d.ts +3 -26
  138. package/dist/internal/Auth.d.ts.map +1 -1
  139. package/dist/internal/Auth.js +6 -37
  140. package/dist/internal/Auth.js.map +1 -1
  141. package/dist/internal/Credential.d.ts +27 -0
  142. package/dist/internal/Credential.d.ts.map +1 -0
  143. package/dist/internal/Credential.js +34 -0
  144. package/dist/internal/Credential.js.map +1 -0
  145. package/dist/internal/EarnRates.js +11 -7
  146. package/dist/internal/EarnRates.js.map +1 -1
  147. package/dist/internal/Log.d.ts +8 -0
  148. package/dist/internal/Log.d.ts.map +1 -1
  149. package/dist/internal/Log.js +4 -0
  150. package/dist/internal/Log.js.map +1 -1
  151. package/dist/internal/MetricSink.d.ts +51 -0
  152. package/dist/internal/MetricSink.d.ts.map +1 -1
  153. package/dist/internal/MetricSink.js +54 -2
  154. package/dist/internal/MetricSink.js.map +1 -1
  155. package/dist/internal/RateLimit.d.ts +4 -6
  156. package/dist/internal/RateLimit.d.ts.map +1 -1
  157. package/dist/internal/RateLimit.js +15 -14
  158. package/dist/internal/RateLimit.js.map +1 -1
  159. package/dist/internal/Store.d.ts +2 -1
  160. package/dist/internal/Store.d.ts.map +1 -1
  161. package/dist/internal/Store.js +57 -18
  162. package/dist/internal/Store.js.map +1 -1
  163. package/dist/internal/Tidx.d.ts +4 -3
  164. package/dist/internal/Tidx.d.ts.map +1 -1
  165. package/dist/internal/Tidx.js +2 -2
  166. package/dist/internal/Tidx.js.map +1 -1
  167. package/dist/internal/Value.d.ts +2 -0
  168. package/dist/internal/Value.d.ts.map +1 -1
  169. package/dist/internal/Value.js +25 -0
  170. package/dist/internal/Value.js.map +1 -1
  171. package/dist/internal/Viem.d.ts +4 -3
  172. package/dist/internal/Viem.d.ts.map +1 -1
  173. package/dist/internal/Viem.js +3 -2
  174. package/dist/internal/Viem.js.map +1 -1
  175. package/dist/internal/WebhookTransfer.d.ts +20 -0
  176. package/dist/internal/WebhookTransfer.d.ts.map +1 -1
  177. package/dist/internal/WebhookTransfer.js +7 -4
  178. package/dist/internal/WebhookTransfer.js.map +1 -1
  179. package/dist/internal/Webhooks.d.ts +141 -0
  180. package/dist/internal/Webhooks.d.ts.map +1 -1
  181. package/dist/internal/Webhooks.js +139 -10
  182. package/dist/internal/Webhooks.js.map +1 -1
  183. package/dist/internal/funding/providers/relay.d.ts +3 -4
  184. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  185. package/dist/internal/funding/providers/relay.js +59 -8
  186. package/dist/internal/funding/providers/relay.js.map +1 -1
  187. package/dist/internal/index.d.ts +1 -0
  188. package/dist/internal/index.d.ts.map +1 -1
  189. package/dist/internal/index.js +1 -0
  190. package/dist/internal/index.js.map +1 -1
  191. package/package.json +3 -3
  192. package/src/ApiKeys.test.ts +20 -0
  193. package/src/ApiKeys.ts +28 -1
  194. package/src/App.test.ts +40 -8
  195. package/src/App.ts +1 -0
  196. package/src/Client.test-d.ts +34 -1
  197. package/src/Webhooks.ts +20 -18
  198. package/src/admin/apps/api-keys.test.ts +9 -0
  199. package/src/admin/apps/api-keys.ts +15 -0
  200. package/src/admin/ui/src/lib/query-client.ts +3 -2
  201. package/src/admin/ui/src/routes/api-keys.tsx +31 -9
  202. package/src/admin/ui.generated.ts +69 -69
  203. package/src/analytics/Analytics.test.ts +42 -0
  204. package/src/analytics/Analytics.ts +232 -0
  205. package/src/analytics/tables/requestEvents.ts +6 -0
  206. package/src/apps/Zones.test.ts +20 -3
  207. package/src/apps/Zones.ts +28 -8
  208. package/src/apps/composition.test.ts +2 -0
  209. package/src/apps/data/App.test.ts +2 -2
  210. package/src/apps/data/App.ts +2 -0
  211. package/src/apps/data/routes/activities.bench.ts +41 -0
  212. package/src/apps/data/routes/activities.test.ts +103 -2
  213. package/src/apps/data/routes/activities.ts +401 -2
  214. package/src/apps/data/routes/balances.bench.ts +21 -0
  215. package/src/apps/data/routes/balances.ts +7 -3
  216. package/src/apps/data/routes/blocks.bench.ts +34 -0
  217. package/src/apps/data/routes/coingecko.bench.ts +53 -0
  218. package/src/apps/data/routes/earn.bench.ts +71 -0
  219. package/src/apps/data/routes/earn.ts +66 -36
  220. package/src/apps/data/routes/exchanges.bench.ts +85 -0
  221. package/src/apps/data/routes/exchanges.test.ts +8 -3
  222. package/src/apps/data/routes/exchanges.ts +134 -82
  223. package/src/apps/data/routes/fee-amm.bench.ts +13 -0
  224. package/src/apps/data/routes/indexer.bench.ts +8 -0
  225. package/src/apps/data/routes/indexer.test.ts +2 -1
  226. package/src/apps/data/routes/indexer.ts +3 -2
  227. package/src/apps/data/routes/receipts.bench.ts +39 -0
  228. package/src/apps/data/routes/receipts.ts +16 -14
  229. package/src/apps/data/routes/rpc.bench.ts +14 -0
  230. package/src/apps/data/routes/rpc.test.ts +27 -0
  231. package/src/apps/data/routes/rpc.ts +5 -1
  232. package/src/apps/data/routes/tokenlist.bench.ts +3 -0
  233. package/src/apps/data/routes/tokens.bench.ts +76 -0
  234. package/src/apps/data/routes/tokens.ts +3 -2
  235. package/src/apps/data/routes/transactions.bench.ts +48 -0
  236. package/src/apps/data/routes/transfers.bench.ts +28 -0
  237. package/src/apps/data/routes/transfers.ts +36 -10
  238. package/src/apps/data/routes/valuation.bench.ts +14 -0
  239. package/src/apps/data/routes/verified-tokens.bench.ts +8 -0
  240. package/src/apps/data/routes/webhooks.bench.ts +26 -0
  241. package/src/apps/data/routes/webhooks.scan.test.ts +326 -0
  242. package/src/apps/data/routes/webhooks.test.ts +53 -6
  243. package/src/apps/data/routes/webhooks.ts +252 -24
  244. package/src/apps/data/routes/zones.bench.ts +32 -0
  245. package/src/apps/data/routes/zones.test.ts +1 -1
  246. package/src/apps/data/routes/zones.ts +1 -1
  247. package/src/apps/funding/routes/chains.test.ts +1 -1
  248. package/src/apps/funding/routes/deposit-addresses.test.ts +217 -3
  249. package/src/apps/funding/routes/deposit-addresses.ts +255 -19
  250. package/src/apps/funding/routes/providers.test.ts +1 -1
  251. package/src/apps/funding/routes/quotes.test.ts +1 -1
  252. package/src/apps/funding/routes/transfers.test.ts +20 -8
  253. package/src/apps/funding/routes/transfers.ts +1 -1
  254. package/src/apps/management/routes/billing.ts +1 -1
  255. package/src/apps/management/routes/faucet.ts +2 -2
  256. package/src/apps/management/routes/me.test.ts +31 -0
  257. package/src/apps/mcp.ts +4 -1
  258. package/src/db/Db.ts +117 -0
  259. package/src/db/Schema.ts +32 -0
  260. package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
  261. package/src/db/tables/fundingDepositAddresses.ts +39 -0
  262. package/src/db/tables/fundingDeposits.test.ts +82 -0
  263. package/src/db/tables/fundingDeposits.ts +130 -0
  264. package/src/db/tables/webhookQueueEvents.ts +285 -0
  265. package/src/db/tables/webhookSubscriptions.ts +15 -0
  266. package/src/internal/Auth.test.ts +61 -98
  267. package/src/internal/Auth.ts +9 -63
  268. package/src/internal/Credential.test.ts +51 -0
  269. package/src/internal/Credential.ts +58 -0
  270. package/src/internal/EarnRates.ts +13 -7
  271. package/src/internal/EarnVaults.test.ts +2 -2
  272. package/src/internal/Log.test.ts +3 -1
  273. package/src/internal/Log.ts +14 -0
  274. package/src/internal/MetricSink.test.ts +108 -2
  275. package/src/internal/MetricSink.ts +112 -2
  276. package/src/internal/RateLimit.test.ts +23 -18
  277. package/src/internal/RateLimit.ts +23 -15
  278. package/src/internal/Store.test.ts +99 -13
  279. package/src/internal/Store.ts +52 -17
  280. package/src/internal/Tidx.test.ts +4 -4
  281. package/src/internal/Tidx.ts +5 -4
  282. package/src/internal/Value.test.ts +25 -0
  283. package/src/internal/Value.ts +29 -0
  284. package/src/internal/Viem.test.ts +4 -0
  285. package/src/internal/Viem.ts +6 -4
  286. package/src/internal/WebhookTransfer.test.ts +89 -0
  287. package/src/internal/WebhookTransfer.ts +11 -7
  288. package/src/internal/Webhooks.test.ts +488 -0
  289. package/src/internal/Webhooks.ts +291 -10
  290. package/src/internal/funding/Provider.test.ts +1 -1
  291. package/src/internal/funding/providers/index.test.ts +73 -3
  292. package/src/internal/funding/providers/relay.test.ts +1 -5
  293. package/src/internal/funding/providers/relay.ts +56 -12
  294. package/src/internal/index.test.ts +1 -0
  295. package/src/internal/index.ts +1 -0
@@ -0,0 +1,3 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ QueryBench.bench({ name: 'getTokenList', path: '/v1/tokenlist' })
@@ -0,0 +1,76 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ const address = {
4
+ 4217: '0xc24357119432595a547cb2c1e41c51d753645519',
5
+ 42431: '0x15496d3123afb2cfadc0ac068eadd11d0f8ae799',
6
+ } as const
7
+
8
+ QueryBench.bench({
9
+ name: 'getTokens',
10
+ path: '/v1/tokens',
11
+ queries: [
12
+ `addresses=${QueryBench.pathUsd}`,
13
+ `addresses=${QueryBench.pathUsd}&include=admin,createdAt,holderCount,quoteToken,transferStats`,
14
+ 'include=admin',
15
+ 'include=createdAt',
16
+ 'include=holderCount',
17
+ 'include=quoteToken',
18
+ 'include=transferStats',
19
+ 'verified=true',
20
+ 'verified=true&include=admin,createdAt,holderCount,quoteToken,transferStats',
21
+ ],
22
+ })
23
+
24
+ QueryBench.bench({
25
+ name: 'getTokenBySymbol',
26
+ path: '/v1/tokens/pathUSD',
27
+ queries: [
28
+ 'include=admin',
29
+ 'include=createdAt',
30
+ 'include=holderCount',
31
+ 'include=quoteToken',
32
+ 'include=transferStats',
33
+ ],
34
+ })
35
+
36
+ QueryBench.bench({
37
+ name: 'getToken',
38
+ path: `/v1/tokens/${QueryBench.pathUsd}`,
39
+ queries: [
40
+ 'include=admin',
41
+ 'include=createdAt',
42
+ 'include=holderCount',
43
+ 'include=quoteToken',
44
+ 'include=transferStats',
45
+ ],
46
+ })
47
+
48
+ QueryBench.bench({
49
+ name: 'getTokenLogo',
50
+ path: `/v1/tokens/${QueryBench.pathUsd}/logo`,
51
+ })
52
+
53
+ QueryBench.bench({
54
+ name: 'getTokenHolders',
55
+ path: `/v1/tokens/${QueryBench.pathUsd}/holders`,
56
+ queries: ['include=token', 'include=totalCount'],
57
+ })
58
+
59
+ QueryBench.bench({
60
+ name: 'getTokenTransactions',
61
+ path: `/v1/tokens/${QueryBench.pathUsd}/transactions`,
62
+ queries: [
63
+ 'include=feeToken.logoUri',
64
+ 'include=feeToken.verified',
65
+ 'include=token',
66
+ 'include=totalCount',
67
+ ],
68
+ })
69
+
70
+ QueryBench.bench({
71
+ name: 'getTokenTransactions?feePayer&include=totalCount',
72
+ path: {
73
+ 4217: `/v1/tokens/${QueryBench.pathUsd}/transactions?feePayer=${address[4217]}&include=totalCount`,
74
+ 42431: `/v1/tokens/${QueryBench.pathUsd}/transactions?feePayer=${address[42431]}&include=totalCount`,
75
+ },
76
+ })
@@ -1228,12 +1228,13 @@ export async function resolveTokens(
1228
1228
  const logos = new Map(logoByAddress)
1229
1229
  const metadataMap = new Map(metadataByAddress)
1230
1230
  const onchainLogos = new Map(
1231
- includeLogoUri
1231
+ includeLogoUri && options.metadata !== undefined
1232
1232
  ? await Timing.time(c, 'token_logo_uri', () =>
1233
1233
  Promise.all(
1234
1234
  addresses
1235
1235
  .filter(
1236
1236
  (address) =>
1237
+ options.metadata?.has(address) === true &&
1237
1238
  !logos.get(address) &&
1238
1239
  !snapshot?.byAddress.get(address)?.logoUri &&
1239
1240
  !metadataMap.get(address)?.logoUri,
@@ -1321,7 +1322,7 @@ function getTokenLogoUri(c: Context<App.Environment>, options: getTokenMetadata.
1321
1322
 
1322
1323
  /**
1323
1324
  * Resolves a token's on-chain metadata (`name`/`symbol`/`decimals`/`currency`/
1324
- * `totalSupply`) via a single `token.getMetadata` RPC, memoized under the shared
1325
+ * `logoUri`/`totalSupply`) via a single `token.getMetadata` RPC, memoized under the shared
1325
1326
  * `token:v1:{chainId}:{address}:metadata` key. Exported so feed enrichers (e.g.
1326
1327
  * `activities`) reuse the same cache entry as the token resource rather than
1327
1328
  * fetching metadata through a parallel path.
@@ -0,0 +1,48 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ const hash = {
4
+ 4217: '0x7f98e4b2bf4d1444b257526f7dd294ac3480d73cd91f723bf0df6062d1ba1c82',
5
+ 42431: '0x1c8ccebb169f863b72388cc057fcecee240032eb7d2c64aa6f6fb8e5a60d3f9a',
6
+ } as const
7
+
8
+ const address = {
9
+ 4217: '0xc24357119432595a547cb2c1e41c51d753645519',
10
+ 42431: '0x15496d3123afb2cfadc0ac068eadd11d0f8ae799',
11
+ } as const
12
+
13
+ QueryBench.bench({
14
+ name: 'getTransactions',
15
+ path: '/v1/transactions',
16
+ queries: [
17
+ 'include=feeToken.logoUri',
18
+ 'include=feeToken.verified',
19
+ 'include=receipt',
20
+ 'include=totalCount',
21
+ 'status=success&include=totalCount',
22
+ ],
23
+ })
24
+
25
+ QueryBench.bench({
26
+ name: 'getTransactions?address&include=totalCount',
27
+ path: {
28
+ 4217: `/v1/transactions?address=${address[4217]}&include=totalCount`,
29
+ 42431: `/v1/transactions?address=${address[42431]}&include=totalCount`,
30
+ },
31
+ })
32
+
33
+ QueryBench.bench({
34
+ name: 'getTransactions?address&include=receipt',
35
+ path: {
36
+ 4217: `/v1/transactions?address=${address[4217]}&include=receipt`,
37
+ 42431: `/v1/transactions?address=${address[42431]}&include=receipt`,
38
+ },
39
+ })
40
+
41
+ QueryBench.bench({
42
+ name: 'getTransaction',
43
+ path: {
44
+ 4217: `/v1/transactions/${hash[4217]}`,
45
+ 42431: `/v1/transactions/${hash[42431]}`,
46
+ },
47
+ queries: ['include=feeToken.logoUri', 'include=feeToken.verified', 'include=receipt'],
48
+ })
@@ -0,0 +1,28 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ const address = {
4
+ 4217: '0xc24357119432595a547cb2c1e41c51d753645519',
5
+ 42431: '0x15496d3123afb2cfadc0ac068eadd11d0f8ae799',
6
+ } as const
7
+
8
+ QueryBench.bench({
9
+ name: 'getTransfers',
10
+ path: '/v1/transfers',
11
+ queries: [
12
+ 'include=crossToken',
13
+ 'include=crossToken,totalCount',
14
+ 'include=memo',
15
+ 'include=token.logoUri',
16
+ 'include=token.verified',
17
+ 'include=totalCount',
18
+ 'valuation.currency=USD',
19
+ ],
20
+ })
21
+
22
+ QueryBench.bench({
23
+ name: 'getTransfers?address&include=totalCount',
24
+ path: {
25
+ 4217: `/v1/transfers?address=${address[4217]}&include=totalCount`,
26
+ 42431: `/v1/transfers?address=${address[42431]}&include=totalCount`,
27
+ },
28
+ })
@@ -437,10 +437,10 @@ export function transfers(options: transfers.Options = {}) {
437
437
  transactionHash: row.transactionHash,
438
438
  }))
439
439
 
440
- // Memo/attribution are opt-in: one `TransferWithMemo` lookup bounded
441
- // to the page's transactions resolves both — the decoded text `memo`,
442
- // and `attribution` when the memo is a known MPP service fingerprint.
443
- if (options.include.includes('memo') && data.length > 0) {
440
+ const includeCrossToken = options.include.includes('crossToken')
441
+ const includeMemo = options.include.includes('memo')
442
+ // Attach memos before cross-token folding changes the matching fields.
443
+ if (includeMemo && includeCrossToken && data.length > 0) {
444
444
  const memoByRow = await memoMap(c, { chainId, rows: data })
445
445
  data = data.map((transfer) => {
446
446
  const entry = memoByRow.get(
@@ -469,7 +469,7 @@ export function transfers(options: transfers.Options = {}) {
469
469
  // own event token is the delivered token), so a `token=` filter matches
470
470
  // cross-token rows by their destination token, and each transfer still
471
471
  // appears exactly once across pages.
472
- if (options.include.includes('crossToken') && data.length > 0) {
472
+ if (includeCrossToken && data.length > 0) {
473
473
  const { fold, suppress } = await crossTokenMap(c, { chainId, rows: data })
474
474
  data = data.flatMap((transfer) => {
475
475
  const key = eventKey(transfer.transactionHash, transfer.logIndex)
@@ -502,11 +502,17 @@ export function transfers(options: transfers.Options = {}) {
502
502
  ]),
503
503
  ),
504
504
  )
505
- const tokensByAddress = new Map<
506
- string,
507
- Awaited<ReturnType<typeof Tokens.getTokenMetadata>>
508
- >()
509
- await Timing.time(c, 'tokens', async () => {
505
+ // Memo and token metadata depend only on the page rows, so resolve
506
+ // them concurrently when cross-token folding is not requested.
507
+ const memoPromise =
508
+ includeMemo && !includeCrossToken && data.length > 0
509
+ ? memoMap(c, { chainId, rows: data })
510
+ : undefined
511
+ const tokensPromise = Timing.time(c, 'tokens', async () => {
512
+ const tokensByAddress = new Map<
513
+ string,
514
+ Awaited<ReturnType<typeof Tokens.getTokenMetadata>>
515
+ >()
510
516
  for (let index = 0; index < uniqueTokens.length; index += tokenReadChunkSize) {
511
517
  const group = uniqueTokens.slice(index, index + tokenReadChunkSize)
512
518
  const entries = await Promise.all(
@@ -536,7 +542,27 @@ export function transfers(options: transfers.Options = {}) {
536
542
  )
537
543
  for (const entry of entries) tokensByAddress.set(...entry)
538
544
  }
545
+ return tokensByAddress
539
546
  })
547
+ const [memoByRow, tokensByAddress] = await Promise.all([memoPromise, tokensPromise])
548
+ if (memoByRow)
549
+ data = data.map((transfer) => {
550
+ const entry = memoByRow.get(
551
+ transferKey(
552
+ transfer.transactionHash,
553
+ transfer.sender,
554
+ transfer.recipient,
555
+ transfer.sourceToken.amount,
556
+ transfer.sourceToken.address,
557
+ ),
558
+ )
559
+ if (!entry) return transfer
560
+ return {
561
+ ...transfer,
562
+ ...(entry.attribution ? { attribution: entry.attribution } : {}),
563
+ ...(entry.memo ? { memo: entry.memo } : {}),
564
+ }
565
+ })
540
566
 
541
567
  // Value each leg via its curated display currency, in the requested
542
568
  // denomination.
@@ -0,0 +1,14 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ const address = {
4
+ 4217: '0xc24357119432595a547cb2c1e41c51d753645519',
5
+ 42431: '0x15496d3123afb2cfadc0ac068eadd11d0f8ae799',
6
+ } as const
7
+
8
+ QueryBench.bench({
9
+ name: 'getAddressValuation',
10
+ path: {
11
+ 4217: `/v1/addresses/${address[4217]}/valuation`,
12
+ 42431: `/v1/addresses/${address[42431]}/valuation`,
13
+ },
14
+ })
@@ -0,0 +1,8 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ QueryBench.bench({ name: 'listVerifiedTokens', path: '/v1/verified-tokens' })
4
+ QueryBench.bench({ name: 'getVerifiedTokenCurrencies', path: '/v1/verified-tokens/currencies' })
5
+ QueryBench.bench({
6
+ name: 'getVerifiedToken',
7
+ path: `/v1/verified-tokens/${QueryBench.pathUsd}`,
8
+ })
@@ -0,0 +1,26 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ QueryBench.bench({ name: 'getWebhookEventTypes', path: '/v1/webhooks/event-types' })
4
+
5
+ QueryBench.bench({
6
+ name: 'listWebhooks',
7
+ path: '/v1/webhooks',
8
+ queries: ['include=totalCount'],
9
+ })
10
+
11
+ QueryBench.bench({
12
+ name: 'getWebhook',
13
+ path: {
14
+ 4217: `/v1/webhooks/${QueryBench.webhookId[4217]}`,
15
+ 42431: `/v1/webhooks/${QueryBench.webhookId[42431]}`,
16
+ },
17
+ })
18
+
19
+ QueryBench.bench({
20
+ name: 'listWebhookDeliveries',
21
+ path: {
22
+ 4217: `/v1/webhooks/${QueryBench.webhookId[4217]}/deliveries`,
23
+ 42431: `/v1/webhooks/${QueryBench.webhookId[42431]}/deliveries`,
24
+ },
25
+ queries: ['include=totalCount'],
26
+ })
@@ -0,0 +1,326 @@
1
+ import { Hex } from 'ox'
2
+ import { encodeAbiParameters, encodeEventTopics, getAddress, parseAbiItem } from 'viem'
3
+
4
+ import * as AppWebhooks from './webhooks.js'
5
+
6
+ const sender = `0x${'aa'.repeat(20)}`
7
+ const recipient = `0x${'bb'.repeat(20)}`
8
+ const callTarget = `0x${'cc'.repeat(20)}`
9
+ const token = '0x20c0000000000000000000000000000000000001'
10
+ const transactionHash = `0x${'ef'.repeat(32)}`
11
+ const transferTopic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
12
+ const customTopic = `0x${'12'.repeat(32)}`
13
+ // 2026-01-01T00:00:00Z in seconds.
14
+ const timestampHex = Hex.fromNumber(1_767_225_600)
15
+
16
+ const addressTopic = (address: string) => `0x${'0'.repeat(24)}${address.slice(2)}`
17
+ const oneToken = `0x${(10n ** 18n).toString(16).padStart(64, '0')}`
18
+
19
+ function transaction(overrides: Record<string, unknown> = {}): Record<string, unknown> {
20
+ return {
21
+ blockHash: `0x${'ab'.repeat(32)}`,
22
+ blockNumber: '0x7b',
23
+ calls: [{ data: '0x', input: '0x', to: callTarget, value: '0x0' }],
24
+ from: sender,
25
+ gas: '0x5208',
26
+ hash: transactionHash,
27
+ input: `0xa9059cbb${'00'.repeat(8)}`,
28
+ nonce: '0x1',
29
+ to: recipient,
30
+ transactionIndex: '0x0',
31
+ type: '0x2',
32
+ value: `0x${(10n ** 18n).toString(16)}`,
33
+ ...overrides,
34
+ }
35
+ }
36
+
37
+ function transferLog(overrides: Record<string, unknown> = {}) {
38
+ return {
39
+ address: token,
40
+ blockNumber: '0x7b',
41
+ data: oneToken,
42
+ logIndex: '0x4',
43
+ topics: [transferTopic, addressTopic(sender), addressTopic(recipient)],
44
+ transactionHash,
45
+ transactionIndex: '0x0',
46
+ ...overrides,
47
+ } as never
48
+ }
49
+
50
+ function customLog(overrides: Record<string, unknown> = {}) {
51
+ return {
52
+ address: callTarget,
53
+ blockNumber: '0x7b',
54
+ data: `0x${'0'.repeat(63)}1`,
55
+ logIndex: '0x7',
56
+ topics: [customTopic, addressTopic(sender)],
57
+ transactionHash,
58
+ transactionIndex: '0x0',
59
+ ...overrides,
60
+ } as never
61
+ }
62
+
63
+ function block(overrides: Record<string, unknown> = {}): Record<string, unknown> {
64
+ return {
65
+ gasLimit: '0x1c9c380',
66
+ gasUsed: '0x5208',
67
+ hash: `0x${'ab'.repeat(32)}`,
68
+ miner: `0x${'12'.repeat(20)}`,
69
+ number: '0x7b',
70
+ parentHash: `0x${'cd'.repeat(32)}`,
71
+ timestamp: timestampHex,
72
+ transactions: [transaction()],
73
+ ...overrides,
74
+ }
75
+ }
76
+
77
+ function subscription(eventType: AppWebhooks.EventType, filters: Record<string, unknown> = {}) {
78
+ return { eventType, filters, id: `wh_${eventType}` }
79
+ }
80
+
81
+ describe('scanBlock', () => {
82
+ test('delivers block, transaction, log, and transfer rows from one block', () => {
83
+ const result = AppWebhooks.scanBlock({
84
+ block: block(),
85
+ logs: [transferLog(), customLog()],
86
+ subscriptions: [
87
+ subscription('block:created'),
88
+ subscription('transaction:included', { from: sender }),
89
+ subscription('log:emitted', { address: callTarget, topic0: customTopic }),
90
+ subscription('token:transfer', { token }),
91
+ ],
92
+ })
93
+
94
+ expect(result.invalid).toEqual([])
95
+ expect(
96
+ result.matches.map(({ blockNumber, logIndex, subscription }) => ({
97
+ blockNumber,
98
+ logIndex,
99
+ subscription: subscription.id,
100
+ })),
101
+ ).toMatchInlineSnapshot(`
102
+ [
103
+ {
104
+ "blockNumber": 123,
105
+ "logIndex": 0,
106
+ "subscription": "wh_block:created",
107
+ },
108
+ {
109
+ "blockNumber": 123,
110
+ "logIndex": 0,
111
+ "subscription": "wh_transaction:included",
112
+ },
113
+ {
114
+ "blockNumber": 123,
115
+ "logIndex": 7,
116
+ "subscription": "wh_log:emitted",
117
+ },
118
+ {
119
+ "blockNumber": 123,
120
+ "logIndex": 4,
121
+ "subscription": "wh_token:transfer",
122
+ },
123
+ ]
124
+ `)
125
+
126
+ const [blockRow, txRow, logRow, transferRow] = result.matches.map((match) => match.data)
127
+ expect(blockRow).toMatchObject({
128
+ gasUsed: 21000,
129
+ miner: `0x${'12'.repeat(20)}`,
130
+ number: 123,
131
+ timestamp: '2026-01-01T00:00:00.000Z',
132
+ transactionCount: 1,
133
+ })
134
+ expect(txRow).toMatchObject({
135
+ hash: transactionHash,
136
+ id: transactionHash,
137
+ timestamp: '2026-01-01T00:00:00.000Z',
138
+ })
139
+ expect(logRow).toMatchObject({
140
+ address: callTarget,
141
+ logIndex: 7,
142
+ topics: [customTopic, addressTopic(sender)],
143
+ })
144
+ expect(transferRow).toMatchObject({
145
+ address: token,
146
+ amount: (10n ** 18n).toString(),
147
+ recipient,
148
+ sender,
149
+ timestamp: '2026-01-01T00:00:00.000Z',
150
+ })
151
+ })
152
+
153
+ test('evaluates every filter operator against transactions', () => {
154
+ const cases: readonly [Record<string, unknown>, boolean][] = [
155
+ [{ from: sender }, true],
156
+ [{ from: { eq: recipient } }, false],
157
+ [{ from: { not: sender } }, false],
158
+ [{ from: { not: recipient } }, true],
159
+ [{ to: { in: [callTarget, recipient] } }, true],
160
+ [{ to: { in: [callTarget] } }, true], // inner call target via includeCalls
161
+ [{ to: { in: [callTarget] }, includeCalls: false }, false],
162
+ [{ value: { gt: '0xde0b6b3a763ffff' } }, true],
163
+ [{ value: { gte: `0x${(10n ** 18n).toString(16)}` } }, true],
164
+ [{ value: { lt: '0x1' } }, false],
165
+ [{ value: { lte: `0x${(10n ** 18n).toString(16)}` } }, true],
166
+ [{ gasLimit: { gte: '0x5208' } }, true],
167
+ [{ nonce: { lte: '0x0' } }, false],
168
+ [{ input: { selector: '0xa9059cbb' } }, true],
169
+ [{ input: { startsWith: '0xa905' } }, true],
170
+ [{ hash: transactionHash }, true],
171
+ [{ calls: { to: callTarget } }, true],
172
+ [{ txType: '0x2' }, true],
173
+ [{ blockNumber: { eq: '0x7b' } }, true],
174
+ [{ timestamp: { gte: timestampHex } }, true],
175
+ ]
176
+
177
+ for (const [filters, expected] of cases) {
178
+ const result = AppWebhooks.scanBlock({
179
+ block: block(),
180
+ logs: [],
181
+ subscriptions: [subscription('transaction:included', filters)],
182
+ })
183
+ expect(result.invalid, JSON.stringify(filters)).toEqual([])
184
+ expect(result.matches.length, JSON.stringify(filters)).toBe(expected ? 1 : 0)
185
+ }
186
+ })
187
+
188
+ test('matches decoded event arguments and raw topics for logs', () => {
189
+ const signature = 'event Transfer(address indexed from, address indexed to, uint256 value)'
190
+ const scan = (filters: Record<string, unknown>) =>
191
+ AppWebhooks.scanBlock({
192
+ block: block(),
193
+ logs: [transferLog()],
194
+ subscriptions: [subscription('log:emitted', filters)],
195
+ })
196
+
197
+ const decoded = scan({ args: { from: sender }, signature })
198
+ expect(decoded.invalid).toEqual([])
199
+ expect(decoded.matches).toHaveLength(1)
200
+ expect(decoded.matches[0]?.data).toMatchObject({
201
+ // Decoding checksums addresses.
202
+ args: { from: getAddress(sender), to: getAddress(recipient), value: (10n ** 18n).toString() },
203
+ event: { signature, topic0: transferTopic },
204
+ })
205
+
206
+ expect(scan({ args: { from: recipient }, signature }).matches).toHaveLength(0)
207
+ expect(scan({ topic0: transferTopic, topic1: addressTopic(sender) }).matches).toHaveLength(1)
208
+ expect(
209
+ scan({
210
+ topic0: transferTopic,
211
+ topic1: addressTopic(sender),
212
+ topic2: { not: addressTopic(recipient) },
213
+ }).matches,
214
+ ).toHaveLength(0)
215
+ expect(scan({ address: token, topic0: transferTopic }).matches).toHaveLength(1)
216
+
217
+ // An args name absent from the ABI invalidates only that subscription.
218
+ const unknownArg = AppWebhooks.scanBlock({
219
+ block: block(),
220
+ logs: [transferLog()],
221
+ subscriptions: [
222
+ subscription('log:emitted', { args: { bogus: sender }, signature }),
223
+ subscription('token:transfer', { token }),
224
+ ],
225
+ })
226
+ expect(unknownArg.invalid).toHaveLength(1)
227
+ expect(unknownArg.matches).toHaveLength(1)
228
+
229
+ // Arg names fail closed even when the block has no decodable logs.
230
+ const unknownArgEmpty = AppWebhooks.scanBlock({
231
+ block: block(),
232
+ logs: [],
233
+ subscriptions: [subscription('log:emitted', { args: { bogus: sender }, signature })],
234
+ })
235
+ expect(unknownArgEmpty.invalid).toHaveLength(1)
236
+
237
+ const invalid = scan({ signature: 'not a signature' })
238
+ expect(invalid.matches).toHaveLength(0)
239
+ expect(invalid.invalid).toHaveLength(1)
240
+ })
241
+
242
+ test('rolls back partial matches when a matcher throws mid-block', () => {
243
+ const signature = 'event Named(string value)'
244
+ const namedTopic = encodeEventTopics({ abi: [parseAbiItem(signature)] })[0]
245
+ const namedLog = (value: string, logIndex: string) =>
246
+ ({
247
+ address: callTarget,
248
+ blockNumber: '0x7b',
249
+ data: encodeAbiParameters([{ name: 'value', type: 'string' }], [value]),
250
+ logIndex,
251
+ topics: [namedTopic],
252
+ transactionHash,
253
+ transactionIndex: '0x0',
254
+ }) as never
255
+
256
+ // The first log matches the numeric comparison; the second's nonnumeric
257
+ // string makes the matcher throw after that match was recorded.
258
+ const result = AppWebhooks.scanBlock({
259
+ block: block(),
260
+ logs: [namedLog('0x2', '0x1'), namedLog('not numeric', '0x2'), transferLog()],
261
+ subscriptions: [
262
+ subscription('log:emitted', { args: { value: { gt: '0x1' } }, signature }),
263
+ subscription('token:transfer', { token }),
264
+ ],
265
+ })
266
+ expect(result.invalid).toHaveLength(1)
267
+ expect(result.invalid[0]?.subscription.id).toBe('wh_log:emitted')
268
+ expect(result.matches.map((match) => match.subscription.id)).toEqual(['wh_token:transfer'])
269
+ })
270
+
271
+ test('matches transfers on each side and ignores non-transfer logs', () => {
272
+ const scan = (filters: Record<string, unknown>, logs = [transferLog()]) =>
273
+ AppWebhooks.scanBlock({
274
+ block: block(),
275
+ logs,
276
+ subscriptions: [subscription('token:transfer', filters)],
277
+ }).matches
278
+
279
+ expect(scan({ sender })).toHaveLength(1)
280
+ expect(scan({ recipient })).toHaveLength(1)
281
+ expect(scan({ address: sender })).toHaveLength(1)
282
+ expect(scan({ address: callTarget })).toHaveLength(0)
283
+ expect(scan({ token: '0x20c0000000000000000000000000000000000002' })).toHaveLength(0)
284
+ // Outside the TIP-20 range and malformed topics both fail decoding.
285
+ expect(scan({}, [transferLog({ address: callTarget })])).toHaveLength(0)
286
+ expect(scan({}, [transferLog({ topics: [transferTopic] })])).toHaveLength(0)
287
+ })
288
+
289
+ test('fails closed on invalid filters and unparsable blocks', () => {
290
+ const mixed = AppWebhooks.scanBlock({
291
+ block: block(),
292
+ logs: [transferLog()],
293
+ subscriptions: [
294
+ subscription('token:transfer', { token: 123 }),
295
+ subscription('token:transfer', { token }),
296
+ ],
297
+ })
298
+ expect(mixed.invalid).toHaveLength(1)
299
+ expect(mixed.invalid[0]?.subscription.filters).toEqual({ token: 123 })
300
+ expect(mixed.matches).toHaveLength(1)
301
+
302
+ const unparsable = AppWebhooks.scanBlock({
303
+ block: block({ number: undefined }),
304
+ logs: [transferLog()],
305
+ subscriptions: [subscription('block:created'), subscription('token:transfer', {})],
306
+ })
307
+ expect(unparsable.matches).toEqual([])
308
+ })
309
+
310
+ test('matches blocks on header fields and never on proposer', () => {
311
+ const scan = (filters: Record<string, unknown>) =>
312
+ AppWebhooks.scanBlock({
313
+ block: block(),
314
+ logs: [],
315
+ subscriptions: [subscription('block:created', filters)],
316
+ }).matches
317
+
318
+ expect(scan({})).toHaveLength(1)
319
+ expect(scan({ number: { eq: '0x7b' } })).toHaveLength(1)
320
+ expect(scan({ miner: `0x${'12'.repeat(20)}` })).toHaveLength(1)
321
+ expect(scan({ gasUsed: { gt: '0x5207' } })).toHaveLength(1)
322
+ expect(scan({ gasUsed: { lt: '0x5208' } })).toHaveLength(0)
323
+ expect(scan({ timestamp: { gte: timestampHex } })).toHaveLength(1)
324
+ expect(scan({ proposer: `0x${'12'.repeat(20)}` })).toHaveLength(0)
325
+ })
326
+ })