tapimo 0.7.2 → 0.8.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 (294) 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 +20434 -19484
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +62 -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 +168 -168
  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 +10305 -12785
  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 +24 -24
  35. package/dist/apps/data/routes/addresses.d.ts +288 -288
  36. package/dist/apps/data/routes/balances.d.ts +6 -6
  37. package/dist/apps/data/routes/blocks.d.ts +39 -39
  38. package/dist/apps/data/routes/coingecko.d.ts +170 -170
  39. package/dist/apps/data/routes/earn.d.ts +436 -6
  40. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  41. package/dist/apps/data/routes/earn.js +631 -4
  42. package/dist/apps/data/routes/earn.js.map +1 -1
  43. package/dist/apps/data/routes/exchanges.d.ts +10 -10
  44. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  45. package/dist/apps/data/routes/indexer.d.ts +6 -6
  46. package/dist/apps/data/routes/indexer.js +4 -1
  47. package/dist/apps/data/routes/indexer.js.map +1 -1
  48. package/dist/apps/data/routes/receipts.d.ts +235 -235
  49. package/dist/apps/data/routes/rpc.d.ts +1 -1
  50. package/dist/apps/data/routes/rpc.js +4 -1
  51. package/dist/apps/data/routes/rpc.js.map +1 -1
  52. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  53. package/dist/apps/data/routes/tokens.d.ts +6 -6
  54. package/dist/apps/data/routes/transactions.d.ts +2 -2
  55. package/dist/apps/data/routes/transfers.d.ts +1 -1
  56. package/dist/apps/data/routes/valuation.d.ts +1 -1
  57. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  58. package/dist/apps/data/routes/webhooks.d.ts +9 -9
  59. package/dist/apps/data/routes/zones.d.ts +14 -14
  60. package/dist/apps/funding/Access.d.ts +16 -0
  61. package/dist/apps/funding/Access.d.ts.map +1 -0
  62. package/dist/apps/funding/Access.js +22 -0
  63. package/dist/apps/funding/Access.js.map +1 -0
  64. package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1717 -3035
  65. package/dist/apps/funding/App.d.ts.map +1 -0
  66. package/dist/apps/funding/App.js +46 -0
  67. package/dist/apps/funding/App.js.map +1 -0
  68. package/dist/apps/funding/Inventory.d.ts +5 -0
  69. package/dist/apps/funding/Inventory.d.ts.map +1 -0
  70. package/dist/apps/funding/Inventory.js +25 -0
  71. package/dist/apps/funding/Inventory.js.map +1 -0
  72. package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
  73. package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
  74. package/dist/apps/funding/ProviderAttempts.js +24 -0
  75. package/dist/apps/funding/ProviderAttempts.js.map +1 -0
  76. package/dist/apps/funding/index.d.ts +2 -0
  77. package/dist/apps/funding/index.d.ts.map +1 -0
  78. package/dist/apps/funding/index.js +2 -0
  79. package/dist/apps/funding/index.js.map +1 -0
  80. package/dist/apps/funding/routes/chains.d.ts +169 -0
  81. package/dist/apps/funding/routes/chains.d.ts.map +1 -0
  82. package/dist/apps/funding/routes/chains.js +80 -0
  83. package/dist/apps/funding/routes/chains.js.map +1 -0
  84. package/dist/apps/funding/routes/deposit-addresses.d.ts +2388 -0
  85. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
  86. package/dist/apps/funding/routes/deposit-addresses.js +860 -0
  87. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
  88. package/dist/apps/funding/routes/providers.d.ts +91 -0
  89. package/dist/apps/funding/routes/providers.d.ts.map +1 -0
  90. package/dist/apps/funding/routes/providers.js +56 -0
  91. package/dist/apps/funding/routes/providers.js.map +1 -0
  92. package/dist/apps/funding/routes/quotes.d.ts +426 -0
  93. package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
  94. package/dist/apps/funding/routes/quotes.js +188 -0
  95. package/dist/apps/funding/routes/quotes.js.map +1 -0
  96. package/dist/apps/funding/routes/transfers.d.ts +1999 -0
  97. package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
  98. package/dist/apps/funding/routes/transfers.js +614 -0
  99. package/dist/apps/funding/routes/transfers.js.map +1 -0
  100. package/dist/apps/index.d.ts +1 -0
  101. package/dist/apps/index.d.ts.map +1 -1
  102. package/dist/apps/index.js +1 -0
  103. package/dist/apps/index.js.map +1 -1
  104. package/dist/apps/management/App.d.ts +430 -165
  105. package/dist/apps/management/App.d.ts.map +1 -1
  106. package/dist/apps/management/routes/api-keys.d.ts +298 -21
  107. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  108. package/dist/apps/management/routes/api-keys.js +75 -1
  109. package/dist/apps/management/routes/api-keys.js.map +1 -1
  110. package/dist/apps/management/routes/billing.d.ts +6 -6
  111. package/dist/apps/management/routes/faucet.d.ts +35 -35
  112. package/dist/apps/management/routes/invitations.d.ts +48 -48
  113. package/dist/apps/management/routes/invite-links.d.ts +38 -38
  114. package/dist/apps/management/routes/members.d.ts +69 -69
  115. package/dist/apps/management/routes/orgs.d.ts +37 -37
  116. package/dist/apps/management/routes/projects.d.ts +25 -25
  117. package/dist/apps/management/routes/scopes.d.ts +1 -1
  118. package/dist/apps/management/routes/usage.d.ts +72 -72
  119. package/dist/apps/management/routes/verified-token-requests.d.ts +18 -18
  120. package/dist/apps/management/routes/webhooks.d.ts +128 -128
  121. package/dist/apps/mcp.d.ts +2 -3
  122. package/dist/apps/mcp.d.ts.map +1 -1
  123. package/dist/apps/mcp.js +16 -14
  124. package/dist/apps/mcp.js.map +1 -1
  125. package/dist/apps/metadata.d.ts +4 -0
  126. package/dist/apps/metadata.d.ts.map +1 -1
  127. package/dist/apps/metadata.js +10 -0
  128. package/dist/apps/metadata.js.map +1 -1
  129. package/dist/apps/mpp/App.d.ts +2 -2
  130. package/dist/cloudflare.d.ts +50 -0
  131. package/dist/cloudflare.d.ts.map +1 -1
  132. package/dist/cloudflare.js +178 -0
  133. package/dist/cloudflare.js.map +1 -1
  134. package/dist/db/Db.d.ts +2 -0
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +37 -0
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +12 -0
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +20 -0
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
  143. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  144. package/dist/db/tables/fundingDepositAddresses.js +12 -0
  145. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  146. package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
  147. package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
  148. package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
  149. package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
  150. package/dist/db/tables/fundingDeposits.d.ts +57 -1
  151. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  152. package/dist/db/tables/fundingDeposits.js +82 -0
  153. package/dist/db/tables/fundingDeposits.js.map +1 -1
  154. package/dist/internal/Auth.d.ts +11 -11
  155. package/dist/internal/Auth.d.ts.map +1 -1
  156. package/dist/internal/Auth.js +41 -15
  157. package/dist/internal/Auth.js.map +1 -1
  158. package/dist/internal/Cache.js +1 -1
  159. package/dist/internal/Cache.js.map +1 -1
  160. package/dist/internal/EdgeCache.d.ts +2 -2
  161. package/dist/internal/EdgeCache.d.ts.map +1 -1
  162. package/dist/internal/EdgeCache.js +10 -4
  163. package/dist/internal/EdgeCache.js.map +1 -1
  164. package/dist/internal/IpAllowlist.d.ts +15 -0
  165. package/dist/internal/IpAllowlist.d.ts.map +1 -0
  166. package/dist/internal/IpAllowlist.js +27 -0
  167. package/dist/internal/IpAllowlist.js.map +1 -0
  168. package/dist/internal/OpenApi.d.ts +1 -1
  169. package/dist/internal/OpenApi.js +1 -1
  170. package/dist/internal/OpenApi.js.map +1 -1
  171. package/dist/internal/Tidx.d.ts +6 -0
  172. package/dist/internal/Tidx.d.ts.map +1 -1
  173. package/dist/internal/Tidx.js +13 -3
  174. package/dist/internal/Tidx.js.map +1 -1
  175. package/dist/internal/Viem.d.ts +8 -0
  176. package/dist/internal/Viem.d.ts.map +1 -1
  177. package/dist/internal/Viem.js +12 -5
  178. package/dist/internal/Viem.js.map +1 -1
  179. package/dist/internal/funding/Deposit.d.ts +42 -0
  180. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  181. package/dist/internal/funding/Deposit.js +37 -5
  182. package/dist/internal/funding/Deposit.js.map +1 -1
  183. package/dist/internal/funding/Evidence.d.ts +1 -5
  184. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  185. package/dist/internal/funding/Evidence.js +4 -3
  186. package/dist/internal/funding/Evidence.js.map +1 -1
  187. package/dist/internal/funding/Provider.d.ts +102 -0
  188. package/dist/internal/funding/Provider.d.ts.map +1 -1
  189. package/dist/internal/funding/Provider.js +5 -0
  190. package/dist/internal/funding/Provider.js.map +1 -1
  191. package/dist/internal/funding/Reconciliation.d.ts +16 -2
  192. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  193. package/dist/internal/funding/Reconciliation.js +257 -48
  194. package/dist/internal/funding/Reconciliation.js.map +1 -1
  195. package/dist/internal/funding/SourceObservation.d.ts +108 -0
  196. package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
  197. package/dist/internal/funding/SourceObservation.js +143 -0
  198. package/dist/internal/funding/SourceObservation.js.map +1 -0
  199. package/dist/internal/funding/Subsidy.d.ts +2 -2
  200. package/dist/internal/funding/Subsidy.d.ts.map +1 -1
  201. package/dist/internal/funding/Subsidy.js +1 -1
  202. package/dist/internal/funding/Subsidy.js.map +1 -1
  203. package/dist/internal/funding/index.d.ts +1 -0
  204. package/dist/internal/funding/index.d.ts.map +1 -1
  205. package/dist/internal/funding/index.js +1 -0
  206. package/dist/internal/funding/index.js.map +1 -1
  207. package/dist/internal/funding/providers/relay.d.ts +43 -1
  208. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  209. package/dist/internal/funding/providers/relay.js +434 -27
  210. package/dist/internal/funding/providers/relay.js.map +1 -1
  211. package/package.json +3 -3
  212. package/src/ApiKey.test.ts +35 -0
  213. package/src/ApiKey.ts +27 -0
  214. package/src/ApiKeys.test.ts +29 -0
  215. package/src/ApiKeys.ts +11 -3
  216. package/src/App.test.ts +23 -5
  217. package/src/App.ts +76 -5
  218. package/src/Client.test-d.ts +171 -19
  219. package/src/admin/apps/api-keys.test.ts +25 -1
  220. package/src/admin/apps/api-keys.ts +6 -2
  221. package/src/admin/ui.generated.ts +67 -67
  222. package/src/analytics/Analytics.ts +4 -0
  223. package/src/apps/composition.test.ts +100 -2
  224. package/src/apps/data/App.test.ts +0 -12
  225. package/src/apps/data/App.ts +4 -8
  226. package/src/apps/data/routes/earn.test.ts +283 -0
  227. package/src/apps/data/routes/earn.ts +800 -3
  228. package/src/apps/data/routes/indexer.ts +2 -1
  229. package/src/apps/data/routes/rpc.ts +2 -0
  230. package/src/apps/data/routes/tokens.test.ts +6 -8
  231. package/src/apps/funding/Access.ts +26 -0
  232. package/src/apps/funding/App.test.ts +78 -0
  233. package/src/apps/funding/App.ts +63 -0
  234. package/src/apps/funding/Inventory.ts +30 -0
  235. package/src/apps/funding/ProviderAttempts.ts +26 -0
  236. package/src/apps/funding/index.ts +1 -0
  237. package/src/apps/funding/routes/chains.test.ts +78 -0
  238. package/src/apps/funding/routes/chains.ts +101 -0
  239. package/src/apps/funding/routes/deposit-addresses.test.ts +1628 -0
  240. package/src/apps/funding/routes/deposit-addresses.ts +1015 -0
  241. package/src/apps/funding/routes/providers.test.ts +165 -0
  242. package/src/apps/funding/routes/providers.ts +73 -0
  243. package/src/apps/funding/routes/quotes.test.ts +511 -0
  244. package/src/apps/funding/routes/quotes.ts +205 -0
  245. package/src/apps/funding/routes/transfers.test.ts +1255 -0
  246. package/src/apps/funding/routes/transfers.ts +704 -0
  247. package/src/apps/index.ts +1 -0
  248. package/src/apps/management/routes/api-keys.test.ts +98 -0
  249. package/src/apps/management/routes/api-keys.ts +96 -1
  250. package/src/apps/mcp.test.ts +17 -2
  251. package/src/apps/mcp.ts +18 -13
  252. package/src/apps/metadata.ts +12 -0
  253. package/src/apps/mpp/internal/Policy.test.ts +7 -1
  254. package/src/cloudflare.ts +279 -0
  255. package/src/db/Db.ts +62 -0
  256. package/src/db/Schema.ts +24 -0
  257. package/src/db/tables/fundingDepositAddresses.ts +28 -0
  258. package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
  259. package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
  260. package/src/db/tables/fundingDeposits.test.ts +138 -0
  261. package/src/db/tables/fundingDeposits.ts +145 -1
  262. package/src/internal/Auth.test.ts +177 -5
  263. package/src/internal/Auth.ts +48 -18
  264. package/src/internal/Cache.test.ts +37 -3
  265. package/src/internal/Cache.ts +1 -1
  266. package/src/internal/EdgeCache.test.ts +7 -7
  267. package/src/internal/EdgeCache.ts +13 -4
  268. package/src/internal/IpAllowlist.test.ts +52 -0
  269. package/src/internal/IpAllowlist.ts +39 -0
  270. package/src/internal/Log.test.ts +7 -1
  271. package/src/internal/OpenApi.ts +1 -1
  272. package/src/internal/Tidx.test.ts +28 -0
  273. package/src/internal/Tidx.ts +19 -3
  274. package/src/internal/Viem.test.ts +28 -0
  275. package/src/internal/Viem.ts +21 -5
  276. package/src/internal/funding/Deposit.ts +74 -5
  277. package/src/internal/funding/Evidence.test.ts +2 -2
  278. package/src/internal/funding/Evidence.ts +4 -9
  279. package/src/internal/funding/Provider.ts +115 -0
  280. package/src/internal/funding/Reconciliation.local.test.ts +7 -0
  281. package/src/internal/funding/Reconciliation.test.ts +1052 -35
  282. package/src/internal/funding/Reconciliation.ts +328 -56
  283. package/src/internal/funding/SourceObservation.test.ts +301 -0
  284. package/src/internal/funding/SourceObservation.ts +267 -0
  285. package/src/internal/funding/Subsidy.ts +2 -2
  286. package/src/internal/funding/index.test.ts +1 -0
  287. package/src/internal/funding/index.ts +1 -0
  288. package/src/internal/funding/providers/relay.test.ts +94 -4
  289. package/src/internal/funding/providers/relay.ts +528 -27
  290. package/dist/apps/data/routes/funding.d.ts.map +0 -1
  291. package/dist/apps/data/routes/funding.js +0 -1557
  292. package/dist/apps/data/routes/funding.js.map +0 -1
  293. package/src/apps/data/routes/funding.test.ts +0 -2994
  294. package/src/apps/data/routes/funding.ts +0 -1821
