@across-protocol/sdk 4.1.63-beta.1 → 4.1.63-beta.3

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 (224) hide show
  1. package/dist/cjs/arch/evm/BlockUtils.d.ts +15 -0
  2. package/dist/cjs/arch/evm/BlockUtils.js +218 -0
  3. package/dist/cjs/arch/evm/BlockUtils.js.map +1 -0
  4. package/dist/cjs/arch/evm/index.d.ts +1 -0
  5. package/dist/cjs/arch/evm/index.js +1 -0
  6. package/dist/cjs/arch/evm/index.js.map +1 -1
  7. package/dist/cjs/arch/svm/SpokeUtils.d.ts +3 -2
  8. package/dist/cjs/arch/svm/SpokeUtils.js +42 -12
  9. package/dist/cjs/arch/svm/SpokeUtils.js.map +1 -1
  10. package/dist/cjs/arch/svm/eventsClient.js +6 -6
  11. package/dist/cjs/arch/svm/eventsClient.js.map +1 -1
  12. package/dist/cjs/arch/svm/utils.d.ts +1 -0
  13. package/dist/cjs/arch/svm/utils.js +10 -2
  14. package/dist/cjs/arch/svm/utils.js.map +1 -1
  15. package/dist/cjs/clients/HubPoolClient.d.ts +3 -2
  16. package/dist/cjs/clients/HubPoolClient.js +2 -1
  17. package/dist/cjs/clients/HubPoolClient.js.map +1 -1
  18. package/dist/cjs/clients/SpokePoolClient/SVMSpokePoolClient.d.ts +1 -1
  19. package/dist/cjs/clients/SpokePoolClient/SVMSpokePoolClient.js +29 -2
  20. package/dist/cjs/clients/SpokePoolClient/SVMSpokePoolClient.js.map +1 -1
  21. package/dist/cjs/clients/SpokePoolClient/SpokePoolClient.js +4 -3
  22. package/dist/cjs/clients/SpokePoolClient/SpokePoolClient.js.map +1 -1
  23. package/dist/cjs/clients/mocks/MockSvmCpiEventsClient.d.ts +33 -0
  24. package/dist/cjs/clients/mocks/MockSvmCpiEventsClient.js +184 -0
  25. package/dist/cjs/clients/mocks/MockSvmCpiEventsClient.js.map +1 -0
  26. package/dist/cjs/clients/mocks/MockSvmSpokePoolClient.d.ts +30 -0
  27. package/dist/cjs/clients/mocks/MockSvmSpokePoolClient.js +90 -0
  28. package/dist/cjs/clients/mocks/MockSvmSpokePoolClient.js.map +1 -0
  29. package/dist/cjs/clients/mocks/index.d.ts +2 -0
  30. package/dist/cjs/clients/mocks/index.js +2 -0
  31. package/dist/cjs/clients/mocks/index.js.map +1 -1
  32. package/dist/cjs/constants.d.ts +1 -0
  33. package/dist/cjs/constants.js +2 -1
  34. package/dist/cjs/constants.js.map +1 -1
  35. package/dist/cjs/providers/index.d.ts +1 -1
  36. package/dist/cjs/providers/index.js +1 -2
  37. package/dist/cjs/providers/index.js.map +1 -1
  38. package/dist/cjs/providers/mocks/MockCachedSolanaRpcFactory.d.ts +5 -0
  39. package/dist/cjs/providers/mocks/MockCachedSolanaRpcFactory.js +21 -0
  40. package/dist/cjs/providers/mocks/MockCachedSolanaRpcFactory.js.map +1 -0
  41. package/dist/cjs/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts +5 -0
  42. package/dist/cjs/providers/mocks/MockRateLimitedSolanaRpcFactory.js +20 -0
  43. package/dist/cjs/providers/mocks/MockRateLimitedSolanaRpcFactory.js.map +1 -0
  44. package/dist/cjs/providers/mocks/MockSolanaRpcFactory.d.ts +13 -0
  45. package/dist/cjs/providers/mocks/MockSolanaRpcFactory.js +76 -0
  46. package/dist/cjs/providers/mocks/MockSolanaRpcFactory.js.map +1 -0
  47. package/dist/cjs/providers/mocks/index.d.ts +4 -0
  48. package/dist/cjs/providers/mocks/index.js +8 -0
  49. package/dist/cjs/providers/mocks/index.js.map +1 -0
  50. package/dist/cjs/providers/{mockProvider.js → mocks/mockEthersProvider.js} +2 -2
  51. package/dist/cjs/providers/mocks/mockEthersProvider.js.map +1 -0
  52. package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js +5 -4
  53. package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
  54. package/dist/cjs/relayFeeCalculator/chain-queries/factory.d.ts +9 -0
  55. package/dist/cjs/relayFeeCalculator/chain-queries/factory.js +1 -1
  56. package/dist/cjs/relayFeeCalculator/chain-queries/factory.js.map +1 -1
  57. package/dist/cjs/relayFeeCalculator/chain-queries/svmQuery.d.ts +6 -0
  58. package/dist/cjs/relayFeeCalculator/chain-queries/svmQuery.js +69 -34
  59. package/dist/cjs/relayFeeCalculator/chain-queries/svmQuery.js.map +1 -1
  60. package/dist/cjs/relayFeeCalculator/relayFeeCalculator.d.ts +11 -0
  61. package/dist/cjs/relayFeeCalculator/relayFeeCalculator.js +9 -3
  62. package/dist/cjs/relayFeeCalculator/relayFeeCalculator.js.map +1 -1
  63. package/dist/cjs/utils/BlockFinder.d.ts +22 -0
  64. package/dist/cjs/utils/BlockFinder.js +10 -0
  65. package/dist/cjs/utils/BlockFinder.js.map +1 -0
  66. package/dist/cjs/utils/BlockUtils.d.ts +2 -27
  67. package/dist/cjs/utils/BlockUtils.js +2 -208
  68. package/dist/cjs/utils/BlockUtils.js.map +1 -1
  69. package/dist/cjs/utils/TokenUtils.d.ts +18 -0
  70. package/dist/cjs/utils/index.d.ts +1 -0
  71. package/dist/cjs/utils/index.js +1 -0
  72. package/dist/cjs/utils/index.js.map +1 -1
  73. package/dist/esm/arch/evm/BlockUtils.d.ts +24 -0
  74. package/dist/esm/arch/evm/BlockUtils.js +250 -0
  75. package/dist/esm/arch/evm/BlockUtils.js.map +1 -0
  76. package/dist/esm/arch/evm/index.d.ts +1 -0
  77. package/dist/esm/arch/evm/index.js +1 -0
  78. package/dist/esm/arch/evm/index.js.map +1 -1
  79. package/dist/esm/arch/svm/SpokeUtils.d.ts +37 -2
  80. package/dist/esm/arch/svm/SpokeUtils.js +80 -13
  81. package/dist/esm/arch/svm/SpokeUtils.js.map +1 -1
  82. package/dist/esm/arch/svm/eventsClient.js +6 -6
  83. package/dist/esm/arch/svm/eventsClient.js.map +1 -1
  84. package/dist/esm/arch/svm/utils.d.ts +4 -0
  85. package/dist/esm/arch/svm/utils.js +11 -1
  86. package/dist/esm/arch/svm/utils.js.map +1 -1
  87. package/dist/esm/clients/HubPoolClient.d.ts +3 -2
  88. package/dist/esm/clients/HubPoolClient.js +3 -2
  89. package/dist/esm/clients/HubPoolClient.js.map +1 -1
  90. package/dist/esm/clients/SpokePoolClient/SVMSpokePoolClient.d.ts +1 -2
  91. package/dist/esm/clients/SpokePoolClient/SVMSpokePoolClient.js +34 -5
  92. package/dist/esm/clients/SpokePoolClient/SVMSpokePoolClient.js.map +1 -1
  93. package/dist/esm/clients/SpokePoolClient/SpokePoolClient.js +6 -4
  94. package/dist/esm/clients/SpokePoolClient/SpokePoolClient.js.map +1 -1
  95. package/dist/esm/clients/mocks/MockSvmCpiEventsClient.d.ts +33 -0
  96. package/dist/esm/clients/mocks/MockSvmCpiEventsClient.js +183 -0
  97. package/dist/esm/clients/mocks/MockSvmCpiEventsClient.js.map +1 -0
  98. package/dist/esm/clients/mocks/MockSvmSpokePoolClient.d.ts +30 -0
  99. package/dist/esm/clients/mocks/MockSvmSpokePoolClient.js +89 -0
  100. package/dist/esm/clients/mocks/MockSvmSpokePoolClient.js.map +1 -0
  101. package/dist/esm/clients/mocks/index.d.ts +2 -0
  102. package/dist/esm/clients/mocks/index.js +2 -0
  103. package/dist/esm/clients/mocks/index.js.map +1 -1
  104. package/dist/esm/constants.d.ts +1 -0
  105. package/dist/esm/constants.js +1 -0
  106. package/dist/esm/constants.js.map +1 -1
  107. package/dist/esm/providers/index.d.ts +1 -1
  108. package/dist/esm/providers/index.js +1 -2
  109. package/dist/esm/providers/index.js.map +1 -1
  110. package/dist/esm/providers/mocks/MockCachedSolanaRpcFactory.d.ts +5 -0
  111. package/dist/esm/providers/mocks/MockCachedSolanaRpcFactory.js +19 -0
  112. package/dist/esm/providers/mocks/MockCachedSolanaRpcFactory.js.map +1 -0
  113. package/dist/esm/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts +5 -0
  114. package/dist/esm/providers/mocks/MockRateLimitedSolanaRpcFactory.js +18 -0
  115. package/dist/esm/providers/mocks/MockRateLimitedSolanaRpcFactory.js.map +1 -0
  116. package/dist/esm/providers/mocks/MockSolanaRpcFactory.d.ts +13 -0
  117. package/dist/esm/providers/mocks/MockSolanaRpcFactory.js +74 -0
  118. package/dist/esm/providers/mocks/MockSolanaRpcFactory.js.map +1 -0
  119. package/dist/esm/providers/mocks/index.d.ts +4 -0
  120. package/dist/esm/providers/mocks/index.js +5 -0
  121. package/dist/esm/providers/mocks/index.js.map +1 -0
  122. package/dist/esm/providers/{mockProvider.js → mocks/mockEthersProvider.js} +2 -2
  123. package/dist/esm/providers/mocks/mockEthersProvider.js.map +1 -0
  124. package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js +6 -5
  125. package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
  126. package/dist/esm/relayFeeCalculator/chain-queries/factory.d.ts +9 -0
  127. package/dist/esm/relayFeeCalculator/chain-queries/factory.js +3 -3
  128. package/dist/esm/relayFeeCalculator/chain-queries/factory.js.map +1 -1
  129. package/dist/esm/relayFeeCalculator/chain-queries/svmQuery.d.ts +18 -0
  130. package/dist/esm/relayFeeCalculator/chain-queries/svmQuery.js +83 -36
  131. package/dist/esm/relayFeeCalculator/chain-queries/svmQuery.js.map +1 -1
  132. package/dist/esm/relayFeeCalculator/relayFeeCalculator.d.ts +21 -1
  133. package/dist/esm/relayFeeCalculator/relayFeeCalculator.js +9 -4
  134. package/dist/esm/relayFeeCalculator/relayFeeCalculator.js.map +1 -1
  135. package/dist/esm/utils/BlockFinder.d.ts +22 -0
  136. package/dist/esm/utils/BlockFinder.js +7 -0
  137. package/dist/esm/utils/BlockFinder.js.map +1 -0
  138. package/dist/esm/utils/BlockUtils.d.ts +2 -36
  139. package/dist/esm/utils/BlockUtils.js +2 -243
  140. package/dist/esm/utils/BlockUtils.js.map +1 -1
  141. package/dist/esm/utils/TokenUtils.d.ts +18 -0
  142. package/dist/esm/utils/index.d.ts +1 -0
  143. package/dist/esm/utils/index.js +1 -0
  144. package/dist/esm/utils/index.js.map +1 -1
  145. package/dist/types/arch/evm/BlockUtils.d.ts +25 -0
  146. package/dist/types/arch/evm/BlockUtils.d.ts.map +1 -0
  147. package/dist/types/arch/evm/index.d.ts +1 -0
  148. package/dist/types/arch/evm/index.d.ts.map +1 -1
  149. package/dist/types/arch/svm/SpokeUtils.d.ts +37 -2
  150. package/dist/types/arch/svm/SpokeUtils.d.ts.map +1 -1
  151. package/dist/types/arch/svm/eventsClient.d.ts.map +1 -1
  152. package/dist/types/arch/svm/utils.d.ts +4 -0
  153. package/dist/types/arch/svm/utils.d.ts.map +1 -1
  154. package/dist/types/clients/HubPoolClient.d.ts +3 -2
  155. package/dist/types/clients/HubPoolClient.d.ts.map +1 -1
  156. package/dist/types/clients/SpokePoolClient/SVMSpokePoolClient.d.ts +1 -2
  157. package/dist/types/clients/SpokePoolClient/SVMSpokePoolClient.d.ts.map +1 -1
  158. package/dist/types/clients/SpokePoolClient/SpokePoolClient.d.ts.map +1 -1
  159. package/dist/types/clients/mocks/MockSvmCpiEventsClient.d.ts +34 -0
  160. package/dist/types/clients/mocks/MockSvmCpiEventsClient.d.ts.map +1 -0
  161. package/dist/types/clients/mocks/MockSvmSpokePoolClient.d.ts +31 -0
  162. package/dist/types/clients/mocks/MockSvmSpokePoolClient.d.ts.map +1 -0
  163. package/dist/types/clients/mocks/index.d.ts +2 -0
  164. package/dist/types/clients/mocks/index.d.ts.map +1 -1
  165. package/dist/types/constants.d.ts +1 -0
  166. package/dist/types/constants.d.ts.map +1 -1
  167. package/dist/types/providers/index.d.ts +1 -1
  168. package/dist/types/providers/index.d.ts.map +1 -1
  169. package/dist/types/providers/mocks/MockCachedSolanaRpcFactory.d.ts +6 -0
  170. package/dist/types/providers/mocks/MockCachedSolanaRpcFactory.d.ts.map +1 -0
  171. package/dist/types/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts +6 -0
  172. package/dist/types/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts.map +1 -0
  173. package/dist/types/providers/mocks/MockSolanaRpcFactory.d.ts +14 -0
  174. package/dist/types/providers/mocks/MockSolanaRpcFactory.d.ts.map +1 -0
  175. package/dist/types/providers/mocks/index.d.ts +5 -0
  176. package/dist/types/providers/mocks/index.d.ts.map +1 -0
  177. package/dist/types/providers/{mockProvider.d.ts → mocks/mockEthersProvider.d.ts} +1 -1
  178. package/dist/types/providers/mocks/mockEthersProvider.d.ts.map +1 -0
  179. package/dist/types/relayFeeCalculator/chain-queries/baseQuery.d.ts.map +1 -1
  180. package/dist/types/relayFeeCalculator/chain-queries/factory.d.ts +9 -0
  181. package/dist/types/relayFeeCalculator/chain-queries/factory.d.ts.map +1 -1
  182. package/dist/types/relayFeeCalculator/chain-queries/svmQuery.d.ts +18 -0
  183. package/dist/types/relayFeeCalculator/chain-queries/svmQuery.d.ts.map +1 -1
  184. package/dist/types/relayFeeCalculator/relayFeeCalculator.d.ts +21 -1
  185. package/dist/types/relayFeeCalculator/relayFeeCalculator.d.ts.map +1 -1
  186. package/dist/types/utils/BlockFinder.d.ts +23 -0
  187. package/dist/types/utils/BlockFinder.d.ts.map +1 -0
  188. package/dist/types/utils/BlockUtils.d.ts +2 -36
  189. package/dist/types/utils/BlockUtils.d.ts.map +1 -1
  190. package/dist/types/utils/TokenUtils.d.ts +18 -0
  191. package/dist/types/utils/TokenUtils.d.ts.map +1 -1
  192. package/dist/types/utils/index.d.ts +1 -0
  193. package/dist/types/utils/index.d.ts.map +1 -1
  194. package/package.json +2 -3
  195. package/src/arch/evm/BlockUtils.ts +209 -0
  196. package/src/arch/evm/index.ts +1 -0
  197. package/src/arch/svm/SpokeUtils.ts +87 -17
  198. package/src/arch/svm/eventsClient.ts +13 -5
  199. package/src/arch/svm/utils.ts +12 -1
  200. package/src/clients/HubPoolClient.ts +3 -3
  201. package/src/clients/SpokePoolClient/SVMSpokePoolClient.ts +27 -3
  202. package/src/clients/SpokePoolClient/SpokePoolClient.ts +6 -3
  203. package/src/clients/mocks/MockSvmCpiEventsClient.ts +226 -0
  204. package/src/clients/mocks/MockSvmSpokePoolClient.ts +119 -0
  205. package/src/clients/mocks/index.ts +2 -0
  206. package/src/constants.ts +1 -0
  207. package/src/providers/index.ts +1 -1
  208. package/src/providers/mocks/MockCachedSolanaRpcFactory.ts +15 -0
  209. package/src/providers/mocks/MockRateLimitedSolanaRpcFactory.ts +14 -0
  210. package/src/providers/mocks/MockSolanaRpcFactory.ts +55 -0
  211. package/src/providers/mocks/index.ts +4 -0
  212. package/src/providers/{mockProvider.ts → mocks/mockEthersProvider.ts} +1 -1
  213. package/src/relayFeeCalculator/chain-queries/baseQuery.ts +6 -6
  214. package/src/relayFeeCalculator/chain-queries/factory.ts +3 -3
  215. package/src/relayFeeCalculator/chain-queries/svmQuery.ts +59 -27
  216. package/src/relayFeeCalculator/relayFeeCalculator.ts +15 -3
  217. package/src/utils/BlockFinder.ts +26 -0
  218. package/src/utils/BlockUtils.ts +5 -215
  219. package/src/utils/index.ts +1 -0
  220. package/dist/cjs/providers/mockProvider.js.map +0 -1
  221. package/dist/esm/providers/mockProvider.js.map +0 -1
  222. package/dist/types/providers/mockProvider.d.ts.map +0 -1
  223. /package/dist/cjs/providers/{mockProvider.d.ts → mocks/mockEthersProvider.d.ts} +0 -0
  224. /package/dist/esm/providers/{mockProvider.d.ts → mocks/mockEthersProvider.d.ts} +0 -0
