@geeqdev/geeq-sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/.idea/geeq_sdk.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/.nvmrc +1 -0
  5. package/CONTRIBUTING.md +20 -0
  6. package/dist/src/address/record-type.d.ts +10 -0
  7. package/dist/src/address/record-type.js +59 -0
  8. package/dist/src/address/record-type.js.map +1 -0
  9. package/dist/src/address/wallet-address.d.ts +34 -0
  10. package/dist/src/address/wallet-address.js +90 -0
  11. package/dist/src/address/wallet-address.js.map +1 -0
  12. package/dist/src/amount.d.ts +43 -0
  13. package/dist/src/amount.js +105 -0
  14. package/dist/src/amount.js.map +1 -0
  15. package/dist/src/assert.d.ts +12 -0
  16. package/dist/src/assert.js +24 -0
  17. package/dist/src/assert.js.map +1 -0
  18. package/dist/src/capnp/adminNetTx.capnp.d.ts +75 -0
  19. package/dist/src/capnp/adminNetTx.capnp.js +66 -0
  20. package/dist/src/capnp/adminNetTx.capnp.js.map +1 -0
  21. package/dist/src/capnp/appLayBlock.capnp.d.ts +26 -0
  22. package/dist/src/capnp/appLayBlock.capnp.js +24 -0
  23. package/dist/src/capnp/appLayBlock.capnp.js.map +1 -0
  24. package/dist/src/capnp/appLayLedger.capnp.d.ts +34 -0
  25. package/dist/src/capnp/appLayLedger.capnp.js +32 -0
  26. package/dist/src/capnp/appLayLedger.capnp.js.map +1 -0
  27. package/dist/src/capnp/appLayRec.capnp.d.ts +129 -0
  28. package/dist/src/capnp/appLayRec.capnp.js +156 -0
  29. package/dist/src/capnp/appLayRec.capnp.js.map +1 -0
  30. package/dist/src/capnp/chainStateView.capnp.d.ts +50 -0
  31. package/dist/src/capnp/chainStateView.capnp.js +48 -0
  32. package/dist/src/capnp/chainStateView.capnp.js.map +1 -0
  33. package/dist/src/capnp/coinAcctRec.capnp.d.ts +178 -0
  34. package/dist/src/capnp/coinAcctRec.capnp.js +189 -0
  35. package/dist/src/capnp/coinAcctRec.capnp.js.map +1 -0
  36. package/dist/src/capnp/genesisBlock.capnp.d.ts +61 -0
  37. package/dist/src/capnp/genesisBlock.capnp.js +55 -0
  38. package/dist/src/capnp/genesisBlock.capnp.js.map +1 -0
  39. package/dist/src/capnp/hubContentStmt.capnp.d.ts +63 -0
  40. package/dist/src/capnp/hubContentStmt.capnp.js +57 -0
  41. package/dist/src/capnp/hubContentStmt.capnp.js.map +1 -0
  42. package/dist/src/capnp/misc.capnp.d.ts +20 -0
  43. package/dist/src/capnp/misc.capnp.js +18 -0
  44. package/dist/src/capnp/misc.capnp.js.map +1 -0
  45. package/dist/src/capnp/netActorRec.capnp.d.ts +65 -0
  46. package/dist/src/capnp/netActorRec.capnp.js +65 -0
  47. package/dist/src/capnp/netActorRec.capnp.js.map +1 -0
  48. package/dist/src/capnp/nodeContentStmt.capnp.d.ts +67 -0
  49. package/dist/src/capnp/nodeContentStmt.capnp.js +61 -0
  50. package/dist/src/capnp/nodeContentStmt.capnp.js.map +1 -0
  51. package/dist/src/capnp/nodePayloadList.capnp.d.ts +28 -0
  52. package/dist/src/capnp/nodePayloadList.capnp.js +26 -0
  53. package/dist/src/capnp/nodePayloadList.capnp.js.map +1 -0
  54. package/dist/src/capnp/nodeTxList.capnp.d.ts +36 -0
  55. package/dist/src/capnp/nodeTxList.capnp.js +34 -0
  56. package/dist/src/capnp/nodeTxList.capnp.js.map +1 -0
  57. package/dist/src/capnp/paramAcctRec.capnp.d.ts +338 -0
  58. package/dist/src/capnp/paramAcctRec.capnp.js +669 -0
  59. package/dist/src/capnp/paramAcctRec.capnp.js.map +1 -0
  60. package/dist/src/capnp/payload.capnp.d.ts +237 -0
  61. package/dist/src/capnp/payload.capnp.js +260 -0
  62. package/dist/src/capnp/payload.capnp.js.map +1 -0
  63. package/dist/src/capnp/proxyAcctRec.capnp.d.ts +223 -0
  64. package/dist/src/capnp/proxyAcctRec.capnp.js +255 -0
  65. package/dist/src/capnp/proxyAcctRec.capnp.js.map +1 -0
  66. package/dist/src/capnp/relayContentStmt.capnp.d.ts +82 -0
  67. package/dist/src/capnp/relayContentStmt.capnp.js +85 -0
  68. package/dist/src/capnp/relayContentStmt.capnp.js.map +1 -0
  69. package/dist/src/capnp/sysAcctRec.capnp.d.ts +48 -0
  70. package/dist/src/capnp/sysAcctRec.capnp.js +46 -0
  71. package/dist/src/capnp/sysAcctRec.capnp.js.map +1 -0
  72. package/dist/src/capnp/userAppTx.capnp.d.ts +230 -0
  73. package/dist/src/capnp/userAppTx.capnp.js +270 -0
  74. package/dist/src/capnp/userAppTx.capnp.js.map +1 -0
  75. package/dist/src/capnp/userValTx.capnp.d.ts +1575 -0
  76. package/dist/src/capnp/userValTx.capnp.js +2004 -0
  77. package/dist/src/capnp/userValTx.capnp.js.map +1 -0
  78. package/dist/src/capnp/valLayBlock.capnp.d.ts +88 -0
  79. package/dist/src/capnp/valLayBlock.capnp.js +86 -0
  80. package/dist/src/capnp/valLayBlock.capnp.js.map +1 -0
  81. package/dist/src/capnp/valLayLedger.capnp.d.ts +85 -0
  82. package/dist/src/capnp/valLayLedger.capnp.js +75 -0
  83. package/dist/src/capnp/valLayLedger.capnp.js.map +1 -0
  84. package/dist/src/chain/geeq-chain.d.ts +474 -0
  85. package/dist/src/chain/geeq-chain.js +721 -0
  86. package/dist/src/chain/geeq-chain.js.map +1 -0
  87. package/dist/src/chain/geeq-node.d.ts +57 -0
  88. package/dist/src/chain/geeq-node.js +62 -0
  89. package/dist/src/chain/geeq-node.js.map +1 -0
  90. package/dist/src/chain/nonce-guard.d.ts +42 -0
  91. package/dist/src/chain/nonce-guard.js +68 -0
  92. package/dist/src/chain/nonce-guard.js.map +1 -0
  93. package/dist/src/chain/select.d.ts +40 -0
  94. package/dist/src/chain/select.js +114 -0
  95. package/dist/src/chain/select.js.map +1 -0
  96. package/dist/src/crypto/account-id.d.ts +35 -0
  97. package/dist/src/crypto/account-id.js +58 -0
  98. package/dist/src/crypto/account-id.js.map +1 -0
  99. package/dist/src/crypto/keypair.d.ts +24 -0
  100. package/dist/src/crypto/keypair.js +158 -0
  101. package/dist/src/crypto/keypair.js.map +1 -0
  102. package/dist/src/crypto/mnemonic.d.ts +34 -0
  103. package/dist/src/crypto/mnemonic.js +57 -0
  104. package/dist/src/crypto/mnemonic.js.map +1 -0
  105. package/dist/src/encoding/base64.d.ts +8 -0
  106. package/dist/src/encoding/base64.js +53 -0
  107. package/dist/src/encoding/base64.js.map +1 -0
  108. package/dist/src/encoding/hex.d.ts +2 -0
  109. package/dist/src/encoding/hex.js +35 -0
  110. package/dist/src/encoding/hex.js.map +1 -0
  111. package/dist/src/errors.d.ts +83 -0
  112. package/dist/src/errors.js +49 -0
  113. package/dist/src/errors.js.map +1 -0
  114. package/dist/src/index.d.ts +59 -0
  115. package/dist/src/index.js +53 -0
  116. package/dist/src/index.js.map +1 -0
  117. package/dist/src/ledger/coin-accounts.d.ts +42 -0
  118. package/dist/src/ledger/coin-accounts.js +37 -0
  119. package/dist/src/ledger/coin-accounts.js.map +1 -0
  120. package/dist/src/ledger/decode.d.ts +18 -0
  121. package/dist/src/ledger/decode.js +113 -0
  122. package/dist/src/ledger/decode.js.map +1 -0
  123. package/dist/src/ledger/geeq-ledger.d.ts +99 -0
  124. package/dist/src/ledger/geeq-ledger.js +125 -0
  125. package/dist/src/ledger/geeq-ledger.js.map +1 -0
  126. package/dist/src/ledger/net-actors.d.ts +65 -0
  127. package/dist/src/ledger/net-actors.js +53 -0
  128. package/dist/src/ledger/net-actors.js.map +1 -0
  129. package/dist/src/ledger/nft-records.d.ts +13 -0
  130. package/dist/src/ledger/nft-records.js +41 -0
  131. package/dist/src/ledger/nft-records.js.map +1 -0
  132. package/dist/src/ledger/params.d.ts +34 -0
  133. package/dist/src/ledger/params.js +71 -0
  134. package/dist/src/ledger/params.js.map +1 -0
  135. package/dist/src/ledger/proxy-accounts.d.ts +26 -0
  136. package/dist/src/ledger/proxy-accounts.js +27 -0
  137. package/dist/src/ledger/proxy-accounts.js.map +1 -0
  138. package/dist/src/net/discovery.d.ts +62 -0
  139. package/dist/src/net/discovery.js +136 -0
  140. package/dist/src/net/discovery.js.map +1 -0
  141. package/dist/src/net/explorer.d.ts +57 -0
  142. package/dist/src/net/explorer.js +64 -0
  143. package/dist/src/net/explorer.js.map +1 -0
  144. package/dist/src/net/read-ledger.d.ts +7 -0
  145. package/dist/src/net/read-ledger.js +39 -0
  146. package/dist/src/net/read-ledger.js.map +1 -0
  147. package/dist/src/net/routes.d.ts +49 -0
  148. package/dist/src/net/routes.js +70 -0
  149. package/dist/src/net/routes.js.map +1 -0
  150. package/dist/src/net/transport.d.ts +55 -0
  151. package/dist/src/net/transport.js +168 -0
  152. package/dist/src/net/transport.js.map +1 -0
  153. package/dist/src/net/websocket.d.ts +49 -0
  154. package/dist/src/net/websocket.js +54 -0
  155. package/dist/src/net/websocket.js.map +1 -0
  156. package/dist/src/runtime/bindings.d.ts +9 -0
  157. package/dist/src/runtime/bindings.js +27 -0
  158. package/dist/src/runtime/bindings.js.map +1 -0
  159. package/dist/src/runtime/init.d.ts +13 -0
  160. package/dist/src/runtime/init.js +40 -0
  161. package/dist/src/runtime/init.js.map +1 -0
  162. package/dist/src/runtime/wasm-loader.browser.d.ts +14 -0
  163. package/dist/src/runtime/wasm-loader.browser.js +20 -0
  164. package/dist/src/runtime/wasm-loader.browser.js.map +1 -0
  165. package/dist/src/runtime/wasm-loader.node.d.ts +7 -0
  166. package/dist/src/runtime/wasm-loader.node.js +14 -0
  167. package/dist/src/runtime/wasm-loader.node.js.map +1 -0
  168. package/dist/src/socket/geeq-socket.d.ts +232 -0
  169. package/dist/src/socket/geeq-socket.js +634 -0
  170. package/dist/src/socket/geeq-socket.js.map +1 -0
  171. package/dist/src/socket/tracked-tx.d.ts +148 -0
  172. package/dist/src/socket/tracked-tx.js +212 -0
  173. package/dist/src/socket/tracked-tx.js.map +1 -0
  174. package/dist/src/socket/tx-status.d.ts +69 -0
  175. package/dist/src/socket/tx-status.js +72 -0
  176. package/dist/src/socket/tx-status.js.map +1 -0
  177. package/dist/src/tx/attestation.d.ts +60 -0
  178. package/dist/src/tx/attestation.js +163 -0
  179. package/dist/src/tx/attestation.js.map +1 -0
  180. package/dist/src/tx/content.d.ts +27 -0
  181. package/dist/src/tx/content.js +62 -0
  182. package/dist/src/tx/content.js.map +1 -0
  183. package/dist/src/tx/encode.d.ts +60 -0
  184. package/dist/src/tx/encode.js +82 -0
  185. package/dist/src/tx/encode.js.map +1 -0
  186. package/dist/src/tx/expiry.d.ts +11 -0
  187. package/dist/src/tx/expiry.js +12 -0
  188. package/dist/src/tx/expiry.js.map +1 -0
  189. package/dist/src/tx/fee.d.ts +16 -0
  190. package/dist/src/tx/fee.js +29 -0
  191. package/dist/src/tx/fee.js.map +1 -0
  192. package/dist/src/tx/metadata.d.ts +39 -0
  193. package/dist/src/tx/metadata.js +66 -0
  194. package/dist/src/tx/metadata.js.map +1 -0
  195. package/dist/src/tx/nft.d.ts +16 -0
  196. package/dist/src/tx/nft.js +98 -0
  197. package/dist/src/tx/nft.js.map +1 -0
  198. package/dist/src/tx/proxy.d.ts +37 -0
  199. package/dist/src/tx/proxy.js +134 -0
  200. package/dist/src/tx/proxy.js.map +1 -0
  201. package/dist/src/tx/transfer.d.ts +31 -0
  202. package/dist/src/tx/transfer.js +123 -0
  203. package/dist/src/tx/transfer.js.map +1 -0
  204. package/dist/src/types.d.ts +448 -0
  205. package/dist/src/types.js +2 -0
  206. package/dist/src/types.js.map +1 -0
  207. package/dist/tests/account-id.test.d.ts +1 -0
  208. package/dist/tests/account-id.test.js +27 -0
  209. package/dist/tests/account-id.test.js.map +1 -0
  210. package/dist/tests/amount.test.d.ts +1 -0
  211. package/dist/tests/amount.test.js +113 -0
  212. package/dist/tests/amount.test.js.map +1 -0
  213. package/dist/tests/attestation.test.d.ts +1 -0
  214. package/dist/tests/attestation.test.js +182 -0
  215. package/dist/tests/attestation.test.js.map +1 -0
  216. package/dist/tests/chain.test.d.ts +1 -0
  217. package/dist/tests/chain.test.js +647 -0
  218. package/dist/tests/chain.test.js.map +1 -0
  219. package/dist/tests/content.test.d.ts +1 -0
  220. package/dist/tests/content.test.js +90 -0
  221. package/dist/tests/content.test.js.map +1 -0
  222. package/dist/tests/documented-api.test.d.ts +1 -0
  223. package/dist/tests/documented-api.test.js +229 -0
  224. package/dist/tests/documented-api.test.js.map +1 -0
  225. package/dist/tests/explorer.test.d.ts +1 -0
  226. package/dist/tests/explorer.test.js +95 -0
  227. package/dist/tests/explorer.test.js.map +1 -0
  228. package/dist/tests/fee.test.d.ts +1 -0
  229. package/dist/tests/fee.test.js +179 -0
  230. package/dist/tests/fee.test.js.map +1 -0
  231. package/dist/tests/fixtures/build-vll.d.ts +71 -0
  232. package/dist/tests/fixtures/build-vll.js +141 -0
  233. package/dist/tests/fixtures/build-vll.js.map +1 -0
  234. package/dist/tests/fixtures/fake-socket.d.ts +44 -0
  235. package/dist/tests/fixtures/fake-socket.js +93 -0
  236. package/dist/tests/fixtures/fake-socket.js.map +1 -0
  237. package/dist/tests/fixtures/fake-transport.d.ts +24 -0
  238. package/dist/tests/fixtures/fake-transport.js +30 -0
  239. package/dist/tests/fixtures/fake-transport.js.map +1 -0
  240. package/dist/tests/keypair.test.d.ts +1 -0
  241. package/dist/tests/keypair.test.js +163 -0
  242. package/dist/tests/keypair.test.js.map +1 -0
  243. package/dist/tests/ledger.test.d.ts +1 -0
  244. package/dist/tests/ledger.test.js +234 -0
  245. package/dist/tests/ledger.test.js.map +1 -0
  246. package/dist/tests/mnemonic.test.d.ts +1 -0
  247. package/dist/tests/mnemonic.test.js +97 -0
  248. package/dist/tests/mnemonic.test.js.map +1 -0
  249. package/dist/tests/nft-transfer.test.d.ts +1 -0
  250. package/dist/tests/nft-transfer.test.js +170 -0
  251. package/dist/tests/nft-transfer.test.js.map +1 -0
  252. package/dist/tests/nft.test.d.ts +1 -0
  253. package/dist/tests/nft.test.js +180 -0
  254. package/dist/tests/nft.test.js.map +1 -0
  255. package/dist/tests/offline.test.d.ts +1 -0
  256. package/dist/tests/offline.test.js +95 -0
  257. package/dist/tests/offline.test.js.map +1 -0
  258. package/dist/tests/proxy.test.d.ts +1 -0
  259. package/dist/tests/proxy.test.js +183 -0
  260. package/dist/tests/proxy.test.js.map +1 -0
  261. package/dist/tests/select.test.d.ts +1 -0
  262. package/dist/tests/select.test.js +200 -0
  263. package/dist/tests/select.test.js.map +1 -0
  264. package/dist/tests/socket.test.d.ts +1 -0
  265. package/dist/tests/socket.test.js +607 -0
  266. package/dist/tests/socket.test.js.map +1 -0
  267. package/dist/tests/transfer.test.d.ts +1 -0
  268. package/dist/tests/transfer.test.js +225 -0
  269. package/dist/tests/transfer.test.js.map +1 -0
  270. package/dist/tests/wallet-address.test.d.ts +1 -0
  271. package/dist/tests/wallet-address.test.js +101 -0
  272. package/dist/tests/wallet-address.test.js.map +1 -0
  273. package/dist/tests/wasm-loader.jest.d.ts +13 -0
  274. package/dist/tests/wasm-loader.jest.js +15 -0
  275. package/dist/tests/wasm-loader.jest.js.map +1 -0
  276. package/example/.env.example +4 -0
  277. package/example/README.md +304 -0
  278. package/example/USAGE.md +74 -0
  279. package/example/index.html +12 -0
  280. package/example/package-lock.json +1557 -0
  281. package/example/package.json +25 -0
  282. package/example/scripts/browser-check.mjs +1421 -0
  283. package/example/scripts/match-derivation.mjs +65 -0
  284. package/example/src/App.vue +138 -0
  285. package/example/src/components/AuthorityPanel.vue +277 -0
  286. package/example/src/components/ChainPanel.vue +267 -0
  287. package/example/src/components/ChainState.vue +59 -0
  288. package/example/src/components/CopyButton.vue +25 -0
  289. package/example/src/components/CreateAssetPanel.vue +313 -0
  290. package/example/src/components/DigitalAssetsPanel.vue +68 -0
  291. package/example/src/components/EvidencePanel.vue +396 -0
  292. package/example/src/components/ExplorerPanel.vue +119 -0
  293. package/example/src/components/KeypairPanel.vue +182 -0
  294. package/example/src/components/PhrasePanel.vue +105 -0
  295. package/example/src/components/SocketWarning.vue +18 -0
  296. package/example/src/components/StepGate.vue +24 -0
  297. package/example/src/components/TransferPanel.vue +416 -0
  298. package/example/src/components/TxDeadline.vue +19 -0
  299. package/example/src/components/TxFee.vue +31 -0
  300. package/example/src/components/TxStatus.vue +105 -0
  301. package/example/src/composables/useSocketConnection.ts +80 -0
  302. package/example/src/env.d.ts +12 -0
  303. package/example/src/main.ts +5 -0
  304. package/example/src/pennies.ts +7 -0
  305. package/example/src/style.css +149 -0
  306. package/example/tsconfig.json +19 -0
  307. package/example/vite.config.ts +26 -0
  308. package/jest.config.js +18 -0
  309. package/package.json +2 -6
  310. package/scripts/copy-wasm.mjs +21 -0
  311. package/scripts/smoke.mjs +382 -0
  312. package/src/address/record-type.ts +77 -0
  313. package/src/address/wallet-address.ts +105 -0
  314. package/src/amount.ts +143 -0
  315. package/src/assert.ts +30 -0
  316. package/src/capnp/README.md +15 -0
  317. package/src/capnp/adminNetTx.capnp.ts +66 -0
  318. package/src/capnp/appLayBlock.capnp.ts +26 -0
  319. package/src/capnp/appLayLedger.capnp.ts +35 -0
  320. package/src/capnp/appLayRec.capnp.ts +159 -0
  321. package/src/capnp/chainStateView.capnp.ts +49 -0
  322. package/src/capnp/coinAcctRec.capnp.ts +193 -0
  323. package/src/capnp/genesisBlock.capnp.ts +56 -0
  324. package/src/capnp/hubContentStmt.capnp.ts +59 -0
  325. package/src/capnp/misc.capnp.ts +19 -0
  326. package/src/capnp/netActorRec.capnp.ts +64 -0
  327. package/src/capnp/nodeContentStmt.capnp.ts +62 -0
  328. package/src/capnp/nodePayloadList.capnp.ts +28 -0
  329. package/src/capnp/nodeTxList.capnp.ts +37 -0
  330. package/src/capnp/paramAcctRec.capnp.ts +669 -0
  331. package/src/capnp/payload.capnp.ts +261 -0
  332. package/src/capnp/proxyAcctRec.capnp.ts +259 -0
  333. package/src/capnp/relayContentStmt.capnp.ts +85 -0
  334. package/src/capnp/sysAcctRec.capnp.ts +47 -0
  335. package/src/capnp/userAppTx.capnp.ts +269 -0
  336. package/src/capnp/userValTx.capnp.ts +1999 -0
  337. package/src/capnp/valLayBlock.capnp.ts +93 -0
  338. package/src/capnp/valLayLedger.capnp.ts +80 -0
  339. package/src/chain/geeq-chain.ts +1094 -0
  340. package/src/chain/geeq-node.ts +105 -0
  341. package/src/chain/nonce-guard.ts +73 -0
  342. package/src/chain/select.ts +149 -0
  343. package/src/crypto/account-id.ts +83 -0
  344. package/src/crypto/keypair.ts +221 -0
  345. package/src/crypto/mnemonic.ts +69 -0
  346. package/src/encoding/base64.ts +58 -0
  347. package/src/encoding/hex.ts +42 -0
  348. package/src/errors.ts +122 -0
  349. package/src/index.ts +195 -0
  350. package/src/ledger/coin-accounts.ts +73 -0
  351. package/src/ledger/decode.ts +147 -0
  352. package/src/ledger/geeq-ledger.ts +184 -0
  353. package/src/ledger/net-actors.ts +129 -0
  354. package/src/ledger/nft-records.ts +49 -0
  355. package/src/ledger/params.ts +82 -0
  356. package/src/ledger/proxy-accounts.ts +51 -0
  357. package/src/net/discovery.ts +231 -0
  358. package/src/net/explorer.ts +123 -0
  359. package/src/net/read-ledger.ts +58 -0
  360. package/src/net/routes.ts +79 -0
  361. package/src/net/transport.ts +221 -0
  362. package/src/net/websocket.ts +94 -0
  363. package/src/runtime/bindings.ts +38 -0
  364. package/src/runtime/init.ts +49 -0
  365. package/src/runtime/wasm-loader.browser.ts +22 -0
  366. package/src/runtime/wasm-loader.node.ts +16 -0
  367. package/src/socket/geeq-socket.ts +827 -0
  368. package/src/socket/tracked-tx.ts +319 -0
  369. package/src/socket/tx-status.ts +131 -0
  370. package/src/tx/attestation.ts +222 -0
  371. package/src/tx/content.ts +82 -0
  372. package/src/tx/encode.ts +107 -0
  373. package/src/tx/expiry.ts +11 -0
  374. package/src/tx/fee.ts +31 -0
  375. package/src/tx/metadata.ts +100 -0
  376. package/src/tx/nft.ts +108 -0
  377. package/src/tx/proxy.ts +156 -0
  378. package/src/tx/transfer.ts +162 -0
  379. package/src/types.ts +503 -0
  380. package/src/wasm/README.md +22 -0
  381. package/src/wasm/browser/geeq_core.d.ts +1224 -0
  382. package/src/wasm/browser/geeq_core.js +6014 -0
  383. package/src/wasm/browser/geeq_core_bg.wasm +0 -0
  384. package/src/wasm/browser/geeq_core_bg.wasm.d.ts +447 -0
  385. package/src/wasm/nodejs/geeq_core.d.ts +753 -0
  386. package/src/wasm/nodejs/geeq_core.js +6016 -0
  387. package/src/wasm/nodejs/geeq_core_bg.wasm +0 -0
  388. package/src/wasm/nodejs/geeq_core_bg.wasm.d.ts +447 -0
  389. package/src/wasm/nodejs/package.json +3 -0
  390. package/tsconfig.build.json +8 -0
  391. package/tsconfig.json +21 -0
  392. package/tsup.config.ts +60 -0
