tapimo 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +10 -7
  2. package/dist/ApiKey.d.ts +3 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +10 -0
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +7 -3
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +9 -2
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +20117 -19183
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +61 -4
  13. package/dist/App.js.map +1 -1
  14. package/dist/admin/App.d.ts +84 -74
  15. package/dist/admin/App.d.ts.map +1 -1
  16. package/dist/admin/apps/api-keys.d.ts +14 -1
  17. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  18. package/dist/admin/apps/api-keys.js +6 -2
  19. package/dist/admin/apps/api-keys.js.map +1 -1
  20. package/dist/admin/apps/organizations.d.ts +2 -0
  21. package/dist/admin/apps/organizations.d.ts.map +1 -1
  22. package/dist/admin/apps/verified-tokens.d.ts +11 -11
  23. package/dist/admin/ui.generated.d.ts +1 -1
  24. package/dist/admin/ui.generated.js +67 -67
  25. package/dist/admin/ui.generated.js.map +1 -1
  26. package/dist/analytics/Analytics.d.ts +2 -0
  27. package/dist/analytics/Analytics.d.ts.map +1 -1
  28. package/dist/analytics/Analytics.js +3 -0
  29. package/dist/analytics/Analytics.js.map +1 -1
  30. package/dist/apps/data/App.d.ts +11696 -13937
  31. package/dist/apps/data/App.d.ts.map +1 -1
  32. package/dist/apps/data/App.js +4 -8
  33. package/dist/apps/data/App.js.map +1 -1
  34. package/dist/apps/data/routes/activities.d.ts +271 -271
  35. package/dist/apps/data/routes/addresses.d.ts +398 -167
  36. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  37. package/dist/apps/data/routes/balances.d.ts +278 -8
  38. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  39. package/dist/apps/data/routes/balances.js +114 -2
  40. package/dist/apps/data/routes/balances.js.map +1 -1
  41. package/dist/apps/data/routes/blocks.d.ts +123 -123
  42. package/dist/apps/data/routes/coingecko.d.ts +170 -170
  43. package/dist/apps/data/routes/earn.d.ts +498 -6
  44. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  45. package/dist/apps/data/routes/earn.js +876 -4
  46. package/dist/apps/data/routes/earn.js.map +1 -1
  47. package/dist/apps/data/routes/exchanges.d.ts +10 -10
  48. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  49. package/dist/apps/data/routes/indexer.d.ts +6 -6
  50. package/dist/apps/data/routes/indexer.js +4 -1
  51. package/dist/apps/data/routes/indexer.js.map +1 -1
  52. package/dist/apps/data/routes/receipts.d.ts +235 -235
  53. package/dist/apps/data/routes/rpc.d.ts +31 -31
  54. package/dist/apps/data/routes/rpc.js +4 -1
  55. package/dist/apps/data/routes/rpc.js.map +1 -1
  56. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  57. package/dist/apps/data/routes/tokens.d.ts +449 -449
  58. package/dist/apps/data/routes/transactions.d.ts +2 -2
  59. package/dist/apps/data/routes/transfers.d.ts +81 -81
  60. package/dist/apps/data/routes/valuation.d.ts +1 -1
  61. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  62. package/dist/apps/data/routes/webhooks.d.ts +9 -9
  63. package/dist/apps/data/routes/zones.d.ts +14 -14
  64. package/dist/apps/funding/Access.d.ts +16 -0
  65. package/dist/apps/funding/Access.d.ts.map +1 -0
  66. package/dist/apps/funding/Access.js +22 -0
  67. package/dist/apps/funding/Access.js.map +1 -0
  68. package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1640 -3217
  69. package/dist/apps/funding/App.d.ts.map +1 -0
  70. package/dist/apps/funding/App.js +61 -0
  71. package/dist/apps/funding/App.js.map +1 -0
  72. package/dist/apps/funding/Inventory.d.ts +5 -0
  73. package/dist/apps/funding/Inventory.d.ts.map +1 -0
  74. package/dist/apps/funding/Inventory.js +25 -0
  75. package/dist/apps/funding/Inventory.js.map +1 -0
  76. package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
  77. package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
  78. package/dist/apps/funding/ProviderAttempts.js +24 -0
  79. package/dist/apps/funding/ProviderAttempts.js.map +1 -0
  80. package/dist/apps/funding/index.d.ts +2 -0
  81. package/dist/apps/funding/index.d.ts.map +1 -0
  82. package/dist/apps/funding/index.js +2 -0
  83. package/dist/apps/funding/index.js.map +1 -0
  84. package/dist/apps/funding/routes/chains.d.ts +169 -0
  85. package/dist/apps/funding/routes/chains.d.ts.map +1 -0
  86. package/dist/apps/funding/routes/chains.js +80 -0
  87. package/dist/apps/funding/routes/chains.js.map +1 -0
  88. package/dist/apps/funding/routes/deposit-addresses.d.ts +1997 -0
  89. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
  90. package/dist/apps/funding/routes/deposit-addresses.js +778 -0
  91. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
  92. package/dist/apps/funding/routes/providers.d.ts +91 -0
  93. package/dist/apps/funding/routes/providers.d.ts.map +1 -0
  94. package/dist/apps/funding/routes/providers.js +56 -0
  95. package/dist/apps/funding/routes/providers.js.map +1 -0
  96. package/dist/apps/funding/routes/quotes.d.ts +426 -0
  97. package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
  98. package/dist/apps/funding/routes/quotes.js +188 -0
  99. package/dist/apps/funding/routes/quotes.js.map +1 -0
  100. package/dist/apps/funding/routes/transfers.d.ts +1999 -0
  101. package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
  102. package/dist/apps/funding/routes/transfers.js +614 -0
  103. package/dist/apps/funding/routes/transfers.js.map +1 -0
  104. package/dist/apps/index.d.ts +1 -0
  105. package/dist/apps/index.d.ts.map +1 -1
  106. package/dist/apps/index.js +1 -0
  107. package/dist/apps/index.js.map +1 -1
  108. package/dist/apps/management/App.d.ts +430 -165
  109. package/dist/apps/management/App.d.ts.map +1 -1
  110. package/dist/apps/management/routes/api-keys.d.ts +298 -21
  111. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  112. package/dist/apps/management/routes/api-keys.js +75 -1
  113. package/dist/apps/management/routes/api-keys.js.map +1 -1
  114. package/dist/apps/management/routes/billing.d.ts +6 -6
  115. package/dist/apps/management/routes/faucet.d.ts +35 -35
  116. package/dist/apps/management/routes/invitations.d.ts +122 -122
  117. package/dist/apps/management/routes/invite-links.d.ts +38 -38
  118. package/dist/apps/management/routes/members.d.ts +69 -69
  119. package/dist/apps/management/routes/orgs.d.ts +99 -99
  120. package/dist/apps/management/routes/projects.d.ts +25 -25
  121. package/dist/apps/management/routes/scopes.d.ts +1 -1
  122. package/dist/apps/management/routes/usage.d.ts +72 -72
  123. package/dist/apps/management/routes/verified-token-requests.d.ts +77 -77
  124. package/dist/apps/management/routes/webhooks.d.ts +128 -128
  125. package/dist/apps/mcp.d.ts +2 -3
  126. package/dist/apps/mcp.d.ts.map +1 -1
  127. package/dist/apps/mcp.js +16 -14
  128. package/dist/apps/mcp.js.map +1 -1
  129. package/dist/apps/metadata.d.ts +4 -0
  130. package/dist/apps/metadata.d.ts.map +1 -1
  131. package/dist/apps/metadata.js +10 -0
  132. package/dist/apps/metadata.js.map +1 -1
  133. package/dist/apps/mpp/App.d.ts +44 -44
  134. package/dist/cloudflare.d.ts +50 -0
  135. package/dist/cloudflare.d.ts.map +1 -1
  136. package/dist/cloudflare.js +178 -0
  137. package/dist/cloudflare.js.map +1 -1
  138. package/dist/db/Db.d.ts +2 -0
  139. package/dist/db/Db.d.ts.map +1 -1
  140. package/dist/db/Db.js +45 -0
  141. package/dist/db/Db.js.map +1 -1
  142. package/dist/db/Schema.d.ts +12 -0
  143. package/dist/db/Schema.d.ts.map +1 -1
  144. package/dist/db/Schema.js +20 -0
  145. package/dist/db/Schema.js.map +1 -1
  146. package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
  147. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  148. package/dist/db/tables/fundingDepositAddresses.js +12 -0
  149. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  150. package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
  151. package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
  152. package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
  153. package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
  154. package/dist/db/tables/fundingDeposits.d.ts +74 -1
  155. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  156. package/dist/db/tables/fundingDeposits.js +126 -0
  157. package/dist/db/tables/fundingDeposits.js.map +1 -1
  158. package/dist/internal/Auth.d.ts +15 -11
  159. package/dist/internal/Auth.d.ts.map +1 -1
  160. package/dist/internal/Auth.js +52 -17
  161. package/dist/internal/Auth.js.map +1 -1
  162. package/dist/internal/Cache.js +1 -1
  163. package/dist/internal/Cache.js.map +1 -1
  164. package/dist/internal/EdgeCache.d.ts +2 -2
  165. package/dist/internal/EdgeCache.d.ts.map +1 -1
  166. package/dist/internal/EdgeCache.js +10 -4
  167. package/dist/internal/EdgeCache.js.map +1 -1
  168. package/dist/internal/IpAllowlist.d.ts +15 -0
  169. package/dist/internal/IpAllowlist.d.ts.map +1 -0
  170. package/dist/internal/IpAllowlist.js +27 -0
  171. package/dist/internal/IpAllowlist.js.map +1 -0
  172. package/dist/internal/OpenApi.d.ts +1 -1
  173. package/dist/internal/OpenApi.js +1 -1
  174. package/dist/internal/OpenApi.js.map +1 -1
  175. package/dist/internal/Tidx.d.ts +6 -0
  176. package/dist/internal/Tidx.d.ts.map +1 -1
  177. package/dist/internal/Tidx.js +13 -3
  178. package/dist/internal/Tidx.js.map +1 -1
  179. package/dist/internal/Viem.d.ts +8 -0
  180. package/dist/internal/Viem.d.ts.map +1 -1
  181. package/dist/internal/Viem.js +12 -5
  182. package/dist/internal/Viem.js.map +1 -1
  183. package/dist/internal/funding/Deposit.d.ts +42 -0
  184. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  185. package/dist/internal/funding/Deposit.js +37 -5
  186. package/dist/internal/funding/Deposit.js.map +1 -1
  187. package/dist/internal/funding/Evidence.d.ts +1 -5
  188. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  189. package/dist/internal/funding/Evidence.js +4 -3
  190. package/dist/internal/funding/Evidence.js.map +1 -1
  191. package/dist/internal/funding/Provider.d.ts +102 -0
  192. package/dist/internal/funding/Provider.d.ts.map +1 -1
  193. package/dist/internal/funding/Provider.js +5 -0
  194. package/dist/internal/funding/Provider.js.map +1 -1
  195. package/dist/internal/funding/Reconciliation.d.ts +16 -2
  196. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  197. package/dist/internal/funding/Reconciliation.js +348 -54
  198. package/dist/internal/funding/Reconciliation.js.map +1 -1
  199. package/dist/internal/funding/SourceObservation.d.ts +108 -0
  200. package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
  201. package/dist/internal/funding/SourceObservation.js +143 -0
  202. package/dist/internal/funding/SourceObservation.js.map +1 -0
  203. package/dist/internal/funding/Subsidy.d.ts +2 -2
  204. package/dist/internal/funding/Subsidy.d.ts.map +1 -1
  205. package/dist/internal/funding/Subsidy.js +1 -1
  206. package/dist/internal/funding/Subsidy.js.map +1 -1
  207. package/dist/internal/funding/index.d.ts +1 -0
  208. package/dist/internal/funding/index.d.ts.map +1 -1
  209. package/dist/internal/funding/index.js +1 -0
  210. package/dist/internal/funding/index.js.map +1 -1
  211. package/dist/internal/funding/providers/relay.d.ts +43 -1
  212. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  213. package/dist/internal/funding/providers/relay.js +438 -27
  214. package/dist/internal/funding/providers/relay.js.map +1 -1
  215. package/package.json +3 -3
  216. package/src/ApiKey.test.ts +35 -0
  217. package/src/ApiKey.ts +27 -0
  218. package/src/ApiKeys.test.ts +29 -0
  219. package/src/ApiKeys.ts +11 -3
  220. package/src/App.test.ts +55 -5
  221. package/src/App.ts +79 -5
  222. package/src/Client.test-d.ts +203 -28
  223. package/src/admin/apps/api-keys.test.ts +25 -1
  224. package/src/admin/apps/api-keys.ts +6 -2
  225. package/src/admin/ui.generated.ts +67 -67
  226. package/src/analytics/Analytics.ts +4 -0
  227. package/src/apps/composition.test.ts +130 -3
  228. package/src/apps/data/App.test.ts +0 -12
  229. package/src/apps/data/App.ts +4 -8
  230. package/src/apps/data/routes/balances.test.ts +42 -0
  231. package/src/apps/data/routes/balances.ts +218 -85
  232. package/src/apps/data/routes/earn.test.ts +363 -0
  233. package/src/apps/data/routes/earn.ts +1141 -3
  234. package/src/apps/data/routes/indexer.ts +2 -1
  235. package/src/apps/data/routes/rpc.ts +2 -0
  236. package/src/apps/data/routes/tokens.test.ts +6 -8
  237. package/src/apps/funding/Access.ts +26 -0
  238. package/src/apps/funding/App.test.ts +78 -0
  239. package/src/apps/funding/App.ts +78 -0
  240. package/src/apps/funding/Inventory.ts +30 -0
  241. package/src/apps/funding/ProviderAttempts.ts +26 -0
  242. package/src/apps/funding/index.ts +1 -0
  243. package/src/apps/funding/routes/chains.test.ts +78 -0
  244. package/src/apps/funding/routes/chains.ts +101 -0
  245. package/src/apps/funding/routes/deposit-addresses.test.ts +1649 -0
  246. package/src/apps/funding/routes/deposit-addresses.ts +932 -0
  247. package/src/apps/funding/routes/providers.test.ts +165 -0
  248. package/src/apps/funding/routes/providers.ts +73 -0
  249. package/src/apps/funding/routes/quotes.test.ts +511 -0
  250. package/src/apps/funding/routes/quotes.ts +205 -0
  251. package/src/apps/funding/routes/transfers.test.ts +1236 -0
  252. package/src/apps/funding/routes/transfers.ts +704 -0
  253. package/src/apps/index.ts +1 -0
  254. package/src/apps/management/routes/api-keys.test.ts +98 -0
  255. package/src/apps/management/routes/api-keys.ts +96 -1
  256. package/src/apps/mcp.test.ts +17 -2
  257. package/src/apps/mcp.ts +18 -13
  258. package/src/apps/metadata.ts +12 -0
  259. package/src/apps/mpp/internal/Policy.test.ts +7 -1
  260. package/src/cloudflare.ts +279 -0
  261. package/src/db/Db.ts +71 -0
  262. package/src/db/Schema.ts +24 -0
  263. package/src/db/tables/fundingDepositAddresses.ts +28 -0
  264. package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
  265. package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
  266. package/src/db/tables/fundingDeposits.test.ts +239 -0
  267. package/src/db/tables/fundingDeposits.ts +214 -1
  268. package/src/internal/Auth.test.ts +205 -5
  269. package/src/internal/Auth.ts +63 -19
  270. package/src/internal/Cache.test.ts +37 -3
  271. package/src/internal/Cache.ts +1 -1
  272. package/src/internal/EdgeCache.test.ts +7 -7
  273. package/src/internal/EdgeCache.ts +13 -4
  274. package/src/internal/IpAllowlist.test.ts +52 -0
  275. package/src/internal/IpAllowlist.ts +39 -0
  276. package/src/internal/Log.test.ts +7 -1
  277. package/src/internal/OpenApi.ts +1 -1
  278. package/src/internal/Tidx.test.ts +28 -0
  279. package/src/internal/Tidx.ts +19 -3
  280. package/src/internal/Viem.test.ts +28 -0
  281. package/src/internal/Viem.ts +21 -5
  282. package/src/internal/funding/Deposit.ts +74 -5
  283. package/src/internal/funding/Evidence.test.ts +2 -2
  284. package/src/internal/funding/Evidence.ts +4 -9
  285. package/src/internal/funding/Provider.ts +115 -0
  286. package/src/internal/funding/Reconciliation.local.test.ts +7 -0
  287. package/src/internal/funding/Reconciliation.test.ts +1364 -109
  288. package/src/internal/funding/Reconciliation.ts +467 -88
  289. package/src/internal/funding/SourceObservation.test.ts +301 -0
  290. package/src/internal/funding/SourceObservation.ts +267 -0
  291. package/src/internal/funding/Subsidy.ts +2 -2
  292. package/src/internal/funding/index.test.ts +1 -0
  293. package/src/internal/funding/index.ts +1 -0
  294. package/src/internal/funding/providers/relay.test.ts +94 -4
  295. package/src/internal/funding/providers/relay.ts +533 -27
  296. package/dist/apps/data/routes/funding.d.ts.map +0 -1
  297. package/dist/apps/data/routes/funding.js +0 -1557
  298. package/dist/apps/data/routes/funding.js.map +0 -1
  299. package/src/apps/data/routes/funding.test.ts +0 -2994
  300. package/src/apps/data/routes/funding.ts +0 -1821
