crypto-aio 0.0.3 → 0.2.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 (700) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +163 -13
  3. package/dist/adapters/avalanche/address.d.ts +15 -0
  4. package/dist/adapters/avalanche/address.js +74 -0
  5. package/dist/adapters/avalanche/address.js.map +1 -0
  6. package/dist/adapters/avalanche/api.d.ts +100 -0
  7. package/dist/adapters/avalanche/api.js +381 -0
  8. package/dist/adapters/avalanche/api.js.map +1 -0
  9. package/dist/adapters/avalanche/builder.d.ts +13 -0
  10. package/dist/adapters/avalanche/builder.js +336 -0
  11. package/dist/adapters/avalanche/builder.js.map +1 -0
  12. package/dist/adapters/avalanche/cb58.d.ts +7 -0
  13. package/dist/adapters/avalanche/cb58.js +49 -0
  14. package/dist/adapters/avalanche/cb58.js.map +1 -0
  15. package/dist/adapters/avalanche/chains.d.ts +21 -0
  16. package/dist/adapters/avalanche/chains.js +79 -0
  17. package/dist/adapters/avalanche/chains.js.map +1 -0
  18. package/dist/adapters/avalanche/codec.d.ts +94 -0
  19. package/dist/adapters/avalanche/codec.js +371 -0
  20. package/dist/adapters/avalanche/codec.js.map +1 -0
  21. package/dist/adapters/avalanche/context.d.ts +59 -0
  22. package/dist/adapters/avalanche/context.js +114 -0
  23. package/dist/adapters/avalanche/context.js.map +1 -0
  24. package/dist/adapters/avalanche/decode.d.ts +16 -0
  25. package/dist/adapters/avalanche/decode.js +101 -0
  26. package/dist/adapters/avalanche/decode.js.map +1 -0
  27. package/dist/adapters/avalanche/driver.d.ts +22 -0
  28. package/dist/adapters/avalanche/driver.js +81 -0
  29. package/dist/adapters/avalanche/driver.js.map +1 -0
  30. package/dist/adapters/avalanche/errors.d.ts +18 -0
  31. package/dist/adapters/avalanche/errors.js +61 -0
  32. package/dist/adapters/avalanche/errors.js.map +1 -0
  33. package/dist/adapters/avalanche/fees.d.ts +23 -0
  34. package/dist/adapters/avalanche/fees.js +105 -0
  35. package/dist/adapters/avalanche/fees.js.map +1 -0
  36. package/dist/adapters/avalanche/index.d.ts +19 -0
  37. package/dist/adapters/avalanche/index.js +8 -0
  38. package/dist/adapters/avalanche/index.js.map +1 -0
  39. package/dist/adapters/avalanche/network.d.ts +37 -0
  40. package/dist/adapters/avalanche/network.js +135 -0
  41. package/dist/adapters/avalanche/network.js.map +1 -0
  42. package/dist/adapters/avalanche/plugin.d.ts +21 -0
  43. package/dist/adapters/avalanche/plugin.js +46 -0
  44. package/dist/adapters/avalanche/plugin.js.map +1 -0
  45. package/dist/adapters/avalanche/presets.d.ts +6 -0
  46. package/dist/adapters/avalanche/presets.js +86 -0
  47. package/dist/adapters/avalanche/presets.js.map +1 -0
  48. package/dist/adapters/avalanche/proofs.d.ts +41 -0
  49. package/dist/adapters/avalanche/proofs.js +270 -0
  50. package/dist/adapters/avalanche/proofs.js.map +1 -0
  51. package/dist/adapters/avalanche/reader.d.ts +40 -0
  52. package/dist/adapters/avalanche/reader.js +231 -0
  53. package/dist/adapters/avalanche/reader.js.map +1 -0
  54. package/dist/adapters/avalanche/sdk.d.ts +182 -0
  55. package/dist/adapters/avalanche/sdk.js +21 -0
  56. package/dist/adapters/avalanche/sdk.js.map +1 -0
  57. package/dist/adapters/avalanche/types.d.ts +86 -0
  58. package/dist/{type/common.js → adapters/avalanche/types.js} +1 -1
  59. package/dist/adapters/avalanche/types.js.map +1 -0
  60. package/dist/adapters/evm/builder.d.ts +11 -0
  61. package/dist/adapters/evm/builder.js +386 -0
  62. package/dist/adapters/evm/builder.js.map +1 -0
  63. package/dist/adapters/evm/chains.d.ts +6 -0
  64. package/dist/adapters/evm/chains.js +203 -0
  65. package/dist/adapters/evm/chains.js.map +1 -0
  66. package/dist/adapters/evm/client.d.ts +84 -0
  67. package/dist/adapters/evm/client.js +335 -0
  68. package/dist/adapters/evm/client.js.map +1 -0
  69. package/dist/adapters/evm/decode.d.ts +35 -0
  70. package/dist/adapters/evm/decode.js +175 -0
  71. package/dist/adapters/evm/decode.js.map +1 -0
  72. package/dist/adapters/evm/driver.d.ts +8 -0
  73. package/dist/adapters/evm/driver.js +61 -0
  74. package/dist/adapters/evm/driver.js.map +1 -0
  75. package/dist/adapters/evm/errors.d.ts +20 -0
  76. package/dist/adapters/evm/errors.js +110 -0
  77. package/dist/adapters/evm/errors.js.map +1 -0
  78. package/dist/adapters/evm/ethers-client.d.ts +43 -0
  79. package/dist/adapters/evm/ethers-client.js +139 -0
  80. package/dist/adapters/evm/ethers-client.js.map +1 -0
  81. package/dist/adapters/evm/fees.d.ts +103 -0
  82. package/dist/adapters/evm/fees.js +277 -0
  83. package/dist/adapters/evm/fees.js.map +1 -0
  84. package/dist/adapters/evm/index.d.ts +20 -0
  85. package/dist/adapters/evm/index.js +12 -0
  86. package/dist/adapters/evm/index.js.map +1 -0
  87. package/dist/adapters/evm/network.d.ts +65 -0
  88. package/dist/adapters/evm/network.js +150 -0
  89. package/dist/adapters/evm/network.js.map +1 -0
  90. package/dist/adapters/evm/plugin.d.ts +35 -0
  91. package/dist/adapters/evm/plugin.js +150 -0
  92. package/dist/adapters/evm/plugin.js.map +1 -0
  93. package/dist/adapters/evm/presets.d.ts +2 -0
  94. package/dist/adapters/evm/presets.js +105 -0
  95. package/dist/adapters/evm/presets.js.map +1 -0
  96. package/dist/adapters/evm/proofs.d.ts +19 -0
  97. package/dist/adapters/evm/proofs.js +370 -0
  98. package/dist/adapters/evm/proofs.js.map +1 -0
  99. package/dist/adapters/evm/rawtx.d.ts +25 -0
  100. package/dist/adapters/evm/rawtx.js +176 -0
  101. package/dist/adapters/evm/rawtx.js.map +1 -0
  102. package/dist/adapters/evm/reader.d.ts +83 -0
  103. package/dist/adapters/evm/reader.js +326 -0
  104. package/dist/adapters/evm/reader.js.map +1 -0
  105. package/dist/adapters/evm/rpc.d.ts +39 -0
  106. package/dist/adapters/evm/rpc.js +152 -0
  107. package/dist/adapters/evm/rpc.js.map +1 -0
  108. package/dist/adapters/evm/tokens.d.ts +10 -0
  109. package/dist/adapters/evm/tokens.js +23 -0
  110. package/dist/adapters/evm/tokens.js.map +1 -0
  111. package/dist/adapters/evm/types.d.ts +265 -0
  112. package/dist/{type/account.js → adapters/evm/types.js} +1 -1
  113. package/dist/adapters/evm/types.js.map +1 -0
  114. package/dist/adapters/evm/web3-client.d.ts +26 -0
  115. package/dist/adapters/evm/web3-client.js +176 -0
  116. package/dist/adapters/evm/web3-client.js.map +1 -0
  117. package/dist/adapters/solana/builder.d.ts +18 -0
  118. package/dist/adapters/solana/builder.js +556 -0
  119. package/dist/adapters/solana/builder.js.map +1 -0
  120. package/dist/adapters/solana/chains.d.ts +13 -0
  121. package/dist/adapters/solana/chains.js +37 -0
  122. package/dist/adapters/solana/chains.js.map +1 -0
  123. package/dist/adapters/solana/decode.d.ts +111 -0
  124. package/dist/adapters/solana/decode.js +595 -0
  125. package/dist/adapters/solana/decode.js.map +1 -0
  126. package/dist/adapters/solana/driver.d.ts +15 -0
  127. package/dist/adapters/solana/driver.js +72 -0
  128. package/dist/adapters/solana/driver.js.map +1 -0
  129. package/dist/adapters/solana/errors.d.ts +36 -0
  130. package/dist/adapters/solana/errors.js +154 -0
  131. package/dist/adapters/solana/errors.js.map +1 -0
  132. package/dist/adapters/solana/fees.d.ts +77 -0
  133. package/dist/adapters/solana/fees.js +214 -0
  134. package/dist/adapters/solana/fees.js.map +1 -0
  135. package/dist/adapters/solana/heights.d.ts +47 -0
  136. package/dist/adapters/solana/heights.js +185 -0
  137. package/dist/adapters/solana/heights.js.map +1 -0
  138. package/dist/adapters/solana/history.d.ts +10 -0
  139. package/dist/adapters/solana/history.js +71 -0
  140. package/dist/adapters/solana/history.js.map +1 -0
  141. package/dist/adapters/solana/index.d.ts +16 -0
  142. package/dist/adapters/solana/index.js +9 -0
  143. package/dist/adapters/solana/index.js.map +1 -0
  144. package/dist/adapters/solana/keys.d.ts +13 -0
  145. package/dist/adapters/solana/keys.js +69 -0
  146. package/dist/adapters/solana/keys.js.map +1 -0
  147. package/dist/adapters/solana/message.d.ts +15 -0
  148. package/dist/adapters/solana/message.js +120 -0
  149. package/dist/adapters/solana/message.js.map +1 -0
  150. package/dist/adapters/solana/network.d.ts +24 -0
  151. package/dist/adapters/solana/network.js +95 -0
  152. package/dist/adapters/solana/network.js.map +1 -0
  153. package/dist/adapters/solana/plugin.d.ts +18 -0
  154. package/dist/adapters/solana/plugin.js +44 -0
  155. package/dist/adapters/solana/plugin.js.map +1 -0
  156. package/dist/adapters/solana/presets.d.ts +2 -0
  157. package/dist/adapters/solana/presets.js +77 -0
  158. package/dist/adapters/solana/presets.js.map +1 -0
  159. package/dist/adapters/solana/programs.d.ts +44 -0
  160. package/dist/adapters/solana/programs.js +159 -0
  161. package/dist/adapters/solana/programs.js.map +1 -0
  162. package/dist/adapters/solana/proofs.d.ts +56 -0
  163. package/dist/adapters/solana/proofs.js +540 -0
  164. package/dist/adapters/solana/proofs.js.map +1 -0
  165. package/dist/adapters/solana/reader.d.ts +82 -0
  166. package/dist/adapters/solana/reader.js +326 -0
  167. package/dist/adapters/solana/reader.js.map +1 -0
  168. package/dist/adapters/solana/rpc.d.ts +102 -0
  169. package/dist/adapters/solana/rpc.js +301 -0
  170. package/dist/adapters/solana/rpc.js.map +1 -0
  171. package/dist/adapters/solana/tokens.d.ts +7 -0
  172. package/dist/adapters/solana/tokens.js +19 -0
  173. package/dist/adapters/solana/tokens.js.map +1 -0
  174. package/dist/adapters/solana/types.d.ts +124 -0
  175. package/dist/{type/contract.js → adapters/solana/types.js} +1 -1
  176. package/dist/adapters/solana/types.js.map +1 -0
  177. package/dist/adapters/solana/web3.d.ts +5 -0
  178. package/dist/adapters/solana/web3.js +61 -0
  179. package/dist/adapters/solana/web3.js.map +1 -0
  180. package/dist/adapters/solana/wire.d.ts +62 -0
  181. package/dist/adapters/solana/wire.js +168 -0
  182. package/dist/adapters/solana/wire.js.map +1 -0
  183. package/dist/adapters/ton/address.d.ts +43 -0
  184. package/dist/adapters/ton/address.js +168 -0
  185. package/dist/adapters/ton/address.js.map +1 -0
  186. package/dist/adapters/ton/api.d.ts +344 -0
  187. package/dist/adapters/ton/api.js +942 -0
  188. package/dist/adapters/ton/api.js.map +1 -0
  189. package/dist/adapters/ton/builder.d.ts +18 -0
  190. package/dist/adapters/ton/builder.js +701 -0
  191. package/dist/adapters/ton/builder.js.map +1 -0
  192. package/dist/adapters/ton/chains.d.ts +16 -0
  193. package/dist/adapters/ton/chains.js +39 -0
  194. package/dist/adapters/ton/chains.js.map +1 -0
  195. package/dist/adapters/ton/decode.d.ts +43 -0
  196. package/dist/adapters/ton/decode.js +219 -0
  197. package/dist/adapters/ton/decode.js.map +1 -0
  198. package/dist/adapters/ton/driver.d.ts +10 -0
  199. package/dist/adapters/ton/driver.js +117 -0
  200. package/dist/adapters/ton/driver.js.map +1 -0
  201. package/dist/adapters/ton/errors.d.ts +36 -0
  202. package/dist/adapters/ton/errors.js +89 -0
  203. package/dist/adapters/ton/errors.js.map +1 -0
  204. package/dist/adapters/ton/fees.d.ts +28 -0
  205. package/dist/adapters/ton/fees.js +121 -0
  206. package/dist/adapters/ton/fees.js.map +1 -0
  207. package/dist/adapters/ton/index.d.ts +16 -0
  208. package/dist/adapters/ton/index.js +9 -0
  209. package/dist/adapters/ton/index.js.map +1 -0
  210. package/dist/adapters/ton/messages.d.ts +157 -0
  211. package/dist/adapters/ton/messages.js +397 -0
  212. package/dist/adapters/ton/messages.js.map +1 -0
  213. package/dist/adapters/ton/native-client.d.ts +10 -0
  214. package/dist/adapters/ton/native-client.js +47 -0
  215. package/dist/adapters/ton/native-client.js.map +1 -0
  216. package/dist/adapters/ton/network.d.ts +58 -0
  217. package/dist/adapters/ton/network.js +207 -0
  218. package/dist/adapters/ton/network.js.map +1 -0
  219. package/dist/adapters/ton/plugin.d.ts +24 -0
  220. package/dist/adapters/ton/plugin.js +57 -0
  221. package/dist/adapters/ton/plugin.js.map +1 -0
  222. package/dist/adapters/ton/presets.d.ts +6 -0
  223. package/dist/adapters/ton/presets.js +94 -0
  224. package/dist/adapters/ton/presets.js.map +1 -0
  225. package/dist/adapters/ton/proofs.d.ts +66 -0
  226. package/dist/adapters/ton/proofs.js +755 -0
  227. package/dist/adapters/ton/proofs.js.map +1 -0
  228. package/dist/adapters/ton/reader.d.ts +235 -0
  229. package/dist/adapters/ton/reader.js +976 -0
  230. package/dist/adapters/ton/reader.js.map +1 -0
  231. package/dist/adapters/ton/tokens.d.ts +10 -0
  232. package/dist/adapters/ton/tokens.js +27 -0
  233. package/dist/adapters/ton/tokens.js.map +1 -0
  234. package/dist/adapters/ton/trace.d.ts +74 -0
  235. package/dist/adapters/ton/trace.js +303 -0
  236. package/dist/adapters/ton/trace.js.map +1 -0
  237. package/dist/adapters/ton/types.d.ts +105 -0
  238. package/dist/{type/transact.js → adapters/ton/types.js} +1 -1
  239. package/dist/adapters/ton/types.js.map +1 -0
  240. package/dist/adapters/ton/wallets.d.ts +86 -0
  241. package/dist/adapters/ton/wallets.js +373 -0
  242. package/dist/adapters/ton/wallets.js.map +1 -0
  243. package/dist/adapters/tron/abi.d.ts +41 -0
  244. package/dist/adapters/tron/abi.js +112 -0
  245. package/dist/adapters/tron/abi.js.map +1 -0
  246. package/dist/adapters/tron/address.d.ts +20 -0
  247. package/dist/adapters/tron/address.js +110 -0
  248. package/dist/adapters/tron/address.js.map +1 -0
  249. package/dist/adapters/tron/builder.d.ts +12 -0
  250. package/dist/adapters/tron/builder.js +569 -0
  251. package/dist/adapters/tron/builder.js.map +1 -0
  252. package/dist/adapters/tron/chains.d.ts +12 -0
  253. package/dist/adapters/tron/chains.js +35 -0
  254. package/dist/adapters/tron/chains.js.map +1 -0
  255. package/dist/adapters/tron/codec.d.ts +10 -0
  256. package/dist/adapters/tron/codec.js +322 -0
  257. package/dist/adapters/tron/codec.js.map +1 -0
  258. package/dist/adapters/tron/decode.d.ts +52 -0
  259. package/dist/adapters/tron/decode.js +280 -0
  260. package/dist/adapters/tron/decode.js.map +1 -0
  261. package/dist/adapters/tron/driver.d.ts +12 -0
  262. package/dist/adapters/tron/driver.js +106 -0
  263. package/dist/adapters/tron/driver.js.map +1 -0
  264. package/dist/adapters/tron/errors.d.ts +74 -0
  265. package/dist/adapters/tron/errors.js +261 -0
  266. package/dist/adapters/tron/errors.js.map +1 -0
  267. package/dist/adapters/tron/fees.d.ts +49 -0
  268. package/dist/adapters/tron/fees.js +166 -0
  269. package/dist/adapters/tron/fees.js.map +1 -0
  270. package/dist/adapters/tron/http.d.ts +173 -0
  271. package/dist/adapters/tron/http.js +729 -0
  272. package/dist/adapters/tron/http.js.map +1 -0
  273. package/dist/adapters/tron/index.d.ts +16 -0
  274. package/dist/adapters/tron/index.js +15 -0
  275. package/dist/adapters/tron/index.js.map +1 -0
  276. package/dist/adapters/tron/network.d.ts +44 -0
  277. package/dist/adapters/tron/network.js +163 -0
  278. package/dist/adapters/tron/network.js.map +1 -0
  279. package/dist/adapters/tron/plugin.d.ts +17 -0
  280. package/dist/adapters/tron/plugin.js +39 -0
  281. package/dist/adapters/tron/plugin.js.map +1 -0
  282. package/dist/adapters/tron/presets.d.ts +5 -0
  283. package/dist/adapters/tron/presets.js +68 -0
  284. package/dist/adapters/tron/presets.js.map +1 -0
  285. package/dist/adapters/tron/proofs.d.ts +80 -0
  286. package/dist/adapters/tron/proofs.js +371 -0
  287. package/dist/adapters/tron/proofs.js.map +1 -0
  288. package/dist/adapters/tron/protobuf.d.ts +36 -0
  289. package/dist/adapters/tron/protobuf.js +90 -0
  290. package/dist/adapters/tron/protobuf.js.map +1 -0
  291. package/dist/adapters/tron/reader.d.ts +57 -0
  292. package/dist/adapters/tron/reader.js +220 -0
  293. package/dist/adapters/tron/reader.js.map +1 -0
  294. package/dist/adapters/tron/tokens.d.ts +9 -0
  295. package/dist/adapters/tron/tokens.js +14 -0
  296. package/dist/adapters/tron/tokens.js.map +1 -0
  297. package/dist/adapters/tron/types.d.ts +166 -0
  298. package/dist/adapters/tron/types.js +3 -0
  299. package/dist/adapters/tron/types.js.map +1 -0
  300. package/dist/adapters/utxo/address.d.ts +51 -0
  301. package/dist/adapters/utxo/address.js +341 -0
  302. package/dist/adapters/utxo/address.js.map +1 -0
  303. package/dist/adapters/utxo/builder.d.ts +35 -0
  304. package/dist/adapters/utxo/builder.js +380 -0
  305. package/dist/adapters/utxo/builder.js.map +1 -0
  306. package/dist/adapters/utxo/chains.d.ts +17 -0
  307. package/dist/adapters/utxo/chains.js +100 -0
  308. package/dist/adapters/utxo/chains.js.map +1 -0
  309. package/dist/adapters/utxo/codec.d.ts +120 -0
  310. package/dist/adapters/utxo/codec.js +408 -0
  311. package/dist/adapters/utxo/codec.js.map +1 -0
  312. package/dist/adapters/utxo/coinselect.d.ts +48 -0
  313. package/dist/adapters/utxo/coinselect.js +131 -0
  314. package/dist/adapters/utxo/coinselect.js.map +1 -0
  315. package/dist/adapters/utxo/context.d.ts +63 -0
  316. package/dist/adapters/utxo/context.js +125 -0
  317. package/dist/adapters/utxo/context.js.map +1 -0
  318. package/dist/adapters/utxo/decode.d.ts +22 -0
  319. package/dist/adapters/utxo/decode.js +121 -0
  320. package/dist/adapters/utxo/decode.js.map +1 -0
  321. package/dist/adapters/utxo/driver.d.ts +16 -0
  322. package/dist/adapters/utxo/driver.js +79 -0
  323. package/dist/adapters/utxo/driver.js.map +1 -0
  324. package/dist/adapters/utxo/ecc.d.ts +9 -0
  325. package/dist/adapters/utxo/ecc.js +26 -0
  326. package/dist/adapters/utxo/ecc.js.map +1 -0
  327. package/dist/adapters/utxo/errors.d.ts +52 -0
  328. package/dist/adapters/utxo/errors.js +191 -0
  329. package/dist/adapters/utxo/errors.js.map +1 -0
  330. package/dist/adapters/utxo/esplora.d.ts +100 -0
  331. package/dist/adapters/utxo/esplora.js +498 -0
  332. package/dist/adapters/utxo/esplora.js.map +1 -0
  333. package/dist/adapters/utxo/fees.d.ts +41 -0
  334. package/dist/adapters/utxo/fees.js +125 -0
  335. package/dist/adapters/utxo/fees.js.map +1 -0
  336. package/dist/adapters/utxo/index.d.ts +17 -0
  337. package/dist/adapters/utxo/index.js +8 -0
  338. package/dist/adapters/utxo/index.js.map +1 -0
  339. package/dist/adapters/utxo/network.d.ts +50 -0
  340. package/dist/adapters/utxo/network.js +165 -0
  341. package/dist/adapters/utxo/network.js.map +1 -0
  342. package/dist/adapters/utxo/plugin.d.ts +17 -0
  343. package/dist/adapters/utxo/plugin.js +41 -0
  344. package/dist/adapters/utxo/plugin.js.map +1 -0
  345. package/dist/adapters/utxo/presets.d.ts +12 -0
  346. package/dist/adapters/utxo/presets.js +47 -0
  347. package/dist/adapters/utxo/presets.js.map +1 -0
  348. package/dist/adapters/utxo/preview.d.ts +3 -0
  349. package/dist/adapters/utxo/preview.js +65 -0
  350. package/dist/adapters/utxo/preview.js.map +1 -0
  351. package/dist/adapters/utxo/proofs.d.ts +66 -0
  352. package/dist/adapters/utxo/proofs.js +469 -0
  353. package/dist/adapters/utxo/proofs.js.map +1 -0
  354. package/dist/adapters/utxo/rawblock.d.ts +46 -0
  355. package/dist/adapters/utxo/rawblock.js +159 -0
  356. package/dist/adapters/utxo/rawblock.js.map +1 -0
  357. package/dist/adapters/utxo/rawtx.d.ts +73 -0
  358. package/dist/adapters/utxo/rawtx.js +264 -0
  359. package/dist/adapters/utxo/rawtx.js.map +1 -0
  360. package/dist/adapters/utxo/reader.d.ts +36 -0
  361. package/dist/adapters/utxo/reader.js +265 -0
  362. package/dist/adapters/utxo/reader.js.map +1 -0
  363. package/dist/adapters/utxo/sdk.d.ts +18 -0
  364. package/dist/adapters/utxo/sdk.js +17 -0
  365. package/dist/adapters/utxo/sdk.js.map +1 -0
  366. package/dist/adapters/utxo/signed-psbt.d.ts +36 -0
  367. package/dist/adapters/utxo/signed-psbt.js +428 -0
  368. package/dist/adapters/utxo/signed-psbt.js.map +1 -0
  369. package/dist/adapters/utxo/spend.d.ts +59 -0
  370. package/dist/adapters/utxo/spend.js +230 -0
  371. package/dist/adapters/utxo/spend.js.map +1 -0
  372. package/dist/adapters/utxo/types.d.ts +167 -0
  373. package/dist/adapters/utxo/types.js +3 -0
  374. package/dist/adapters/utxo/types.js.map +1 -0
  375. package/dist/core/assets/service.d.ts +18 -0
  376. package/dist/core/assets/service.js +69 -0
  377. package/dist/core/assets/service.js.map +1 -0
  378. package/dist/core/blockchain/default-ref.d.ts +4 -0
  379. package/dist/core/blockchain/default-ref.js +16 -0
  380. package/dist/core/blockchain/default-ref.js.map +1 -0
  381. package/dist/core/blockchain/handle.d.ts +179 -0
  382. package/dist/core/blockchain/handle.js +579 -0
  383. package/dist/core/blockchain/handle.js.map +1 -0
  384. package/dist/core/blockchain/internal.d.ts +19 -0
  385. package/dist/core/blockchain/internal.js +16 -0
  386. package/dist/core/blockchain/internal.js.map +1 -0
  387. package/dist/core/blockchain/mapping.d.ts +22 -0
  388. package/dist/core/blockchain/mapping.js +136 -0
  389. package/dist/core/blockchain/mapping.js.map +1 -0
  390. package/dist/core/config/env.d.ts +8 -0
  391. package/dist/core/config/env.js +60 -0
  392. package/dist/core/config/env.js.map +1 -0
  393. package/dist/core/config/merge.d.ts +26 -0
  394. package/dist/core/config/merge.js +144 -0
  395. package/dist/core/config/merge.js.map +1 -0
  396. package/dist/core/config/resolve.d.ts +12 -0
  397. package/dist/core/config/resolve.js +251 -0
  398. package/dist/core/config/resolve.js.map +1 -0
  399. package/dist/core/config/types.d.ts +180 -0
  400. package/dist/core/config/types.js +3 -0
  401. package/dist/core/config/types.js.map +1 -0
  402. package/dist/core/container/builtins.d.ts +4 -0
  403. package/dist/core/container/builtins.js +13 -0
  404. package/dist/core/container/builtins.js.map +1 -0
  405. package/dist/core/container/container.d.ts +62 -0
  406. package/dist/core/container/container.js +500 -0
  407. package/dist/core/container/container.js.map +1 -0
  408. package/dist/core/container/default.d.ts +15 -0
  409. package/dist/core/container/default.js +70 -0
  410. package/dist/core/container/default.js.map +1 -0
  411. package/dist/core/container/internals.d.ts +54 -0
  412. package/dist/core/container/internals.js +21 -0
  413. package/dist/core/container/internals.js.map +1 -0
  414. package/dist/core/container/pool.d.ts +27 -0
  415. package/dist/core/container/pool.js +77 -0
  416. package/dist/core/container/pool.js.map +1 -0
  417. package/dist/core/driver/types.d.ts +379 -0
  418. package/dist/core/driver/types.js +3 -0
  419. package/dist/core/driver/types.js.map +1 -0
  420. package/dist/core/errors/codes.d.ts +163 -0
  421. package/dist/core/errors/codes.js +53 -0
  422. package/dist/core/errors/codes.js.map +1 -0
  423. package/dist/core/errors/error.d.ts +66 -0
  424. package/dist/core/errors/error.js +129 -0
  425. package/dist/core/errors/error.js.map +1 -0
  426. package/dist/core/events/bus.d.ts +12 -0
  427. package/dist/core/events/bus.js +46 -0
  428. package/dist/core/events/bus.js.map +1 -0
  429. package/dist/core/events/logger.d.ts +13 -0
  430. package/dist/core/events/logger.js +42 -0
  431. package/dist/core/events/logger.js.map +1 -0
  432. package/dist/core/events/types.d.ts +131 -0
  433. package/dist/core/events/types.js +3 -0
  434. package/dist/core/events/types.js.map +1 -0
  435. package/dist/core/lifecycle/engine-rules.d.ts +106 -0
  436. package/dist/core/lifecycle/engine-rules.js +267 -0
  437. package/dist/core/lifecycle/engine-rules.js.map +1 -0
  438. package/dist/core/lifecycle/engine.d.ts +502 -0
  439. package/dist/core/lifecycle/engine.js +1750 -0
  440. package/dist/core/lifecycle/engine.js.map +1 -0
  441. package/dist/core/lifecycle/evaluate.d.ts +19 -0
  442. package/dist/core/lifecycle/evaluate.js +77 -0
  443. package/dist/core/lifecycle/evaluate.js.map +1 -0
  444. package/dist/core/lifecycle/intent.d.ts +11 -0
  445. package/dist/core/lifecycle/intent.js +88 -0
  446. package/dist/core/lifecycle/intent.js.map +1 -0
  447. package/dist/core/lifecycle/monitor.d.ts +138 -0
  448. package/dist/core/lifecycle/monitor.js +616 -0
  449. package/dist/core/lifecycle/monitor.js.map +1 -0
  450. package/dist/core/lifecycle/observations.d.ts +17 -0
  451. package/dist/core/lifecycle/observations.js +59 -0
  452. package/dist/core/lifecycle/observations.js.map +1 -0
  453. package/dist/core/lifecycle/reconcile.d.ts +58 -0
  454. package/dist/core/lifecycle/reconcile.js +112 -0
  455. package/dist/core/lifecycle/reconcile.js.map +1 -0
  456. package/dist/core/lifecycle/views.d.ts +49 -0
  457. package/dist/core/lifecycle/views.js +57 -0
  458. package/dist/core/lifecycle/views.js.map +1 -0
  459. package/dist/core/lifecycle/workers.d.ts +88 -0
  460. package/dist/core/lifecycle/workers.js +347 -0
  461. package/dist/core/lifecycle/workers.js.map +1 -0
  462. package/dist/core/model/address.d.ts +32 -0
  463. package/dist/core/model/address.js +43 -0
  464. package/dist/core/model/address.js.map +1 -0
  465. package/dist/core/model/amount.d.ts +25 -0
  466. package/dist/core/model/amount.js +102 -0
  467. package/dist/core/model/amount.js.map +1 -0
  468. package/dist/core/model/asset.d.ts +28 -0
  469. package/dist/core/model/asset.js +47 -0
  470. package/dist/core/model/asset.js.map +1 -0
  471. package/dist/core/model/capability.d.ts +3 -0
  472. package/dist/core/model/capability.js +18 -0
  473. package/dist/core/model/capability.js.map +1 -0
  474. package/dist/core/model/chain.d.ts +63 -0
  475. package/dist/core/model/chain.js +8 -0
  476. package/dist/core/model/chain.js.map +1 -0
  477. package/dist/core/model/fee.d.ts +36 -0
  478. package/dist/core/model/fee.js +17 -0
  479. package/dist/core/model/fee.js.map +1 -0
  480. package/dist/core/model/ids.d.ts +38 -0
  481. package/dist/core/model/ids.js +14 -0
  482. package/dist/core/model/ids.js.map +1 -0
  483. package/dist/core/model/intent.d.ts +63 -0
  484. package/dist/core/model/intent.js +81 -0
  485. package/dist/core/model/intent.js.map +1 -0
  486. package/dist/core/model/ordering.d.ts +28 -0
  487. package/dist/core/model/ordering.js +29 -0
  488. package/dist/core/model/ordering.js.map +1 -0
  489. package/dist/core/model/transaction.d.ts +123 -0
  490. package/dist/{type/lib-types.js → core/model/transaction.js} +1 -1
  491. package/dist/core/model/transaction.js.map +1 -0
  492. package/dist/core/observe/scanner.d.ts +135 -0
  493. package/dist/core/observe/scanner.js +358 -0
  494. package/dist/core/observe/scanner.js.map +1 -0
  495. package/dist/core/ordering/reservations.d.ts +16 -0
  496. package/dist/core/ordering/reservations.js +53 -0
  497. package/dist/core/ordering/reservations.js.map +1 -0
  498. package/dist/core/ordering/sequence.d.ts +61 -0
  499. package/dist/core/ordering/sequence.js +155 -0
  500. package/dist/core/ordering/sequence.js.map +1 -0
  501. package/dist/core/registry/adapters.d.ts +10 -0
  502. package/dist/core/registry/adapters.js +56 -0
  503. package/dist/core/registry/adapters.js.map +1 -0
  504. package/dist/core/registry/assets.d.ts +18 -0
  505. package/dist/core/registry/assets.js +90 -0
  506. package/dist/core/registry/assets.js.map +1 -0
  507. package/dist/core/registry/chains.d.ts +10 -0
  508. package/dist/core/registry/chains.js +80 -0
  509. package/dist/core/registry/chains.js.map +1 -0
  510. package/dist/core/registry/plugin.d.ts +41 -0
  511. package/dist/core/registry/plugin.js +109 -0
  512. package/dist/core/registry/plugin.js.map +1 -0
  513. package/dist/core/registry/providers.d.ts +31 -0
  514. package/dist/core/registry/providers.js +43 -0
  515. package/dist/core/registry/providers.js.map +1 -0
  516. package/dist/core/registry/schemes.d.ts +30 -0
  517. package/dist/core/registry/schemes.js +84 -0
  518. package/dist/core/registry/schemes.js.map +1 -0
  519. package/dist/core/secret/fragments.d.ts +33 -0
  520. package/dist/core/secret/fragments.js +110 -0
  521. package/dist/core/secret/fragments.js.map +1 -0
  522. package/dist/core/secret/redact.d.ts +10 -0
  523. package/dist/core/secret/redact.js +110 -0
  524. package/dist/core/secret/redact.js.map +1 -0
  525. package/dist/core/secret/secret.d.ts +14 -0
  526. package/dist/core/secret/secret.js +42 -0
  527. package/dist/core/secret/secret.js.map +1 -0
  528. package/dist/core/signing/callback.d.ts +10 -0
  529. package/dist/core/signing/callback.js +22 -0
  530. package/dist/core/signing/callback.js.map +1 -0
  531. package/dist/core/signing/guard.d.ts +18 -0
  532. package/dist/core/signing/guard.js +72 -0
  533. package/dist/core/signing/guard.js.map +1 -0
  534. package/dist/core/signing/hd.d.ts +36 -0
  535. package/dist/core/signing/hd.js +167 -0
  536. package/dist/core/signing/hd.js.map +1 -0
  537. package/dist/core/signing/local.d.ts +48 -0
  538. package/dist/core/signing/local.js +190 -0
  539. package/dist/core/signing/local.js.map +1 -0
  540. package/dist/core/signing/orchestrator.d.ts +40 -0
  541. package/dist/core/signing/orchestrator.js +300 -0
  542. package/dist/core/signing/orchestrator.js.map +1 -0
  543. package/dist/core/signing/types.d.ts +64 -0
  544. package/dist/core/signing/types.js +3 -0
  545. package/dist/core/signing/types.js.map +1 -0
  546. package/dist/core/signing/wallet.d.ts +48 -0
  547. package/dist/core/signing/wallet.js +161 -0
  548. package/dist/core/signing/wallet.js.map +1 -0
  549. package/dist/core/store/clone.d.ts +25 -0
  550. package/dist/core/store/clone.js +71 -0
  551. package/dist/core/store/clone.js.map +1 -0
  552. package/dist/core/store/format.d.ts +20 -0
  553. package/dist/core/store/format.js +86 -0
  554. package/dist/core/store/format.js.map +1 -0
  555. package/dist/core/store/memory.d.ts +38 -0
  556. package/dist/core/store/memory.js +325 -0
  557. package/dist/core/store/memory.js.map +1 -0
  558. package/dist/core/store/types.d.ts +258 -0
  559. package/dist/core/store/types.js +119 -0
  560. package/dist/core/store/types.js.map +1 -0
  561. package/dist/core/transport/backoff.d.ts +8 -0
  562. package/dist/core/transport/backoff.js +27 -0
  563. package/dist/core/transport/backoff.js.map +1 -0
  564. package/dist/core/transport/circuit.d.ts +25 -0
  565. package/dist/core/transport/circuit.js +61 -0
  566. package/dist/core/transport/circuit.js.map +1 -0
  567. package/dist/core/transport/http-transport.d.ts +30 -0
  568. package/dist/core/transport/http-transport.js +1703 -0
  569. package/dist/core/transport/http-transport.js.map +1 -0
  570. package/dist/core/transport/rate-limit.d.ts +27 -0
  571. package/dist/core/transport/rate-limit.js +78 -0
  572. package/dist/core/transport/rate-limit.js.map +1 -0
  573. package/dist/core/transport/stale-view.d.ts +11 -0
  574. package/dist/core/transport/stale-view.js +17 -0
  575. package/dist/core/transport/stale-view.js.map +1 -0
  576. package/dist/core/transport/types.d.ts +159 -0
  577. package/dist/core/transport/types.js +6 -0
  578. package/dist/core/transport/types.js.map +1 -0
  579. package/dist/core/util/bytes.d.ts +9 -0
  580. package/dist/core/util/bytes.js +40 -0
  581. package/dist/core/util/bytes.js.map +1 -0
  582. package/dist/core/util/clock.d.ts +6 -0
  583. package/dist/core/util/clock.js +26 -0
  584. package/dist/core/util/clock.js.map +1 -0
  585. package/dist/core/util/freeze.d.ts +8 -0
  586. package/dist/core/util/freeze.js +31 -0
  587. package/dist/core/util/freeze.js.map +1 -0
  588. package/dist/core/util/json.d.ts +35 -0
  589. package/dist/core/util/json.js +174 -0
  590. package/dist/core/util/json.js.map +1 -0
  591. package/dist/core/util/names.d.ts +27 -0
  592. package/dist/core/util/names.js +56 -0
  593. package/dist/core/util/names.js.map +1 -0
  594. package/dist/index.d.ts +65 -14
  595. package/dist/index.js +107 -49
  596. package/dist/index.js.map +1 -1
  597. package/dist/native.d.ts +21 -0
  598. package/dist/native.js +44 -0
  599. package/dist/native.js.map +1 -0
  600. package/dist/testing/contracts/api.d.ts +12 -0
  601. package/dist/testing/contracts/api.js +58 -0
  602. package/dist/testing/contracts/api.js.map +1 -0
  603. package/dist/testing/contracts/cursors.d.ts +6 -0
  604. package/dist/testing/contracts/cursors.js +43 -0
  605. package/dist/testing/contracts/cursors.js.map +1 -0
  606. package/dist/testing/contracts/locks.d.ts +10 -0
  607. package/dist/testing/contracts/locks.js +63 -0
  608. package/dist/testing/contracts/locks.js.map +1 -0
  609. package/dist/testing/contracts/operations.d.ts +21 -0
  610. package/dist/testing/contracts/operations.js +737 -0
  611. package/dist/testing/contracts/operations.js.map +1 -0
  612. package/dist/testing/contracts/sequences.d.ts +6 -0
  613. package/dist/testing/contracts/sequences.js +68 -0
  614. package/dist/testing/contracts/sequences.js.map +1 -0
  615. package/dist/testing/env.d.ts +68 -0
  616. package/dist/testing/env.js +105 -0
  617. package/dist/testing/env.js.map +1 -0
  618. package/dist/testing/fake-chain.d.ts +118 -0
  619. package/dist/testing/fake-chain.js +536 -0
  620. package/dist/testing/fake-chain.js.map +1 -0
  621. package/dist/testing/fake-clock.d.ts +15 -0
  622. package/dist/testing/fake-clock.js +86 -0
  623. package/dist/testing/fake-clock.js.map +1 -0
  624. package/dist/testing/fake-driver.d.ts +15 -0
  625. package/dist/testing/fake-driver.js +458 -0
  626. package/dist/testing/fake-driver.js.map +1 -0
  627. package/dist/testing/fake-fetch.d.ts +32 -0
  628. package/dist/testing/fake-fetch.js +81 -0
  629. package/dist/testing/fake-fetch.js.map +1 -0
  630. package/dist/testing/fake-plugin.d.ts +30 -0
  631. package/dist/testing/fake-plugin.js +66 -0
  632. package/dist/testing/fake-plugin.js.map +1 -0
  633. package/dist/testing/faulty-store.d.ts +38 -0
  634. package/dist/testing/faulty-store.js +71 -0
  635. package/dist/testing/faulty-store.js.map +1 -0
  636. package/dist/testing/generation.d.ts +60 -0
  637. package/dist/testing/generation.js +252 -0
  638. package/dist/testing/generation.js.map +1 -0
  639. package/dist/testing/index.d.ts +28 -0
  640. package/dist/testing/index.js +51 -0
  641. package/dist/testing/index.js.map +1 -0
  642. package/package.json +198 -45
  643. package/dist/libs/eth/account.d.ts +0 -9
  644. package/dist/libs/eth/account.js +0 -58
  645. package/dist/libs/eth/account.js.map +0 -1
  646. package/dist/libs/eth/contract.d.ts +0 -7
  647. package/dist/libs/eth/contract.js +0 -42
  648. package/dist/libs/eth/contract.js.map +0 -1
  649. package/dist/libs/eth/index.d.ts +0 -24
  650. package/dist/libs/eth/index.js +0 -72
  651. package/dist/libs/eth/index.js.map +0 -1
  652. package/dist/libs/eth/transact.d.ts +0 -7
  653. package/dist/libs/eth/transact.js +0 -26
  654. package/dist/libs/eth/transact.js.map +0 -1
  655. package/dist/libs/index.d.ts +0 -2
  656. package/dist/libs/index.js +0 -19
  657. package/dist/libs/index.js.map +0 -1
  658. package/dist/libs/trx/account.d.ts +0 -9
  659. package/dist/libs/trx/account.js +0 -55
  660. package/dist/libs/trx/account.js.map +0 -1
  661. package/dist/libs/trx/contract.d.ts +0 -7
  662. package/dist/libs/trx/contract.js +0 -27
  663. package/dist/libs/trx/contract.js.map +0 -1
  664. package/dist/libs/trx/index.d.ts +0 -22
  665. package/dist/libs/trx/index.js +0 -64
  666. package/dist/libs/trx/index.js.map +0 -1
  667. package/dist/libs/trx/transact.d.ts +0 -7
  668. package/dist/libs/trx/transact.js +0 -26
  669. package/dist/libs/trx/transact.js.map +0 -1
  670. package/dist/tool/common.d.ts +0 -28
  671. package/dist/tool/common.js +0 -33
  672. package/dist/tool/common.js.map +0 -1
  673. package/dist/tool/eth-tools.d.ts +0 -3
  674. package/dist/tool/eth-tools.js +0 -33
  675. package/dist/tool/eth-tools.js.map +0 -1
  676. package/dist/tool/index.d.ts +0 -5
  677. package/dist/tool/index.js +0 -22
  678. package/dist/tool/index.js.map +0 -1
  679. package/dist/tool/logging.d.ts +0 -3
  680. package/dist/tool/logging.js +0 -11
  681. package/dist/tool/logging.js.map +0 -1
  682. package/dist/tool/options.d.ts +0 -6
  683. package/dist/tool/options.js +0 -41
  684. package/dist/tool/options.js.map +0 -1
  685. package/dist/tool/trx-tools.d.ts +0 -3
  686. package/dist/tool/trx-tools.js +0 -20
  687. package/dist/tool/trx-tools.js.map +0 -1
  688. package/dist/type/account.d.ts +0 -9
  689. package/dist/type/account.js.map +0 -1
  690. package/dist/type/common.d.ts +0 -18
  691. package/dist/type/common.js.map +0 -1
  692. package/dist/type/contract.d.ts +0 -10
  693. package/dist/type/contract.js.map +0 -1
  694. package/dist/type/index.d.ts +0 -5
  695. package/dist/type/index.js +0 -22
  696. package/dist/type/index.js.map +0 -1
  697. package/dist/type/lib-types.d.ts +0 -9
  698. package/dist/type/lib-types.js.map +0 -1
  699. package/dist/type/transact.d.ts +0 -7
  700. package/dist/type/transact.js.map +0 -1