@@ -0,0 +1,209 @@
1
+ import assert from "assert";
2
+ import { Provider, Block as EthersBlock } from "@ethersproject/abstract-provider";
3
+ import { clamp, sortedIndexBy } from "lodash";
4
+ import { chainIsOPStack, getNetworkName } from "../../utils/NetworkUtils";
5
+ import { isDefined } from "../../utils/TypeGuards";
6
+ import {
7
+ BlockFinder,
8
+ type Block,
9
+ type BlockFinderOpts as Opts,
10
+ type BlockTimeAverage,
11
+ type BlockFinderHints,
12
+ } from "../../utils/BlockFinder";
13
+ import { getCurrentTime } from "../../utils/TimeUtils";
14
+ import { CHAIN_IDs } from "../../constants";
15
+
16
+ // Extension of the EthersBlock type which implements `Block`.
17
+ interface EVMBlock extends Block, EthersBlock {}
18
+
19
+ // Archive requests typically commence at 128 blocks past the head of the chain.
20
+ // Round down to 120 blocks to avoid slipping into archive territory.
21
+ const defaultBlockRange = 120;
22
+
23
+ // Default offset to the high block number. This is subtracted from the block number of the high block
24
+ // when it is queried from the network, rather than having been specified by the caller. This is useful
25
+ // since the supplied Provider instance may be backed by multiple RPC providers, which can lead to some
26
+ // providers running slower than others and taking time to synchronise on the latest block.
27
+ const defaultHighBlockOffset = 10;
28
+
29
+ // Retain computations for 15 minutes.
30
+ const cacheTTL = 60 * 15;
31
+ const now = getCurrentTime(); // Seed the cache with initial values.
32
+ const blockTimes: { [chainId: number]: BlockTimeAverage } = {
33
+ [CHAIN_IDs.INK]: { average: 1, timestamp: now, blockRange: 1 },
34
+ [CHAIN_IDs.LINEA]: { average: 3, timestamp: now, blockRange: 1 },
35
+ [CHAIN_IDs.MAINNET]: { average: 12.5, timestamp: now, blockRange: 1 },
36
+ [CHAIN_IDs.OPTIMISM]: { average: 2, timestamp: now, blockRange: 1 },
37
+ [CHAIN_IDs.UNICHAIN]: { average: 1, timestamp: now, blockRange: 1 },
38
+ };
39
+
40
+ /**
41
+ * @description Compute the average block time over a block range.
42
+ * @returns Average number of seconds per block.
43
+ */
44
+ export async function averageBlockTime(
45
+ provider: Provider,
46
+ { highBlock, highBlockOffset, blockRange }: Opts = {}
47
+ ): Promise<Pick<BlockTimeAverage, "average" | "blockRange">> {
48
+ // Does not block for StaticJsonRpcProvider.
49
+ const { chainId } = await provider.getNetwork();
50
+
51
+ // OP stack chains inherit Optimism block times, but can be overridden.
52
+ const cache = blockTimes[chainId] ?? (chainIsOPStack(chainId) ? blockTimes[CHAIN_IDs.OPTIMISM] : undefined);
53
+
54
+ const now = getCurrentTime();
55
+ if (isDefined(cache) && now < cache.timestamp + cacheTTL) {
56
+ return { average: cache.average, blockRange: cache.blockRange };
57
+ }
58
+
59
+ // If the caller was not specific about highBlock, resolve it via the RPC provider. Subtract an offset
60
+ // to account for various RPC provider sync issues that might occur when querting the latest block.
61
+ if (!isDefined(highBlock)) {
62
+ highBlock = await provider.getBlockNumber();
63
+ highBlock -= highBlockOffset ?? defaultHighBlockOffset;
64
+ }
65
+ blockRange ??= defaultBlockRange;
66
+
67
+ const earliestBlockNumber = highBlock - blockRange;
68
+ const [firstBlock, lastBlock] = await Promise.all([
69
+ provider.getBlock(earliestBlockNumber),
70
+ provider.getBlock(highBlock),
71
+ ]);
72
+ [firstBlock, lastBlock].forEach((block: Block | undefined) => {
73
+ if (!isDefined(block?.timestamp)) {
74
+ const network = getNetworkName(chainId);
75
+ const blockNumber = block === firstBlock ? earliestBlockNumber : highBlock;
76
+ throw new Error(`BlockFinder: Failed to fetch block ${blockNumber} on ${network}`);
77
+ }
78
+ });
79
+
80
+ const average = (lastBlock.timestamp - firstBlock.timestamp) / blockRange;
81
+ blockTimes[chainId] = { timestamp: now, average, blockRange };
82
+
83
+ return { average, blockRange };
84
+ }
85
+
86
+ async function estimateBlocksElapsed(seconds: number, cushionPercentage = 0.0, provider: Provider): Promise<number> {
87
+ const cushionMultiplier = cushionPercentage + 1.0;
88
+ const { average } = await averageBlockTime(provider);
89
+ return Math.floor((seconds * cushionMultiplier) / average);
90
+ }
91
+
92
+ export class EVMBlockFinder extends BlockFinder<EVMBlock> {
93
+ constructor(
94
+ private readonly provider: Provider,
95
+ private readonly blocks: EVMBlock[] = []
96
+ ) {
97
+ super();
98
+ }
99
+
100
+ /**
101
+ * @notice Gets the latest block whose timestamp is <= the provided timestamp.
102
+ * @param number Timestamp timestamp to search.
103
+ * @param hints Optional low and high block to bound the search space.
104
+ */
105
+ public async getBlockForTimestamp(timestamp: number | string, hints: BlockFinderHints = {}): Promise<EVMBlock> {
106
+ timestamp = Number(timestamp);
107
+ assert(timestamp !== undefined && timestamp !== null, "timestamp must be provided");
108
+ // If the last block we have stored is too early, grab the latest block.
109
+ if (this.blocks.length === 0 || this.blocks[this.blocks.length - 1].timestamp < timestamp) {
110
+ const block = await this.getLatestBlock();
111
+ if (timestamp >= block.timestamp) return block;
112
+ }
113
+
114
+ // Prime the BlockFinder cache with any supplied hints.
115
+ // If the hint is accurate, then this will bypass the subsequent estimation.
116
+ await Promise.all(
117
+ Object.values(hints)
118
+ .filter((blockNumber) => isDefined(blockNumber))
119
+ .map((blockNumber) => this.getBlock(blockNumber))
120
+ );
121
+
122
+ // Check the first block. If it's greater than our timestamp, we need to find an earlier block.
123
+ if (this.blocks[0].timestamp > timestamp) {
124
+ const initialBlock = this.blocks[0];
125
+ // We use a 2x cushion to reduce the number of iterations in the following loop and increase the chance
126
+ // that the first block we find sets a floor for the target timestamp. The loop converges on the correct block
127
+ // slower than the following incremental search performed by `findBlock`, so we want to minimize the number of
128
+ // loop iterations in favor of searching more blocks over the `findBlock` search.
129
+ const cushion = 1;
130
+ const incrementDistance = Math.max(
131
+ // Ensure the increment block distance is _at least_ a single block to prevent an infinite loop.
132
+ await estimateBlocksElapsed(initialBlock.timestamp - timestamp, cushion, this.provider),
133
+ 1
134
+ );
135
+
136
+ // Search backwards by a constant increment until we find a block before the timestamp or hit block 0.
137
+ for (let multiplier = 1; ; multiplier++) {
138
+ const distance = multiplier * incrementDistance;
139
+ const blockNumber = Math.max(0, initialBlock.number - distance);
140
+ const block = await this.getBlock(blockNumber);
141
+ if (block.timestamp <= timestamp) break; // Found an earlier block.
142
+ assert(blockNumber > 0, "timestamp is before block 0"); // Block 0 was not earlier than this timestamp. The row.
143
+ }
144
+ }
145
+
146
+ // Find the index where the block would be inserted and use that as the end block (since it is >= the timestamp).
147
+ const index = sortedIndexBy(this.blocks, { timestamp } as Block, "timestamp");
148
+ return this.findBlock(this.blocks[index - 1], this.blocks[index], timestamp);
149
+ }
150
+
151
+ // Grabs the most recent block and caches it.
152
+ private async getLatestBlock() {
153
+ const block = await this.provider.getBlock("latest");
154
+ const index = sortedIndexBy(this.blocks, block, "number");
155
+ if (this.blocks[index]?.number !== block.number) this.blocks.splice(index, 0, block);
156
+ return this.blocks[index];
157
+ }
158
+
159
+ // Grabs the block for a particular number and caches it.
160
+ private async getBlock(number: number) {
161
+ let index = sortedIndexBy(this.blocks, { number } as Block, "number");
162
+ if (this.blocks[index]?.number === number) return this.blocks[index]; // Return early if block already exists.
163
+ const block = await this.provider.getBlock(number);
164
+
165
+ // Recompute the index after the async call since the state of this.blocks could have changed!
166
+ index = sortedIndexBy(this.blocks, { number } as Block, "number");
167
+
168
+ // Rerun this check to avoid duplicate insertion.
169
+ if (this.blocks[index]?.number === number) return this.blocks[index];
170
+ this.blocks.splice(index, 0, block); // A simple insert at index.
171
+ return block;
172
+ }
173
+
174
+ // Return the latest block, between startBlock and endBlock, whose timestamp is <= timestamp.
175
+ // Effectively, this is an interpolation search algorithm to minimize block requests.
176
+ // Note: startBlock and endBlock _must_ be different blocks.
177
+ private async findBlock(_startBlock: EVMBlock, _endBlock: EVMBlock, timestamp: number): Promise<EVMBlock> {
178
+ const [startBlock, endBlock] = [_startBlock, _endBlock];
179
+ // In the case of equality, the endBlock is expected to be passed as the one whose timestamp === the requested
180
+ // timestamp.
181
+ if (endBlock.timestamp === timestamp) return endBlock;
182
+
183
+ // If there's no equality, but the blocks are adjacent, return the startBlock, since we want the returned block's
184
+ // timestamp to be <= the requested timestamp.
185
+ if (endBlock.number === startBlock.number + 1) return startBlock;
186
+
187
+ assert(endBlock.number !== startBlock.number, "startBlock cannot equal endBlock");
188
+ assert(
189
+ timestamp < endBlock.timestamp && timestamp > startBlock.timestamp,
190
+ "timestamp not in between start and end blocks"
191
+ );
192
+
193
+ // Interpolating the timestamp we're searching for to block numbers.
194
+ const totalTimeDifference = endBlock.timestamp - startBlock.timestamp;
195
+ const totalBlockDistance = endBlock.number - startBlock.number;
196
+ const blockPercentile = (timestamp - startBlock.timestamp) / totalTimeDifference;
197
+ const estimatedBlock = startBlock.number + Math.round(blockPercentile * totalBlockDistance);
198
+
199
+ // Clamp ensures the estimated block is strictly greater than the start block and strictly less than the end block.
200
+ const newBlock = await this.getBlock(clamp(estimatedBlock, startBlock.number + 1, endBlock.number - 1));
201
+
202
+ // Depending on whether the new block is below or above the timestamp, narrow the search space accordingly.
203
+ if (newBlock.timestamp < timestamp) {
204
+ return this.findBlock(newBlock, endBlock, timestamp);
205
+ } else {
206
+ return this.findBlock(startBlock, newBlock, timestamp);
207
+ }
208
+ }
209
+ }
@@ -1 +1,2 @@
1
1
  export * from "./SpokeUtils";