@@ -0,0 +1,301 @@
1
+ import * as NodeBuffer from 'node:buffer'
2
+
3
+ import { AbiEvent } from 'ox'
4
+
5
+ import * as FundingDeposits from '../../db/tables/fundingDeposits.js'
6
+ import * as TestApp from '../../../test/App.js'
7
+ import * as TestFunding from '../../../test/Funding.js'
8
+ import * as TestRelay from '../../../test/Relay.js'
9
+ import * as DepositAddress from './DepositAddress.js'
10
+ import * as Reconciliation from './Reconciliation.js'
11
+ import * as SourceObservation from './SourceObservation.js'
12
+ import * as Relay from './providers/relay.js'
13
+
14
+ const transferEvent = AbiEvent.from(
15
+ 'event Transfer(address indexed from, address indexed to, uint256 value)',
16
+ )
17
+
18
+ describe('scan', () => {
19
+ test('finds confirmed transfers into active routes', async () => {
20
+ const route = TestFunding.transferSnapshot()
21
+ const address = `0x${'44'.repeat(20)}`
22
+ const sender = `0x${'55'.repeat(20)}`
23
+ const transactionHash = `0x${'aa'.repeat(32)}`
24
+ const requests: unknown[] = []
25
+ const server = await TestRelay.createServer(async (request, response) => {
26
+ const chunks: Uint8Array[] = []
27
+ for await (const chunk of request) chunks.push(chunk)
28
+ const body = JSON.parse(NodeBuffer.Buffer.concat(chunks).toString()) as {
29
+ id: number
30
+ method: string
31
+ }
32
+ requests.push(body)
33
+ const result =
34
+ body.method === 'eth_blockNumber'
35
+ ? '0x10'
36
+ : [
37
+ {
38
+ address: route.sourceToken.address,
39
+ blockHash: `0x${'bb'.repeat(32)}`,
40
+ blockNumber: '0x10',
41
+ data: `0x${1_000_000n.toString(16).padStart(64, '0')}`,
42
+ logIndex: '0x2',
43
+ removed: false,
44
+ topics: [
45
+ AbiEvent.getSelector(transferEvent),
46
+ topicAddress(sender),
47
+ topicAddress(address),
48
+ ],
49
+ transactionHash,
50
+ transactionIndex: '0x0',
51
+ },
52
+ ]
53
+ response.writeHead(200, { 'content-type': 'application/json' })
54
+ response.end(JSON.stringify({ id: body.id, jsonrpc: '2.0', result }))
55
+ })
56
+
57
+ try {
58
+ const result = await SourceObservation.scan({
59
+ chainId: route.sourceChain.id,
60
+ routes: [{ address, id: 'fda_source_observation' }],
61
+ rpcUrls: [server.url],
62
+ tokenAddress: route.sourceToken.address,
63
+ })
64
+
65
+ expect({ requests, result }).toMatchInlineSnapshot(`
66
+ {
67
+ "requests": [
68
+ {
69
+ "id": 0,
70
+ "jsonrpc": "2.0",
71
+ "method": "eth_blockNumber",
72
+ },
73
+ {
74
+ "id": 1,
75
+ "jsonrpc": "2.0",
76
+ "method": "eth_getLogs",
77
+ "params": [
78
+ {
79
+ "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
80
+ "fromBlock": "0x1",
81
+ "toBlock": "0x10",
82
+ "topics": [
83
+ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
84
+ null,
85
+ [
86
+ "0x0000000000000000000000004444444444444444444444444444444444444444",
87
+ ],
88
+ ],
89
+ },
90
+ ],
91
+ },
92
+ ],
93
+ "result": {
94
+ "cursor": 16n,
95
+ "head": 16n,
96
+ "observations": [
97
+ {
98
+ "addressId": "fda_source_observation",
99
+ "amount": "1000000",
100
+ "blockNumber": 16n,
101
+ "chainId": "eip155:8453",
102
+ "sender": "0x5555555555555555555555555555555555555555",
103
+ "tokenAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
104
+ "transactionHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
105
+ "transferIndex": 2,
106
+ },
107
+ ],
108
+ },
109
+ }
110
+ `)
111
+ } finally {
112
+ await server.closeAsync()
113
+ }
114
+ })
115
+ })
116
+
117
+ describe('observe', () => {
118
+ test('creates one chain-detected deposit and attaches the Relay request later', async () => {
119
+ const database = TestApp.databaseFactory()
120
+ const route = TestFunding.transferSnapshot()
121
+ const providerAddress = `0x${'44'.repeat(20)}`
122
+ const sender = `0x${'55'.repeat(20)}`
123
+ const sourceTransactionHash = `0x${'aa'.repeat(32)}`
124
+ const address = await DepositAddress.create(database(), {
125
+ apiKeyId: 'key_source_observation',
126
+ deliveryStrategy: 'provider',
127
+ environment: 'production',
128
+ id: 'fda_source_observation',
129
+ now: new Date('2026-01-01T00:00:00.000Z'),
130
+ orgId: 'org_source_observation',
131
+ providerOutputToken: route.destinationToken,
132
+ snapshot: {
133
+ address: providerAddress,
134
+ destinationChain: route.destinationChain,
135
+ destinationToken: route.destinationToken,
136
+ provider: route.provider,
137
+ recipient: route.recipient,
138
+ refundAddress: `0x${'66'.repeat(20)}`,
139
+ sourceChain: route.sourceChain,
140
+ sourceToken: route.sourceToken,
141
+ subsidize: false,
142
+ },
143
+ })
144
+ const input = {
145
+ addressId: address.id,
146
+ amount: '1000000',
147
+ chainId: route.sourceChain.id,
148
+ now: new Date('2026-01-01T00:00:01.000Z'),
149
+ recipient: providerAddress,
150
+ sender,
151
+ tokenAddress: route.sourceToken.address,
152
+ transactionHash: sourceTransactionHash,
153
+ transferIndex: 2,
154
+ }
155
+ const [first, second] = await Promise.all([
156
+ SourceObservation.observe(database(), input),
157
+ SourceObservation.observe(database(), input),
158
+ ])
159
+ const routes = await SourceObservation.listRoutes(database(), {
160
+ providerId: 'relay',
161
+ sourceChainId: route.sourceChain.id,
162
+ sourceTokenKey: route.sourceToken.tokenKey,
163
+ })
164
+ const server = await TestRelay.createServer((request, response) => {
165
+ request.resume()
166
+ response.writeHead(200, { 'content-type': 'application/json' })
167
+ response.end(JSON.stringify({ requests: [] }))
168
+ })
169
+ const dispatches: Parameters<Reconciliation.createTracker.Options['dispatch']>[] = []
170
+
171
+ try {
172
+ const tracker = Reconciliation.createTracker({
173
+ db: database,
174
+ dispatch: async (...parameters) => {
175
+ dispatches.push(parameters)
176
+ },
177
+ providers: [Relay.relay({ apiKey: 'relay-api-key', baseUrl: server.url })],
178
+ verifyTransfers: async () => [],
179
+ })
180
+ const pending = await tracker.reconcile({
181
+ addressId: address.id,
182
+ trigger: 'chain',
183
+ type: 'funding:deposit-address:reconcile',
184
+ })
185
+ const reconciled = await Reconciliation.reconcileRequest(database(), {
186
+ address,
187
+ request: {
188
+ createdAt: '2026-01-01T00:00:02.000Z',
189
+ destinationTransactionHashes: [],
190
+ id: 'relay-source-request',
191
+ sourceTransactionHashes: [sourceTransactionHash],
192
+ status: 'pending',
193
+ updatedAt: '2026-01-01T00:00:03.000Z',
194
+ },
195
+ trigger: 'chain',
196
+ verifyTransfers: async () => [
197
+ {
198
+ amount: '1000000',
199
+ sender,
200
+ transactionHash: sourceTransactionHash,
201
+ transferIndex: 2,
202
+ },
203
+ ],
204
+ })
205
+ const [record] = await FundingDeposits.listBySourceTransaction(database(), {
206
+ depositAddressId: address.id,
207
+ sourceTransactionHash,
208
+ })
209
+
210
+ expect({
211
+ dispatches,
212
+ outcomes: [first.type, second.type].toSorted(),
213
+ pending,
214
+ reconciled,
215
+ record: record
216
+ ? {
217
+ detectionTrigger: record.detectionTrigger,
218
+ providerRequestId: record.providerRequestId,
219
+ providerRequestIds: record.providerRequestIds,
220
+ providerTransferIndex: record.providerTransferIndex,
221
+ sourceAmount: record.snapshot.sourceAmount,
222
+ sourceTransferIndex: record.sourceTransferIndex,
223
+ status: record.status,
224
+ }
225
+ : undefined,
226
+ routes,
227
+ sameDeposit: (() => {
228
+ if (first.type === 'ignored' || second.type === 'ignored') return false
229
+ return first.record.id === second.record.id
230
+ })(),
231
+ }).toMatchInlineSnapshot(
232
+ {
233
+ dispatches: [[{ addressId: expect.any(String) }, { delaySeconds: 1 }]],
234
+ },
235
+ `
236
+ {
237
+ "dispatches": [
238
+ [
239
+ {
240
+ "addressId": Any<String>,
241
+ "followUp": {
242
+ "attempt": 0,
243
+ "kind": "provider",
244
+ },
245
+ "trigger": "chain",
246
+ "type": "funding:deposit-address:reconcile",
247
+ },
248
+ {
249
+ "delaySeconds": 1,
250
+ },
251
+ ],
252
+ ],
253
+ "outcomes": [
254
+ "created",
255
+ "existing",
256
+ ],
257
+ "pending": {
258
+ "created": 0,
259
+ "type": "completed",
260
+ "updated": 0,
261
+ },
262
+ "reconciled": {
263
+ "created": 0,
264
+ "terminal": false,
265
+ "updated": 1,
266
+ },
267
+ "record": {
268
+ "detectionTrigger": "chain",
269
+ "providerRequestId": "relay-source-request",
270
+ "providerRequestIds": [
271
+ "relay-source-request",
272
+ ],
273
+ "providerTransferIndex": 0,
274
+ "sourceAmount": {
275
+ "baseUnits": "1000000",
276
+ "currency": "USD",
277
+ "decimals": 6,
278
+ "formatted": "1",
279
+ },
280
+ "sourceTransferIndex": 2,
281
+ "status": "bridging",
282
+ },
283
+ "routes": [
284
+ {
285
+ "address": "0x4444444444444444444444444444444444444444",
286
+ "id": "fda_source_observation",
287
+ },
288
+ ],
289
+ "sameDeposit": true,
290
+ }
291
+ `,
292
+ )
293
+ } finally {
294
+ await server.closeAsync()
295
+ }
296
+ })
297
+ })
298
+
299
+ function topicAddress(address: string) {
300
+ return `0x${address.slice(2).toLowerCase().padStart(64, '0')}`
301
+ }
@@ -0,0 +1,267 @@
1
+ import { AbiEvent, Address, RpcTransport } from 'ox'
2
+ import { createClient, custom, fallback } from 'viem'
3
+ import { getBlockNumber, getLogs } from 'viem/actions'
4
+
5
+ import type * as Db from '../../db/Db.js'
6
+ import * as FundingDepositAddresses from '../../db/tables/fundingDepositAddresses.js'
7
+ import * as FundingDeposits from '../../db/tables/fundingDeposits.js'
8
+ import * as Value from '../Value.js'
9
+ import * as Deposit from './Deposit.js'
10
+
11
+ const addressBatchSize = 50
12
+ const defaultBackfillBlocks = 16n
13
+ const defaultConfirmations = 1
14
+ const defaultMaximumBlocks = 16n
15
+ const transferEvent = AbiEvent.from(
16
+ 'event Transfer(address indexed from, address indexed to, uint256 value)',
17
+ )
18
+
19
+ /** Active reusable address watched by one chain-and-token observer shard. */
20
+ export type Route = {
21
+ /** Provider-owned reusable address. */
22
+ address: string
23
+ /** Funding deposit address id. */
24
+ id: string
25
+ }
26
+
27
+ /** Verified source transfer discovered directly from an EVM log. */
28
+ export type Observation = {
29
+ /** Funding deposit address that received the transfer. */
30
+ addressId: string
31
+ /** Source token quantity in base units. */
32
+ amount: string
33
+ /** Confirmed source block containing the transfer. */
34
+ blockNumber: bigint
35
+ /** Source chain CAIP-2 id. */
36
+ chainId: string
37
+ /** Source-chain sender. */
38
+ sender: string
39
+ /** Source token contract. */
40
+ tokenAddress: string
41
+ /** Source transaction hash. */
42
+ transactionHash: string
43
+ /** Transfer log position within the source transaction. */
44
+ transferIndex: number
45
+ }
46
+
47
+ /** Scans confirmed EVM blocks for transfers into active reusable addresses. */
48
+ export async function scan(options: scan.Options): Promise<scan.Result> {
49
+ if (options.routes.length === 0)
50
+ return { cursor: options.cursor ?? 0n, head: options.cursor ?? 0n, observations: [] }
51
+ if (!Address.validate(options.tokenAddress, { strict: false }))
52
+ throw new ConfigurationError('source token address')
53
+ if (options.rpcUrls.length === 0) throw new ConfigurationError('source RPC URL')
54
+
55
+ const client = createClient({
56
+ transport: fallback(
57
+ options.rpcUrls.map((url) => {
58
+ const transport = RpcTransport.fromHttp(url, { fetchFn: options.fetch ?? globalThis.fetch })
59
+ return custom(transport, { retryCount: 0 })
60
+ }),
61
+ ),
62
+ })
63
+ const head = await getBlockNumber(client, { cacheTime: 0 })
64
+ const confirmations = BigInt(options.confirmations ?? defaultConfirmations)
65
+ const confirmed = head >= confirmations ? head - confirmations + 1n : 0n
66
+ const backfillBlocks = options.backfillBlocks ?? defaultBackfillBlocks
67
+ const fromBlock = (() => {
68
+ if (options.cursor !== undefined) return options.cursor + 1n
69
+ if (confirmed >= backfillBlocks) return confirmed - backfillBlocks + 1n
70
+ return 0n
71
+ })()
72
+ if (fromBlock > confirmed)
73
+ return { cursor: options.cursor ?? confirmed, head: confirmed, observations: [] }
74
+ const maximumBlocks = options.maximumBlocks ?? defaultMaximumBlocks
75
+ const toBlock = minimum(confirmed, fromBlock + maximumBlocks - 1n)
76
+ const routesByAddress = new Map(
77
+ options.routes.map((route) => {
78
+ if (!Address.validate(route.address, { strict: false }))
79
+ throw new ConfigurationError('deposit address')
80
+ return [route.address.toLowerCase(), route] as const
81
+ }),
82
+ )
83
+ const addresses = [...routesByAddress.keys()] as Address.Address[]
84
+ const observations: Observation[] = []
85
+ for (let index = 0; index < addresses.length; index += addressBatchSize) {
86
+ const batch = addresses.slice(index, index + addressBatchSize)
87
+ const logs = await getLogs(client, {
88
+ address: options.tokenAddress as Address.Address,
89
+ args: { to: batch },
90
+ event: transferEvent,
91
+ fromBlock,
92
+ strict: true,
93
+ toBlock,
94
+ })
95
+ for (const log of logs) {
96
+ if (log.blockNumber === null || log.logIndex === null || !log.transactionHash) continue
97
+ const event = (() => {
98
+ try {
99
+ return AbiEvent.decode(transferEvent, { data: log.data, topics: log.topics })
100
+ } catch {
101
+ return undefined
102
+ }
103
+ })()
104
+ const route = event ? routesByAddress.get(event.to.toLowerCase()) : undefined
105
+ if (!event || !route) continue
106
+ observations.push({
107
+ addressId: route.id,
108
+ amount: event.value.toString(),
109
+ blockNumber: log.blockNumber,
110
+ chainId: options.chainId,
111
+ sender: event.from.toLowerCase(),
112
+ tokenAddress: options.tokenAddress.toLowerCase(),
113
+ transactionHash: log.transactionHash,
114
+ transferIndex: log.logIndex,
115
+ })
116
+ }
117
+ }
118
+ observations.sort(
119
+ (left, right) =>
120
+ Number(left.blockNumber - right.blockNumber) || left.transferIndex - right.transferIndex,
121
+ )
122
+ return { cursor: toBlock, head: confirmed, observations }
123
+ }
124
+
125
+ export declare namespace scan {
126
+ /** EVM shard state and scan bounds. */
127
+ type Options = {
128
+ /** Initial confirmed-block backfill when no cursor exists. */
129
+ backfillBlocks?: bigint | undefined
130
+ /** Source chain CAIP-2 id. */
131
+ chainId: string
132
+ /** Required block confirmations. */
133
+ confirmations?: number | undefined
134
+ /** Last confirmed block processed by the shard. */
135
+ cursor?: bigint | undefined
136
+ /** Fetch implementation override. */
137
+ fetch?: typeof globalThis.fetch | undefined
138
+ /** Maximum confirmed blocks processed in one invocation. */
139
+ maximumBlocks?: bigint | undefined
140
+ /** Active reusable addresses in the shard. */
141
+ routes: readonly Route[]
142
+ /** Ordered source-chain RPC endpoints. */
143
+ rpcUrls: readonly string[]
144
+ /** Source token contract. */
145
+ tokenAddress: string
146
+ }
147
+
148
+ /** Confirmed cursor and verified transfers produced by one scan. */
149
+ type Result = {
150
+ /** Last confirmed block processed by the shard. */
151
+ cursor: bigint
152
+ /** Latest block satisfying the confirmation policy. */
153
+ head: bigint
154
+ /** Verified transfers into active addresses. */
155
+ observations: readonly Observation[]
156
+ }
157
+ }
158
+
159
+ /** Lists the active Relay addresses assigned to one source observer shard. */
160
+ export async function listRoutes(
161
+ db: Db.Db,
162
+ options: listRoutes.Options,
163
+ ): Promise<readonly Route[]> {
164
+ const records = await FundingDepositAddresses.listActiveSources(db, options)
165
+ return records.map((record) => ({ address: record.address, id: record.id }))
166
+ }
167
+
168
+ export declare namespace listRoutes {
169
+ /** Provider and source asset identifying one observer shard. */
170
+ type Options = {
171
+ /** Funding provider that owns the deposit addresses. */
172
+ providerId: string
173
+ /** Source chain CAIP-2 id. */
174
+ sourceChainId: string
175
+ /** Source token key. */
176
+ sourceTokenKey: string
177
+ }
178
+ }
179
+
180
+ /** Persists one verified source transfer before provider indexing. */
181
+ export async function observe(db: Db.Db, input: observe.Input): Promise<observe.Result> {
182
+ const address = await FundingDepositAddresses.get(db, input.addressId)
183
+ if (
184
+ !address ||
185
+ address.providerId !== 'relay' ||
186
+ address.status !== 'active' ||
187
+ address.snapshot.sourceChain.id !== input.chainId ||
188
+ !Address.isEqual(
189
+ address.snapshot.sourceToken.address as Address.Address,
190
+ input.tokenAddress as Address.Address,
191
+ ) ||
192
+ !Address.isEqual(address.address as Address.Address, input.recipient as Address.Address)
193
+ )
194
+ return { type: 'ignored' }
195
+
196
+ return FundingDeposits.withSourceTransaction(db, {
197
+ depositAddressId: address.id,
198
+ fn: async (tx) => {
199
+ const existing = await FundingDeposits.listBySourceTransaction(tx, {
200
+ depositAddressId: address.id,
201
+ sourceTransactionHash: input.transactionHash,
202
+ })
203
+ const record =
204
+ existing.find((candidate) => candidate.sourceTransferIndex === input.transferIndex) ??
205
+ existing.find((candidate) => candidate.sourceTransferIndex === null)
206
+ if (record) return { record, type: 'existing' as const }
207
+ return Deposit.createFromSource(tx, {
208
+ now: input.now,
209
+ snapshot: {
210
+ ...address.snapshot,
211
+ depositAddressId: address.id,
212
+ sender: input.sender,
213
+ sourceAmount: Value.tokenAmount({
214
+ baseUnits: input.amount,
215
+ currency: address.snapshot.sourceToken.currency,
216
+ decimals: address.snapshot.sourceToken.decimals,
217
+ }),
218
+ sourceTransactionHashes: [input.transactionHash],
219
+ },
220
+ sourceTransactionHash: input.transactionHash,
221
+ sourceTransferIndex: input.transferIndex,
222
+ })
223
+ },
224
+ sourceTransactionHash: input.transactionHash,
225
+ })
226
+ }
227
+
228
+ export declare namespace observe {
229
+ /** Verified source transfer and route identity. */
230
+ type Input = {
231
+ /** Funding deposit address id. */
232
+ addressId: string
233
+ /** Source token quantity in base units. */
234
+ amount: string
235
+ /** Source chain CAIP-2 id. */
236
+ chainId: string
237
+ /** Detection time override. */
238
+ now?: Date | undefined
239
+ /** Provider-owned reusable address that received the transfer. */
240
+ recipient: string
241
+ /** Source-chain sender. */
242
+ sender: string
243
+ /** Source token contract. */
244
+ tokenAddress: string
245
+ /** Source transaction hash. */
246
+ transactionHash: string
247
+ /** Transfer log position within the source transaction. */
248
+ transferIndex: number
249
+ }
250
+
251
+ /** Persistence outcome for one chain-observed source transfer. */
252
+ type Result =
253
+ | { record: FundingDeposits.Record; type: 'created' | 'existing' }
254
+ | { type: 'ignored' }
255
+ }
256
+
257
+ function minimum(left: bigint, right: bigint) {
258
+ return left < right ? left : right
259
+ }
260
+
261
+ class ConfigurationError extends Error {
262
+ override name = 'FundingSourceObservation.ConfigurationError'
263
+
264
+ constructor(field: string) {
265
+ super(`Invalid ${field} configured for funding source observation.`)
266
+ }
267
+ }
@@ -129,7 +129,7 @@ export declare namespace Settler {
129
129
  }