@@ -0,0 +1,1703 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpTransport = void 0;
4
+ const error_1 = require("../errors/error");
5
+ const fragments_1 = require("../secret/fragments");
6
+ const secret_1 = require("../secret/secret");
7
+ const bytes_1 = require("../util/bytes");
8
+ const json_1 = require("../util/json");
9
+ const backoff_1 = require("./backoff");
10
+ const circuit_1 = require("./circuit");
11
+ const rate_limit_1 = require("./rate-limit");
12
+ const types_1 = require("./types");
13
+ const DEFAULTS = {
14
+ maxAttempts: 3,
15
+ baseDelayMs: 200,
16
+ maxDelayMs: 5_000,
17
+ timeoutMs: 15_000,
18
+ maxLagBlocks: 5,
19
+ proofQuorum: 2,
20
+ degradedProofs: false,
21
+ failureThreshold: 5,
22
+ openMs: 30_000,
23
+ healthIntervalMs: 15_000,
24
+ /** An answer's byte cap; a full Solana block in `jsonParsed` stays far below. */
25
+ maxResponseBytes: 64 * 1024 * 1024,
26
+ };
27
+ const MAX_RETRY_AFTER_MS = 60_000;
28
+ /** Consecutive failed health refreshes (identity or height probe, or failing requests)
29
+ * after which an endpoint stops counting toward a proof quorum's size (a sustained
30
+ * outage, not a hiccup). Liveness traded against safety: with two endpoints, the other
31
+ * one then proves alone, so proof reads want three or more. */
32
+ const HEALTH_MISS_LIMIT = 3;
33
+ const TIMEOUT = new Error('transport timeout');
34
+ /**
35
+ * Whether a probe failed only because the endpoint rate-limited it, directly or as the
36
+ * cause of a failed identity check. Such a probe learned nothing about the endpoint's
37
+ * health, which keeps its last good height and identity. Keyless TronGrid and toncenter
38
+ * answer probes HTTP 429 under load; counting that as a failure made a live endpoint
39
+ * unusable.
40
+ */
41
+ function rateLimited(error) {
42
+ return ((0, error_1.isCryptoAioError)(error, 'RATE_LIMITED') ||
43
+ (error instanceof Error && (0, error_1.isCryptoAioError)(error.cause, 'RATE_LIMITED')));
44
+ }
45
+ /** Statuses that must never carry a body on the Response passed back to the SDK. */
46
+ const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
47
+ /** Races `promise` against `signal`, rejecting with the signal's abort
48
+ * reason if it fires first. Guards against a probe callback that ignores its own signal
49
+ * argument and never settles on its own. */
50
+ function raceAbort(promise, signal) {
51
+ if (signal.aborted)
52
+ return Promise.reject(signal.reason);
53
+ return new Promise((resolve, reject) => {
54
+ const onAbort = () => reject(signal.reason);
55
+ signal.addEventListener('abort', onAbort, { once: true });
56
+ promise.then(resolve, reject).finally(() => {
57
+ signal.removeEventListener('abort', onAbort);
58
+ });
59
+ });
60
+ }
61
+ const POSITIVE_MS_OPTIONS = [
62
+ 'timeoutMs',
63
+ 'baseDelayMs',
64
+ 'maxDelayMs',
65
+ 'healthIntervalMs',
66
+ ];
67
+ /** Validated at construction so bad config fails fast, never with the URL in the message. */
68
+ function validateOptions(options) {
69
+ if (options.maxAttempts !== undefined &&
70
+ (!Number.isInteger(options.maxAttempts) || options.maxAttempts < 1)) {
71
+ throw new error_1.ConfigError('CONFIG_INVALID', 'maxAttempts must be an integer >= 1');
72
+ }
73
+ for (const key of POSITIVE_MS_OPTIONS) {
74
+ const value = options[key];
75
+ if (value !== undefined && !(Number.isFinite(value) && value > 0)) {
76
+ throw new error_1.ConfigError('CONFIG_INVALID', `${key} must be a finite number > 0`);
77
+ }
78
+ }
79
+ const cap = options.maxResponseBytes;
80
+ if (cap !== undefined && !(Number.isSafeInteger(cap) && cap > 0)) {
81
+ throw new error_1.ConfigError('CONFIG_INVALID', 'maxResponseBytes must be an integer > 0');
82
+ }
83
+ }
84
+ function resolveOptions(options = {}) {
85
+ const out = { ...DEFAULTS };
86
+ for (const [key, value] of Object.entries(options)) {
87
+ if (value !== undefined)
88
+ out[key] = value;
89
+ }
90
+ return out;
91
+ }
92
+ function joinUrl(base, path, query) {
93
+ const url = new URL(base);
94
+ if (path && path !== '/') {
95
+ url.pathname =
96
+ url.pathname.replace(/\/$/, '') + (path.startsWith('/') ? path : `/${path}`);
97
+ }
98
+ new URLSearchParams(query).forEach((value, key) => url.searchParams.append(key, value));
99
+ return url.toString();
100
+ }
101
+ /** Serialized once, before any endpoint is attempted; a bigint or other non-JSON value
102
+ * never reaches an endpoint and never counts as an endpoint failure. */
103
+ function serializeJson(payload) {
104
+ try {
105
+ return JSON.stringify(payload);
106
+ }
107
+ catch (error) {
108
+ throw new error_1.ValidationError('INVALID_INTENT', 'request payload could not be serialized', {
109
+ cause: error,
110
+ });
111
+ }
112
+ }
113
+ /** Builds the request once, before #run, so an invalid or forbidden method, a
114
+ * GET/HEAD with a body or a bad header value is a local config error — no fetch, no breaker
115
+ * bookkeeping, never ambiguous. The message never repeats the input. */
116
+ function buildRequest(url, init, what) {
117
+ try {
118
+ return new Request(url, init);
119
+ }
120
+ catch {
121
+ throw new error_1.ConfigError('CONFIG_INVALID', `${what} has an invalid method, header or body`);
122
+ }
123
+ }
124
+ function rpcLabel(payload) {
125
+ if (Array.isArray(payload))
126
+ return 'batch';
127
+ const method = payload?.method;
128
+ return typeof method === 'string' ? method : 'rpc';
129
+ }
130
+ /** Low-cardinality event label: method+route when a route template is given, else the bare method. */
131
+ function routeLabel(method, route) {
132
+ return route ? `${method} ${route}` : method;
133
+ }
134
+ const IDENTITY_FIELD_CHARS = /[^A-Za-z0-9:_.-]/g;
135
+ /** Strips everything but a safe character set and caps length, for event fields sourced from provider answers. */
136
+ function sanitizeIdentityField(value) {
137
+ const cleaned = value.replace(IDENTITY_FIELD_CHARS, '').slice(0, 64);
138
+ return cleaned.length > 0 ? cleaned : 'invalid';
139
+ }
140
+ /**
141
+ * Whether every quorum result matches the first under `quorumJson`, compared whole or, with
142
+ * a `quorumKey`, on the key's projection. A key that throws (or projects something
143
+ * `quorumJson` rejects) counts as a disagreement, never a foreign error. `quorumJson`,
144
+ * not `canonicalJson`, so an object shaped like a bigint tag never agrees with a revived
145
+ * bigint.
146
+ */
147
+ function quorumAgrees(values, quorumKey) {
148
+ const agree = (key) => {
149
+ const expected = key(values[0]);
150
+ return values.every((value) => key(value) === expected);
151
+ };
152
+ if (!quorumKey)
153
+ return agree(json_1.quorumJson);
154
+ try {
155
+ return agree((value) => (0, json_1.quorumJson)(quorumKey(value)));
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
161
+ class HttpTransport {
162
+ id;
163
+ #endpoints;
164
+ #opts;
165
+ /** Errors from an attempt whose `fetch` was invoked and answered, so the request may
166
+ * have been delivered — every such failure except HTTP 401/403/429 and JSON-RPC-level rate
167
+ * limiting (those mean the request was never actually processed). The cost of erring
168
+ * this way: some undelivered broadcasts are monitored as ambiguous, the safe side. */
169
+ #maybeDelivered = new WeakSet();
170
+ /** Identity-check failures caused only by the owning request's caller
171
+ * aborting — they say nothing about the endpoint, so a refresh that joined one counts the
172
+ * endpoint as not attempted rather than failed. */
173
+ #abandonedChecks = new WeakSet();
174
+ /** Identity-check failures caused by the owning request's own deadline (possibly
175
+ * a caller-shortened `timeoutMs`); a refresh that joined one learned nothing under its
176
+ * own deadline, so it is no health miss. */
177
+ #timedOutChecks = new WeakSet();
178
+ #clock;
179
+ #events;
180
+ #log;
181
+ #random;
182
+ #probes = {};
183
+ #best;
184
+ #highest;
185
+ /** The highest height verified by an identity-checked endpoint; lowered only by
186
+ * `#decayPeak`. */
187
+ #verifiedPeak;
188
+ /** Completed refreshes in a row whose verified best stayed more than `maxLagBlocks`
189
+ * below the peak (see `#decayPeak`). */
190
+ #peakMisses = 0;
191
+ #lastHealthAt = Number.NEGATIVE_INFINITY;
192
+ /** Set after a refresh where every probe failed, so ensureFreshHealth backs
193
+ * off instead of storming the same down endpoints on every read during an outage. */
194
+ #nextRefreshAt = Number.NEGATIVE_INFINITY;
195
+ /** Set as a trial answers or refuses (its breaker closed), so the next
196
+ * health check refreshes at once and the endpoint rejoins the count by the ordinary rules.
197
+ * A refresh clears it when it starts, so one already running when the flag was set (and
198
+ * which may have read the breaker before it closed) does not count. */
199
+ #refreshDue = false;
200
+ #healthRun;
201
+ #rpcId = 0;
202
+ constructor(endpoints, deps) {
203
+ if (endpoints.length === 0) {
204
+ throw new error_1.ConfigError('CONFIG_INVALID', 'a transport needs at least one endpoint');
205
+ }
206
+ validateOptions(deps.options ?? {});
207
+ this.id = deps.id ?? (0, bytes_1.randomId)('tr');
208
+ this.#opts = resolveOptions(deps.options);
209
+ this.#clock = deps.clock;
210
+ this.#events = deps.events;
211
+ this.#log = deps.log;
212
+ this.#random = deps.random ?? Math.random;
213
+ const names = new Set();
214
+ this.#endpoints = endpoints.map((config, index) => {
215
+ const id = config.name ?? `endpoint-${index}`;
216
+ if (names.has(id))
217
+ throw new error_1.ConfigError('CONFIG_INVALID', `duplicate endpoint name '${id}'`);
218
+ names.add(id);
219
+ const url = (0, secret_1.reveal)(config.url);
220
+ let parsed;
221
+ try {
222
+ parsed = new URL(url);
223
+ }
224
+ catch {
225
+ throw new error_1.ConfigError('CONFIG_INVALID', `endpoint '${id}' has an invalid URL`);
226
+ }
227
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
228
+ throw new error_1.ConfigError('CONFIG_INVALID', `endpoint '${id}' must use http or https`);
229
+ }
230
+ const headers = {};
231
+ for (const [name, value] of Object.entries(config.headers ?? {})) {
232
+ const revealed = (0, secret_1.reveal)(value);
233
+ // Validated once here (never with the value in the message) so a bad
234
+ // header can never surface later as a per-attempt local error.
235
+ try {
236
+ new Headers({ [name]: revealed });
237
+ }
238
+ catch {
239
+ throw new error_1.ConfigError('CONFIG_INVALID', `endpoint '${id}' has an invalid value for header '${name}'`);
240
+ }
241
+ headers[name] = revealed;
242
+ }
243
+ return {
244
+ id,
245
+ kind: config.kind ?? 'rpc',
246
+ url,
247
+ headers,
248
+ scrub: (0, fragments_1.createScrubber)(`<${id}>`, (0, fragments_1.endpointSecrets)(url, headers)),
249
+ priority: config.priority ?? 0,
250
+ ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
251
+ breaker: new circuit_1.CircuitBreaker({ failureThreshold: this.#opts.failureThreshold, openMs: this.#opts.openMs }, this.#clock),
252
+ ...(config.rateLimit
253
+ ? {
254
+ bucket: new rate_limit_1.TokenBucket(config.rateLimit.rps, config.rateLimit.burst ?? Math.max(1, Math.ceil(config.rateLimit.rps)), this.#clock),
255
+ }
256
+ : {}),
257
+ identity: 'unchecked',
258
+ notBefore: Number.NEGATIVE_INFINITY,
259
+ healthMisses: 0,
260
+ failures: 0,
261
+ probeTokens: 0,
262
+ };
263
+ });
264
+ }
265
+ // rpc/rpcRaw/http are `async` on purpose: serializing the body happens synchronously,
266
+ // before any endpoint is touched, and `async` turns a bad payload into a rejected Promise
267
+ // rather than a synchronous throw, keeping the Transport contract's `Promise<T>` honest.
268
+ async rpc(method, params = [], options = {}) {
269
+ const id = ++this.#rpcId;
270
+ const body = serializeJson({ jsonrpc: '2.0', id, method, params });
271
+ return this.#run(method, options, (endpoint, signal) => this.#rpcOnce(endpoint, method, id, body, signal, options.exactIntegers));
272
+ }
273
+ async rpcRaw(payload, options = {}) {
274
+ const label = rpcLabel(payload);
275
+ const body = serializeJson(payload);
276
+ return this.#run(label, options, async (endpoint, signal) => {
277
+ const { json } = await this.#exchange(endpoint, label, this.#jsonPost(endpoint.url, body), signal, 'rpc', label, options.exactIntegers);
278
+ return json;
279
+ });
280
+ }
281
+ async http(request, options = {}) {
282
+ const bodyText = request.body === undefined
283
+ ? undefined
284
+ : typeof request.body === 'string'
285
+ ? request.body
286
+ : serializeJson(request.body);
287
+ buildRequest(types_1.PLACEHOLDER_ORIGIN, {
288
+ method: request.method,
289
+ ...(request.headers ? { headers: request.headers } : {}),
290
+ ...(bodyText !== undefined ? { body: bodyText } : {}),
291
+ }, 'http request');
292
+ return this.#run(routeLabel(request.method, request.route), options, (endpoint, signal) => this.#httpOnce(endpoint, request, bodyText, signal, options.exactIntegers));
293
+ }
294
+ createFetch(classify) {
295
+ const bridged = async (input, init) => {
296
+ const raw = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
297
+ // A relative or otherwise invalid URL is a config error; the message never
298
+ // repeats the caller's input.
299
+ let url;
300
+ try {
301
+ url = new URL(raw);
302
+ }
303
+ catch {
304
+ throw new error_1.ConfigError('CONFIG_INVALID', 'bridged fetch requires an absolute URL');
305
+ }
306
+ if (url.origin !== types_1.PLACEHOLDER_ORIGIN) {
307
+ throw new error_1.ConfigError('CONFIG_INVALID', 'bridged fetch accepts only transport placeholder URLs');
308
+ }
309
+ // A Request input keeps its own method, headers and body unless init overrides them.
310
+ const method = init?.method ?? (input instanceof Request ? input.method : 'GET');
311
+ const headerSource = init?.headers ?? (input instanceof Request ? input.headers : undefined);
312
+ let body = init?.body ?? undefined;
313
+ if (body === undefined && input instanceof Request && input.body) {
314
+ // A Request input's own body is preserved (read once, replayed on every retry).
315
+ body = new Uint8Array(await input.arrayBuffer());
316
+ }
317
+ else if (body instanceof ReadableStream) {
318
+ // A stream body can only be read once; buffer it up front so every retry attempt
319
+ // can replay the same bytes.
320
+ body = new Uint8Array(await new Response(body).arrayBuffer());
321
+ }
322
+ // The whole request is built once, here, before #run — a
323
+ // bad SDK header value, a GET/HEAD with a body or an invalid or forbidden method fails
324
+ // with no fetch, no breaker bookkeeping and no ambiguity tag, never as a per-attempt
325
+ // local error. Every attempt reuses its method, headers and body bytes (headers and
326
+ // bytes together, so e.g. a multipart boundary always matches its body).
327
+ const template = buildRequest(url, {
328
+ method,
329
+ ...(headerSource !== undefined ? { headers: headerSource } : {}),
330
+ ...(body !== undefined ? { body } : {}),
331
+ }, 'bridged fetch request');
332
+ const payload = template.body
333
+ ? new Uint8Array(await template.arrayBuffer())
334
+ : undefined;
335
+ // Bridged SDK calls have no route template; the raw path never becomes an event label.
336
+ const label = template.method;
337
+ const options = classify?.(url, init) ?? {};
338
+ const signal = init?.signal ?? options.signal;
339
+ return this.#run(label, { ...options, ...(signal ? { signal } : {}) }, async (endpoint, deadline) => {
340
+ // Cloning an already-valid Headers instance never throws.
341
+ const requestHeaders = new Headers(template.headers);
342
+ for (const [name, value] of Object.entries(endpoint.headers))
343
+ requestHeaders.set(name, value);
344
+ const started = this.#clock.now();
345
+ const response = await this.#fetch(joinUrl(endpoint.url, url.pathname, url.search), {
346
+ method: template.method,
347
+ headers: requestHeaders,
348
+ ...(payload !== undefined ? { body: payload } : {}),
349
+ signal: deadline,
350
+ redirect: 'error',
351
+ });
352
+ // An error-status response body is never returned to the SDK; drain it so the
353
+ // connection can be released instead of leaking it.
354
+ try {
355
+ this.#throwForStatus(endpoint, response);
356
+ }
357
+ catch (error) {
358
+ // cancel() returns a promise that can reject asynchronously; an unhandled
359
+ // rejection here can crash the process, so it's always caught, even when body
360
+ // is null.
361
+ void response.body?.cancel().catch(() => undefined);
362
+ throw error;
363
+ }
364
+ // Buffer the whole body here, while the deadline and the caller's signal are
365
+ // still attached, and hand the SDK a fresh Response whose `url` is always '' — the
366
+ // real endpoint URL (and any secret it carries) never reaches the SDK.
367
+ const buffer = await this.#body(endpoint, response);
368
+ this.#emitResponse(endpoint, label, started, buffer.byteLength);
369
+ // The Response constructor throws if a body is given alongside a status that
370
+ // must never carry one.
371
+ const responseHeaders = new Headers(response.headers);
372
+ // Drop headers describing the original (possibly compressed) wire body —
373
+ // they no longer describe this already-decoded buffer.
374
+ responseHeaders.delete('content-encoding');
375
+ responseHeaders.delete('content-length');
376
+ return new Response(NULL_BODY_STATUSES.has(response.status) ? null : buffer, {
377
+ status: response.status,
378
+ statusText: response.statusText,
379
+ headers: responseHeaders,
380
+ });
381
+ });
382
+ };
383
+ return bridged;
384
+ }
385
+ /** Least-invasive detector for "no health probe was ever configured" — mirrors the same
386
+ * condition `ensureFreshHealth` already uses to skip probing entirely. */
387
+ hasProbes() {
388
+ return Boolean(this.#probes.height) || Boolean(this.#probes.identity);
389
+ }
390
+ setProbes(probes) {
391
+ this.#probes = probes;
392
+ // A new probe set invalidates any previously confirmed identity, the health timers
393
+ // and the stored heights. #highest stays: it is the stale-view guard, rebuilt from
394
+ // verified endpoints only if one turns out mismatched.
395
+ this.#lastHealthAt = Number.NEGATIVE_INFINITY;
396
+ this.#nextRefreshAt = Number.NEGATIVE_INFINITY;
397
+ this.#best = undefined;
398
+ for (const endpoint of this.#endpoints) {
399
+ endpoint.identity = 'unchecked';
400
+ endpoint.identityCheck = undefined;
401
+ endpoint.identityRetryAt = undefined;
402
+ endpoint.height = undefined;
403
+ endpoint.healthMisses = 0;
404
+ endpoint.lastMissAt = undefined;
405
+ }
406
+ }
407
+ async ensureFreshHealth(signal) {
408
+ if (!this.#probes.height && !this.#probes.identity)
409
+ return;
410
+ // An in-flight refresh is awaited before the staleness check, so a second concurrent
411
+ // caller can't slip through and read endpoint state mid-refresh (e.g. heights still
412
+ // unset). This caller's own signal is raced against it; the shared run itself is not
413
+ // cancelled by it.
414
+ if (this.#healthRun)
415
+ await this.#join(this.#healthRun, signal);
416
+ if (!this.#refreshDue) {
417
+ if (this.#clock.now() - this.#lastHealthAt < this.#opts.healthIntervalMs)
418
+ return;
419
+ // After a fully-failed refresh, back off until #nextRefreshAt instead
420
+ // of re-probing every down endpoint on every read during an outage. Reads in this
421
+ // window simply see no fresh health and proceed with the existing
422
+ // no-eligible-endpoint semantics.
423
+ if (this.#clock.now() < this.#nextRefreshAt)
424
+ return;
425
+ }
426
+ await this.refreshHealth(signal);
427
+ }
428
+ refreshHealth(signal) {
429
+ // The shared run is started at most once and is never bound to any one caller's
430
+ // signal; each caller instead races its own signal against it via #join.
431
+ this.#healthRun ??= this.#refresh().finally(() => {
432
+ this.#healthRun = undefined;
433
+ });
434
+ return this.#join(this.#healthRun, signal);
435
+ }
436
+ /** Resolves/rejects with `run`, but also rejects early (with only THIS caller's promise)
437
+ * if `signal` aborts first. `run` itself is left untouched either way. */
438
+ #join(run, signal) {
439
+ if (!signal)
440
+ return run;
441
+ if (signal.aborted)
442
+ return Promise.reject(signal.reason);
443
+ return new Promise((resolve, reject) => {
444
+ const onAbort = () => reject(signal.reason);
445
+ signal.addEventListener('abort', onAbort, { once: true });
446
+ run.then(resolve, reject).finally(() => {
447
+ signal.removeEventListener('abort', onAbort);
448
+ });
449
+ });
450
+ }
451
+ status() {
452
+ return this.#endpoints.map((e) => {
453
+ const lag = e.height !== undefined && this.#best !== undefined
454
+ ? this.#best - e.height
455
+ : undefined;
456
+ const state = e.identity === 'mismatch'
457
+ ? 'disabled'
458
+ : e.breaker.state === 'open'
459
+ ? 'open'
460
+ : e.breaker.state === 'half-open'
461
+ ? 'half-open'
462
+ : this.#lagging(e)
463
+ ? 'lagging'
464
+ : // With a height probe, an endpoint is healthy only once its height is
465
+ // known: a confirmed identity alone says nothing about its head.
466
+ e.height !== undefined || (e.identity === 'ok' && !this.#probes.height)
467
+ ? 'healthy'
468
+ : 'unknown';
469
+ return {
470
+ id: e.id,
471
+ kind: e.kind,
472
+ state,
473
+ failures: e.failures,
474
+ ...(e.height !== undefined ? { height: e.height } : {}),
475
+ ...(lag !== undefined ? { lag } : {}),
476
+ ...(e.latencyMs !== undefined ? { latencyMs: e.latencyMs } : {}),
477
+ ...(e.identity === 'mismatch' ? { reason: 'identity-mismatch' } : {}),
478
+ };
479
+ });
480
+ }
481
+ highestHeight() {
482
+ return this.#highest;
483
+ }
484
+ get maxLagBlocks() {
485
+ return this.#opts.maxLagBlocks;
486
+ }
487
+ // ---- request orchestration -------------------------------------------------------
488
+ async #run(label, options, work) {
489
+ const purpose = options.purpose ?? (options.quorum !== undefined ? 'proof' : 'read');
490
+ // A proof quorum under purpose 'read' counts endpoints by health too, so it keeps
491
+ // health fresh as well; otherwise a dead endpoint would never miss and stall it.
492
+ if (purpose === 'monitor' || purpose === 'proof' || options.quorum === 'proof')
493
+ await this.ensureFreshHealth(options.signal);
494
+ if (options.quorum !== undefined)
495
+ return this.#quorum(label, purpose, options, work);
496
+ if ((options.fanout ?? 1) > 1)
497
+ return this.#fanout(label, purpose, options, work);
498
+ return this.#withRetry(label, purpose, options, work);
499
+ }
500
+ async #withRetry(label, purpose, options, work) {
501
+ const retry = options.retry ?? 'safe';
502
+ const attempts = retry === 'never-auto' ? 1 : this.#opts.maxAttempts;
503
+ const tried = new Set();
504
+ let last;
505
+ let mayHaveSent = false;
506
+ for (let attempt = 0; attempt < attempts; attempt++) {
507
+ let endpoint;
508
+ try {
509
+ // The raw call-level override (possibly undefined) is passed through
510
+ // so #pick can fall back to the endpoint's own timeoutMs, matching #attempt.
511
+ endpoint = await this.#pick(purpose, tried, options.signal, options.timeoutMs);
512
+ }
513
+ catch (error) {
514
+ if (options.signal?.aborted)
515
+ throw error;
516
+ if (last)
517
+ break;
518
+ throw error;
519
+ }
520
+ tried.add(endpoint.id);
521
+ try {
522
+ return await this.#attempt(endpoint, label, attempt, options, work);
523
+ }
524
+ catch (error) {
525
+ const { failure, definitive } = this.#endpointFailure(error, options);
526
+ if (this.#maybeDelivered.has(failure))
527
+ mayHaveSent = true;
528
+ if (definitive)
529
+ throw this.#externalize(this.#finalize(failure, retry, mayHaveSent));
530
+ last = failure;
531
+ // A rate-limit wait is #pick's job; only back off for other failures.
532
+ if (attempt + 1 < attempts && failure.code !== 'RATE_LIMITED') {
533
+ await this.#clock.sleep(this.#delay(attempt), options.signal);
534
+ }
535
+ }
536
+ }
537
+ throw this.#externalize(this.#finalize(last, retry, mayHaveSent));
538
+ }
539
+ async #quorum(label, purpose, options, work) {
540
+ const proof = isProofQuorum(purpose, options);
541
+ const { sized, inRange } = this.#quorumCandidates(purpose, proof);
542
+ // Only the endpoints the quorum counts answer toward it. A recovering endpoint (out
543
+ // of the count, its breaker half-open) is tried alongside them, from the start, so
544
+ // it is tried even when too few counted endpoints can answer; its answer only
545
+ // blocks. The trial lets it rejoin even when only proof reads would ask it: only a
546
+ // request closes its breaker, and while the breaker is not closed every health
547
+ // refresh records a miss for it.
548
+ const counted = new Set(sized);
549
+ const trial = proof ? this.#trial(label, inRange, counted, options, work) : undefined;
550
+ let decided;
551
+ try {
552
+ decided = await this.#decide(label, purpose, proof, counted, options, work);
553
+ }
554
+ catch (error) {
555
+ await trial;
556
+ throw error;
557
+ }
558
+ const outcome = await trial;
559
+ // The caller aborted while the trial ran: the read ends as any aborted read does.
560
+ if (options.signal?.aborted)
561
+ throw options.signal.reason;
562
+ if (outcome && outcome.kind !== 'failed') {
563
+ const agrees = outcome.kind === 'answer' &&
564
+ 'results' in decided &&
565
+ quorumAgrees([...decided.results.map((r) => r.value), outcome.value], options.quorumKey);
566
+ if (!agrees) {
567
+ const asked = 'results' in decided
568
+ ? decided.results.map((r) => r.endpoint)
569
+ : decided.endpoints;
570
+ throw this.#inconsistent(label, [...asked, outcome.endpoint].map((e) => e.id));
571
+ }
572
+ }
573
+ if ('refusal' in decided)
574
+ throw decided.refusal;
575
+ return decided.results[0].value;
576
+ }
577
+ /**
578
+ * The counted endpoints' verdict (no other endpoint answers toward it): their agreeing
579
+ * answers, or the definitive error they all returned alike. Anything
580
+ * else throws: too few answers, a disagreement, or (outside a proof quorum) the first
581
+ * definitive error.
582
+ */
583
+ async #decide(label, purpose, proof, counted, options, work) {
584
+ const requested = options.quorum === 'proof' ? this.#opts.proofQuorum : (options.quorum ?? 1);
585
+ // Sized from the full candidate set, not the rate-limit-filtered
586
+ // eligible set — a required endpoint being rate-limited must not silently shrink the
587
+ // quorum. A required endpoint that's rate-limited therefore fails the call with a
588
+ // retryable error instead of resolving from fewer endpoints than needed.
589
+ // Nor may lag, an unknown height, an unconfirmed identity or an open
590
+ // breaker shrink a proof quorum (#quorumCandidates), so one liar is never alone.
591
+ // Nor, by default, may an endpoint that dropped out of the count (down for three
592
+ // health intervals): a proof waits for it, so the one endpoint left, which may be a
593
+ // liar, never proves alone. `degradedProofs` trades that for liveness.
594
+ const available = proof && !this.#opts.degradedProofs
595
+ ? this.#endpoints.filter((e) => e.identity !== 'mismatch').length
596
+ : counted.size;
597
+ const needed = Math.max(1, Math.min(requested, available));
598
+ const eligible = () => {
599
+ const now = this.#clock.now();
600
+ return this.#quorumCandidates(purpose, proof).inRange.filter((e) => e.notBefore <= now && counted.has(e));
601
+ };
602
+ // If fewer endpoints are eligible right now than needed, fail fast —
603
+ // before querying any of them — instead of querying what's available and discovering
604
+ // the shortfall only afterward. This is also where lag that leaves too few
605
+ // endpoints decides nothing (retryable).
606
+ if (eligible().length < needed) {
607
+ throw this.#externalize(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `quorum of ${needed} not reachable for ${label}`, { context: { transportId: this.id } }));
608
+ }
609
+ const results = [];
610
+ // Under a proof quorum, one endpoint's definitive error is its answer, not yet the
611
+ // quorum's: it is kept, and the other endpoints are still asked, so one endpoint's
612
+ // revert never decides alone (e.g. marking a token unresolvable). Known gap: outside
613
+ // a proof quorum, the first definitive error still ends the read at once.
614
+ const refusals = [];
615
+ const tried = new Set();
616
+ let last;
617
+ while (results.length + refusals.length < needed) {
618
+ const endpoint = eligible().find((e) => !tried.has(e.id));
619
+ if (!endpoint)
620
+ break;
621
+ tried.add(endpoint.id);
622
+ try {
623
+ results.push({
624
+ endpoint,
625
+ value: await this.#attempt(endpoint, label, tried.size - 1, options, work),
626
+ });
627
+ }
628
+ catch (error) {
629
+ const { failure, definitive } = this.#endpointFailure(error, options);
630
+ if (!definitive)
631
+ last = failure;
632
+ else if (proof)
633
+ refusals.push({ endpoint, error: failure });
634
+ else
635
+ throw failure;
636
+ }
637
+ }
638
+ if (results.length + refusals.length < needed) {
639
+ throw this.#externalize(last ??
640
+ new error_1.ProviderError('PROVIDER_UNAVAILABLE', `quorum of ${needed} not reachable for ${label}`, { context: { transportId: this.id } }));
641
+ }
642
+ const [refusal] = refusals;
643
+ if (refusal) {
644
+ // A definitive error decides only when every endpoint of the quorum returned
645
+ // an equivalent one; a refusal against an answer, or unlike refusals, decide nothing.
646
+ if (results.length === 0 &&
647
+ refusals.every((r) => sameRefusal(r.error, refusal.error))) {
648
+ return { refusal: refusal.error, endpoints: refusals.map((r) => r.endpoint) };
649
+ }
650
+ throw this.#inconsistent(label, [...results, ...refusals].map((r) => r.endpoint.id));
651
+ }
652
+ if (results.length === 0 ||
653
+ !quorumAgrees(results.map((r) => r.value), options.quorumKey)) {
654
+ throw this.#inconsistent(label, results.map((r) => r.endpoint.id));
655
+ }
656
+ return { results };
657
+ }
658
+ /**
659
+ * The trial of a recovering endpoint, one per read: among the in-range endpoints
660
+ * the quorum does not count (health misses) whose breaker is half-open, the first by
661
+ * priority, then name. Its request is an ordinary attempt (its rate limit, identity check
662
+ * and breaker bookkeeping, so an answer closes the breaker and a failure counts against
663
+ * it), except that it never waits for a rate-limit token: with none free now,
664
+ * it is not sent this read (a failure). The read waits for a trial it sent, up to the
665
+ * call's timeout. Each concurrent proof read in a half-open window may send its own
666
+ * trial. Never rejects: the outcome is an answer, a refusal (a definitive error) or a
667
+ * failure, which the verdict ignores. Known gap: with a `burst` of 1, the health
668
+ * refresh a proof read runs first can spend the endpoint's only token on its probe, so
669
+ * the trial is skipped and the endpoint can stay out; proof endpoints want a `burst` of
670
+ * 2 or more.
671
+ */
672
+ #trial(label, inRange, counted, options, work) {
673
+ const now = this.#clock.now();
674
+ const [endpoint] = inRange
675
+ .filter((e) => !counted.has(e) && e.breaker.state === 'half-open' && e.notBefore <= now)
676
+ .sort((a, b) => a.priority - b.priority || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
677
+ if (!endpoint)
678
+ return undefined;
679
+ // An answer or a refusal closed the endpoint's breaker, so the next
680
+ // health check refreshes at once and it rejoins the count by the ordinary rules. Set
681
+ // here, as the trial settles, not when the read does: whatever becomes of the counted
682
+ // endpoints (a failure, the caller's abort, a slow answer), no later read finds the
683
+ // endpoint closed but still out of the count, so its veto never lapses.
684
+ return this.#attempt(endpoint, label, 0, options, work, false).then((value) => {
685
+ this.#refreshDue = true;
686
+ return { endpoint, kind: 'answer', value };
687
+ }, (error) => {
688
+ const refused = !options.signal?.aborted && (0, error_1.isCryptoAioError)(error) && !error.retryable;
689
+ if (refused)
690
+ this.#refreshDue = true;
691
+ return { endpoint, kind: refused ? 'refusal' : 'failed' };
692
+ });
693
+ }
694
+ /** A quorum whose endpoints disagree: a retryable `PROVIDER_INCONSISTENT`, announced. */
695
+ #inconsistent(label, endpointIds) {
696
+ this.#events.emit('provider.inconsistent', {
697
+ transportId: this.id,
698
+ method: label,
699
+ endpointIds,
700
+ });
701
+ return new error_1.ProviderError('PROVIDER_INCONSISTENT', `endpoints disagree on ${label}`, {
702
+ context: { transportId: this.id },
703
+ });
704
+ }
705
+ async #fanout(label, purpose, options, work) {
706
+ const targets = this.#eligible(purpose).slice(0, options.fanout);
707
+ if (targets.length === 0)
708
+ throw this.#noHealthyEndpoint();
709
+ const settled = await Promise.allSettled(targets.map((endpoint, index) => this.#attempt(endpoint, label, index, options, work)));
710
+ for (const outcome of settled)
711
+ if (outcome.status === 'fulfilled')
712
+ return outcome.value;
713
+ if (options.signal?.aborted)
714
+ throw options.signal.reason;
715
+ const errors = settled.map((outcome) => outcome.reason);
716
+ // Any settled attempt that reached the network makes the whole fanout ambiguous.
717
+ const mayHaveSent = errors.some((e) => this.#maybeDelivered.has(e));
718
+ const retry = options.retry ?? 'safe';
719
+ const definitive = errors.find((e) => !e.retryable);
720
+ if (definitive)
721
+ throw this.#externalize(this.#finalize(definitive, retry, mayHaveSent));
722
+ throw this.#externalize(this.#finalize(errors[0], retry, mayHaveSent));
723
+ }
724
+ async #attempt(endpoint, label, attempt, options, work,
725
+ /** False for a trial, which never waits for a rate-limit token. */
726
+ waitForToken = true) {
727
+ const timeoutMs = options.timeoutMs ?? endpoint.timeoutMs ?? this.#opts.timeoutMs;
728
+ // A first-use identity probe takes its own token (#direct), so it runs before this
729
+ // request takes one: the probe and the request never leave back to back. When it fails,
730
+ // the request takes no token, and the failure is thrown inside the `try` below, where
731
+ // the check always ran, so its message, cause and breaker bookkeeping are unchanged.
732
+ const identityFailure = await this.#identityFirst(endpoint, timeoutMs, options.signal);
733
+ // The token-bucket wait runs before the per-attempt deadline is armed,
734
+ // with its own timeoutMs-bounded budget. A wait that times out (or that the caller
735
+ // aborts) never touches the breaker and is never tagged ambiguous — no fetch has
736
+ // happened yet.
737
+ if (identityFailure === undefined) {
738
+ await this.#takeToken(endpoint, timeoutMs, options.signal, waitForToken);
739
+ }
740
+ const { signal, cancel } = this.#deadline(timeoutMs, options.signal);
741
+ const started = this.#clock.now();
742
+ // Only set once THIS attempt's own onAttempt() call ran, so only this
743
+ // attempt may release a half-open probe slot it actually claimed.
744
+ let ownsProbe = false;
745
+ try {
746
+ if (identityFailure !== undefined)
747
+ throw identityFailure;
748
+ await this.#ensureIdentity(endpoint, signal);
749
+ // onAttempt() itself reports whether this attempt claimed the slot.
750
+ ownsProbe = endpoint.breaker.onAttempt();
751
+ this.#events.emit('rpc.request', {
752
+ transportId: this.id,
753
+ endpointId: endpoint.id,
754
+ method: label,
755
+ attempt,
756
+ });
757
+ const value = await work(endpoint, signal);
758
+ endpoint.breaker.onSuccess();
759
+ endpoint.failures = 0;
760
+ endpoint.latencyMs = this.#clock.now() - started;
761
+ return value;
762
+ }
763
+ catch (error) {
764
+ if (options.signal?.aborted) {
765
+ // Only free the half-open slot if THIS attempt actually claimed it —
766
+ // an abort during the identity check or the token wait never frees another
767
+ // request's half-open slot.
768
+ if (ownsProbe)
769
+ endpoint.breaker.onAbandon();
770
+ throw options.signal.reason;
771
+ }
772
+ const failure = this.#classify(error, endpoint, signal);
773
+ if (failure.retryable) {
774
+ endpoint.breaker.onFailure();
775
+ endpoint.failures += 1;
776
+ }
777
+ else {
778
+ // A definitive answer proves the endpoint is reachable and healthy.
779
+ endpoint.breaker.onSuccess();
780
+ endpoint.failures = 0;
781
+ }
782
+ this.#events.emit('rpc.error', {
783
+ transportId: this.id,
784
+ endpointId: endpoint.id,
785
+ method: label,
786
+ latencyMs: this.#clock.now() - started,
787
+ code: failure.code,
788
+ retryable: failure.retryable,
789
+ });
790
+ throw failure;
791
+ }
792
+ finally {
793
+ cancel();
794
+ }
795
+ }
796
+ /**
797
+ * Runs a pending first-use identity check under its own deadline, before the request
798
+ * takes its token, and returns its failure for `#attempt` to throw where the check
799
+ * always ran. Only the caller's abort propagates from here.
800
+ */
801
+ async #identityFirst(endpoint, timeoutMs, outer) {
802
+ if (!this.#identityProbed() || endpoint.identity !== 'unchecked')
803
+ return undefined;
804
+ const { signal, cancel } = this.#deadline(timeoutMs, outer);
805
+ try {
806
+ await this.#ensureIdentity(endpoint, signal);
807
+ return undefined;
808
+ }
809
+ catch (error) {
810
+ if (outer?.aborted)
811
+ throw outer.reason;
812
+ return error;
813
+ }
814
+ finally {
815
+ cancel();
816
+ }
817
+ }
818
+ /** Bounds the token-bucket wait by the caller's signal and the call's
819
+ * timeoutMs, run before the per-attempt deadline is armed. A wait that exceeds timeoutMs
820
+ * fails with a retryable rate-limit error, without touching the breaker — no fetch has
821
+ * happened yet, so this can never be tagged ambiguous either. */
822
+ async #takeToken(endpoint, timeoutMs, outer, wait = true) {
823
+ if (!endpoint.bucket)
824
+ return;
825
+ if (!wait) {
826
+ // With no token free now, no wait: the same retryable RATE_LIMITED, before
827
+ // any fetch or breaker bookkeeping.
828
+ if (endpoint.bucket.tryTakeNow())
829
+ return;
830
+ throw new error_1.ProviderError('RATE_LIMITED', `no rate limit token free for endpoint '${endpoint.id}'`, { context: this.#context(endpoint) });
831
+ }
832
+ const { signal, cancel } = this.#deadline(timeoutMs, outer);
833
+ try {
834
+ await endpoint.bucket.take(signal);
835
+ }
836
+ catch {
837
+ if (outer?.aborted)
838
+ throw outer.reason;
839
+ throw new error_1.ProviderError('RATE_LIMITED', `rate limit wait exceeded the call timeout for endpoint '${endpoint.id}'`, { context: this.#context(endpoint) });
840
+ }
841
+ finally {
842
+ cancel();
843
+ }
844
+ }
845
+ /** Endpoints that are structurally usable for `purpose` (breaker, identity, lag/height),
846
+ * ignoring any per-endpoint rate-limit wait. */
847
+ #candidates(purpose) {
848
+ // A single proof read measures lag as a quorum read does, against the corroborated
849
+ // height, so one over-reporting endpoint never leaves itself the only candidate.
850
+ if (purpose === 'proof')
851
+ return this.#quorumCandidates(purpose, true).inRange;
852
+ const strict = purpose === 'monitor';
853
+ return this.#usable().filter((e) => !strict || !this.#excludedForHeight(e));
854
+ }
855
+ /**
856
+ * A quorum read's endpoints: `sized`, which the quorum's size counts, and `inRange`, the
857
+ * only ones asked.
858
+ *
859
+ * `sized`: for a proof quorum (`proof`, see `isProofQuorum`) whose refreshes can see
860
+ * a dead endpoint (a height probe, or an identity probe with an expected identity),
861
+ * every endpoint not proven mismatched, whatever its height, identity state (confirmed,
862
+ * not yet checked or identity-throttled) or breaker state, until `HEALTH_MISS_LIMIT`
863
+ * health refreshes in a row, at most one per `healthIntervalMs`, failed its identity or
864
+ * height probe or found its requests failing (breaker not closed, or failures in a row
865
+ * at `failureThreshold`): a sustained outage. Such an endpoint cannot be asked, so it
866
+ * makes the read decide nothing rather than letting fewer endpoints decide it. It
867
+ * counts again once it serves requests and a later refresh succeeds; meanwhile it never
868
+ * answers toward the quorum, and only its half-open trial (`#trial`) can block a read.
869
+ * Otherwise (no such probe, or another quorum) it is the usable endpoints (the prior,
870
+ * weaker rule).
871
+ *
872
+ * `inRange`: the usable endpoints (breaker, identity, throttle). For a monitor or proof
873
+ * purpose with a height probe, only those with a known height (only a confirmed endpoint
874
+ * has one) at most `maxLagBlocks` behind the corroborated height, the second-highest
875
+ * known height, so one endpoint that over-reports its head never marks honest ones as
876
+ * lagging. With two known heights the corroborated height is the lower one, so the lag
877
+ * filter excludes neither: it takes three endpoints to exclude a lagging one, and a proof
878
+ * read should be anchored to a height (asked at an explicit block), so a lagging endpoint
879
+ * disagrees or errs instead of answering for an older state.
880
+ *
881
+ * Known gap: an endpoint that disagrees is never demoted, so a liar among the first
882
+ * endpoints asked keeps proof reads deciding nothing (liveness only).
883
+ */
884
+ #quorumCandidates(purpose, proof) {
885
+ const usable = this.#usable();
886
+ // When no refresh can see a dead endpoint (no probe, or only an
887
+ // identity probe without an expected identity, where #refresh does no I/O), nothing
888
+ // ever records a miss, so counting endpoints that cannot answer would stall proofs for
889
+ // good; the count is then the usable endpoints (the prior, weaker rule: a liar can
890
+ // prove alone once the honest endpoints' breakers open). So every family sets both an
891
+ // identity probe, with its expected identity, and a height probe.
892
+ const sized = proof && (this.#probes.height !== undefined || this.#identityProbed())
893
+ ? this.#endpoints.filter((e) => e.identity !== 'mismatch' && e.healthMisses < HEALTH_MISS_LIMIT)
894
+ : usable;
895
+ const strict = purpose === 'monitor' || purpose === 'proof';
896
+ if (!strict || !this.#probes.height)
897
+ return { sized, inRange: usable };
898
+ const known = usable.filter((e) => e.height !== undefined);
899
+ const reference = corroboratedHeight(known);
900
+ const lag = BigInt(this.#opts.maxLagBlocks);
901
+ const inRange = reference === undefined
902
+ ? known
903
+ : known.filter((e) => reference - e.height <= lag);
904
+ return { sized, inRange };
905
+ }
906
+ /** Endpoints that pass the identity, identity-throttle and breaker filters, best first. */
907
+ #usable() {
908
+ const now = this.#clock.now();
909
+ return this.#endpoints
910
+ .filter((e) => e.identity !== 'mismatch' &&
911
+ // An identity-throttled endpoint is excluded the same way a
912
+ // rate-limited one is — never picked, and a throttle hit never reaches the breaker.
913
+ (e.identityRetryAt === undefined || e.identityRetryAt <= now) &&
914
+ e.breaker.canRequest())
915
+ .sort((a, b) => a.priority - b.priority ||
916
+ a.failures - b.failures ||
917
+ (a.latencyMs ?? 0) - (b.latencyMs ?? 0));
918
+ }
919
+ #eligible(purpose) {
920
+ const now = this.#clock.now();
921
+ return this.#candidates(purpose).filter((e) => e.notBefore <= now);
922
+ }
923
+ #noHealthyEndpoint() {
924
+ return new error_1.ProviderError('PROVIDER_UNAVAILABLE', 'no healthy endpoint available', {
925
+ context: { transportId: this.id },
926
+ });
927
+ }
928
+ /** Picks the best available endpoint, waiting only when every structurally-usable
929
+ * endpoint is still rate-limited, and only until the earliest one frees up.
930
+ * That wait is bounded by the call's timeoutMs — a wait longer than that
931
+ * fails at once with a retryable rate-limit error instead of stalling the whole call. */
932
+ async #pick(purpose, tried, signal, callTimeoutMs) {
933
+ for (;;) {
934
+ const eligible = this.#eligible(purpose);
935
+ if (eligible.length > 0) {
936
+ const untried = eligible.filter((e) => !tried.has(e.id));
937
+ const pool = untried.length > 0 ? untried : eligible;
938
+ return (pool.find((e) => !e.bucket || e.bucket.msUntilToken() === 0) ??
939
+ pool[0]);
940
+ }
941
+ const waiting = this.#candidates(purpose);
942
+ if (waiting.length === 0)
943
+ throw this.#noHealthyEndpoint();
944
+ const earliest = waiting.reduce((min, e) => e.notBefore < min.notBefore ? e : min);
945
+ const waitMs = Math.max(0, earliest.notBefore - this.#clock.now());
946
+ // Matches #attempt's own effective-timeout resolution — the earliest
947
+ // endpoint's own timeoutMs, when set, bounds its wait instead of only the transport
948
+ // default.
949
+ const boundMs = callTimeoutMs ?? earliest.timeoutMs ?? this.#opts.timeoutMs;
950
+ if (waitMs > boundMs) {
951
+ throw new error_1.ProviderError('RATE_LIMITED', 'endpoint rate limit wait exceeds the call timeout', { context: this.#context(earliest) });
952
+ }
953
+ await this.#clock.sleep(waitMs, signal);
954
+ }
955
+ }
956
+ #lagging(endpoint) {
957
+ return (endpoint.height !== undefined &&
958
+ this.#best !== undefined &&
959
+ this.#best - endpoint.height > BigInt(this.#opts.maxLagBlocks));
960
+ }
961
+ /** Once a height probe is configured, an endpoint whose height is unknown (its probe
962
+ * never ran or failed) is excluded from monitor/proof reads the same way a lagging one is. */
963
+ #excludedForHeight(endpoint) {
964
+ if (!this.#probes.height)
965
+ return false;
966
+ if (endpoint.height === undefined)
967
+ return true;
968
+ return this.#lagging(endpoint);
969
+ }
970
+ /** Clamps Retry-After, or falls back to backoff, and records the endpoint's notBefore. */
971
+ #applyRateLimit(endpoint, retryAfterMs) {
972
+ const delayMs = retryAfterMs !== undefined
973
+ ? Math.min(retryAfterMs, MAX_RETRY_AFTER_MS)
974
+ : (0, backoff_1.backoffDelay)(endpoint.failures, this.#opts, this.#random);
975
+ endpoint.notBefore = this.#clock.now() + delayMs;
976
+ return delayMs;
977
+ }
978
+ #delay(attempt) {
979
+ return (0, backoff_1.backoffDelay)(attempt, this.#opts, this.#random);
980
+ }
981
+ /** `mayHaveSent` covers every attempt that was possibly delivered, not just this one;
982
+ * once true, even a later definitive error can't rule out an earlier ambiguous attempt. */
983
+ #finalize(error, retry, mayHaveSent) {
984
+ return retry === 'ambiguous-on-failure' && mayHaveSent
985
+ ? (0, error_1.withContext)(error, {}, { ambiguous: true })
986
+ : error;
987
+ }
988
+ /** PROVIDER_MISCONFIGURED is only retryable internally, to allow failover between
989
+ * endpoints; once every endpoint has been exhausted, the caller must see it as final. */
990
+ #externalize(error) {
991
+ return error.code === 'PROVIDER_MISCONFIGURED' && error.retryable
992
+ ? (0, error_1.withContext)(error, {}, { retryable: false })
993
+ : error;
994
+ }
995
+ /** Shared retry-loop catch handling. Caller aborts propagate as-is; everything else is
996
+ * classified as either a definitive (stop retrying) or a retryable endpoint failure. */
997
+ #endpointFailure(error, options) {
998
+ if (options.signal?.aborted)
999
+ throw options.signal.reason;
1000
+ const failure = error;
1001
+ return { failure, definitive: !failure.retryable };
1002
+ }
1003
+ #deadline(ms, outer) {
1004
+ const controller = new AbortController();
1005
+ const onOuter = () => controller.abort(outer?.reason);
1006
+ if (outer?.aborted)
1007
+ controller.abort(outer.reason);
1008
+ else
1009
+ outer?.addEventListener('abort', onOuter, { once: true });
1010
+ const timer = new AbortController();
1011
+ void this.#clock.sleep(ms, timer.signal).then(() => controller.abort(TIMEOUT), () => undefined);
1012
+ return {
1013
+ signal: controller.signal,
1014
+ cancel: () => {
1015
+ timer.abort();
1016
+ outer?.removeEventListener('abort', onOuter);
1017
+ },
1018
+ };
1019
+ }
1020
+ // ---- single requests -------------------------------------------------------------
1021
+ #fetch(url, init) {
1022
+ return (this.#opts.fetch ?? globalThis.fetch)(url, init);
1023
+ }
1024
+ /** The JSON-RPC POST shape is identical whether the caller goes through `rpc()` or the
1025
+ * direct per-endpoint health-probe calls. */
1026
+ #jsonPost(url, body) {
1027
+ return { method: 'POST', url, headers: { 'content-type': 'application/json' }, body };
1028
+ }
1029
+ async #rpcOnce(endpoint, method, id, body, signal, exactIntegers = false) {
1030
+ const { json } = await this.#exchange(endpoint, method, this.#jsonPost(endpoint.url, body), signal, 'rpc', method, exactIntegers);
1031
+ return this.#unwrapRpc(endpoint, method, id, json);
1032
+ }
1033
+ async #httpOnce(endpoint, request, bodyText, signal, exactIntegers = false) {
1034
+ const query = request.query
1035
+ ? new URLSearchParams({ ...request.query }).toString()
1036
+ : '';
1037
+ const hasBody = bodyText !== undefined;
1038
+ const mode = request.responseType ?? 'json';
1039
+ const { text, json, bytes } = await this.#exchange(endpoint, routeLabel(request.method, request.route), {
1040
+ method: request.method,
1041
+ url: joinUrl(endpoint.url, request.path, query),
1042
+ headers: {
1043
+ ...(hasBody
1044
+ ? {
1045
+ 'content-type': typeof request.body === 'string' ? 'text/plain' : 'application/json',
1046
+ }
1047
+ : {}),
1048
+ ...request.headers,
1049
+ },
1050
+ ...(hasBody ? { body: bodyText } : {}),
1051
+ }, signal, mode,
1052
+ // Error texts name the route template, as events do, never the concrete path, which
1053
+ // carries addresses and transaction ids: error texts end up in logs and alerting.
1054
+ routeLabel(request.method, request.route), exactIntegers);
1055
+ return (mode === 'text' ? text : mode === 'bytes' ? bytes : json);
1056
+ }
1057
+ /**
1058
+ * The probes' single-attempt calls. Each request first takes a token from the
1059
+ * endpoint's own bucket, bounded by the probe's deadline (`signal`), so probes and
1060
+ * requests share one rate limit and a rate-limited endpoint never answers a probe 429.
1061
+ * The probe takes it with priority, ahead of requests already waiting, so a queue of
1062
+ * requests never starves it past its deadline into an unknown height.
1063
+ */
1064
+ #direct(endpoint, signal) {
1065
+ return {
1066
+ rpc: async (method, params) => {
1067
+ const id = ++this.#rpcId;
1068
+ const body = serializeJson({ jsonrpc: '2.0', id, method, params: params ?? [] });
1069
+ await this.#probeToken(endpoint, signal);
1070
+ return this.#rpcOnce(endpoint, method, id, body, signal);
1071
+ },
1072
+ http: async (request) => {
1073
+ const bodyText = request.body === undefined
1074
+ ? undefined
1075
+ : typeof request.body === 'string'
1076
+ ? request.body
1077
+ : serializeJson(request.body);
1078
+ await this.#probeToken(endpoint, signal);
1079
+ return this.#httpOnce(endpoint, request, bodyText, signal);
1080
+ },
1081
+ };
1082
+ }
1083
+ /** A probe call's token, taken with priority and counted for #refresh's backoff. */
1084
+ async #probeToken(endpoint, signal) {
1085
+ if (!endpoint.bucket)
1086
+ return;
1087
+ await endpoint.bucket.take(signal, true);
1088
+ endpoint.probeTokens += 1;
1089
+ }
1090
+ async #exchange(endpoint, label, request, signal, mode, errorLabel = label, exactIntegers = false) {
1091
+ const started = this.#clock.now();
1092
+ const response = await this.#fetch(request.url, {
1093
+ method: request.method,
1094
+ headers: { ...endpoint.headers, ...request.headers },
1095
+ ...(request.body !== undefined ? { body: request.body } : {}),
1096
+ signal,
1097
+ redirect: 'error',
1098
+ });
1099
+ const bytes = await this.#body(endpoint, response);
1100
+ // A binary 2xx answer is returned as it came: UTF-8 decoding would corrupt it (and
1101
+ // decode a whole block for nothing). An error body is text whatever the mode.
1102
+ const text = mode === 'bytes' && response.ok ? '' : new TextDecoder().decode(bytes);
1103
+ this.#throwForStatus(endpoint, response);
1104
+ const context = this.#context(endpoint);
1105
+ let json;
1106
+ if ((mode !== 'text' && mode !== 'bytes') || !response.ok) {
1107
+ try {
1108
+ json = text.length > 0 ? (0, json_1.parseJson)(text, exactIntegers) : null;
1109
+ }
1110
+ catch {
1111
+ if (response.ok) {
1112
+ // An unparseable 2xx body is inherently ambiguous — the server accepted the
1113
+ // request but we can't tell what it said.
1114
+ throw this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `endpoint returned a non-JSON body (HTTP ${response.status})`, { context }));
1115
+ }
1116
+ }
1117
+ }
1118
+ if (!response.ok) {
1119
+ const isRpcError = mode === 'rpc' && json !== null && typeof json === 'object' && 'error' in json;
1120
+ if (!isRpcError) {
1121
+ if (mode === 'rpc') {
1122
+ // A 4xx in RPC mode without a JSON-RPC envelope is an endpoint-local failure,
1123
+ // not a definitive protocol answer: retry and fail over. The server did
1124
+ // respond, so a later definitive failure on this call still inherits ambiguity.
1125
+ throw this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `${errorLabel} did not return a JSON-RPC envelope (HTTP ${response.status})`, { context }));
1126
+ }
1127
+ // A REST 4xx that isn't 401/403/429/408 (peeled off in
1128
+ // #throwForStatus) is a definitive, non-retryable answer from the endpoint — it
1129
+ // must not itself set mayHaveSent, though it still inherits ambiguity from an
1130
+ // earlier tagged attempt via #withRetry/#fanout's accumulated mayHaveSent.
1131
+ throw new error_1.ProviderError('RPC_ERROR', `${errorLabel} refused (HTTP ${response.status})`, {
1132
+ context,
1133
+ details: {
1134
+ status: response.status,
1135
+ body: endpoint.scrub(text, 300),
1136
+ },
1137
+ });
1138
+ }
1139
+ }
1140
+ this.#emitResponse(endpoint, label, started, bytes.byteLength);
1141
+ return { text, json, bytes };
1142
+ }
1143
+ /**
1144
+ * An answer's body, at most `maxResponseBytes`. A longer one, by its declared length
1145
+ * or as it streams, is cancelled and fails as a retryable `PROVIDER_UNAVAILABLE`,
1146
+ * tagged as possibly delivered, since the server did answer. Without the cap one
1147
+ * endpoint could exhaust memory with an endless answer.
1148
+ */
1149
+ async #body(endpoint, response) {
1150
+ const limit = this.#opts.maxResponseBytes;
1151
+ const tooLarge = () => this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `endpoint answered more than ${limit} bytes`, { context: this.#context(endpoint) }));
1152
+ const declared = Number(response.headers.get('content-length') ?? Number.NaN);
1153
+ if (declared > limit) {
1154
+ void response.body?.cancel().catch(() => undefined);
1155
+ throw tooLarge();
1156
+ }
1157
+ if (!response.body)
1158
+ return new Uint8Array(0);
1159
+ const reader = response.body.getReader();
1160
+ const chunks = [];
1161
+ let size = 0;
1162
+ for (;;) {
1163
+ const { done, value } = await reader.read();
1164
+ if (done)
1165
+ break;
1166
+ size += value.byteLength;
1167
+ if (size > limit) {
1168
+ void reader.cancel().catch(() => undefined);
1169
+ throw tooLarge();
1170
+ }
1171
+ chunks.push(value);
1172
+ }
1173
+ const out = new Uint8Array(size);
1174
+ let at = 0;
1175
+ for (const chunk of chunks) {
1176
+ out.set(chunk, at);
1177
+ at += chunk.byteLength;
1178
+ }
1179
+ return out;
1180
+ }
1181
+ /** The `rpc.response` event is emitted identically from `#exchange` and the SDK bridge. */
1182
+ #emitResponse(endpoint, method, started, byteLength) {
1183
+ this.#events.emit('rpc.response', {
1184
+ transportId: this.id,
1185
+ endpointId: endpoint.id,
1186
+ method,
1187
+ latencyMs: this.#clock.now() - started,
1188
+ bytes: byteLength,
1189
+ });
1190
+ }
1191
+ #throwForStatus(endpoint, response) {
1192
+ const context = this.#context(endpoint);
1193
+ const status = response.status;
1194
+ if (status === 408) {
1195
+ // Retryable in every mode: a request timeout is an endpoint-local failure that
1196
+ // may have reached the server before it gave up.
1197
+ throw this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', 'endpoint request timed out (HTTP 408)', { context }));
1198
+ }
1199
+ if (status === 429) {
1200
+ // Rate-limiting means the request was never actually processed — excluded from
1201
+ // ambiguity, like 401/403 below.
1202
+ const retryAfterMs = (0, backoff_1.parseRetryAfter)(response.headers.get('retry-after'), this.#clock.now());
1203
+ const delayMs = this.#applyRateLimit(endpoint, retryAfterMs);
1204
+ throw new error_1.ProviderError('RATE_LIMITED', 'endpoint rate limited (HTTP 429)', {
1205
+ context,
1206
+ details: { retryAfterMs: delayMs },
1207
+ });
1208
+ }
1209
+ if (status === 401 || status === 403) {
1210
+ // Credentials were rejected before any processing — excluded from ambiguity.
1211
+ throw new error_1.ProviderError('PROVIDER_MISCONFIGURED', `endpoint rejected the credentials (HTTP ${status})`, {
1212
+ context,
1213
+ details: { httpStatus: status },
1214
+ retryable: true,
1215
+ });
1216
+ }
1217
+ if (status >= 500) {
1218
+ // A 5xx is inherently ambiguous — the server may have processed the request.
1219
+ throw this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `endpoint error (HTTP ${status})`, {
1220
+ context,
1221
+ }));
1222
+ }
1223
+ }
1224
+ #unwrapRpc(endpoint, method, id, json) {
1225
+ const context = this.#context(endpoint);
1226
+ // Step 1: the body must be an object. The server did respond, so this attempt may
1227
+ // have been delivered even though its answer was unusable.
1228
+ if (json === null || typeof json !== 'object' || Array.isArray(json)) {
1229
+ throw this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `malformed JSON-RPC response to ${method}`, { context }));
1230
+ }
1231
+ const body = json;
1232
+ const hasResult = 'result' in body;
1233
+ const hasError = 'error' in body && body.error !== undefined && body.error !== null;
1234
+ const errorCode = hasError && typeof body.error?.code === 'number' ? body.error.code : undefined;
1235
+ // Step 2: id must match, except a null id is only valid alongside a parse/invalid-request error.
1236
+ const idIsNullForProtocolError = body.id === null && (errorCode === -32700 || errorCode === -32600);
1237
+ const idMatches = idIsNullForProtocolError || String(body.id) === String(id);
1238
+ // Step 3: exactly one of result/error. Any of these three problems is an endpoint-local
1239
+ // failure, not a definitive protocol answer: retry and fail over. Same reasoning
1240
+ // as step 1 — the server responded, so this attempt may have been delivered.
1241
+ if (!idMatches || (hasResult && hasError) || (!hasResult && !hasError)) {
1242
+ throw this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `invalid JSON-RPC envelope for ${method}`, { context }));
1243
+ }
1244
+ // Step 4: only now classify the error or return the result.
1245
+ if (hasError) {
1246
+ const err = body.error;
1247
+ const code = typeof err.code === 'number' ? err.code : undefined;
1248
+ const message = endpoint.scrub(typeof err.message === 'string' ? err.message : 'unknown error', 300);
1249
+ const rawData = err.data;
1250
+ const data = rawData === undefined
1251
+ ? undefined
1252
+ : endpoint.scrub(typeof rawData === 'string' ? rawData : stringifyData(rawData), 512);
1253
+ const details = {
1254
+ rpcCode: code,
1255
+ rpcMessage: message,
1256
+ ...(data !== undefined ? { rpcData: data } : {}),
1257
+ };
1258
+ if (code === -32005 ||
1259
+ code === 429 ||
1260
+ /rate limit|too many requests|request limit/i.test(message)) {
1261
+ // No Retry-After header at the JSON-RPC level, so back off. A JSON-RPC-level
1262
+ // rate limit carries the same "never processed" meaning as an HTTP 429 — excluded
1263
+ // from ambiguity for the same reason.
1264
+ const retryAfterMs = this.#applyRateLimit(endpoint, undefined);
1265
+ throw new error_1.ProviderError('RATE_LIMITED', `endpoint rate limited ${method}`, {
1266
+ context,
1267
+ details: { ...details, retryAfterMs },
1268
+ });
1269
+ }
1270
+ // A definitive JSON-RPC application error (e.g. "nonce too low") is not itself tagged:
1271
+ // the server told us exactly what happened, so a lone instance of it is never
1272
+ // ambiguous. It still inherits ambiguity from an earlier possibly-delivered attempt
1273
+ // via #withRetry/#fanout's accumulated `mayHaveSent`.
1274
+ throw new error_1.ProviderError('RPC_ERROR', `${method} failed: ${message}`, {
1275
+ context,
1276
+ details,
1277
+ });
1278
+ }
1279
+ return body.result;
1280
+ }
1281
+ // ---- health ----------------------------------------------------------------------
1282
+ /** Same condition under which #ensureIdentity actually probes. */
1283
+ #identityProbed() {
1284
+ return (this.#probes.identity !== undefined && this.#probes.expectedIdentity !== undefined);
1285
+ }
1286
+ /** The highest known height among endpoints that may feed health heights — with an
1287
+ * identity probe configured, only endpoints whose identity is 'ok', since an endpoint
1288
+ * that serves another network reports that network's height. */
1289
+ #verifiedMaxHeight() {
1290
+ const probed = this.#identityProbed();
1291
+ let max;
1292
+ for (const e of this.#endpoints) {
1293
+ if (e.height === undefined || e.identity === 'mismatch')
1294
+ continue;
1295
+ if (probed && e.identity !== 'ok')
1296
+ continue;
1297
+ if (max === undefined || e.height > max)
1298
+ max = e.height;
1299
+ }
1300
+ return max;
1301
+ }
1302
+ async #ensureIdentity(endpoint, signal) {
1303
+ const probe = this.#probes.identity;
1304
+ const expected = this.#probes.expectedIdentity;
1305
+ if (!probe || expected === undefined || endpoint.identity === 'ok')
1306
+ return;
1307
+ if (endpoint.identity === 'unchecked') {
1308
+ // A prior probe attempt failed (not a confirmed mismatch): back off until the next
1309
+ // health interval instead of re-probing on every attempt.
1310
+ if (endpoint.identityRetryAt !== undefined &&
1311
+ this.#clock.now() < endpoint.identityRetryAt) {
1312
+ throw new error_1.ProviderError('PROVIDER_UNAVAILABLE', `endpoint '${endpoint.id}' identity not yet confirmed`, { context: this.#context(endpoint) });
1313
+ }
1314
+ const shared = endpoint.identityCheck;
1315
+ if (shared) {
1316
+ // A joiner races its own signal against the shared check, the same way
1317
+ // #join does — its caller's abort or its own deadline rejects only this caller, and
1318
+ // the check runs on for its owner. Only TIMEOUT (this joiner's deadline) is wrapped;
1319
+ // a caller's abort reason and the check's own failure propagate as they are.
1320
+ try {
1321
+ await raceAbort(shared, signal);
1322
+ }
1323
+ catch (error) {
1324
+ if (error !== TIMEOUT)
1325
+ throw error;
1326
+ throw this.#identityProbeFailed(endpoint, error, signal);
1327
+ }
1328
+ }
1329
+ else {
1330
+ const check = this.#checkIdentity(endpoint, probe, expected, signal);
1331
+ endpoint.identityCheck = check;
1332
+ // Cleared from outside the check, once settled and only while still current: a probe
1333
+ // that throws synchronously can't leave a settled check behind, and a check replaced
1334
+ // by setProbes can't clear its successor.
1335
+ const clear = () => {
1336
+ if (endpoint.identityCheck === check)
1337
+ endpoint.identityCheck = undefined;
1338
+ };
1339
+ void check.then(clear, clear);
1340
+ await check;
1341
+ }
1342
+ }
1343
+ if (endpoint.identity === 'mismatch') {
1344
+ throw new error_1.ProviderError('PROVIDER_MISCONFIGURED', `endpoint '${endpoint.id}' serves a different network than configured`, {
1345
+ context: this.#context(endpoint),
1346
+ retryable: true,
1347
+ });
1348
+ }
1349
+ }
1350
+ /** The owner's identity probe; joiners share its promise via endpoint.identityCheck. */
1351
+ async #checkIdentity(endpoint, probe, expected, signal) {
1352
+ try {
1353
+ // Races the probe against `signal` so a probe that ignores its own
1354
+ // signal argument can't hang #ensureIdentity forever.
1355
+ const actual = await raceAbort(probe(this.#direct(endpoint, signal)), signal);
1356
+ if (actual === expected) {
1357
+ endpoint.identity = 'ok';
1358
+ endpoint.identityRetryAt = undefined;
1359
+ return;
1360
+ }
1361
+ this.#disable(endpoint, expected, actual);
1362
+ }
1363
+ catch (error) {
1364
+ // Any probe failure (a definitive RPC error, a transport error, a malformed reply) is
1365
+ // an endpoint-local failure, not a confirmed mismatch: identity stays 'unchecked' and
1366
+ // the endpoint fails over.
1367
+ // A caller abort is not a probe failure — it must not set the
1368
+ // throttle, or a single-endpoint transport could be locked out entirely.
1369
+ const callerAborted = signal.aborted && signal.reason !== TIMEOUT;
1370
+ if (!callerAborted) {
1371
+ // A rate-limited probe waits out the endpoint's own delay (its Retry-After, or
1372
+ // backoff), not a whole health interval: a 429 never locks a live endpoint out.
1373
+ endpoint.identityRetryAt = rateLimited(error)
1374
+ ? endpoint.notBefore
1375
+ : this.#clock.now() + this.#opts.healthIntervalMs;
1376
+ }
1377
+ const failure = this.#identityProbeFailed(endpoint, error, signal);
1378
+ if (callerAborted)
1379
+ this.#abandonedChecks.add(failure);
1380
+ else if (signal.aborted)
1381
+ this.#timedOutChecks.add(failure);
1382
+ throw failure;
1383
+ }
1384
+ }
1385
+ /**
1386
+ * Records a genuine failed health refresh, at most one per `healthIntervalMs`,
1387
+ * so a caller that refreshes often (`refreshHealth`, `getNetworkStatus`) never turns one
1388
+ * hiccup into three misses and the endpoint's exclusion from a proof quorum's count.
1389
+ */
1390
+ #recordMiss(endpoint) {
1391
+ const now = this.#clock.now();
1392
+ if (endpoint.lastMissAt !== undefined &&
1393
+ now - endpoint.lastMissAt < this.#opts.healthIntervalMs) {
1394
+ return;
1395
+ }
1396
+ endpoint.healthMisses += 1;
1397
+ endpoint.lastMissAt = now;
1398
+ }
1399
+ /**
1400
+ * Whether the endpoint's requests are failing: its circuit breaker is not closed (open
1401
+ * or half-open), or its consecutive request failures reached the breaker's
1402
+ * `failureThreshold`. An endpoint that answers probes but no request must leave a
1403
+ * proof quorum's count as a probe-dead one does. Read only; `#refresh` still never does
1404
+ * breaker bookkeeping.
1405
+ */
1406
+ #failingRequests(endpoint) {
1407
+ return (endpoint.breaker.state !== 'closed' ||
1408
+ endpoint.failures >= this.#opts.failureThreshold);
1409
+ }
1410
+ /** A proven identity mismatch: the endpoint serves a different network and is disabled. */
1411
+ #disable(endpoint, expected, actual) {
1412
+ endpoint.identity = 'mismatch';
1413
+ // A disabled endpoint's height stops counting at once; #best and
1414
+ // #highest are rebuilt from identity-verified endpoints with known heights only,
1415
+ // never below a verified peak, even while a verified endpoint's height is unknown.
1416
+ endpoint.height = undefined;
1417
+ this.#best = this.#verifiedMaxHeight();
1418
+ this.#highest = maxHeight(this.#verifiedPeak, this.#best);
1419
+ this.#events.emit('provider.misconfigured', {
1420
+ transportId: this.id,
1421
+ endpointId: endpoint.id,
1422
+ expected: sanitizeIdentityField(expected),
1423
+ // An endpoint could answer its identity probe with our own key.
1424
+ actual: sanitizeIdentityField(endpoint.scrub(String(actual), 256)),
1425
+ });
1426
+ this.#log.warn('endpoint serves a different network; disabled', {
1427
+ endpointId: endpoint.id,
1428
+ });
1429
+ }
1430
+ /**
1431
+ * With no height probe, the identity probe is a confirmed endpoint's only
1432
+ * health signal, so `#refresh` re-runs it (under the refresh's own deadline), or a dead
1433
+ * endpoint would never miss and a proof quorum would count it for good. A different
1434
+ * answer disables the endpoint as a first check does; a failure is a health miss only,
1435
+ * never an identity throttle, so a confirmed endpoint stays in service for requests.
1436
+ */
1437
+ async #reconfirmIdentity(endpoint, signal) {
1438
+ const probe = this.#probes.identity;
1439
+ const expected = this.#probes.expectedIdentity;
1440
+ if (!probe || expected === undefined)
1441
+ return;
1442
+ const actual = await raceAbort(probe(this.#direct(endpoint, signal)), signal);
1443
+ if (actual !== expected)
1444
+ this.#disable(endpoint, expected, actual);
1445
+ }
1446
+ /** An identity probe failure as a retryable, sanitized, untagged endpoint-local error. */
1447
+ #identityProbeFailed(endpoint, error, signal) {
1448
+ return new error_1.ProviderError('PROVIDER_UNAVAILABLE', `identity probe failed for endpoint '${endpoint.id}'`, {
1449
+ context: this.#context(endpoint),
1450
+ cause: this.#classify(error, endpoint, signal),
1451
+ });
1452
+ }
1453
+ async #refresh() {
1454
+ this.#refreshDue = false;
1455
+ const probe = this.#probes.height;
1456
+ const identityProbed = this.#identityProbed();
1457
+ const targets = this.#endpoints.filter((e) => e.identity !== 'mismatch');
1458
+ const tokensBefore = targets.map((e) => e.probeTokens);
1459
+ // Only a refresh where at least one endpoint's probe(s) actually succeeded
1460
+ // counts as fresh (see #lastHealthAt below).
1461
+ let anySucceeded = false;
1462
+ // Set when an endpoint was not really attempted (see #abandonedChecks).
1463
+ let anyAbandoned = false;
1464
+ await Promise.all(targets.map(async (endpoint) => {
1465
+ // An endpoint that asked us to wait (a 429's Retry-After, or backoff) is
1466
+ // not probed until then, and keeps its last height and identity meanwhile: neither
1467
+ // a miss nor a success.
1468
+ if (endpoint.notBefore > this.#clock.now())
1469
+ return;
1470
+ // The shared run is never bound to any single caller's signal; each probe only
1471
+ // ever times out against its own deadline.
1472
+ const { signal: deadline, cancel } = this.#deadline(endpoint.timeoutMs ?? this.#opts.timeoutMs);
1473
+ try {
1474
+ if (identityProbed) {
1475
+ // A throttled identity probe is skipped here entirely
1476
+ // rather than re-attempted and left to #ensureIdentity's own internal throttle
1477
+ // check to reject — there's no traffic behind a throttle hit, so nothing should
1478
+ // even look like an attempt.
1479
+ const identityThrottled = endpoint.identityRetryAt !== undefined &&
1480
+ this.#clock.now() < endpoint.identityRetryAt;
1481
+ // With no height probe, a confirmed identity is re-probed as the
1482
+ // endpoint's health signal. A skipped (throttled) one is neither a miss nor a
1483
+ // success.
1484
+ if (!identityThrottled) {
1485
+ if (!probe && endpoint.identity === 'ok')
1486
+ await this.#reconfirmIdentity(endpoint, deadline);
1487
+ else
1488
+ await this.#ensureIdentity(endpoint, deadline);
1489
+ }
1490
+ // Only an endpoint verified 'ok' in this refresh is height-probed
1491
+ // and feeds #best/#highest/lag/anySucceeded. A throttled or still-unchecked one
1492
+ // is cleared and does not count as succeeded.
1493
+ if (endpoint.identity !== 'ok') {
1494
+ endpoint.height = undefined;
1495
+ return;
1496
+ }
1497
+ }
1498
+ if (probe) {
1499
+ endpoint.height = await raceAbort(probe(this.#direct(endpoint, deadline)), deadline);
1500
+ }
1501
+ // Identity and height failures feed one counter, which a refresh whose
1502
+ // probes all succeeded (with no height probe, the identity alone) resets.
1503
+ // Unless the endpoint's requests fail: then its answered probes are a
1504
+ // miss too, so an endpoint that answers probes but no request leaves a proof
1505
+ // quorum's count as a probe-dead one does, while a shorter outage still counts.
1506
+ if (this.#failingRequests(endpoint)) {
1507
+ this.#recordMiss(endpoint);
1508
+ }
1509
+ else {
1510
+ endpoint.healthMisses = 0;
1511
+ endpoint.lastMissAt = undefined;
1512
+ }
1513
+ anySucceeded = true;
1514
+ }
1515
+ catch (error) {
1516
+ // A rate-limited probe learned nothing, so the
1517
+ // endpoint keeps its last good height and identity and records no miss.
1518
+ if (rateLimited(error))
1519
+ return;
1520
+ // A failed identity or height probe clears the stored height
1521
+ // instead of leaving it stale, so the endpoint counts as unknown.
1522
+ endpoint.height = undefined;
1523
+ // Only a failure under this refresh's own deadline is a miss. A
1524
+ // joined identity check that its request's caller aborted, or that the request's
1525
+ // own (possibly caller-shortened) deadline ended while this refresh's had not,
1526
+ // learned nothing here, so it counts as not attempted. The height probe itself
1527
+ // runs only under this refresh's own deadline.
1528
+ const foreign = this.#abandonedChecks.has(error) ||
1529
+ (this.#timedOutChecks.has(error) && !deadline.aborted);
1530
+ if (foreign)
1531
+ anyAbandoned = true;
1532
+ else
1533
+ this.#recordMiss(endpoint);
1534
+ // #refresh never does breaker bookkeeping — no onAttempt, onSuccess,
1535
+ // onFailure or onAbandon. A probe failure here only affects height/identity state,
1536
+ // never endpoint.breaker or endpoint.failures. The breaker tracks request
1537
+ // traffic, so after an outage only a request closes it: reads may wait out
1538
+ // `openMs`.
1539
+ }
1540
+ finally {
1541
+ cancel();
1542
+ }
1543
+ }));
1544
+ // Rises only with identity-verified heights (and decays only through #decayPeak).
1545
+ const best = this.#verifiedMaxHeight();
1546
+ if (best !== undefined) {
1547
+ this.#best = best;
1548
+ this.#highest = maxHeight(this.#highest, best);
1549
+ // With an identity probe, `best` came from identity-verified endpoints only.
1550
+ if (this.#identityProbed())
1551
+ this.#verifiedPeak = maxHeight(this.#verifiedPeak, best);
1552
+ this.#decayPeak(best);
1553
+ }
1554
+ for (const status of this.status()) {
1555
+ // 'half-open' has no matching value in the provider.health event payload; 'unknown'
1556
+ // is likewise not worth reporting.
1557
+ if (status.state === 'unknown' || status.state === 'half-open')
1558
+ continue;
1559
+ this.#events.emit('provider.health', {
1560
+ transportId: this.id,
1561
+ endpointId: status.id,
1562
+ state: status.state,
1563
+ ...(status.height !== undefined ? { height: status.height.toString() } : {}),
1564
+ ...(status.lag !== undefined ? { lag: status.lag.toString() } : {}),
1565
+ });
1566
+ }
1567
+ // Only a completed refresh counts as fresh; #healthRun's `finally` clears the
1568
+ // in-flight marker regardless, but the staleness clock only advances here.
1569
+ // If every endpoint's probe(s) failed, #lastHealthAt is left unset (rather
1570
+ // than stamped) so the next ensureFreshHealth call probes again instead of trusting a
1571
+ // fully-failed refresh as fresh for a whole healthIntervalMs.
1572
+ // But an immediate re-probe on every read during an outage is its own
1573
+ // storm, so a fully-failed refresh instead sets a short backoff.
1574
+ // A refresh with an endpoint left unattempted (a joined identity check its
1575
+ // caller aborted) never arms the backoff, so the next read re-probes it.
1576
+ // The backoff is floored at the time each endpoint's bucket needs to refill
1577
+ // the tokens this refresh's probes took from it plus one, so repeated failing probes
1578
+ // (which take their tokens with priority) never take every token of a slow bucket. The
1579
+ // extra token keeps a window where only requests may take one: without it, a refresh
1580
+ // that starts as a token refills ties with the requests waiting for it.
1581
+ if (anySucceeded) {
1582
+ this.#lastHealthAt = this.#clock.now();
1583
+ }
1584
+ else if (!anyAbandoned) {
1585
+ let refillMs = 0;
1586
+ targets.forEach((e, i) => {
1587
+ const taken = e.probeTokens - (tokensBefore[i] ?? e.probeTokens);
1588
+ if (e.bucket && taken > 0)
1589
+ refillMs = Math.max(refillMs, e.bucket.refillMs(taken + 1));
1590
+ });
1591
+ this.#nextRefreshAt =
1592
+ this.#clock.now() +
1593
+ Math.max(Math.min(this.#opts.healthIntervalMs, 1_000), refillMs);
1594
+ }
1595
+ }
1596
+ /**
1597
+ * Lets a forged peak decay. A high-water mark that only rises lets one probe that saw a
1598
+ * forged far-future head make every view stale until restart (a TON probe, for one,
1599
+ * can report any masterchain height within 32 bits). A peak that no verified endpoint
1600
+ * comes within `maxLagBlocks` of, for three completed refreshes in a row
1601
+ * (`HEALTH_MISS_LIMIT`, as for a proof quorum's misses), falls back to the refresh's
1602
+ * verified best. Liveness only: a stale view decides nothing, and proofs never read
1603
+ * this mark.
1604
+ */
1605
+ #decayPeak(best) {
1606
+ const peak = this.#highest;
1607
+ if (peak === undefined || best + BigInt(this.#opts.maxLagBlocks) >= peak) {
1608
+ this.#peakMisses = 0;
1609
+ return;
1610
+ }
1611
+ this.#peakMisses += 1;
1612
+ if (this.#peakMisses < HEALTH_MISS_LIMIT)
1613
+ return;
1614
+ this.#peakMisses = 0;
1615
+ this.#highest = best;
1616
+ this.#verifiedPeak = best;
1617
+ }
1618
+ // ---- errors ----------------------------------------------------------------------
1619
+ #context(endpoint) {
1620
+ return { transportId: this.id, endpointId: endpoint.id };
1621
+ }
1622
+ #classify(error, endpoint, signal) {
1623
+ const context = this.#context(endpoint);
1624
+ if ((0, error_1.isCryptoAioError)(error))
1625
+ return error.context.endpointId ? error : (0, error_1.withContext)(error, context);
1626
+ if (signal.aborted && signal.reason === TIMEOUT) {
1627
+ return this.#markSent(new error_1.TimeoutError('TIMEOUT', `request to endpoint '${endpoint.id}' timed out`, {
1628
+ context,
1629
+ }));
1630
+ }
1631
+ const inner = error instanceof Error && error.cause instanceof Error ? error.cause : error;
1632
+ const clean = new Error(endpoint.scrub(inner instanceof Error ? inner.message : String(inner), 1_000));
1633
+ clean.name = inner instanceof Error ? inner.name : 'Error';
1634
+ clean.stack = `${clean.name}: ${clean.message}`;
1635
+ // A raw (non-CryptoAioError) throw from fetch is a network error after the call.
1636
+ return this.#markSent(new error_1.ProviderError('PROVIDER_UNAVAILABLE', `request to endpoint '${endpoint.id}' failed: ${clean.message}`, { context, cause: clean }));
1637
+ }
1638
+ /** Tags an error as "the attempt may have reached the network", for ambiguity tracking. */
1639
+ #markSent(error) {
1640
+ this.#maybeDelivered.add(error);
1641
+ return error;
1642
+ }
1643
+ }
1644
+ exports.HttpTransport = HttpTransport;
1645
+ /**
1646
+ * JSON-RPC error data as text; a revived `bigint` is written as a decimal string, and
1647
+ * a top-level one as its bare digits, as a plain number would be.
1648
+ */
1649
+ function stringifyData(data) {
1650
+ if (typeof data === 'bigint')
1651
+ return data.toString();
1652
+ return JSON.stringify(data, (_key, value) => typeof value === 'bigint' ? value.toString() : value);
1653
+ }
1654
+ /**
1655
+ * Whether a quorum read is held to the proof rules (sizing on every endpoint not proven
1656
+ * mismatched, and a definitive error decides only when the whole quorum returns it
1657
+ * alike): every
1658
+ * `quorum: 'proof'` read, whatever its purpose (EVM token metadata is read under 'read'
1659
+ * and cached), and any quorum read under a monitor or proof purpose.
1660
+ */
1661
+ function isProofQuorum(purpose, options) {
1662
+ return options.quorum === 'proof' || purpose === 'monitor' || purpose === 'proof';
1663
+ }
1664
+ /**
1665
+ * Whether two definitive errors are the same answer: the same code, the
1666
+ * same HTTP status (`details.status`) and the same JSON-RPC code (`details.rpcCode`), each
1667
+ * possibly absent from both, and for an implementation-defined JSON-RPC code, the same
1668
+ * message text (`details.rpcMessage`). Anything else is a different answer. Only this
1669
+ * boolean leaves here: no message text reaches an error, event or log.
1670
+ */
1671
+ function sameRefusal(a, b) {
1672
+ const left = a.details ?? {};
1673
+ const right = b.details ?? {};
1674
+ if (a.code !== b.code ||
1675
+ left.status !== right.status ||
1676
+ left.rpcCode !== right.rpcCode) {
1677
+ return false;
1678
+ }
1679
+ return !implementationDefined(left.rpcCode) || left.rpcMessage === right.rpcMessage;
1680
+ }
1681
+ /**
1682
+ * A JSON-RPC error code whose meaning each server defines (-32000 to -32099, and
1683
+ * -32603, "internal error"), so one code can stand for unlike errors, such as a revert and
1684
+ * a missing header.
1685
+ */
1686
+ function implementationDefined(code) {
1687
+ return (typeof code === 'number' && ((code <= -32000 && code >= -32099) || code === -32603));
1688
+ }
1689
+ /** The highest height at least two endpoints have reached (the second-highest). */
1690
+ function corroboratedHeight(endpoints) {
1691
+ const heights = endpoints
1692
+ .map((e) => e.height)
1693
+ .filter((h) => h !== undefined)
1694
+ .sort((a, b) => (a > b ? -1 : a < b ? 1 : 0));
1695
+ return heights[1];
1696
+ }
1697
+ /** The higher of two optional heights. */
1698
+ function maxHeight(a, b) {
1699
+ if (a === undefined)
1700
+ return b;
1701
+ return b !== undefined && b > a ? b : a;
1702
+ }
1703
+ //# sourceMappingURL=http-transport.js.map