2
+ export * from "./BlockUtils";
@@ -21,22 +21,29 @@ import {
21
21
  import assert from "assert";
22
22
  import { arrayify, hexZeroPad, hexlify } from "ethers/lib/utils";
23
23
  import { Logger } from "winston";
24
+
24
25
  import { CHAIN_IDs } from "../../constants";
25
- import { Deposit, FillStatus, FillWithBlock, RelayData } from "../../interfaces";
26
+ import { Deposit, DepositWithBlock, FillStatus, FillWithBlock, RelayData } from "../../interfaces";
26
27
  import {
27
28
  BigNumber,
29
+ isUnsafeDepositId,
28
30
  SvmAddress,
29
- chainIsSvm,
30
- chunk,
31
31
  getTokenInfo,
32
32
  isDefined,
33
- isUnsafeDepositId,
34
- keccak256,
35
33
  toAddressType,
34
+ keccak256,
35
+ chainIsSvm,
36
+ chunk,
36
37
  } from "../../utils";
37
- import { SvmCpiEventsClient, getEventAuthority, getFillStatusPda, getStatePda, unwrapEventData } from "./";
38
+ import { getStatePda, SvmCpiEventsClient, getFillStatusPda, unwrapEventData, getEventAuthority } from "./";
38
39
  import { SVMEventNames, SVMProvider } from "./types";
39
40
 
41
+ /**
42
+ * @note: Average Solana slot duration is about 400-500ms. We can be conservative
43
+ * and choose 400 to ensure that the most slots get included in our ranges
44
+ */
45
+ export const SLOT_DURATION_MS = 400;
46
+
40
47
  /**
41
48
  * @param spokePool SpokePool Contract instance.
42
49
  * @param deposit V3Deopsit instance.
@@ -81,17 +88,76 @@ export function getDepositIdAtBlock(_contract: unknown, _blockTag: number): Prom
81
88
  throw new Error("getDepositIdAtBlock: not implemented");
82
89
  }
83
90
 
84
- export function findDepositBlock(
85
- _spokePool: unknown,
91
+ /**
92
+ * Finds deposit events within a 2-day window ending at the specified slot.
93
+ *
94
+ * @remarks
95
+ * This implementation uses a slot-limited search approach because Solana PDA state has
96
+ * limitations that prevent directly referencing old deposit IDs. Unlike EVM chains where
97
+ * we might use binary search across the entire chain history, in Solana we must query within
98
+ * a constrained slot range.
99
+ *
100
+ * The search window is calculated by:
101
+ * 1. Using the provided slot (or current confirmed slot if none is provided)
102
+ * 2. Looking back 2 days worth of slots from that point
103
+ *
104
+ * We use a 2-day window because:
105
+ * 1. Most valid deposits that need to be processed will be recent
106
+ * 2. This covers multiple bundle submission periods
107
+ * 3. It balances performance with practical deposit age
108
+ *
109
+ * @important
110
+ * This function may return `undefined` for valid deposit IDs that are older than the search
111
+ * window (approximately 2 days before the specified slot). This is an acceptable limitation
112
+ * as deposits this old are typically not relevant to current operations.
113
+ *
114
+ * @param eventClient - SvmCpiEventsClient instance
115
+ * @param depositId - The deposit ID to search for
116
+ * @param slot - The slot to search up to (defaults to current slot). The search will look
117
+ * for deposits between (slot - secondsLookback) and slot.
118
+ * @param secondsLookback - The number of seconds to look back for deposits (defaults to 2 days).
119
+ * @returns The deposit if found within the slot window, undefined otherwise
120
+ */
121
+ export async function findDeposit(
122
+ eventClient: SvmCpiEventsClient,
86
123
  depositId: BigNumber,
87
- _lowBlock: number,
88
- _highBlock?: number
89
- ): Promise<number | undefined> {
124
+ slot?: bigint,
125
+ secondsLookback = 2 * 24 * 60 * 60 // 2 days
126
+ ): Promise<DepositWithBlock | undefined> {
90
127
  // We can only perform this search when we have a safe deposit ID.
91
128
  if (isUnsafeDepositId(depositId)) {
92
129
  throw new Error(`Cannot binary search for depositId ${depositId}`);
93
130
  }
94
- throw new Error("findDepositBlock: not implemented");
131
+
132
+ const provider = eventClient.getRpc();
133
+ const currentSlot = await provider.getSlot({ commitment: "confirmed" }).send();
134
+
135
+ // If no slot is provided, use the current slot
136
+ // If a slot is provided, ensure it's not in the future
137
+ const endSlot = slot !== undefined ? BigInt(Math.min(Number(slot), Number(currentSlot))) : currentSlot;
138
+
139
+ // Calculate start slot (approximately secondsLookback seconds earlier)
140
+ const slotsInElapsed = BigInt(Math.round((secondsLookback * 1000) / SLOT_DURATION_MS));
141
+ const startSlot = endSlot - slotsInElapsed;
142
+
143
+ // Query for the deposit events with this limited slot range. Filter by deposit id.
144
+ const depositEvent = (await eventClient.queryEvents("FundsDeposited", startSlot, endSlot))?.find((event) =>
145
+ depositId.eq((event.data as unknown as { depositId: BigNumber }).depositId)
146
+ );
147
+
148
+ // If no deposit event is found, return undefined
149
+ if (!depositEvent) {
150
+ return undefined;
151
+ }
152
+
153
+ // Return the deposit event with block info
154
+ return {
155
+ txnRef: depositEvent.signature.toString(),
156
+ blockNumber: Number(depositEvent.slot),
157
+ txnIndex: 0,
158
+ logIndex: 0,
159
+ ...(unwrapEventData(depositEvent.data) as Record<string, unknown>),
160
+ } as DepositWithBlock;
95
161
  }
96
162
 
97
163
  /**
@@ -318,9 +384,9 @@ export async function fillRelayInstruction(
318
384
  getFillStatusPda(spokePool.toV2Address(), deposit, deposit.destinationChainId),
319
385
  getEventAuthority(),
320
386
  ]);
321
- const depositIdBuffer = Buffer.alloc(32);
322
- const shortenedBuffer = Buffer.from(deposit.depositId.toHexString().slice(2), "hex");
323
- shortenedBuffer.copy(depositIdBuffer, 32 - shortenedBuffer.length);
387
+ const depositIdBuffer = new Uint8Array(32);
388
+ const shortenedBuffer = new Uint8Array(Buffer.from(deposit.depositId.toHexString().slice(2), "hex"));
389
+ depositIdBuffer.set(shortenedBuffer, 32 - shortenedBuffer.length);
324
390
 
325
391
  return SvmSpokeClient.getFillRelayInstruction({
326
392
  signer: relayer,
@@ -343,7 +409,7 @@ export async function fillRelayInstruction(
343
409
  originChainId: BigInt(deposit.originChainId),
344
410
  fillDeadline: deposit.fillDeadline,
345
411
  exclusivityDeadline: deposit.exclusivityDeadline,
346
- depositId: new Uint8Array(depositIdBuffer),
412
+ depositId: depositIdBuffer,
347
413
  message: new Uint8Array(Buffer.from(deposit.message.slice(2), "hex")),
348
414
  }),
349
415
  repaymentChainId: some(BigInt(repaymentChainId)),
@@ -409,7 +475,11 @@ export async function getAssociatedTokenAddress(
409
475
  ): Promise<Address<string>> {
410
476
  const [associatedToken] = await getProgramDerivedAddress({
411
477
  programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
412
- seeds: [owner.toBuffer(), SvmAddress.from(tokenProgramId).toBuffer(), mint.toBuffer()],
478
+ seeds: [
479
+ new Uint8Array(owner.toBuffer()),
480
+ new Uint8Array(SvmAddress.from(tokenProgramId).toBuffer()),
481
+ new Uint8Array(mint.toBuffer()),
482
+ ],
413
483
  });
414
484
  return associatedToken;
415
485
  }
@@ -1,7 +1,15 @@
1
1
  import { Idl } from "@coral-xyz/anchor";
2
2
  import { getDeployedAddress, SvmSpokeIdl } from "@across-protocol/contracts";
3
3
  import { getSolanaChainId } from "@across-protocol/contracts/dist/src/svm/web3-v1";
4
- import web3, { Address, Commitment, GetSignaturesForAddressApi, GetTransactionApi, Signature } from "@solana/kit";
4
+ import {
5
+ address,
6
+ Address,
7
+ Commitment,
8
+ getProgramDerivedAddress,
9
+ GetSignaturesForAddressApi,
10
+ GetTransactionApi,
11
+ Signature,
12
+ } from "@solana/kit";
5
13
  import { bs58, chainIsSvm, getMessageHash } from "../../utils";
6
14
  import { EventName, EventWithData, SVMProvider } from "./types";
7
15
  import { decodeEvent, isDevnet } from "./utils";
@@ -56,12 +64,12 @@ export class SvmCpiEventsClient {
56
64
  }
57
65
 
58
66
  public static async createFor(rpc: SVMProvider, programId: string, idl: Idl): Promise<SvmCpiEventsClient> {
59
- const address = web3.address(programId);
60
- const [eventAuthority] = await web3.getProgramDerivedAddress({
61
- programAddress: address,
67
+ const programAddress = address(programId);
68
+ const [eventAuthority] = await getProgramDerivedAddress({
69
+ programAddress,
62
70
  seeds: ["__event_authority"],
63
71
  });
64
- return new SvmCpiEventsClient(rpc, address, eventAuthority, idl);
72
+ return new SvmCpiEventsClient(rpc, programAddress, eventAuthority, idl);
65
73
  }
66
74
 
67
75
  /**
@@ -1,4 +1,5 @@
1
- import { SvmSpokeClient } from "@across-protocol/contracts";
1
+ import bs58 from "bs58";
2
+ import { ethers } from "ethers";
2
3
  import { BN, BorshEventCoder, Idl } from "@coral-xyz/anchor";
3
4
  import {
4
5
  Address,
@@ -9,6 +10,7 @@ import {
9
10
  isAddress,
10
11
  type TransactionSigner,
11
12
  } from "@solana/kit";
13
+ import { SvmSpokeClient } from "@across-protocol/contracts";
12
14
  import { FillType, RelayData } from "../../interfaces";
13
15
  import { BigNumber, SvmAddress, getRelayDataHash, isUint8Array } from "../../utils";
14
16
  import { EventName, SVMEventNames, SVMProvider } from "./types";
@@ -220,3 +222,12 @@ export const getEventAuthority = async () => {
220
222
  });
221
223
  return eventAuthority;
222
224
  };
225
+
226
+ /**
227
+ * Returns a random SVM address.
228
+ */
229
+ export function getRandomSvmAddress() {
230
+ const bytes = ethers.utils.randomBytes(32);
231
+ const base58Address = bs58.encode(bytes);
232
+ return address(base58Address);
233
+ }
@@ -22,9 +22,9 @@ import {
22
22
  TokenRunningBalance,
23
23
  } from "../interfaces";
24
24
  import * as lpFeeCalculator from "../lpFeeCalculator";
25
+ import { EVMBlockFinder } from "../arch/evm";
25
26
  import {
26
27
  BigNumber,
27
- BlockFinder,
28
28
  bnZero,
29
29
  dedupArray,
30
30
  EventSearchConfig,
@@ -97,7 +97,7 @@ export class HubPoolClient extends BaseAbstractClient {
97
97
  protected pendingRootBundle: PendingRootBundle | undefined;
98
98
 
99
99
  public currentTime: number | undefined;
100
- public readonly blockFinder: BlockFinder;
100
+ public readonly blockFinder: EVMBlockFinder;
101
101
 
102
102
  constructor(
103
103
  readonly logger: winston.Logger,
@@ -121,7 +121,7 @@ export class HubPoolClient extends BaseAbstractClient {
121
121
  this.firstHeightToSearch = eventSearchConfig.from;
122
122
 
123
123
  const provider = this.hubPool.provider;
124
- this.blockFinder = new BlockFinder(provider);
124
+ this.blockFinder = new EVMBlockFinder(provider);
125
125
  }
126
126
 
127
127
  protected hubPoolEventFilters(): Record<HubPoolEvent, EventFilter> {
@@ -7,6 +7,7 @@ import {
7
7
  getTimestampForSlot,
8
8
  getStatePda,
9
9
  SvmCpiEventsClient,
10
+ findDeposit,
10
11
  relayFillStatus,
11
12
  fillStatusArray,
12
13
  } from "../../arch/svm";
@@ -15,6 +16,8 @@ import {
15
16
  BigNumber,
16
17
  DepositSearchResult,
17
18
  EventSearchConfig,
19
+ InvalidFill,
20
+ isZeroAddress,
18
21
  MakeOptional,
19
22
  sortEventsAscendingInPlace,
20
23
  } from "../../utils";
@@ -208,10 +211,31 @@ export class SvmSpokePoolClient extends SpokePoolClient {
208
211
 
209
212
  /**
210
213
  * Finds a deposit based on its deposit ID on the SVM chain.
211
- * TODO: Implement SVM state query for deposit details.
212
214
  */
213
- public findDeposit(_depositId: BigNumber): Promise<DepositSearchResult> {
214
- throw new Error("findDeposit not implemented for SVM");
215
+ public async findDeposit(depositId: BigNumber): Promise<DepositSearchResult> {
216
+ const deposit = await findDeposit(this.svmEventsClient, depositId);
217
+ if (!deposit) {
218
+ return {
219
+ found: false,
220
+ code: InvalidFill.DepositIdNotFound,
221
+ reason: `Deposit with ID ${depositId} not found`,
222
+ };
223
+ }
224
+ // Because we have additional context about this deposit, we can enrich it
225
+ // with additional information.
226
+ return {
227
+ found: true,
228
+ deposit: {
229
+ ...deposit,
230
+ quoteBlockNumber: await this.getBlockNumber(Number(deposit.quoteTimestamp)),
231
+ originChainId: this.chainId,
232
+ fromLiteChain: this.isOriginLiteChain(deposit),
233
+ toLiteChain: this.isDestinationLiteChain(deposit),
234
+ outputToken: isZeroAddress(deposit.outputToken)
235
+ ? this.getDestinationTokenForDeposit(deposit)
236
+ : deposit.outputToken,
237
+ },
238
+ };
215
239
  }
216
240
 
217
241
  /**
@@ -18,6 +18,7 @@ import {
18
18
  toAddress,
19
19
  validateFillForDeposit,
20
20
  chainIsEvm,
21
+ chainIsProd,
21
22
  } from "../../utils";
22
23
  import { duplicateEvent, sortEventsAscendingInPlace } from "../../utils/EventUtils";
23
24
  import { ZERO_ADDRESS } from "../../constants";
@@ -349,7 +350,7 @@ export abstract class SpokePoolClient extends BaseAbstractClient {
349
350
  fillCount: number;
350
351
  invalidFills: FillWithBlock[];
351
352
  } {
352
- const { outputAmount } = deposit;
353
+ const { outputAmount, originChainId } = deposit;
353
354
  const fillsForDeposit = this.depositHashesToFills[this.getDepositHash(deposit)];
354
355
  // If no fills then the full amount is remaining.
355
356
  if (fillsForDeposit === undefined || fillsForDeposit.length === 0) {
@@ -405,8 +406,10 @@ export abstract class SpokePoolClient extends BaseAbstractClient {
405
406
  }
406
407
  return newInvalidFill;
407
408
  });
409
+ // Log invalid and unrepayable fills as warns if we are on a production network.
410
+ const logLevel = chainIsProd(originChainId) ? "warn" : "debug";
408
411
  if (invalidFillsForDeposit.length > 0) {
409
- this.logger.warn({
412
+ this.logger[logLevel]({
410
413
  at: "SpokePoolClient",
411
414
  chainId: this.chainId,
412
415
  message: "Invalid fills found matching deposit ID",
@@ -417,7 +420,7 @@ export abstract class SpokePoolClient extends BaseAbstractClient {
417
420
  }
418
421
  const unrepayableFillsForDeposit = unrepayableFills.filter((x) => x.depositId.eq(deposit.depositId));
419
422
  if (unrepayableFillsForDeposit.length > 0) {
420
- this.logger.warn({
423
+ this.logger[logLevel]({
421
424
  at: "SpokePoolClient",
422
425
  chainId: this.chainId,
423
426
  message: "Unrepayable fills found where we need to switch repayment address and or chain",