@@ -1,9 +1,11 @@
1
1
  import * as NodeBuffer from 'node:buffer'
2
2
 
3
- import { AbiEvent } from 'ox'
3
+ import { AbiEvent, AbiFunction, AbiParameters, Address, Hex } from 'ox'
4
4
 
5
+ import * as Metrics from '../../Metrics.js'
5
6
  import * as FundingCatalog from '../../db/tables/fundingCatalog.js'
6
7
  import * as FundingDepositAddresses from '../../db/tables/fundingDepositAddresses.js'
8
+ import * as FundingDepositRequestObservations from '../../db/tables/fundingDepositRequestObservations.js'
7
9
  import * as FundingDeposits from '../../db/tables/fundingDeposits.js'
8
10
  import * as TestApp from '../../../test/App.js'
9
11
  import * as TestFunding from '../../../test/Funding.js'
@@ -20,7 +22,7 @@ const db = TestApp.database()
20
22
  const transferTopic = AbiEvent.getSelector('event Transfer(address,address,uint256)')
21
23
 
22
24
  describe('createTracker', () => {
23
- test('requires action when Relay completes without destination funds', async () => {
25
+ test('keeps Relay success pending without destination evidence', async () => {
24
26
  const route = TestFunding.transferSnapshot()
25
27
  const depositAddress = `0x${'44'.repeat(20)}`
26
28
  const sourceTransactionHash = `0x${'aa'.repeat(32)}`
@@ -44,6 +46,15 @@ describe('createTracker', () => {
44
46
  subsidize: false,
45
47
  },
46
48
  })
49
+ const counts: Parameters<Metrics.Metrics['count']>[] = []
50
+ const metrics = Metrics.from({
51
+ count(...parameters) {
52
+ counts.push(parameters)
53
+ },
54
+ flush() {},
55
+ gauge() {},
56
+ histogram() {},
57
+ })
47
58
  const server = await TestRelay.createServer(async (request, response) => {
48
59
  if (request.method === 'GET') {
49
60
  response.writeHead(200, { 'content-type': 'application/json' })
@@ -53,7 +64,7 @@ describe('createTracker', () => {
53
64
  {
54
65
  createdAt: '2026-01-01T00:01:00.000Z',
55
66
  data: {
56
- inTxs: [{ hash: providerTransactionHash }],
67
+ inTxs: [{ txHash: providerTransactionHash }],
57
68
  outTxs: [],
58
69
  },
59
70
  depositAddress: { depositTxHash: sourceTransactionHash },
@@ -132,6 +143,7 @@ describe('createTracker', () => {
132
143
  dispatch: async (message) => {
133
144
  messages.push(message)
134
145
  },
146
+ metrics,
135
147
  now: () => new Date('2027-01-01T00:00:00.000Z'),
136
148
  providers: [provider],
137
149
  verifyTransfers: Evidence.createVerifier({
@@ -142,15 +154,32 @@ describe('createTracker', () => {
142
154
  const tick = await tracker.tick()
143
155
  const message = messages[0]
144
156
  if (!message) throw new Error('Expected a reconciliation message.')
145
- const reconciliation = await tracker.reconcile(message)
146
- const duplicate = await tracker.reconcile(message)
157
+ const queuedBeforeAttribution: Provider.Webhook.receive.Queued = {
158
+ addressId: message.addressId,
159
+ pollLeaseVersion: message.pollLeaseVersion,
160
+ type: message.type,
161
+ }
162
+ const reconciliation = await tracker.reconcile(queuedBeforeAttribution)
163
+ const duplicate = await tracker.reconcile(queuedBeforeAttribution)
147
164
  const [record] = await FundingDeposits.listBySourceTransaction(db, {
148
165
  depositAddressId: address.id,
149
166
  sourceTransactionHash,
150
167
  })
168
+ const observation = await FundingDepositRequestObservations.get(db, {
169
+ depositAddressId: address.id,
170
+ providerRequestId: 'relay-child-request',
171
+ })
151
172
  const storedAddress = await FundingDepositAddresses.get(db, address.id)
152
173
 
153
- expect({ duplicate, reconciliation, record, storedAddress, tick }).toMatchInlineSnapshot(
174
+ expect({
175
+ counts,
176
+ duplicate,
177
+ observation,
178
+ reconciliation,
179
+ record,
180
+ storedAddress,
181
+ tick,
182
+ }).toMatchInlineSnapshot(
154
183
  {
155
184
  record: {
156
185
  createdAt: expect.any(String),
@@ -159,6 +188,7 @@ describe('createTracker', () => {
159
188
  snapshot: { depositAddressId: expect.any(String) },
160
189
  updatedAt: expect.any(String),
161
190
  },
191
+ observation: { depositAddressId: expect.any(String) },
162
192
  storedAddress: {
163
193
  id: expect.any(String),
164
194
  providerOutputToken: expect.any(Object),
@@ -167,9 +197,52 @@ describe('createTracker', () => {
167
197
  },
168
198
  `
169
199
  {
200
+ "counts": [
201
+ [
202
+ "funding_deposit_reconciliation_count",
203
+ 1,
204
+ {
205
+ "outcome": "enqueued",
206
+ "provider": "relay",
207
+ "trigger": "poll",
208
+ },
209
+ ],
210
+ [
211
+ "funding_deposit_detection_count",
212
+ 1,
213
+ {
214
+ "provider": "relay",
215
+ "trigger": "poll",
216
+ },
217
+ ],
218
+ [
219
+ "funding_deposit_reconciliation_count",
220
+ 1,
221
+ {
222
+ "outcome": "follow_up_enqueued",
223
+ "provider": "relay",
224
+ "trigger": "poll",
225
+ },
226
+ ],
227
+ [
228
+ "funding_deposit_reconciliation_count",
229
+ 1,
230
+ {
231
+ "outcome": "completed",
232
+ "provider": "relay",
233
+ "trigger": "poll",
234
+ },
235
+ ],
236
+ ],
170
237
  "duplicate": {
171
238
  "type": "busy",
172
239
  },
240
+ "observation": {
241
+ "depositAddressId": Any<String>,
242
+ "pollObservedAt": "2027-01-01T00:00:00.000Z",
243
+ "providerRequestId": "relay-child-request",
244
+ "webhookReceivedAt": null,
245
+ },
173
246
  "reconciliation": {
174
247
  "created": 1,
175
248
  "type": "completed",
@@ -178,9 +251,11 @@ describe('createTracker', () => {
178
251
  "record": {
179
252
  "createdAt": Any<String>,
180
253
  "depositAddressId": Any<String>,
254
+ "detectionTrigger": "poll",
181
255
  "environment": "production",
182
256
  "id": Any<String>,
183
257
  "orgId": "org_reconciliation",
258
+ "pollObservedAt": "2027-01-01T00:00:00.000Z",
184
259
  "projectId": null,
185
260
  "providerOutputAmount": null,
186
261
  "providerOutputToken": {
@@ -258,15 +333,13 @@ describe('createTracker', () => {
258
333
  "sourceChainId": "eip155:8453",
259
334
  "sourceTransactionHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
260
335
  "sourceTransferIndex": 0,
261
- "status": "action-required",
262
- "statusReason": {
263
- "code": "delivery_failed",
264
- "message": "The provider completed without delivering destination funds.",
265
- },
336
+ "status": "bridging",
337
+ "statusReason": null,
266
338
  "subsidyAmount": null,
267
339
  "tempoGasPaid": null,
268
340
  "updatedAt": Any<String>,
269
341
  "version": 3,
342
+ "webhookReceivedAt": null,
270
343
  },
271
344
  "storedAddress": {
272
345
  "address": "0x4444444444444444444444444444444444444444",
@@ -288,7 +361,9 @@ describe('createTracker', () => {
288
361
  "providerRequestIds": [],
289
362
  "providerState": {
290
363
  "reconciliation": {
291
- "pendingRequestIds": [],
364
+ "pendingRequestIds": [
365
+ "relay-child-request",
366
+ ],
292
367
  "requestIds": [
293
368
  "relay-child-request",
294
369
  ],
@@ -403,6 +478,7 @@ describe('createTracker', () => {
403
478
  status: 'success',
404
479
  updatedAt: '2026-01-06T00:02:00.000Z',
405
480
  },
481
+ trigger: 'manual',
406
482
  verifyTransfers: Evidence.createVerifier({ db }),
407
483
  })
408
484
  const [record] = await FundingDeposits.listBySourceTransaction(db, {
@@ -425,9 +501,11 @@ describe('createTracker', () => {
425
501
  "record": {
426
502
  "createdAt": Any<String>,
427
503
  "depositAddressId": Any<String>,
504
+ "detectionTrigger": "manual",
428
505
  "environment": "production",
429
506
  "id": Any<String>,
430
507
  "orgId": "org_unsubsidized_reconciliation",
508
+ "pollObservedAt": null,
431
509
  "projectId": null,
432
510
  "providerOutputAmount": {
433
511
  "baseUnits": "950000",
@@ -532,6 +610,7 @@ describe('createTracker', () => {
532
610
  "tempoGasPaid": null,
533
611
  "updatedAt": Any<String>,
534
612
  "version": 3,
613
+ "webhookReceivedAt": null,
535
614
  },
536
615
  "result": {
537
616
  "created": 1,
@@ -577,7 +656,7 @@ describe('createTracker', () => {
577
656
  requests: [
578
657
  {
579
658
  createdAt: '2026-01-04T00:01:00.000Z',
580
- data: { inTxs: [{ hash: sourceTransactionHash }], outTxs: [] },
659
+ data: { inTxs: [{ txHash: sourceTransactionHash }], outTxs: [] },
581
660
  depositAddress: { depositTxHash: sourceTransactionHash },
582
661
  id: 'relay-observed-request',
583
662
  status: 'pending',
@@ -688,6 +767,7 @@ describe('createTracker', () => {
688
767
  "tokenKey": "eip155:4217/erc20:0x20c000000000000000000000b9537d11c60e8b50",
689
768
  "verified": true,
690
769
  },
770
+ "detectionTrigger": "webhook",
691
771
  "id": Any<String>,
692
772
  "provider": {
693
773
  "id": "relay",
@@ -750,9 +830,25 @@ describe('createTracker', () => {
750
830
  subsidize: false,
751
831
  },
752
832
  })
833
+ const counts: Parameters<Metrics.Metrics['count']>[] = []
834
+ const dispatches: Parameters<Reconciliation.createTracker.Options['dispatch']>[] = []
835
+ const histograms: Parameters<Metrics.Metrics['histogram']>[] = []
836
+ const metrics = Metrics.from({
837
+ count(...parameters) {
838
+ counts.push(parameters)
839
+ },
840
+ flush() {},
841
+ gauge() {},
842
+ histogram(...parameters) {
843
+ histograms.push(parameters)
844
+ },
845
+ })
753
846
  const requestTargets: string[] = []
754
847
  let delivered = false
848
+ let dispatchFailure = true
755
849
  let includeAddressRequest = true
850
+ let indexed = false
851
+ let providerUpdateVisible = false
756
852
  let returnOlderDirect = false
757
853
  const server = await TestRelay.createServer(async (request, response) => {
758
854
  if (request.method === 'GET') {
@@ -763,19 +859,20 @@ describe('createTracker', () => {
763
859
  response.end(
764
860
  JSON.stringify({
765
861
  requests:
766
- includeAddressRequest || direct
862
+ indexed && (includeAddressRequest || direct)
767
863
  ? [
768
864
  {
769
865
  createdAt: '2026-01-02T00:01:00.000Z',
770
866
  data: {
771
- inTxs: [{ hash: sourceTransactionHash }],
772
- outTxs: delivered ? [{ hash: destinationTransactionHash }] : [],
867
+ inTxs: [{ txHash: sourceTransactionHash }],
868
+ outTxs: delivered ? [{ txHash: destinationTransactionHash }] : [],
773
869
  },
774
870
  depositAddress: { depositTxHash: sourceTransactionHash },
775
871
  id: 'relay-pending-request',
776
872
  status: delivered ? 'success' : 'pending',
777
873
  updatedAt: (() => {
778
874
  if (direct && returnOlderDirect) return '2026-01-01T00:00:00.000Z'
875
+ if (!providerUpdateVisible) return '2026-01-02T00:02:00.000Z'
779
876
  if (delivered) return '2026-01-02T00:03:00.500Z'
780
877
  return '2026-01-02T00:03:00Z'
781
878
  })(),
@@ -846,27 +943,58 @@ describe('createTracker', () => {
846
943
  tokens: [],
847
944
  })
848
945
  const provider = Relay.relay({ apiKey: 'relay-api-key', baseUrl: server.url })
946
+ let trackerNow = new Date('2026-01-02T00:03:10.000Z')
849
947
  const tracker = Reconciliation.createTracker({
850
948
  db,
851
- dispatch: async () => undefined,
852
- now: () => new Date('2027-01-02T00:00:00.000Z'),
949
+ dispatch: async (...parameters) => {
950
+ if (dispatchFailure) {
951
+ dispatchFailure = false
952
+ throw new Error('Reconciliation queue unavailable.')
953
+ }
954
+ dispatches.push(parameters)
955
+ },
956
+ metrics,
957
+ now: () => trackerNow,
853
958
  providers: [provider],
854
959
  verifyTransfers: Evidence.createVerifier({ db }),
855
960
  })
856
961
 
857
- const pending = await tracker.reconcile({
962
+ const message = {
858
963
  addressId: address.id,
964
+ requestId: 'relay-pending-request',
965
+ trigger: 'webhook',
859
966
  type: 'funding:deposit-address:reconcile',
967
+ webhookTiming: {
968
+ enqueuedAt: '2026-01-02T00:03:07.000Z',
969
+ providerStatus: 'pending',
970
+ providerUpdatedAt: '2026-01-02T00:03:00.000Z',
971
+ receivedAt: '2026-01-02T00:03:06.000Z',
972
+ sentAt: '2026-01-02T00:03:05.000Z',
973
+ },
974
+ } satisfies Provider.Webhook.receive.Dispatchable
975
+ await expect(tracker.reconcile(message)).rejects.toThrowErrorMatchingInlineSnapshot(
976
+ `[Error: Reconciliation queue unavailable.]`,
977
+ )
978
+ const pending = await tracker.reconcile(message)
979
+ const [firstFollowUp] = dispatches
980
+ if (!firstFollowUp) throw new Error('Expected a provider follow-up message.')
981
+ indexed = true
982
+ const pendingFollowUp = await tracker.reconcile(firstFollowUp[0])
983
+ const exhausted = await tracker.reconcile({
984
+ ...firstFollowUp[0],
985
+ followUp: { attempt: 5, kind: 'provider' },
860
986
  })
987
+ const [, secondFollowUp] = dispatches
988
+ if (!secondFollowUp) throw new Error('Expected a second provider follow-up message.')
861
989
  delivered = true
862
990
  includeAddressRequest = false
863
- const completed = await tracker.reconcile({
864
- addressId: address.id,
865
- type: 'funding:deposit-address:reconcile',
866
- })
991
+ providerUpdateVisible = true
992
+ trackerNow = new Date('2026-01-02T00:03:15.000Z')
993
+ const completed = await tracker.reconcile(secondFollowUp[0])
867
994
  includeAddressRequest = true
868
995
  const replay = await tracker.reconcile({
869
996
  addressId: address.id,
997
+ trigger: 'webhook',
870
998
  type: 'funding:deposit-address:reconcile',
871
999
  })
872
1000
  includeAddressRequest = false
@@ -874,6 +1002,7 @@ describe('createTracker', () => {
874
1002
  const webhook = await tracker.reconcile({
875
1003
  addressId: address.id,
876
1004
  requestId: 'relay-pending-request',
1005
+ trigger: 'webhook',
877
1006
  type: 'funding:deposit-address:reconcile',
878
1007
  })
879
1008
  const [record] = await FundingDeposits.listBySourceTransaction(db, {
@@ -884,7 +1013,12 @@ describe('createTracker', () => {
884
1013
 
885
1014
  expect({
886
1015
  completed,
1016
+ counts,
1017
+ dispatches,
1018
+ exhausted,
1019
+ histograms,
887
1020
  pending,
1021
+ pendingFollowUp,
888
1022
  record,
889
1023
  replay,
890
1024
  requestTargets,
@@ -892,6 +1026,10 @@ describe('createTracker', () => {
892
1026
  webhook,
893
1027
  }).toMatchInlineSnapshot(
894
1028
  {
1029
+ dispatches: [
1030
+ [{ addressId: expect.any(String) }, { delaySeconds: 5 }],
1031
+ [{ addressId: expect.any(String) }, { delaySeconds: 5 }],
1032
+ ],
895
1033
  record: {
896
1034
  createdAt: expect.any(String),
897
1035
  depositAddressId: expect.any(String),
@@ -912,7 +1050,267 @@ describe('createTracker', () => {
912
1050
  "type": "completed",
913
1051
  "updated": 1,
914
1052
  },
1053
+ "counts": [
1054
+ [
1055
+ "funding_deposit_reconciliation_count",
1056
+ 1,
1057
+ {
1058
+ "outcome": "follow_up_enqueue_failed",
1059
+ "provider": "relay",
1060
+ "trigger": "webhook",
1061
+ },
1062
+ ],
1063
+ [
1064
+ "funding_deposit_reconciliation_count",
1065
+ 1,
1066
+ {
1067
+ "outcome": "failed",
1068
+ "provider": "relay",
1069
+ "trigger": "webhook",
1070
+ },
1071
+ ],
1072
+ [
1073
+ "funding_deposit_reconciliation_count",
1074
+ 1,
1075
+ {
1076
+ "outcome": "follow_up_enqueued",
1077
+ "provider": "relay",
1078
+ "trigger": "webhook",
1079
+ },
1080
+ ],
1081
+ [
1082
+ "funding_deposit_reconciliation_count",
1083
+ 1,
1084
+ {
1085
+ "outcome": "completed",
1086
+ "provider": "relay",
1087
+ "trigger": "webhook",
1088
+ },
1089
+ ],
1090
+ [
1091
+ "funding_deposit_detection_count",
1092
+ 1,
1093
+ {
1094
+ "provider": "relay",
1095
+ "trigger": "webhook",
1096
+ },
1097
+ ],
1098
+ [
1099
+ "funding_deposit_reconciliation_count",
1100
+ 1,
1101
+ {
1102
+ "outcome": "follow_up_enqueued",
1103
+ "provider": "relay",
1104
+ "trigger": "webhook",
1105
+ },
1106
+ ],
1107
+ [
1108
+ "funding_deposit_reconciliation_count",
1109
+ 1,
1110
+ {
1111
+ "outcome": "completed",
1112
+ "provider": "relay",
1113
+ "trigger": "webhook",
1114
+ },
1115
+ ],
1116
+ [
1117
+ "funding_deposit_reconciliation_count",
1118
+ 1,
1119
+ {
1120
+ "outcome": "follow_up_exhausted",
1121
+ "provider": "relay",
1122
+ "trigger": "webhook",
1123
+ },
1124
+ ],
1125
+ [
1126
+ "funding_deposit_reconciliation_count",
1127
+ 1,
1128
+ {
1129
+ "outcome": "completed",
1130
+ "provider": "relay",
1131
+ "trigger": "webhook",
1132
+ },
1133
+ ],
1134
+ [
1135
+ "funding_deposit_reconciliation_count",
1136
+ 1,
1137
+ {
1138
+ "outcome": "completed",
1139
+ "provider": "relay",
1140
+ "trigger": "webhook",
1141
+ },
1142
+ ],
1143
+ [
1144
+ "funding_deposit_reconciliation_count",
1145
+ 1,
1146
+ {
1147
+ "outcome": "completed",
1148
+ "provider": "relay",
1149
+ "trigger": "webhook",
1150
+ },
1151
+ ],
1152
+ [
1153
+ "funding_deposit_reconciliation_count",
1154
+ 1,
1155
+ {
1156
+ "outcome": "completed",
1157
+ "provider": "relay",
1158
+ "trigger": "webhook",
1159
+ },
1160
+ ],
1161
+ ],
1162
+ "dispatches": [
1163
+ [
1164
+ {
1165
+ "addressId": Any<String>,
1166
+ "followUp": {
1167
+ "attempt": 0,
1168
+ "kind": "provider",
1169
+ },
1170
+ "requestId": "relay-pending-request",
1171
+ "trigger": "webhook",
1172
+ "type": "funding:deposit-address:reconcile",
1173
+ "webhookTiming": {
1174
+ "enqueuedAt": "2026-01-02T00:03:07.000Z",
1175
+ "providerStatus": "pending",
1176
+ "providerUpdatedAt": "2026-01-02T00:03:00.000Z",
1177
+ "receivedAt": "2026-01-02T00:03:06.000Z",
1178
+ "sentAt": "2026-01-02T00:03:05.000Z",
1179
+ },
1180
+ },
1181
+ {
1182
+ "delaySeconds": 5,
1183
+ },
1184
+ ],
1185
+ [
1186
+ {
1187
+ "addressId": Any<String>,
1188
+ "followUp": {
1189
+ "attempt": 1,
1190
+ "kind": "provider",
1191
+ },
1192
+ "requestId": "relay-pending-request",
1193
+ "trigger": "webhook",
1194
+ "type": "funding:deposit-address:reconcile",
1195
+ "webhookTiming": {
1196
+ "enqueuedAt": "2026-01-02T00:03:07.000Z",
1197
+ "providerStatus": "pending",
1198
+ "providerUpdatedAt": "2026-01-02T00:03:00.000Z",
1199
+ "receivedAt": "2026-01-02T00:03:06.000Z",
1200
+ "sentAt": "2026-01-02T00:03:05.000Z",
1201
+ },
1202
+ },
1203
+ {
1204
+ "delaySeconds": 5,
1205
+ },
1206
+ ],
1207
+ ],
1208
+ "exhausted": {
1209
+ "created": 0,
1210
+ "type": "completed",
1211
+ "updated": 1,
1212
+ },
1213
+ "histograms": [
1214
+ [
1215
+ "funding_deposit_reconciliation_duration_ms",
1216
+ 0,
1217
+ {
1218
+ "provider": "relay",
1219
+ "trigger": "webhook",
1220
+ },
1221
+ ],
1222
+ [
1223
+ "funding_deposit_reconciliation_duration_ms",
1224
+ 0,
1225
+ {
1226
+ "provider": "relay",
1227
+ "trigger": "webhook",
1228
+ },
1229
+ ],
1230
+ [
1231
+ "funding_deposit_reconciliation_duration_ms",
1232
+ 0,
1233
+ {
1234
+ "provider": "relay",
1235
+ "trigger": "webhook",
1236
+ },
1237
+ ],
1238
+ [
1239
+ "funding_deposit_webhook_latency_ms",
1240
+ 5000,
1241
+ {
1242
+ "provider": "relay",
1243
+ "segment": "provider_to_sent",
1244
+ "status": "pending",
1245
+ },
1246
+ ],
1247
+ [
1248
+ "funding_deposit_webhook_latency_ms",
1249
+ 1000,
1250
+ {
1251
+ "provider": "relay",
1252
+ "segment": "sent_to_received",
1253
+ "status": "pending",
1254
+ },
1255
+ ],
1256
+ [
1257
+ "funding_deposit_webhook_latency_ms",
1258
+ 1000,
1259
+ {
1260
+ "provider": "relay",
1261
+ "segment": "received_to_enqueued",
1262
+ "status": "pending",
1263
+ },
1264
+ ],
1265
+ [
1266
+ "funding_deposit_webhook_latency_ms",
1267
+ 8000,
1268
+ {
1269
+ "provider": "relay",
1270
+ "segment": "enqueued_to_reconciled",
1271
+ "status": "pending",
1272
+ },
1273
+ ],
1274
+ [
1275
+ "funding_deposit_webhook_latency_ms",
1276
+ 15000,
1277
+ {
1278
+ "provider": "relay",
1279
+ "segment": "provider_to_reconciled",
1280
+ "status": "pending",
1281
+ },
1282
+ ],
1283
+ [
1284
+ "funding_deposit_reconciliation_duration_ms",
1285
+ 0,
1286
+ {
1287
+ "provider": "relay",
1288
+ "trigger": "webhook",
1289
+ },
1290
+ ],
1291
+ [
1292
+ "funding_deposit_reconciliation_duration_ms",
1293
+ 0,
1294
+ {
1295
+ "provider": "relay",
1296
+ "trigger": "webhook",
1297
+ },
1298
+ ],
1299
+ [
1300
+ "funding_deposit_reconciliation_duration_ms",
1301
+ 0,
1302
+ {
1303
+ "provider": "relay",
1304
+ "trigger": "webhook",
1305
+ },
1306
+ ],
1307
+ ],
915
1308
  "pending": {
1309
+ "created": 0,
1310
+ "type": "completed",
1311
+ "updated": 0,
1312
+ },
1313
+ "pendingFollowUp": {
916
1314
  "created": 1,
917
1315
  "type": "completed",
918
1316
  "updated": 1,
@@ -920,9 +1318,11 @@ describe('createTracker', () => {
920
1318
  "record": {
921
1319
  "createdAt": Any<String>,
922
1320
  "depositAddressId": Any<String>,
1321
+ "detectionTrigger": "webhook",
923
1322
  "environment": "production",
924
1323
  "id": Any<String>,
925
1324
  "orgId": "org_pending_reconciliation",
1325
+ "pollObservedAt": null,
926
1326
  "projectId": null,
927
1327
  "providerOutputAmount": {
928
1328
  "baseUnits": "1000000",
@@ -1026,7 +1426,8 @@ describe('createTracker', () => {
1026
1426
  "subsidyAmount": null,
1027
1427
  "tempoGasPaid": null,
1028
1428
  "updatedAt": Any<String>,
1029
- "version": 5,
1429
+ "version": 7,
1430
+ "webhookReceivedAt": null,
1030
1431
  },
1031
1432
  "replay": {
1032
1433
  "created": 0,
@@ -1034,12 +1435,17 @@ describe('createTracker', () => {
1034
1435
  "updated": 0,
1035
1436
  },
1036
1437
  "requestTargets": [
1037
- "/requests/v2?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1038
- "/requests/v2?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1039
- "/requests/v2?id=relay-pending-request&limit=50&sortBy=updatedAt&sortDirection=desc",
1040
- "/requests/v2?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1041
- "/requests/v2?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1042
- "/requests/v2?id=relay-pending-request&limit=50&sortBy=updatedAt&sortDirection=desc",
1438
+ "/requests/v3?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1439
+ "/requests/v3?id=relay-pending-request&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1440
+ "/requests/v3?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1441
+ "/requests/v3?id=relay-pending-request&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1442
+ "/requests/v3?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1443
+ "/requests/v3?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1444
+ "/requests/v3?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1445
+ "/requests/v3?id=relay-pending-request&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1446
+ "/requests/v3?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1447
+ "/requests/v3?depositAddress=0x8888888888888888888888888888888888888888&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1448
+ "/requests/v3?id=relay-pending-request&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc",
1043
1449
  ],
1044
1450
  "storedAddress": {
1045
1451
  "address": "0x8888888888888888888888888888888888888888",
@@ -1049,12 +1455,12 @@ describe('createTracker', () => {
1049
1455
  "destinationTokenKey": "eip155:4217/erc20:0x20c000000000000000000000b9537d11c60e8b50",
1050
1456
  "environment": "production",
1051
1457
  "id": Any<String>,
1052
- "lastPolledAt": "2027-01-02T00:00:00.000Z",
1053
- "nextPollAt": "2027-01-02T00:01:00.000Z",
1458
+ "lastPolledAt": "2026-01-02T00:03:15.000Z",
1459
+ "nextPollAt": "2026-01-02T00:04:15.000Z",
1054
1460
  "orgId": "org_pending_reconciliation",
1055
1461
  "pollFailureCount": 0,
1056
1462
  "pollLeaseUntil": null,
1057
- "pollLeaseVersion": 4,
1463
+ "pollLeaseVersion": 7,
1058
1464
  "projectId": null,
1059
1465
  "providerId": "relay",
1060
1466
  "providerOutputToken": Any<Object>,
@@ -1066,6 +1472,15 @@ describe('createTracker', () => {
1066
1472
  "relay-pending-request",
1067
1473
  ],
1068
1474
  "updatedAt": "2026-01-02T00:03:00.500Z",
1475
+ "webhookTiming": {
1476
+ "enqueuedAt": "2026-01-02T00:03:07.000Z",
1477
+ "providerStatus": "pending",
1478
+ "providerUpdatedAt": "2026-01-02T00:03:00.000Z",
1479
+ "receivedAt": "2026-01-02T00:03:06.000Z",
1480
+ "reconciliationCompletedAt": "2026-01-02T00:03:15.000Z",
1481
+ "requestId": "relay-pending-request",
1482
+ "sentAt": "2026-01-02T00:03:05.000Z",
1483
+ },
1069
1484
  },
1070
1485
  },
1071
1486
  "recipient": "0x1111111111111111111111111111111111111111",
@@ -1091,6 +1506,527 @@ describe('createTracker', () => {
1091
1506
  }
1092
1507
  })
1093
1508
 
1509
+ test('completes subsidized Relay delivery from authenticated chain evidence', async () => {
1510
+ const route = TestFunding.transferSnapshot()
1511
+ const ambiguousOrderId = `0x${'3d'.repeat(32)}`
1512
+ const ambiguousSweepTransactionHash = `0x${'3e'.repeat(32)}`
1513
+ const depositAddress = `0x${'31'.repeat(20)}`
1514
+ const depository = `0x${'32'.repeat(20)}`
1515
+ const destinationTransactionHash = `0x${'33'.repeat(32)}`
1516
+ const orderId = `0x${'34'.repeat(32)}`
1517
+ const refundAddress = `0x${'38'.repeat(20)}`
1518
+ const solver = `0x${'35'.repeat(20)}`
1519
+ const sourceSweepTransactionHash = `0x${'36'.repeat(32)}`
1520
+ const sourceTransactionHash = `0x${'37'.repeat(32)}`
1521
+ const address = await DepositAddress.create(db, {
1522
+ apiKeyId: 'key_delivery_observation',
1523
+ deliveryStrategy: 'provider',
1524
+ environment: 'production',
1525
+ now: new Date('2026-01-04T00:00:00.000Z'),
1526
+ orgId: 'org_delivery_observation',
1527
+ providerOutputToken: route.destinationToken,
1528
+ snapshot: {
1529
+ address: depositAddress,
1530
+ destinationChain: route.destinationChain,
1531
+ destinationToken: route.destinationToken,
1532
+ provider: { id: 'relay', name: 'Relay' },
1533
+ recipient: route.recipient,
1534
+ refundAddress,
1535
+ sourceChain: route.sourceChain,
1536
+ sourceToken: route.sourceToken,
1537
+ subsidize: true,
1538
+ },
1539
+ })
1540
+ const counts: Parameters<Metrics.Metrics['count']>[] = []
1541
+ const dispatches: Parameters<Reconciliation.createTracker.Options['dispatch']>[] = []
1542
+ const metrics = Metrics.from({
1543
+ count(...parameters) {
1544
+ counts.push(parameters)
1545
+ },
1546
+ flush() {},
1547
+ gauge() {},
1548
+ histogram() {},
1549
+ })
1550
+ const rpcMethods: string[] = []
1551
+ let correlated = false
1552
+ let delivered = false
1553
+ const server = await TestRelay.createServer(async (request, response) => {
1554
+ if (request.method === 'GET') {
1555
+ const url = new URL(request.url ?? '/', server.url)
1556
+ response.writeHead(200, { 'content-type': 'application/json' })
1557
+ response.end(
1558
+ JSON.stringify(
1559
+ url.pathname === '/chains'
1560
+ ? {
1561
+ chains: [
1562
+ {
1563
+ id: Number(route.sourceChain.id.slice('eip155:'.length)),
1564
+ protocol: { v2: { depository } },
1565
+ solverAddresses: [],
1566
+ },
1567
+ {
1568
+ id: Number(route.destinationChain.id.slice('eip155:'.length)),
1569
+ solverAddresses: [solver],
1570
+ },
1571
+ ],
1572
+ }
1573
+ : {
1574
+ requests: [
1575
+ {
1576
+ createdAt: '2026-01-04T00:01:00.000Z',
1577
+ data: { outTxs: [] },
1578
+ depositAddress: { depositTxHash: sourceTransactionHash },
1579
+ id: 'relay-observed-request',
1580
+ ...(() => {
1581
+ if (!correlated) return {}
1582
+ return {
1583
+ protocol: {
1584
+ deposit: { origin: { depositor: refundAddress, depository } },
1585
+ orderId,
1586
+ solver: { address: solver },
1587
+ },
1588
+ }
1589
+ })(),
1590
+ status: 'pending',
1591
+ updatedAt: '2026-01-04T00:02:00.000Z',
1592
+ },
1593
+ ],
1594
+ },
1595
+ ),
1596
+ )
1597
+ return
1598
+ }
1599
+
1600
+ const chunks: Uint8Array[] = []
1601
+ for await (const chunk of request) chunks.push(chunk)
1602
+ const body = JSON.parse(NodeBuffer.Buffer.concat(chunks).toString()) as {
1603
+ id: number
1604
+ method: string
1605
+ params?: readonly unknown[] | undefined
1606
+ }
1607
+ rpcMethods.push(body.method)
1608
+ const parameter = body.params?.[0]
1609
+ const result = (() => {
1610
+ if (body.method === 'eth_blockNumber') return '0x100'
1611
+ if (body.method === 'eth_getTransactionByHash')
1612
+ return parameter === destinationTransactionHash
1613
+ ? rpcTransaction({
1614
+ blockNumber: 0xf0,
1615
+ from: solver,
1616
+ hash: destinationTransactionHash,
1617
+ input: AbiFunction.encodeData(
1618
+ AbiFunction.from('function transfer(address recipient,uint256 amount)'),
1619
+ [route.recipient as Address.Address, 1_000_000n],
1620
+ ),
1621
+ to: route.destinationToken.address,
1622
+ })
1623
+ : rpcTransaction({
1624
+ blockNumber: 0x82,
1625
+ from: depositAddress,
1626
+ hash:
1627
+ parameter === sourceSweepTransactionHash
1628
+ ? sourceSweepTransactionHash
1629
+ : ambiguousSweepTransactionHash,
1630
+ input: (() => {
1631
+ if (parameter === sourceSweepTransactionHash)
1632
+ return AbiFunction.encodeData(
1633
+ AbiFunction.from(
1634
+ 'function depositErc20(address depositor,address token,bytes32 id)',
1635
+ ),
1636
+ [
1637
+ refundAddress as Address.Address,
1638
+ route.sourceToken.address as Address.Address,
1639
+ orderId as Hex.Hex,
1640
+ ],
1641
+ )
1642
+ return AbiFunction.encodeData(
1643
+ AbiFunction.from(
1644
+ 'function depositErc20(address depositor,address token,uint256 amount,bytes32 id)',
1645
+ ),
1646
+ [
1647
+ refundAddress as Address.Address,
1648
+ route.sourceToken.address as Address.Address,
1649
+ 1_000_000n,
1650
+ ambiguousOrderId as Hex.Hex,
1651
+ ],
1652
+ )
1653
+ })(),
1654
+ to: depository,
1655
+ })
1656
+ if (body.method === 'eth_getLogs') {
1657
+ const filter = parameter as { address?: string | undefined }
1658
+ if (filter.address?.toLowerCase() === depository.toLowerCase())
1659
+ return [
1660
+ relayDepositLog({
1661
+ amount: 1_000_000n,
1662
+ blockNumber: 0x81,
1663
+ depository,
1664
+ from: refundAddress,
1665
+ orderId: ambiguousOrderId,
1666
+ token: route.sourceToken.address,
1667
+ transactionHash: ambiguousSweepTransactionHash,
1668
+ }),
1669
+ relayDepositLog({
1670
+ amount: 1_000_000n,
1671
+ blockNumber: 0x82,
1672
+ depository,
1673
+ from: refundAddress,
1674
+ orderId,
1675
+ token: route.sourceToken.address,
1676
+ transactionHash: sourceSweepTransactionHash,
1677
+ }),
1678
+ ]
1679
+ if (delivered)
1680
+ return [
1681
+ transferLog({
1682
+ amount: 1_000_000n,
1683
+ blockNumber: 0xf0,
1684
+ recipient: route.recipient,
1685
+ sender: solver,
1686
+ token: route.destinationToken.address,
1687
+ transactionHash: destinationTransactionHash,
1688
+ }),
1689
+ ]
1690
+ return []
1691
+ }
1692
+ if (body.method === 'eth_getTransactionReceipt') {
1693
+ const hash = parameter
1694
+ return {
1695
+ blockNumber: hash === sourceTransactionHash ? '0x80' : '0xf0',
1696
+ logs: [
1697
+ hash === sourceTransactionHash
1698
+ ? transferLog({
1699
+ amount: 1_000_000n,
1700
+ recipient: depositAddress,
1701
+ sender: `0x${'3c'.repeat(20)}`,
1702
+ token: route.sourceToken.address,
1703
+ })
1704
+ : transferLog({
1705
+ amount: 1_000_000n,
1706
+ recipient: route.recipient,
1707
+ sender: solver,
1708
+ token: route.destinationToken.address,
1709
+ }),
1710
+ ],
1711
+ status: '0x1',
1712
+ }
1713
+ }
1714
+ throw new Error(`Unexpected RPC method: ${body.method}`)
1715
+ })()
1716
+ response.writeHead(200, { 'content-type': 'application/json' })
1717
+ response.end(JSON.stringify({ id: body.id, jsonrpc: '2.0', result }))
1718
+ })
1719
+
1720
+ try {
1721
+ await FundingCatalog.publish(db, {
1722
+ chainTokens: [],
1723
+ chains: [
1724
+ {
1725
+ aliases: [],
1726
+ id: route.destinationChain.id as Chain.Id,
1727
+ name: route.destinationChain.name,
1728
+ parentChainId: null,
1729
+ rpcUrls: [server.url],
1730
+ slug: 'destination',
1731
+ },
1732
+ {
1733
+ aliases: [],
1734
+ id: route.sourceChain.id as Chain.Id,
1735
+ name: route.sourceChain.name,
1736
+ parentChainId: null,
1737
+ rpcUrls: [server.url],
1738
+ slug: 'source',
1739
+ },
1740
+ ],
1741
+ routes: [],
1742
+ tokens: [],
1743
+ })
1744
+ const provider = Relay.relay({
1745
+ apiKey: 'relay-api-key',
1746
+ baseUrl: server.url,
1747
+ now: () => new Date('2026-01-04T00:03:00.000Z'),
1748
+ observation: { db },
1749
+ })
1750
+ const tracker = Reconciliation.createTracker({
1751
+ db,
1752
+ dispatch: async (...parameters) => {
1753
+ dispatches.push(parameters)
1754
+ },
1755
+ metrics,
1756
+ now: () => new Date('2026-01-04T00:03:00.000Z'),
1757
+ providers: [provider],
1758
+ verifyTransfers: Evidence.createVerifier({ db }),
1759
+ })
1760
+ const message = {
1761
+ addressId: address.id,
1762
+ requestId: 'relay-observed-request',
1763
+ trigger: 'webhook',
1764
+ type: 'funding:deposit-address:reconcile',
1765
+ } satisfies Provider.Webhook.receive.Dispatchable
1766
+ const pending = await tracker.reconcile(message)
1767
+ const [followUp] = dispatches
1768
+ if (!followUp) throw new Error('Expected a delivery follow-up message.')
1769
+ correlated = true
1770
+ delivered = true
1771
+ const completed = await tracker.reconcile(followUp[0])
1772
+ const [record] = await FundingDeposits.listBySourceTransaction(db, {
1773
+ depositAddressId: address.id,
1774
+ sourceTransactionHash,
1775
+ })
1776
+
1777
+ expect({
1778
+ completed,
1779
+ counts,
1780
+ dispatches: dispatches.map(([message, dispatchOptions]) => [
1781
+ { ...message, addressId: '<address-id>' },
1782
+ dispatchOptions,
1783
+ ]),
1784
+ pending,
1785
+ record,
1786
+ rpcMethods,
1787
+ }).toMatchInlineSnapshot(
1788
+ {
1789
+ record: {
1790
+ createdAt: expect.any(String),
1791
+ depositAddressId: expect.any(String),
1792
+ id: expect.any(String),
1793
+ snapshot: { depositAddressId: expect.any(String) },
1794
+ updatedAt: expect.any(String),
1795
+ },
1796
+ },
1797
+ `
1798
+ {
1799
+ "completed": {
1800
+ "created": 0,
1801
+ "type": "completed",
1802
+ "updated": 1,
1803
+ },
1804
+ "counts": [
1805
+ [
1806
+ "funding_deposit_delivery_observation_count",
1807
+ 1,
1808
+ {
1809
+ "outcome": "pending",
1810
+ "provider": "relay",
1811
+ },
1812
+ ],
1813
+ [
1814
+ "funding_deposit_detection_count",
1815
+ 1,
1816
+ {
1817
+ "provider": "relay",
1818
+ "trigger": "webhook",
1819
+ },
1820
+ ],
1821
+ [
1822
+ "funding_deposit_reconciliation_count",
1823
+ 1,
1824
+ {
1825
+ "outcome": "follow_up_enqueued",
1826
+ "provider": "relay",
1827
+ "trigger": "webhook",
1828
+ },
1829
+ ],
1830
+ [
1831
+ "funding_deposit_reconciliation_count",
1832
+ 1,
1833
+ {
1834
+ "outcome": "completed",
1835
+ "provider": "relay",
1836
+ "trigger": "webhook",
1837
+ },
1838
+ ],
1839
+ [
1840
+ "funding_deposit_delivery_observation_count",
1841
+ 1,
1842
+ {
1843
+ "outcome": "verified",
1844
+ "provider": "relay",
1845
+ },
1846
+ ],
1847
+ [
1848
+ "funding_deposit_reconciliation_count",
1849
+ 1,
1850
+ {
1851
+ "outcome": "completed",
1852
+ "provider": "relay",
1853
+ "trigger": "webhook",
1854
+ },
1855
+ ],
1856
+ ],
1857
+ "dispatches": [
1858
+ [
1859
+ {
1860
+ "addressId": "<address-id>",
1861
+ "followUp": {
1862
+ "attempt": 0,
1863
+ "kind": "delivery",
1864
+ },
1865
+ "requestId": "relay-observed-request",
1866
+ "trigger": "webhook",
1867
+ "type": "funding:deposit-address:reconcile",
1868
+ },
1869
+ {
1870
+ "delaySeconds": 1,
1871
+ },
1872
+ ],
1873
+ ],
1874
+ "pending": {
1875
+ "created": 1,
1876
+ "type": "completed",
1877
+ "updated": 1,
1878
+ },
1879
+ "record": {
1880
+ "createdAt": Any<String>,
1881
+ "depositAddressId": Any<String>,
1882
+ "detectionTrigger": "webhook",
1883
+ "environment": "production",
1884
+ "id": Any<String>,
1885
+ "orgId": "org_delivery_observation",
1886
+ "pollObservedAt": null,
1887
+ "projectId": null,
1888
+ "providerOutputAmount": {
1889
+ "baseUnits": "1000000",
1890
+ "currency": "USD",
1891
+ "decimals": 6,
1892
+ "formatted": "1",
1893
+ },
1894
+ "providerOutputToken": {
1895
+ "address": "0x20c000000000000000000000b9537d11c60e8b50",
1896
+ "currency": "USD",
1897
+ "decimals": 6,
1898
+ "name": "Bridged USDC (Stargate)",
1899
+ "standard": "TIP-20",
1900
+ "symbol": "USDC.e",
1901
+ "tokenKey": "eip155:4217/erc20:0x20c000000000000000000000b9537d11c60e8b50",
1902
+ "verified": true,
1903
+ },
1904
+ "providerRequestId": "relay-observed-request",
1905
+ "providerRequestIds": [
1906
+ "relay-observed-request",
1907
+ ],
1908
+ "providerState": {
1909
+ "relay": {
1910
+ "destinationObservedAt": "2026-01-04T00:03:00.000Z",
1911
+ "orderId": "0x3434343434343434343434343434343434343434343434343434343434343434",
1912
+ "sourceDepository": "0x3232323232323232323232323232323232323232",
1913
+ "sourceObservedAt": "2026-01-04T00:03:00.000Z",
1914
+ "sourceSweepTransactionHash": "0x3636363636363636363636363636363636363636363636363636363636363636",
1915
+ },
1916
+ },
1917
+ "providerTransactionHashes": [
1918
+ "0x3636363636363636363636363636363636363636363636363636363636363636",
1919
+ "0x3333333333333333333333333333333333333333333333333333333333333333",
1920
+ ],
1921
+ "providerTransferIndex": 0,
1922
+ "retryState": null,
1923
+ "settlementTransaction": null,
1924
+ "settlementTransactionHash": null,
1925
+ "snapshot": {
1926
+ "depositAddressId": Any<String>,
1927
+ "destinationAmount": {
1928
+ "baseUnits": "1000000",
1929
+ "currency": "USD",
1930
+ "decimals": 6,
1931
+ "formatted": "1",
1932
+ },
1933
+ "destinationAmountRequired": {
1934
+ "baseUnits": "1000000",
1935
+ "currency": "USD",
1936
+ "decimals": 6,
1937
+ "formatted": "1",
1938
+ },
1939
+ "destinationChain": {
1940
+ "addressFormat": "hex",
1941
+ "id": "eip155:4217",
1942
+ "kind": "evm",
1943
+ "name": "Tempo",
1944
+ },
1945
+ "destinationToken": {
1946
+ "address": "0x20c000000000000000000000b9537d11c60e8b50",
1947
+ "currency": "USD",
1948
+ "decimals": 6,
1949
+ "name": "Bridged USDC (Stargate)",
1950
+ "standard": "TIP-20",
1951
+ "symbol": "USDC.e",
1952
+ "tokenKey": "eip155:4217/erc20:0x20c000000000000000000000b9537d11c60e8b50",
1953
+ "verified": true,
1954
+ },
1955
+ "destinationTransactionHashes": [
1956
+ "0x3333333333333333333333333333333333333333333333333333333333333333",
1957
+ ],
1958
+ "provider": {
1959
+ "id": "relay",
1960
+ "name": "Relay",
1961
+ },
1962
+ "recipient": "0x1111111111111111111111111111111111111111",
1963
+ "refundAddress": "0x3838383838383838383838383838383838383838",
1964
+ "sender": "0x3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c",
1965
+ "sourceAmount": {
1966
+ "baseUnits": "1000000",
1967
+ "currency": "USD",
1968
+ "decimals": 6,
1969
+ "formatted": "1",
1970
+ },
1971
+ "sourceChain": {
1972
+ "addressFormat": "hex",
1973
+ "id": "eip155:8453",
1974
+ "kind": "evm",
1975
+ "name": "Base",
1976
+ },
1977
+ "sourceToken": {
1978
+ "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
1979
+ "currency": "USD",
1980
+ "decimals": 6,
1981
+ "name": "USD Coin",
1982
+ "standard": "ERC-20",
1983
+ "symbol": "USDC",
1984
+ "tokenKey": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
1985
+ "verified": true,
1986
+ },
1987
+ "sourceTransactionHashes": [
1988
+ "0x3737373737373737373737373737373737373737373737373737373737373737",
1989
+ ],
1990
+ },
1991
+ "sourceChainId": "eip155:8453",
1992
+ "sourceTransactionHash": "0x3737373737373737373737373737373737373737373737373737373737373737",
1993
+ "sourceTransferIndex": 0,
1994
+ "status": "completed",
1995
+ "statusReason": null,
1996
+ "subsidyAmount": null,
1997
+ "tempoGasPaid": null,
1998
+ "updatedAt": Any<String>,
1999
+ "version": 5,
2000
+ "webhookReceivedAt": null,
2001
+ },
2002
+ "rpcMethods": [
2003
+ "eth_getTransactionReceipt",
2004
+ "eth_blockNumber",
2005
+ "eth_getTransactionReceipt",
2006
+ "eth_blockNumber",
2007
+ "eth_getLogs",
2008
+ "eth_getTransactionByHash",
2009
+ "eth_getTransactionByHash",
2010
+ "eth_getTransactionReceipt",
2011
+ "eth_blockNumber",
2012
+ "eth_getTransactionReceipt",
2013
+ "eth_blockNumber",
2014
+ "eth_getLogs",
2015
+ "eth_getTransactionByHash",
2016
+ "eth_blockNumber",
2017
+ "eth_getLogs",
2018
+ "eth_getTransactionByHash",
2019
+ "eth_getTransactionReceipt",
2020
+ "eth_blockNumber",
2021
+ ],
2022
+ }
2023
+ `,
2024
+ )
2025
+ } finally {
2026
+ await server.closeAsync()
2027
+ }
2028
+ })
2029
+
1094
2030
  test('renews the address lease during a slow provider read', async () => {
1095
2031
  const route = TestFunding.transferSnapshot()
1096
2032
  const depositAddress = `0x${'16'.repeat(20)}`
@@ -1136,6 +2072,7 @@ describe('createTracker', () => {
1136
2072
 
1137
2073
  const reconciliation = tracker.reconcile({
1138
2074
  addressId: address.id,
2075
+ trigger: 'webhook',
1139
2076
  type: 'funding:deposit-address:reconcile',
1140
2077
  })
1141
2078
  await providerStarted.promise
@@ -1368,9 +2305,11 @@ describe('createTracker', () => {
1368
2305
  {
1369
2306
  "createdAt": Any<String>,
1370
2307
  "depositAddressId": Any<String>,
2308
+ "detectionTrigger": "poll",
1371
2309
  "environment": "production",
1372
2310
  "id": Any<String>,
1373
2311
  "orgId": "org_source_evidence_reconciliation",
2312
+ "pollObservedAt": null,
1374
2313
  "projectId": null,
1375
2314
  "providerOutputAmount": null,
1376
2315
  "providerOutputToken": {
@@ -1453,13 +2392,16 @@ describe('createTracker', () => {
1453
2392
  "tempoGasPaid": null,
1454
2393
  "updatedAt": Any<String>,
1455
2394
  "version": 2,
2395
+ "webhookReceivedAt": null,
1456
2396
  },
1457
2397
  {
1458
2398
  "createdAt": Any<String>,
1459
2399
  "depositAddressId": Any<String>,
2400
+ "detectionTrigger": "poll",
1460
2401
  "environment": "production",
1461
2402
  "id": Any<String>,
1462
2403
  "orgId": "org_source_evidence_reconciliation",
2404
+ "pollObservedAt": null,
1463
2405
  "projectId": null,
1464
2406
  "providerOutputAmount": null,
1465
2407
  "providerOutputToken": {
@@ -1535,6 +2477,7 @@ describe('createTracker', () => {
1535
2477
  "tempoGasPaid": null,
1536
2478
  "updatedAt": Any<String>,
1537
2479
  "version": 1,
2480
+ "webhookReceivedAt": null,
1538
2481
  },
1539
2482
  ],
1540
2483
  "result": {
@@ -1585,8 +2528,8 @@ describe('createTracker', () => {
1585
2528
  {
1586
2529
  createdAt: '2026-01-03T00:01:00.000Z',
1587
2530
  data: {
1588
- inTxs: [{ hash: sourceTransactionHash }],
1589
- outTxs: [{ hash: destinationTransactionHash }],
2531
+ inTxs: [{ txHash: sourceTransactionHash }],
2532
+ outTxs: [{ txHash: destinationTransactionHash }],
1590
2533
  },
1591
2534
  depositAddress: { depositTxHash: sourceTransactionHash },
1592
2535
  id: 'relay-multiple-transfer-request',
@@ -1679,6 +2622,7 @@ describe('createTracker', () => {
1679
2622
 
1680
2623
  const result = await tracker.reconcile({
1681
2624
  addressId: address.id,
2625
+ trigger: 'webhook',
1682
2626
  type: 'funding:deposit-address:reconcile',
1683
2627
  })
1684
2628
  const records = (
@@ -1723,9 +2667,11 @@ describe('createTracker', () => {
1723
2667
  {
1724
2668
  "createdAt": Any<String>,
1725
2669
  "depositAddressId": Any<String>,
2670
+ "detectionTrigger": "webhook",
1726
2671
  "environment": "production",
1727
2672
  "id": Any<String>,
1728
2673
  "orgId": "org_precision_reconciliation",
2674
+ "pollObservedAt": null,
1729
2675
  "projectId": null,
1730
2676
  "providerOutputAmount": {
1731
2677
  "baseUnits": "2000000",
@@ -1830,13 +2776,16 @@ describe('createTracker', () => {
1830
2776
  "tempoGasPaid": null,
1831
2777
  "updatedAt": Any<String>,
1832
2778
  "version": 3,
2779
+ "webhookReceivedAt": null,
1833
2780
  },
1834
2781
  {
1835
2782
  "createdAt": Any<String>,
1836
2783
  "depositAddressId": Any<String>,
2784
+ "detectionTrigger": "webhook",
1837
2785
  "environment": "production",
1838
2786
  "id": Any<String>,
1839
2787
  "orgId": "org_precision_reconciliation",
2788
+ "pollObservedAt": null,
1840
2789
  "projectId": null,
1841
2790
  "providerOutputAmount": {
1842
2791
  "baseUnits": "1000000",
@@ -1941,6 +2890,7 @@ describe('createTracker', () => {
1941
2890
  "tempoGasPaid": null,
1942
2891
  "updatedAt": Any<String>,
1943
2892
  "version": 3,
2893
+ "webhookReceivedAt": null,
1944
2894
  },
1945
2895
  ],
1946
2896
  "result": {
@@ -1999,19 +2949,86 @@ describe('createTracker', () => {
1999
2949
  function transferLog(options: transferLog.Options) {
2000
2950
  return {
2001
2951
  address: options.token,
2952
+ ...(options.blockNumber !== undefined
2953
+ ? { blockNumber: `0x${options.blockNumber.toString(16)}` }
2954
+ : {}),
2002
2955
  data: `0x${options.amount.toString(16).padStart(64, '0')}`,
2003
2956
  logIndex: `0x${(options.logIndex ?? 0).toString(16)}`,
2004
2957
  topics: [transferTopic, topicAddress(options.sender), topicAddress(options.recipient)],
2958
+ ...(options.transactionHash ? { transactionHash: options.transactionHash } : {}),
2005
2959
  }
2006
2960
  }
2007
2961
 
2008
2962
  declare namespace transferLog {
2009
2963
  type Options = {
2010
2964
  amount: bigint
2965
+ blockNumber?: number | undefined
2011
2966
  logIndex?: number | undefined
2012
2967
  recipient: string
2013
2968
  sender: string
2014
2969
  token: string
2970
+ transactionHash?: string | undefined
2971
+ }
2972
+ }
2973
+
2974
+ function relayDepositLog(options: relayDepositLog.Options) {
2975
+ const event = AbiEvent.from(
2976
+ 'event RelayErc20Deposit(address from, address token, uint256 amount, bytes32 id)',
2977
+ )
2978
+ return {
2979
+ address: options.depository,
2980
+ blockNumber: `0x${options.blockNumber.toString(16)}`,
2981
+ data: AbiParameters.encode(event.inputs, [
2982
+ options.from as Address.Address,
2983
+ options.token as Address.Address,
2984
+ options.amount,
2985
+ options.orderId as Hex.Hex,
2986
+ ]),
2987
+ logIndex: '0x0',
2988
+ topics: [AbiEvent.getSelector(event)],
2989
+ transactionHash: options.transactionHash,
2990
+ }
2991
+ }
2992
+
2993
+ declare namespace relayDepositLog {
2994
+ type Options = {
2995
+ amount: bigint
2996
+ blockNumber: number
2997
+ depository: string
2998
+ from: string
2999
+ orderId: string
3000
+ token: string
3001
+ transactionHash: string
3002
+ }
3003
+ }
3004
+
3005
+ function rpcTransaction(options: rpcTransaction.Options) {
3006
+ return {
3007
+ blockHash: `0x${'39'.repeat(32)}`,
3008
+ blockNumber: `0x${options.blockNumber.toString(16)}`,
3009
+ from: options.from,
3010
+ gas: '0x5208',
3011
+ gasPrice: '0x1',
3012
+ hash: options.hash,
3013
+ input: options.input,
3014
+ nonce: '0x1',
3015
+ r: `0x${'3a'.repeat(32)}`,
3016
+ s: `0x${'3b'.repeat(32)}`,
3017
+ to: options.to,
3018
+ transactionIndex: '0x0',
3019
+ type: '0x0',
3020
+ v: '0x1b',
3021
+ value: '0x0',
3022
+ }
3023
+ }
3024
+
3025
+ declare namespace rpcTransaction {
3026
+ type Options = {
3027
+ blockNumber: number
3028
+ from: string
3029
+ hash: string
3030
+ input: string
3031
+ to: string
2015
3032
  }
2016
3033
  }
2017
3034