130
130
  }
131
131
 
132
- /** Creates a subsidy settler backed by one dedicated pathUSD account. */
132
+ /** Creates a subsidy settler backed by a Tempo account that holds pathUSD. */
133
133
  export function create(options: create.Options): Settler {
134
134
  const client = options.getClient()
135
135
  return {
@@ -225,7 +225,7 @@ export function create(options: create.Options): Settler {
225
225
  export declare namespace create {
226
226
  /** Dependencies and policy for one subsidy settler. */
227
227
  type Options = {
228
- /** Dedicated account that signs subsidies and holds pathUSD. */
228
+ /** Tempo account that signs subsidies and holds pathUSD. */
229
229
  account: LocalAccount
230
230
  /** Trusted Tempo RPC client resolver. */
231
231
  getClient: Viem.GetClient
@@ -12,6 +12,7 @@ test('exports funding modules', () => {
12
12
  "Provider",
13
13
  "Reconciliation",
14
14
  "Route",
15
+ "SourceObservation",
15
16
  "Subsidy",
16
17
  "Token",
17
18
  "Transfer",
@@ -7,6 +7,7 @@ export * as Evidence from './Evidence.js'
7
7
  export * as Provider from './Provider.js'
8
8
  export * as Reconciliation from './Reconciliation.js'
9
9
  export * as Route from './Route.js'
10
+ export * as SourceObservation from './SourceObservation.js'
10
11
  export * as Subsidy from './Subsidy.js'
11
12
  export * as Token from './Token.js'
12
13
  export * as Transfer from './Transfer.js'