@@ -0,0 +1,1094 @@
1
+ import { assertDuration, assertUint32 } from '../assert'
2
+ import { resolveAccountId } from '../crypto/account-id'
3
+ import type { AccountRef } from '../crypto/account-id'
4
+ import { toBase64 } from '../encoding/base64'
5
+ import {
6
+ accountNotAttestable,
7
+ accountNotFound,
8
+ amountBelowMinimum,
9
+ chainMismatch,
10
+ insufficientFunds,
11
+ invalidArgument,
12
+ nftNotOwned,
13
+ noChain,
14
+ } from '../errors'
15
+ import { decimalsOf, formatGeeq, toPennies } from '../amount'
16
+ import type { GeeqAmount } from '../amount'
17
+ import { decodeWalletAddressSync } from '../address/wallet-address'
18
+ import { createCoinTransferTxSync } from '../tx/transfer'
19
+ import { createNftMintProxyTxSync, nftMintProxiesIn, nftMintProxyId } from '../tx/proxy'
20
+ import { createNftMintTxSync } from '../tx/nft'
21
+ import type {
22
+ AttestationTx,
23
+ CoinTransferTx,
24
+ GeeqChainState,
25
+ GeeqFee,
26
+ GeeqKeypair,
27
+ GeeqNft,
28
+ GeeqNftMintProxies,
29
+ NftMintProxyTx,
30
+ NftMintTx,
31
+ } from '../types'
32
+ import { discover } from '../net/discovery'
33
+ import type { Discovery, GeeqDiscoveredNode } from '../net/discovery'
34
+ import type { GeeqLedger } from '../ledger/geeq-ledger'
35
+ import type { GeeqLedgerNode } from '../ledger/net-actors'
36
+ import { readLedger, readNftRecords } from '../net/read-ledger'
37
+ import { defaultTransport, raceAbort } from '../net/transport'
38
+ import type { GeeqTransport } from '../net/transport'
39
+ import { initGeeq } from '../runtime/init'
40
+ import { createAttestationTxSync } from '../tx/attestation'
41
+ import { ATTEST_HASH_BYTES, attestHashOf } from '../tx/content'
42
+ import type { GeeqContent } from '../tx/content'
43
+ import { GeeqNode } from './geeq-node'
44
+ import type { NodeRequestConfig } from './geeq-node'
45
+ import { NonceGuard } from './nonce-guard'
46
+ import { GEEQ_NODE_PORT, originOf, selectNode, serviceOriginOf } from './select'
47
+
48
+ const DEFAULT_TIMEOUT_MS = 10_000
49
+ const DEFAULT_LEDGER_MAX_AGE_MS = 5_000
50
+ const DEFAULT_RETRIES = 1
51
+
52
+ export interface GeeqChainOptions {
53
+ /**
54
+ * How many pennies make one Geeq: a power of ten, as a bigint.
55
+ *
56
+ * The chain counts only pennies and does not publish this, so the app says — use the
57
+ * divisor its wallet and explorer show amounts with. {@link GeeqChain.toPennies} reads
58
+ * `{ geeq }` figures with it, and {@link GeeqChain.formatGeeq} and the chain's own
59
+ * messages show amounts with it.
60
+ */
61
+ readonly penniesPerGeeq: bigint
62
+ /**
63
+ * The chain's node directory — the simulator URL for your environment — which the SDK
64
+ * asks for an active node. Works for every chain and environment.
65
+ *
66
+ * Supply this or {@link endpoint}, not both. The directory is the only thing that
67
+ * knows which nodes exist and which are running; the ledger then says which actor the
68
+ * chosen one is. Note the directory is a deployment control plane with no
69
+ * authentication — the SDK reads two of its routes and never touches the rest.
70
+ */
71
+ readonly discovery?: string | URL
72
+ /**
73
+ * A specific node, when you already know one.
74
+ *
75
+ * Supply this or {@link discovery}, not both.
76
+ */
77
+ readonly endpoint?: string | URL
78
+ /**
79
+ * Which chain to use, by the name its directory gives it.
80
+ *
81
+ * Only meaningful with {@link discovery} — a name exists nowhere but the directory.
82
+ * Matched case-insensitively.
83
+ */
84
+ readonly chainName?: string
85
+ /**
86
+ * Which chain to use, by protocol number.
87
+ *
88
+ * With {@link discovery} this selects the chain when no {@link chainName} is given,
89
+ * and asserts it when one is. With {@link endpoint} it asserts the node serves the
90
+ * chain you expect, and a mismatch throws `chain-mismatch` on the first ledger read.
91
+ */
92
+ readonly chainNumber?: number
93
+ /** Defaults to a call through `globalThis.fetch`. */
94
+ readonly transport?: GeeqTransport
95
+ /** Per request. Defaults to 10000. */
96
+ readonly timeoutMs?: number
97
+ /** Extra headers on every request — a proxy token, a trace id. */
98
+ readonly headers?: Readonly<Record<string, string>>
99
+ /** How long a read ledger may be reused. Defaults to 5000. 0 disables the cache. */
100
+ readonly ledgerMaxAgeMs?: number
101
+ /** Skip host matching. Required behind a gateway or when using a DNS name. */
102
+ readonly nodeNumber?: number
103
+ /**
104
+ * The port discovered nodes are reached on. Defaults to 2082.
105
+ *
106
+ * Deployment configuration, not node selection — override it only when running
107
+ * against nodes that are not behind the standard TLS front door, such as a local
108
+ * test harness.
109
+ */
110
+ readonly nodePort?: number
111
+ /** Retries on transport failure only, never on a response. Defaults to 1. */
112
+ readonly retries?: number
113
+ readonly signal?: AbortSignal
114
+ }
115
+
116
+ export interface AttestationRequest {
117
+ /** The account that signs. Must be a coin-asset account on this chain. */
118
+ readonly keypair: GeeqKeypair
119
+ /**
120
+ * What to attest. Hashed here with BLAKE3-32; the content never leaves the device.
121
+ *
122
+ * A `File` straight from a file input works, as does a string or raw bytes.
123
+ */
124
+ readonly content?: GeeqContent
125
+ /** Or supply the digest yourself. Exactly one of `content` and `attestHash`. */
126
+ readonly attestHash?: Uint8Array
127
+ /** Public labels, checked against this chain's limits before anything is signed. */
128
+ readonly metadata?: readonly (string | Uint8Array)[]
129
+ /** Override the derived nonce. See {@link NonceGuard} for when that is needed. */
130
+ readonly nonce?: number
131
+ readonly signal?: AbortSignal
132
+ }
133
+
134
+ export interface CoinTransferRequest {
135
+ /** The account paying. Must be a coin-asset account on this chain. */
136
+ readonly from: GeeqKeypair
137
+ /**
138
+ * Who receives it.
139
+ *
140
+ * A base58 wallet address is the usual thing — it is what a person pastes — and the
141
+ * chain it encodes is checked against this one. A raw account id or public key works
142
+ * too, but carries no chain to check.
143
+ */
144
+ readonly to: string | AccountRef
145
+ /**
146
+ * How much. State the unit: `{ geeq: "1.5" }`, read at {@link GeeqChainOptions.penniesPerGeeq},
147
+ * or `{ pennies }` as the chain counts them. May be left out
148
+ * when sending NFTs, which then go with the chain's minimum transfer.
149
+ */
150
+ readonly amount?: GeeqAmount
151
+ /** NFTs to send, by id — see {@link GeeqChain.findNfts}. The sender must hold each one. */
152
+ readonly nfts?: readonly Uint8Array[]
153
+ /** Override the derived nonce. See {@link NonceGuard}. */
154
+ readonly nonce?: number
155
+ readonly signal?: AbortSignal
156
+ }
157
+
158
+ export interface ChainStateRequest {
159
+ readonly account: AccountRef
160
+ readonly maxAgeMs?: number
161
+ readonly signal?: AbortSignal
162
+ }
163
+
164
+ export interface NftMintProxyRequest {
165
+ /**
166
+ * The account paying for the proxy, which also becomes its admin: the only key that
167
+ * can later mint NFTs through it. Must be a coin-asset account on this chain.
168
+ */
169
+ readonly admin: GeeqKeypair
170
+ /**
171
+ * Coin moved onto the proxy, `{ geeq }` or `{ pennies }` like any amount. It pays for the
172
+ * mints made through it, so fund it for more than one.
173
+ */
174
+ readonly amount: GeeqAmount
175
+ /**
176
+ * Which of the admin's proxies this is, from 1. Leave it out to take the next one —
177
+ * one past the highest the ledger holds for this admin, see
178
+ * {@link GeeqChain.findNftMintProxies}. An index already in use is refused.
179
+ */
180
+ readonly index?: number
181
+ /** Override the derived nonce. See {@link NonceGuard}. */
182
+ readonly nonce?: number
183
+ readonly signal?: AbortSignal
184
+ }
185
+
186
+ export interface NftMintProxiesRequest {
187
+ /**
188
+ * The admin whose proxies to find. A keypair will do; so will a bare public key, since
189
+ * listing needs no signature. Not an account id: an index is derived from the key.
190
+ */
191
+ readonly admin: { readonly publicKey: Uint8Array }
192
+ /** How old a cached ledger may be. 0 reads afresh — a new proxy shows only once confirmed. */
193
+ readonly maxAgeMs?: number
194
+ readonly signal?: AbortSignal
195
+ }
196
+
197
+ export interface NftsRequest {
198
+ /** Whose NFTs. A keypair will do, as will an account id or a public key. */
199
+ readonly owner: AccountRef
200
+ /** How old a cached ledger may be. 0 reads afresh — a new NFT shows only once confirmed. */
201
+ readonly maxAgeMs?: number
202
+ readonly signal?: AbortSignal
203
+ }
204
+
205
+ export interface NftMintRequest {
206
+ /** The proxy's admin: the only key that can mint through it. It signs the mint. */
207
+ readonly admin: GeeqKeypair
208
+ /**
209
+ * Which of the admin's proxies mints it, by index — see
210
+ * {@link GeeqChain.findNftMintProxies}. The proxy pays the fee and {@link amount}, and
211
+ * spends its own nonce, not the admin's.
212
+ */
213
+ readonly index: number
214
+ /**
215
+ * What the NFT stands for. Hashed here with BLAKE3-32; the content never leaves the
216
+ * device. A `File` straight from a file input works, as does a string or raw bytes.
217
+ */
218
+ readonly content?: GeeqContent
219
+ /** Or supply the digest yourself. Exactly one of `content` and `attestHash`. */
220
+ readonly attestHash?: Uint8Array
221
+ /** Public labels stored with the NFT, checked against this chain's limits before signing. */
222
+ readonly metadata?: readonly (string | Uint8Array)[]
223
+ /** Who receives the NFT. Defaults to the admin. An address is checked against this chain. */
224
+ readonly to?: string | AccountRef
225
+ /** Coin the proxy sends to the receiver with it. Defaults to the chain's minimum transfer. */
226
+ readonly amount?: GeeqAmount
227
+ /** Override the proxy's derived nonce. See {@link NonceGuard}. */
228
+ readonly nonce?: number
229
+ readonly signal?: AbortSignal
230
+ }
231
+
232
+ /** The process-wide default, installed by {@link GeeqChain.init}. */
233
+ let defaultChain: GeeqChain | null = null
234
+
235
+ /**
236
+ * Refuse a nonsense option before anything is read. Left to the network layer, a negative
237
+ * `retries` sends no request at all and a zero `timeoutMs` times every request out — both
238
+ * reported as an unreachable node, which is the one thing they are not.
239
+ */
240
+ function assertChainOptions(options: GeeqChainOptions): void {
241
+ assertDuration(options.timeoutMs, 'timeoutMs')
242
+ if (options.timeoutMs === 0) {
243
+ throw invalidArgument('timeoutMs must be more than 0, or every request times out at once.')
244
+ }
245
+ assertDuration(options.ledgerMaxAgeMs, 'ledgerMaxAgeMs')
246
+ const { retries, nodePort } = options
247
+ if (retries !== undefined && (!Number.isInteger(retries) || retries < 0)) {
248
+ throw invalidArgument(`retries must be a whole number, 0 or more, received ${String(retries)}.`)
249
+ }
250
+ if (nodePort !== undefined && (!Number.isInteger(nodePort) || nodePort < 1 || nodePort > 65_535)) {
251
+ throw invalidArgument(`nodePort must be a port from 1 to 65535, received ${JSON.stringify(nodePort)}.`)
252
+ }
253
+ if (options.chainNumber !== undefined) assertUint32(options.chainNumber, 'chainNumber')
254
+ if (options.nodeNumber !== undefined) assertUint32(options.nodeNumber, 'nodeNumber')
255
+ }
256
+
257
+ /**
258
+ * Refuse a transaction its payer cannot cover. The node checks the balance against the
259
+ * amount plus the fee, so this does too — after building, because the fee is charged on
260
+ * the signed bytes.
261
+ */
262
+ function assertAffordable(
263
+ penniesPerGeeq: bigint,
264
+ payer: string,
265
+ balance: bigint,
266
+ amount: bigint,
267
+ fee: GeeqFee | undefined,
268
+ block: number,
269
+ ): void {
270
+ const cost = amount + (fee?.total ?? 0n)
271
+ if (balance >= cost) return
272
+
273
+ const geeq = (pennies: bigint) => formatGeeq(pennies, penniesPerGeeq)
274
+ const needed =
275
+ fee === undefined
276
+ ? geeq(amount)
277
+ : amount === 0n
278
+ ? `a fee of ${geeq(fee.total)}`
279
+ : `${geeq(amount)} plus a fee of ${geeq(fee.total)}`
280
+ throw insufficientFunds(
281
+ `${payer} holds ${geeq(balance)} geeq at block ${block}, and this needs ${needed} geeq.`,
282
+ )
283
+ }
284
+
285
+ /**
286
+ * A chain, reached through one node.
287
+ *
288
+ * Holds the node directory, the metadata limits and a short-lived ledger cache, so an
289
+ * attestation needs only a keypair and something to attest. Everything a transaction is
290
+ * pinned to — chain number, target block, node number, nonce — is read from the ledger
291
+ * rather than asked of the caller, because none of it can be known offline and a wrong
292
+ * value is rejected by the chain.
293
+ *
294
+ * A chain is bound to the endpoint it was connected to. To use a different node, connect
295
+ * another one: a different node means a different address, certificate and CORS origin,
296
+ * which is not something to switch silently.
297
+ *
298
+ * In a reactive framework, mark an instance raw (Vue's `markRaw`) rather than letting it
299
+ * be proxied.
300
+ */
301
+ export class GeeqChain {
302
+ /** The node this chain reads from and builds transactions for. */
303
+ readonly endpoint: string
304
+ /** What the directory said about the nodes on this chain, when one was used. */
305
+ readonly candidates: readonly GeeqDiscoveredNode[]
306
+ /** The chain's name, when a directory supplied one. Display only — chains are numbered. */
307
+ readonly chainName: string | undefined
308
+ /** How many pennies make one Geeq, as given to {@link GeeqChain.connect}. */
309
+ readonly penniesPerGeeq: bigint
310
+
311
+ private readonly request!: NodeRequestConfig
312
+ private readonly ledgerMaxAgeMs!: number
313
+ private readonly nonces!: NonceGuard
314
+ private readonly declaredChainNumber!: number | undefined
315
+ private readonly nodeNumberOverride!: number | undefined
316
+ private cached!: GeeqLedger | null
317
+ private resolved!: GeeqNode | null
318
+ private inflight!: Promise<GeeqLedger> | null
319
+
320
+ /** @internal */
321
+ constructor(init: {
322
+ endpoint: string
323
+ candidates: readonly GeeqDiscoveredNode[]
324
+ chainName: string | undefined
325
+ chainNumber: number | undefined
326
+ nodeNumber: number | undefined
327
+ penniesPerGeeq: bigint
328
+ request: NodeRequestConfig
329
+ ledgerMaxAgeMs: number
330
+ }) {
331
+ this.endpoint = init.endpoint
332
+ this.candidates = Object.freeze([...init.candidates])
333
+ this.chainName = init.chainName
334
+ this.penniesPerGeeq = init.penniesPerGeeq
335
+
336
+ // Non-enumerable and TS-private, never `#private` — see GeeqNode for why.
337
+ const hidden = (key: string, value: unknown, writable = false): void => {
338
+ Object.defineProperty(this, key, { value, enumerable: false, writable })
339
+ }
340
+ hidden('request', init.request)
341
+ hidden('ledgerMaxAgeMs', init.ledgerMaxAgeMs)
342
+ hidden('declaredChainNumber', init.chainNumber)
343
+ hidden('nodeNumberOverride', init.nodeNumber)
344
+ hidden('nonces', new NonceGuard())
345
+ hidden('cached', null, true)
346
+ hidden('resolved', null, true)
347
+ hidden('inflight', null, true)
348
+ }
349
+
350
+ /**
351
+ * Choose a chain and a node, without reading anything from the chain itself.
352
+ *
353
+ * With `discovery`, the directory is asked which nodes exist and one of the active
354
+ * ones is taken. With `endpoint`, that node is used as given. Either way **no ledger
355
+ * is fetched here** — a ledger read is only valid for a few blocks, so it happens when
356
+ * a transaction is actually being built rather than when the app starts.
357
+ */
358
+ static async connect(options: GeeqChainOptions): Promise<GeeqChain> {
359
+ const hasDiscovery = options.discovery !== undefined
360
+ const hasEndpoint = options.endpoint !== undefined
361
+ if (hasDiscovery === hasEndpoint) {
362
+ throw invalidArgument(
363
+ 'Provide exactly one of discovery (a node directory to pick an active node ' +
364
+ 'from) or endpoint (a node you already know).',
365
+ )
366
+ }
367
+ decimalsOf(options.penniesPerGeeq)
368
+ assertChainOptions(options)
369
+
370
+ const request: NodeRequestConfig = Object.freeze({
371
+ transport: options.transport ?? defaultTransport,
372
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
373
+ retries: options.retries ?? DEFAULT_RETRIES,
374
+ headers: options.headers,
375
+ })
376
+
377
+ let endpoint: string
378
+ let candidates: readonly GeeqDiscoveredNode[] = []
379
+ let chainName: string | undefined = options.chainName
380
+ let chainNumber = options.chainNumber
381
+
382
+ if (hasDiscovery) {
383
+ const found: Discovery = await discover({
384
+ origin: serviceOriginOf(options.discovery as string | URL),
385
+ ...(options.chainName === undefined ? {} : { chainName: options.chainName }),
386
+ chainNumber: options.chainNumber,
387
+ ...(options.nodePort === undefined ? {} : { port: options.nodePort }),
388
+ request: {
389
+ ...request,
390
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
391
+ },
392
+ })
393
+ candidates = found.nodes
394
+ chainName = found.chain.name
395
+ chainNumber = found.chain.chainNumber
396
+ // Spread across the usable nodes rather than everyone hammering the first one.
397
+ // The directory's order is stable, so picking [0] would concentrate every client
398
+ // of every app on one machine.
399
+ endpoint = (found.usable[Math.floor(Math.random() * found.usable.length)] as GeeqDiscoveredNode).url
400
+ } else {
401
+ endpoint = originOf(options.endpoint as string | URL)
402
+ }
403
+
404
+ return new GeeqChain({
405
+ endpoint,
406
+ candidates,
407
+ chainName,
408
+ chainNumber,
409
+ nodeNumber: options.nodeNumber,
410
+ penniesPerGeeq: options.penniesPerGeeq,
411
+ request,
412
+ ledgerMaxAgeMs: options.ledgerMaxAgeMs ?? DEFAULT_LEDGER_MAX_AGE_MS,
413
+ })
414
+ }
415
+
416
+ /** {@link GeeqChain.connect}, and install the result as the process-wide default. */
417
+ static async init(options: GeeqChainOptions): Promise<GeeqChain> {
418
+ const chain = await GeeqChain.connect(options)
419
+ defaultChain = chain
420
+ return chain
421
+ }
422
+
423
+ /** The default chain. Throws `not-initialized` when there is none. */
424
+ static current(): GeeqChain {
425
+ if (defaultChain === null) throw noChain('GeeqChain.current()')
426
+ return defaultChain
427
+ }
428
+
429
+ /** Whether a default chain has been installed. */
430
+ static get initialized(): boolean {
431
+ return defaultChain !== null
432
+ }
433
+
434
+ /** Clear the default. Call this from `afterEach` so tests do not leak into each other. */
435
+ static reset(): void {
436
+ defaultChain = null
437
+ }
438
+
439
+ /** Resolve an amount to pennies, reading a `{ geeq }` figure at {@link penniesPerGeeq}. */
440
+ toPennies(amount: GeeqAmount): bigint {
441
+ return toPennies(amount, this.penniesPerGeeq)
442
+ }
443
+
444
+ /** Show pennies as a Geeq figure at {@link penniesPerGeeq}, as the Geeq wallet does. */
445
+ formatGeeq(pennies: bigint): string {
446
+ return formatGeeq(pennies, this.penniesPerGeeq)
447
+ }
448
+
449
+ /**
450
+ * This chain's number.
451
+ *
452
+ * Known immediately when a directory named the chain, or once a ledger has been read.
453
+ * `undefined` in between — with a bare endpoint and no declared number, only the node
454
+ * can say.
455
+ */
456
+ get chainNumber(): number | undefined {
457
+ return this.cached?.chainNumber ?? this.declaredChainNumber
458
+ }
459
+
460
+ /**
461
+ * The node, once a ledger read has identified it in the net actor list.
462
+ *
463
+ * `undefined` until then: a node's number is the one thing the directory cannot say,
464
+ * so it is not known before the first read.
465
+ */
466
+ get node(): GeeqNode | undefined {
467
+ return this.resolved ?? undefined
468
+ }
469
+
470
+ /** The net actor directory from the last ledger read, if there has been one. */
471
+ get nodes(): readonly GeeqLedgerNode[] {
472
+ return this.cached?.nodes ?? []
473
+ }
474
+
475
+ /** Those of them that accept transactions. */
476
+ get processingNodes(): readonly GeeqLedgerNode[] {
477
+ return this.cached?.processingNodes ?? []
478
+ }
479
+
480
+ /** The ledger from the last read, if it is still cached. */
481
+ get ledger(): GeeqLedger | undefined {
482
+ return this.cached ?? undefined
483
+ }
484
+
485
+ /**
486
+ * A URL for another node in this chain.
487
+ *
488
+ * Every node answers client traffic on {@link GEEQ_NODE_PORT}, so only the scheme is
489
+ * carried over from the endpoint in use. The port recorded against a node in the
490
+ * ledger is its own internal listener and is never what you connect to.
491
+ */
492
+ endpointFor(host: string): string {
493
+ return `${new URL(this.endpoint).protocol}//${host}:${GEEQ_NODE_PORT}`
494
+ }
495
+
496
+ /**
497
+ * The current ledger, from cache when it is fresh enough.
498
+ *
499
+ * Staleness is safe in exactly one direction. The node accepts a target block within
500
+ * `[N-8, N+1]` of its own head, and a cached ledger can only be *behind*, so the
501
+ * target it yields is never in the future — the only rejected direction. The default
502
+ * five seconds is set by the node list and metadata limits, which have no such
503
+ * tolerance, not by the block window.
504
+ */
505
+ async getLedger(
506
+ options: { readonly maxAgeMs?: number; readonly signal?: AbortSignal } = {},
507
+ ): Promise<GeeqLedger> {
508
+ const maxAge = options.maxAgeMs ?? this.ledgerMaxAgeMs
509
+ const cached = this.cached
510
+ if (cached !== null && maxAge > 0 && Date.now() - cached.readAt < maxAge) return cached
511
+ return this.read(options.signal)
512
+ }
513
+
514
+ /** Discard the cached ledger and read again. */
515
+ async refresh(options: { readonly signal?: AbortSignal } = {}): Promise<GeeqLedger> {
516
+ this.cached = null
517
+ return this.read(options.signal)
518
+ }
519
+
520
+ /**
521
+ * One read at a time.
522
+ *
523
+ * Same single-flight shape as `initGeeq`, including clearing the promise on failure so
524
+ * a later call can retry rather than being stuck with the first error. Without it, a
525
+ * page that builds several transactions at once pulls the whole ledger several times
526
+ * from an endpoint with no rate limiting of any kind.
527
+ *
528
+ * The read is shared, so it carries no caller's signal: one caller aborting must not
529
+ * fail the others waiting on it. Each caller stops waiting on its own signal instead,
530
+ * and the read runs on, bounded by `timeoutMs`, to fill the cache for whoever is next.
531
+ */
532
+ private read(signal?: AbortSignal): Promise<GeeqLedger> {
533
+ if (signal?.aborted === true) return Promise.reject(signal.reason)
534
+ if (this.inflight !== null) return raceAbort(this.inflight, signal)
535
+
536
+ const pending = readLedger(this.endpoint, this.request)
537
+ .then((ledger) => {
538
+ const declared = this.declaredChainNumber
539
+ if (declared !== undefined && ledger.chainNumber !== declared) {
540
+ throw chainMismatch(
541
+ `This chain was opened as chain ${declared} but the node at ${this.endpoint} ` +
542
+ `serves chain ${ledger.chainNumber}. A transaction signed for the wrong ` +
543
+ 'chain is rejected, so either drop chainNumber to accept the node\'s, or ' +
544
+ `use a node on chain ${declared}.`,
545
+ )
546
+ }
547
+
548
+ // The node's own number lives only in the ledger, so this is the first moment it
549
+ // can be known — and the first moment the node can be checked for being able to
550
+ // take work at all.
551
+ const selected = selectNode(ledger, this.endpoint, {
552
+ ...(this.nodeNumberOverride === undefined ? {} : { nodeNumber: this.nodeNumberOverride }),
553
+ })
554
+ this.resolved = new GeeqNode({ ...this.request, ...selected, url: this.endpoint })
555
+ this.cached = ledger
556
+ return ledger
557
+ })
558
+ .finally(() => {
559
+ this.inflight = null
560
+ })
561
+
562
+ this.inflight = pending
563
+ return raceAbort(pending, signal)
564
+ }
565
+
566
+ /**
567
+ * Everything a transaction must be pinned to, read from this chain.
568
+ *
569
+ * Throws rather than guessing when the account cannot sign: absent from the ledger
570
+ * (the ordinary state of an unfunded keypair), or present as a record type the
571
+ * protocol will not accept.
572
+ */
573
+ async getChainState(request: ChainStateRequest): Promise<GeeqChainState> {
574
+ const ledger = await this.getLedger({
575
+ ...(request.maxAgeMs === undefined ? {} : { maxAgeMs: request.maxAgeMs }),
576
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
577
+ })
578
+ return this.stateIn(ledger, request.account)
579
+ }
580
+
581
+ /**
582
+ * What every transaction built from `ledger` is pinned to, bar the signer's nonce: the
583
+ * same for a signer's own transaction and for a proxy's.
584
+ */
585
+ private pinnedTo(
586
+ ledger: GeeqLedger,
587
+ ): Omit<GeeqChainState, 'nonce' | 'balance' | 'minimumTransfer'> {
588
+ return {
589
+ chainNumber: ledger.chainNumber,
590
+ blockNumber: ledger.targetBlockNumber,
591
+ nodeNumber: (this.resolved as GeeqNode).nodeNumber,
592
+ metadataLimits: ledger.metadataLimits,
593
+ geeqNorm: ledger.geeqNorm,
594
+ ledgerBlockNumber: ledger.blockNumber,
595
+ readAt: ledger.readAt,
596
+ nodeUrl: this.endpoint,
597
+ }
598
+ }
599
+
600
+ /** {@link getChainState} against a ledger already in hand, so a build reads only one. */
601
+ private stateIn(ledger: GeeqLedger, ref: AccountRef): GeeqChainState {
602
+ const accountId = resolveAccountId(ref)
603
+ const account = ledger.findCoinAccount(accountId)
604
+ const shown = toBase64(accountId)
605
+
606
+ if (account === undefined) {
607
+ throw accountNotFound(
608
+ `Account ${shown} is not in the ledger of chain ${ledger.chainNumber} at block ` +
609
+ `${ledger.blockNumber}. An account exists on a Geeq chain only once it has ` +
610
+ 'received coins, so a freshly generated keypair will not be there. Fund it, ' +
611
+ 'then try again.',
612
+ )
613
+ }
614
+
615
+ if (account.kind !== 'asset' || account.nonce === undefined) {
616
+ const described =
617
+ account.kind === 'hl'
618
+ ? 'a hash-lock record, which carries no nonce at all'
619
+ : account.kind === 'micro'
620
+ ? 'a micro-coin record'
621
+ : 'a record whose subtype could not be read'
622
+ throw accountNotAttestable(
623
+ `Account ${shown} is in the ledger at block ${ledger.blockNumber} as ${described}, ` +
624
+ 'but an attestation must be signed by a coin-asset account. Sign with a ' +
625
+ 'coin-asset key on this chain instead.',
626
+ )
627
+ }
628
+
629
+ return Object.freeze({
630
+ ...this.pinnedTo(ledger),
631
+ nonce: account.nonce,
632
+ balance: account.balance,
633
+ minimumTransfer: ledger.minimumTransfer,
634
+ })
635
+ }
636
+
637
+ /**
638
+ * The ledger a build is made from, and the state of `signer` in it.
639
+ *
640
+ * A cached ledger is up to a few seconds old, so "this account already built against
641
+ * this block" may only mean "we are looking at an old block". Look again once before
642
+ * letting the nonce guard refuse — otherwise a caller who waited out the block interval
643
+ * is told to wait for a block that has already passed. An explicit nonce is the caller
644
+ * taking responsibility, so it skips the check.
645
+ *
646
+ * Everything the build then needs — what the sender holds, which proxies exist — comes
647
+ * from this same ledger, so it all describes one block.
648
+ */
649
+ private async readForBuild(
650
+ signer: GeeqKeypair,
651
+ explicitNonce: number | undefined,
652
+ signal: AbortSignal | undefined,
653
+ ): Promise<{ readonly ledger: GeeqLedger; readonly chainState: GeeqChainState }> {
654
+ const options = signal === undefined ? {} : { signal }
655
+ const [cached] = await Promise.all([this.getLedger(options), initGeeq()])
656
+ const ledger = await this.recheckIfClaimed(
657
+ cached,
658
+ explicitNonce === undefined ? toBase64(signer.accountId) : undefined,
659
+ options,
660
+ )
661
+ const chainState = this.stateIn(ledger, signer)
662
+ return {
663
+ ledger,
664
+ chainState:
665
+ explicitNonce === undefined ? chainState : { ...chainState, nonce: explicitNonce },
666
+ }
667
+ }
668
+
669
+ /** Read afresh if `guardKey` already built against `ledger`'s block. See {@link readForBuild}. */
670
+ private async recheckIfClaimed(
671
+ ledger: GeeqLedger,
672
+ guardKey: string | undefined,
673
+ options: { readonly signal?: AbortSignal },
674
+ ): Promise<GeeqLedger> {
675
+ if (guardKey === undefined || !this.nonces.wouldCollide(guardKey, ledger.blockNumber)) {
676
+ return ledger
677
+ }
678
+ return this.refresh(options)
679
+ }
680
+
681
+ /**
682
+ * Build and sign an attestation, deriving everything the chain decides.
683
+ *
684
+ * This is where the ledger is read, because this is the first moment the values it
685
+ * carries are actually needed — and they go stale within a handful of blocks, so
686
+ * reading them any earlier would only mean reading them twice.
687
+ *
688
+ * The result is **not submitted**. Hand it to {@link GeeqSocket.track} first, so its
689
+ * verdict has somewhere to arrive, then POST `transaction` and `payload` to
690
+ * {@link AttestationTx.nodeUrl}; any other node rejects it, because the node number is
691
+ * checked with strict equality.
692
+ */
693
+ async createAttestation(input: AttestationRequest): Promise<AttestationTx> {
694
+ if ((input.content === undefined) === (input.attestHash === undefined)) {
695
+ throw invalidArgument(
696
+ 'Provide exactly one of content (hashed here with BLAKE3) or attestHash (a ' +
697
+ `${ATTEST_HASH_BYTES}-byte digest you produced yourself).`,
698
+ )
699
+ }
700
+
701
+ const attestHash = input.attestHash ?? (await attestHashOf(input.content as GeeqContent))
702
+ const { chainState } = await this.readForBuild(input.keypair, input.nonce, input.signal)
703
+
704
+ const tx = createAttestationTxSync({
705
+ keypair: input.keypair,
706
+ attestHash,
707
+ ...(input.metadata === undefined ? {} : { metadata: input.metadata }),
708
+ chainState,
709
+ })
710
+ assertAffordable(this.penniesPerGeeq, 'This account', chainState.balance, 0n, tx.fee, chainState.ledgerBlockNumber)
711
+ this.claimNonce(input.keypair, input.nonce, chainState, tx)
712
+ return tx
713
+ }
714
+
715
+ /**
716
+ * Record the nonce a finished build took, so a second build against the same block is
717
+ * refused rather than sent to share it.
718
+ *
719
+ * Claimed only once a transaction really exists, so a build that failed validation does
720
+ * not consume the account's one nonce for this block. An explicit nonce is the caller
721
+ * taking responsibility, so it bypasses the guard entirely.
722
+ */
723
+ private claimNonce(
724
+ signer: GeeqKeypair,
725
+ explicitNonce: number | undefined,
726
+ chainState: GeeqChainState,
727
+ tx: { readonly transactionHash: Uint8Array },
728
+ ): void {
729
+ if (explicitNonce !== undefined) return
730
+ this.nonces.claim(
731
+ toBase64(signer.accountId),
732
+ chainState.ledgerBlockNumber,
733
+ chainState.nonce,
734
+ toBase64(tx.transactionHash),
735
+ )
736
+ }
737
+
738
+ /**
739
+ * Resolve a recipient, checking the chain it belongs to when one is stated.
740
+ *
741
+ * An address carries a chain number, and an address from another chain is almost
742
+ * always a paste from the wrong place. The account id inside it would still be
743
+ * technically usable here, which is exactly why this is worth refusing rather than
744
+ * quietly sending.
745
+ */
746
+ private resolveRecipient(to: string | AccountRef, chainNumber: number): Uint8Array {
747
+ if (typeof to !== 'string') return resolveAccountId(to)
748
+
749
+ const decoded = decodeWalletAddressSync(to)
750
+ if (decoded.chainNumber !== chainNumber) {
751
+ throw chainMismatch(
752
+ `The recipient address is for chain ${decoded.chainNumber} but this is chain ` +
753
+ `${chainNumber}. An address encodes its chain, so this one was almost certainly ` +
754
+ 'copied from somewhere else — check it before sending.',
755
+ )
756
+ }
757
+ return decoded.accountId
758
+ }
759
+
760
+ /**
761
+ * Send coin to another account, and NFTs with it if you name them.
762
+ *
763
+ * Reads the ledger, so it also knows what the sender holds and what the chain's minimum
764
+ * transfer is. The amount is checked against the minimum, and the sender's NFTs against
765
+ * the ones named, before anything is signed; the balance against the amount plus the
766
+ * fee once the signed transaction gives the fee. NFTs always travel with coin: leave
767
+ * `amount` out and they go with the chain's minimum transfer.
768
+ *
769
+ * The result is **not submitted**. Hand it to {@link GeeqSocket.track} first, then POST
770
+ * `transaction` to {@link CoinTransferTx.nodeUrl} using `coinTransferSubmitBody`, which
771
+ * sends it as type `uvt` — a different shape from an attestation, and with no separate
772
+ * payload.
773
+ */
774
+ async createTransfer(input: CoinTransferRequest): Promise<CoinTransferTx> {
775
+ const nfts = input.nfts ?? []
776
+ if (input.amount === undefined && nfts.length === 0) {
777
+ throw invalidArgument('Say what to send: an amount, NFTs, or both.')
778
+ }
779
+ const asked = input.amount === undefined ? undefined : this.toPennies(input.amount)
780
+ const { ledger, chainState } = await this.readForBuild(input.from, input.nonce, input.signal)
781
+
782
+ const to = this.resolveRecipient(input.to, chainState.chainNumber)
783
+
784
+ const minimum = chainState.minimumTransfer
785
+ const pennies = asked ?? minimum
786
+ if (pennies === undefined) {
787
+ throw invalidArgument(
788
+ 'This chain publishes no minimum transfer to send with the NFTs by default, so pass amount.',
789
+ )
790
+ }
791
+ if (minimum !== undefined && pennies < minimum) {
792
+ throw amountBelowMinimum(
793
+ `This chain will not accept a transfer below ${this.formatGeeq(minimum)} geeq, and ` +
794
+ `${this.formatGeeq(pennies)} is smaller. The minimum comes from the chain's own ` +
795
+ 'minTxAmt and geeqNorm parameters, read at block ' +
796
+ `${chainState.ledgerBlockNumber}.`,
797
+ )
798
+ }
799
+
800
+ // The transaction names each NFT by id and by its place in the sender's list, read
801
+ // from the same ledger as the nonce, so both describe one block.
802
+ const held = ledger.findCoinAccount(input.from)?.nfts ?? []
803
+ const entries = nfts.map((id) => {
804
+ const position = held.findIndex((nft) => toBase64(nft.id) === toBase64(id))
805
+ if (position === -1) {
806
+ throw nftNotOwned(
807
+ `This account does not hold NFT ${toBase64(id)} at block ${ledger.blockNumber}. ` +
808
+ 'findNfts lists the ones it does; one just sent away is gone once a block confirms it.',
809
+ )
810
+ }
811
+ if (held[position]?.counterparty === true) {
812
+ throw invalidArgument(`NFT ${toBase64(id)} is a counterparty NFT, which a plain transfer cannot move.`)
813
+ }
814
+ return { id, position }
815
+ })
816
+
817
+ // The chain gives an NFT only to a wallet account (or a multisig proxy). A proxy's
818
+ // address is the likely paste here.
819
+ if (entries.length > 0) {
820
+ const proxy = ledger.findProxyAccount(to)
821
+ const account = ledger.findCoinAccount(to)
822
+ if ((proxy !== undefined && proxy.kind !== 'multisig') || (account !== undefined && account.kind !== 'asset')) {
823
+ throw invalidArgument(
824
+ `The recipient is a ${proxy?.kind ?? account?.kind} record, and the chain gives NFTs ` +
825
+ 'only to a wallet account. Send them to a wallet address instead.',
826
+ )
827
+ }
828
+ }
829
+
830
+ const tx = createCoinTransferTxSync({
831
+ keypair: input.from,
832
+ to,
833
+ amount: pennies,
834
+ nfts: entries,
835
+ chainState,
836
+ })
837
+ assertAffordable(this.penniesPerGeeq, 'This account', chainState.balance, pennies, tx.fee, chainState.ledgerBlockNumber)
838
+ this.claimNonce(input.from, input.nonce, chainState, tx)
839
+ return tx
840
+ }
841
+
842
+ /**
843
+ * The NFTs an account holds on this chain, in the order its record lists them, each with
844
+ * the hash and labels it was minted with.
845
+ *
846
+ * Which NFTs it holds comes from the validation layer ledger; what each stands for, from
847
+ * the application layer ledger, which is read afresh every time. An account the chain
848
+ * does not know holds none. A new NFT shows once a block has confirmed it, so pass
849
+ * `maxAgeMs: 0` to look straight after one was minted or sent.
850
+ */
851
+ async findNfts(request: NftsRequest): Promise<readonly GeeqNft[]> {
852
+ const signal = request.signal === undefined ? {} : { signal: request.signal }
853
+ const [ledger, records] = await Promise.all([
854
+ this.getLedger({
855
+ ...(request.maxAgeMs === undefined ? {} : { maxAgeMs: request.maxAgeMs }),
856
+ ...signal,
857
+ }),
858
+ readNftRecords(this.endpoint, { ...this.request, ...signal }),
859
+ ])
860
+ const held = ledger.findCoinAccount(request.owner)?.nfts ?? []
861
+ return Object.freeze(
862
+ held.map((nft) => {
863
+ const record = records.get(toBase64(nft.id))
864
+ return Object.freeze({ ...nft, attestHash: record?.attestHash, metadata: record?.metadata ?? [] })
865
+ }),
866
+ )
867
+ }
868
+
869
+ /**
870
+ * The NFT mint proxies an admin has on this chain, each with its index, and the index
871
+ * its next proxy takes: one past the highest in use, or 1 for an admin with none.
872
+ *
873
+ * A proxy appears once a block has confirmed it, so pass `maxAgeMs: 0` to look
874
+ * straight after one was submitted.
875
+ */
876
+ async findNftMintProxies(request: NftMintProxiesRequest): Promise<GeeqNftMintProxies> {
877
+ const [ledger] = await Promise.all([
878
+ this.getLedger({
879
+ ...(request.maxAgeMs === undefined ? {} : { maxAgeMs: request.maxAgeMs }),
880
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
881
+ }),
882
+ initGeeq(),
883
+ ])
884
+ return nftMintProxiesIn(ledger, request.admin)
885
+ }
886
+
887
+ /**
888
+ * Create a proxy to mint NFTs through.
889
+ *
890
+ * A proxy is a record of its own: it holds coin, carries its own nonce, and names one
891
+ * admin — `admin` here — who signs every mint made through it. Its id is derived from
892
+ * the admin's key and an index, as the Geeq wallet derives it: by default the next
893
+ * index {@link findNftMintProxies} reports, so it is found again from the key alone.
894
+ *
895
+ * Checks the funding against the chain's minimum, and the admin's balance against the
896
+ * funding plus the fee. The result is **not submitted** — track it, then post it with
897
+ * `coinTransferSubmitBody`, exactly as a transfer. The proxy exists once a block
898
+ * confirms it.
899
+ */
900
+ async createNftMintProxy(input: NftMintProxyRequest): Promise<NftMintProxyTx> {
901
+ const pennies = this.toPennies(input.amount)
902
+ const { ledger, chainState } = await this.readForBuild(input.admin, input.nonce, input.signal)
903
+
904
+ // The node holds proxy funding to the same minimum as any transfer.
905
+ const minimum = chainState.minimumTransfer
906
+ if (minimum !== undefined && pennies < minimum) {
907
+ throw amountBelowMinimum(
908
+ `A proxy must be funded with at least ${this.formatGeeq(minimum)} geeq, and ` +
909
+ `${this.formatGeeq(pennies)} is smaller. It is the same minimum as a transfer, from ` +
910
+ "the chain's minTxAmt and geeqNorm parameters, read at block " +
911
+ `${chainState.ledgerBlockNumber}.`,
912
+ )
913
+ }
914
+
915
+ const existing = nftMintProxiesIn(ledger, input.admin)
916
+ const index = input.index ?? existing.nextIndex
917
+ if (existing.proxies.some((proxy) => proxy.index === index)) {
918
+ throw invalidArgument(
919
+ `This admin already has a proxy at index ${index}. Leave index out to take the ` +
920
+ `next one, ${existing.nextIndex}.`,
921
+ )
922
+ }
923
+
924
+ const tx = createNftMintProxyTxSync({
925
+ keypair: input.admin,
926
+ index,
927
+ amount: pennies,
928
+ chainState,
929
+ })
930
+ assertAffordable(this.penniesPerGeeq, 'This account', chainState.balance, pennies, tx.fee, chainState.ledgerBlockNumber)
931
+ this.claimNonce(input.admin, input.nonce, chainState, tx)
932
+ return tx
933
+ }
934
+
935
+ /**
936
+ * Mint an NFT through one of the admin's proxies.
937
+ *
938
+ * The proxy is the sender: it pays the fee and the coin sent with the NFT, and spends
939
+ * its own nonce, not the admin's. The admin signs. The NFT itself is the content's hash
940
+ * and labels, as an attestation is, and its id is derived from them — so the same
941
+ * content with the same labels through the same proxy cannot be minted twice.
942
+ *
943
+ * Reads the ledger to find the proxy and check what it holds. The result is **not
944
+ * submitted** — track it, then post it with `attestationSubmitBody`, exactly as an
945
+ * attestation: two fields, as `uat`.
946
+ */
947
+ async mintNft(input: NftMintRequest): Promise<NftMintTx> {
948
+ if ((input.content === undefined) === (input.attestHash === undefined)) {
949
+ throw invalidArgument(
950
+ 'Provide exactly one of content (hashed here with BLAKE3) or attestHash (a ' +
951
+ `${ATTEST_HASH_BYTES}-byte digest you produced yourself).`,
952
+ )
953
+ }
954
+ assertUint32(input.index, 'index')
955
+
956
+ const attestHash = input.attestHash ?? (await attestHashOf(input.content as GeeqContent))
957
+ const signal = input.signal === undefined ? {} : { signal: input.signal }
958
+
959
+ const [cached] = await Promise.all([this.getLedger(signal), initGeeq()])
960
+ const proxyKey = toBase64(nftMintProxyId(input.admin.publicKey, input.index))
961
+
962
+ // The proxy spends its own nonce, so the guard is keyed on the proxy, not the admin.
963
+ const ledger = await this.recheckIfClaimed(
964
+ cached,
965
+ input.nonce === undefined ? proxyKey : undefined,
966
+ signal,
967
+ )
968
+
969
+ const proxy = ledger
970
+ .findProxyAccounts({ admin: input.admin })
971
+ .find((found) => toBase64(found.accountId) === proxyKey)
972
+ if (proxy === undefined) {
973
+ throw accountNotFound(
974
+ `This admin has no NFT mint proxy at index ${input.index} in the ledger of chain ` +
975
+ `${ledger.chainNumber} at block ${ledger.blockNumber}. A new proxy appears once a ` +
976
+ 'block has confirmed it; findNftMintProxies lists the ones there are.',
977
+ )
978
+ }
979
+
980
+ const minimum = ledger.minimumTransfer
981
+ const pennies = input.amount === undefined ? minimum : this.toPennies(input.amount)
982
+ if (pennies === undefined) {
983
+ throw invalidArgument(
984
+ 'This chain publishes no minimum transfer to send with the NFT by default, so pass amount.',
985
+ )
986
+ }
987
+ if (minimum !== undefined && pennies < minimum) {
988
+ throw amountBelowMinimum(
989
+ `A mint must send at least ${this.formatGeeq(minimum)} geeq with the NFT, and ` +
990
+ `${this.formatGeeq(pennies)} is smaller. It is the same minimum as a transfer, from ` +
991
+ "the chain's minTxAmt and geeqNorm parameters, read at block " +
992
+ `${ledger.blockNumber}.`,
993
+ )
994
+ }
995
+ const tx = createNftMintTxSync({
996
+ keypair: input.admin,
997
+ proxyAccountId: proxy.accountId,
998
+ attestHash,
999
+ ...(input.metadata === undefined ? {} : { metadata: input.metadata }),
1000
+ to:
1001
+ input.to === undefined
1002
+ ? input.admin.accountId
1003
+ : this.resolveRecipient(input.to, ledger.chainNumber),
1004
+ amount: pennies,
1005
+ chainState: { ...this.pinnedTo(ledger), nonce: input.nonce ?? proxy.nonce },
1006
+ })
1007
+ // The proxy pays, not the admin. Topping it up is a plain transfer to its address.
1008
+ assertAffordable(this.penniesPerGeeq, `Proxy ${input.index}`, proxy.balance, pennies, tx.fee, ledger.blockNumber)
1009
+
1010
+ if (input.nonce === undefined) {
1011
+ this.nonces.claim(proxyKey, ledger.blockNumber, proxy.nonce, toBase64(tx.transactionHash))
1012
+ }
1013
+
1014
+ return tx
1015
+ }
1016
+
1017
+ /**
1018
+ * Whether a built transaction is now too old to send: the chain is past its
1019
+ * `validUntilBlock`, and a node would refuse it with an HTTP 400.
1020
+ *
1021
+ * Reads the ledger afresh, because a cached one can only be behind the chain and would
1022
+ * say "not yet" when it already is. Call it before submitting a transaction that may
1023
+ * have waited.
1024
+ */
1025
+ async isExpired(tx: { readonly validUntilBlock: number }): Promise<boolean> {
1026
+ const ledger = await this.getLedger({ maxAgeMs: 0 })
1027
+ return ledger.blockNumber > tx.validUntilBlock
1028
+ }
1029
+
1030
+ /**
1031
+ * Give back the nonce a built transaction took, because it will never be sent — the
1032
+ * user changed something and is building again. The next build may then take the same
1033
+ * nonce in the same block instead of being refused with `nonce-collision`.
1034
+ *
1035
+ * Only for a transaction that was not submitted: if it reaches the node as well as its
1036
+ * rebuild, the two share a nonce and the node rejects both.
1037
+ */
1038
+ discard(tx: { readonly transactionHash: Uint8Array }): void {
1039
+ this.nonces.release(toBase64(tx.transactionHash))
1040
+ }
1041
+
1042
+ /** Forget which accounts have built a transaction against the current block. */
1043
+ forgetNonces(): void {
1044
+ this.nonces.clear()
1045
+ }
1046
+
1047
+ /** Drop cached state, and step down as the default if this chain holds that slot. */
1048
+ dispose(): void {
1049
+ this.cached = null
1050
+ this.resolved = null
1051
+ this.inflight = null
1052
+ this.nonces.clear()
1053
+ if (defaultChain === this) defaultChain = null
1054
+ }
1055
+
1056
+ toJSON(): Record<string, unknown> {
1057
+ return {
1058
+ chainNumber: this.chainNumber,
1059
+ endpoint: this.endpoint,
1060
+ node: this.node,
1061
+ ledgerBlockNumber: this.cached?.blockNumber,
1062
+ candidateCount: this.candidates.length,
1063
+ }
1064
+ }
1065
+
1066
+ toString(): string {
1067
+ return `GeeqChain(chain ${this.chainNumber ?? 'unread'} via ${this.endpoint})`
1068
+ }
1069
+ }
1070
+
1071
+ /**
1072
+ * Connect to a chain and make it the default.
1073
+ *
1074
+ * Thin alias for {@link GeeqChain.init} — the name most applications reach for, and one
1075
+ * line so there is no second implementation to keep in step.
1076
+ *
1077
+ * ```ts
1078
+ * await initChain({ endpoint: 'https://1.2.3.4:2082' })
1079
+ * const tx = await attest({ keypair, content: fileBytes })
1080
+ * ```
1081
+ */
1082
+ export function initChain(options: GeeqChainOptions): Promise<GeeqChain> {
1083
+ return GeeqChain.init(options)
1084
+ }
1085
+
1086
+ /** Build an attestation on the default chain. See {@link GeeqChain.createAttestation}. */
1087
+ export function attest(input: AttestationRequest): Promise<AttestationTx> {
1088
+ return GeeqChain.current().createAttestation(input)
1089
+ }
1090
+
1091
+ /** Send coin on the default chain. See {@link GeeqChain.createTransfer}. */
1092
+ export function transfer(input: CoinTransferRequest): Promise<CoinTransferTx> {
1093
+ return GeeqChain.current().createTransfer(input)
1094
+ }