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