@getomnichain/omnichain 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 (296) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/abstract_gas_pricing.d.ts +31 -0
  4. package/dist/abstract_gas_pricing.d.ts.map +1 -0
  5. package/dist/abstract_gas_pricing.js +22 -0
  6. package/dist/abstract_gas_pricing.js.map +1 -0
  7. package/dist/address.d.ts +10 -0
  8. package/dist/address.d.ts.map +1 -0
  9. package/dist/address.factory.d.ts +26 -0
  10. package/dist/address.factory.d.ts.map +1 -0
  11. package/dist/address.factory.js +75 -0
  12. package/dist/address.factory.js.map +1 -0
  13. package/dist/address.js +13 -0
  14. package/dist/address.js.map +1 -0
  15. package/dist/address_field.decorator.d.ts +17 -0
  16. package/dist/address_field.decorator.d.ts.map +1 -0
  17. package/dist/address_field.decorator.js +34 -0
  18. package/dist/address_field.decorator.js.map +1 -0
  19. package/dist/chain.base.d.ts +79 -0
  20. package/dist/chain.base.d.ts.map +1 -0
  21. package/dist/chain.base.js +113 -0
  22. package/dist/chain.base.js.map +1 -0
  23. package/dist/chain_ids.d.ts +122 -0
  24. package/dist/chain_ids.d.ts.map +1 -0
  25. package/dist/chain_ids.js +193 -0
  26. package/dist/chain_ids.js.map +1 -0
  27. package/dist/errors.d.ts +42 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +62 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/evm/evm_address.d.ts +11 -0
  32. package/dist/evm/evm_address.d.ts.map +1 -0
  33. package/dist/evm/evm_address.js +56 -0
  34. package/dist/evm/evm_address.js.map +1 -0
  35. package/dist/evm/evm_chain.d.ts +123 -0
  36. package/dist/evm/evm_chain.d.ts.map +1 -0
  37. package/dist/evm/evm_chain.js +685 -0
  38. package/dist/evm/evm_chain.js.map +1 -0
  39. package/dist/evm/evm_chains.d.ts +83 -0
  40. package/dist/evm/evm_chains.d.ts.map +1 -0
  41. package/dist/evm/evm_chains.js +493 -0
  42. package/dist/evm/evm_chains.js.map +1 -0
  43. package/dist/evm/evm_gas_estimate.d.ts +45 -0
  44. package/dist/evm/evm_gas_estimate.d.ts.map +1 -0
  45. package/dist/evm/evm_gas_estimate.js +70 -0
  46. package/dist/evm/evm_gas_estimate.js.map +1 -0
  47. package/dist/evm/evm_gas_pricing.d.ts +40 -0
  48. package/dist/evm/evm_gas_pricing.d.ts.map +1 -0
  49. package/dist/evm/evm_gas_pricing.js +56 -0
  50. package/dist/evm/evm_gas_pricing.js.map +1 -0
  51. package/dist/evm/evm_token.d.ts +7 -0
  52. package/dist/evm/evm_token.d.ts.map +1 -0
  53. package/dist/evm/evm_token.js +22 -0
  54. package/dist/evm/evm_token.js.map +1 -0
  55. package/dist/evm/evm_tokens.d.ts +81 -0
  56. package/dist/evm/evm_tokens.d.ts.map +1 -0
  57. package/dist/evm/evm_tokens.js +104 -0
  58. package/dist/evm/evm_tokens.js.map +1 -0
  59. package/dist/evm/evm_transaction_status.d.ts +108 -0
  60. package/dist/evm/evm_transaction_status.d.ts.map +1 -0
  61. package/dist/evm/evm_transaction_status.js +177 -0
  62. package/dist/evm/evm_transaction_status.js.map +1 -0
  63. package/dist/evm/index.d.ts +10 -0
  64. package/dist/evm/index.d.ts.map +1 -0
  65. package/dist/evm/index.js +10 -0
  66. package/dist/evm/index.js.map +1 -0
  67. package/dist/evm/unsigned_evm_transaction.d.ts +19 -0
  68. package/dist/evm/unsigned_evm_transaction.d.ts.map +1 -0
  69. package/dist/evm/unsigned_evm_transaction.js +18 -0
  70. package/dist/evm/unsigned_evm_transaction.js.map +1 -0
  71. package/dist/index.d.ts +18 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +18 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/is_address.decorator.d.ts +3 -0
  76. package/dist/is_address.decorator.d.ts.map +1 -0
  77. package/dist/is_address.decorator.js +51 -0
  78. package/dist/is_address.decorator.js.map +1 -0
  79. package/dist/network_type.d.ts +73 -0
  80. package/dist/network_type.d.ts.map +1 -0
  81. package/dist/network_type.js +144 -0
  82. package/dist/network_type.js.map +1 -0
  83. package/dist/priority.d.ts +23 -0
  84. package/dist/priority.d.ts.map +1 -0
  85. package/dist/priority.js +23 -0
  86. package/dist/priority.js.map +1 -0
  87. package/dist/solana/index.d.ts +9 -0
  88. package/dist/solana/index.d.ts.map +1 -0
  89. package/dist/solana/index.js +9 -0
  90. package/dist/solana/index.js.map +1 -0
  91. package/dist/solana/solana_address.d.ts +20 -0
  92. package/dist/solana/solana_address.d.ts.map +1 -0
  93. package/dist/solana/solana_address.js +45 -0
  94. package/dist/solana/solana_address.js.map +1 -0
  95. package/dist/solana/solana_chain.d.ts +213 -0
  96. package/dist/solana/solana_chain.d.ts.map +1 -0
  97. package/dist/solana/solana_chain.js +704 -0
  98. package/dist/solana/solana_chain.js.map +1 -0
  99. package/dist/solana/solana_chains.d.ts +24 -0
  100. package/dist/solana/solana_chains.d.ts.map +1 -0
  101. package/dist/solana/solana_chains.js +84 -0
  102. package/dist/solana/solana_chains.js.map +1 -0
  103. package/dist/solana/solana_gas_pricing.d.ts +30 -0
  104. package/dist/solana/solana_gas_pricing.d.ts.map +1 -0
  105. package/dist/solana/solana_gas_pricing.js +26 -0
  106. package/dist/solana/solana_gas_pricing.js.map +1 -0
  107. package/dist/solana/solana_token.d.ts +19 -0
  108. package/dist/solana/solana_token.d.ts.map +1 -0
  109. package/dist/solana/solana_token.js +34 -0
  110. package/dist/solana/solana_token.js.map +1 -0
  111. package/dist/solana/solana_tokens.d.ts +22 -0
  112. package/dist/solana/solana_tokens.d.ts.map +1 -0
  113. package/dist/solana/solana_tokens.js +26 -0
  114. package/dist/solana/solana_tokens.js.map +1 -0
  115. package/dist/solana/solana_transaction_status.d.ts +59 -0
  116. package/dist/solana/solana_transaction_status.d.ts.map +1 -0
  117. package/dist/solana/solana_transaction_status.js +144 -0
  118. package/dist/solana/solana_transaction_status.js.map +1 -0
  119. package/dist/solana/unsigned_solana_transaction.d.ts +31 -0
  120. package/dist/solana/unsigned_solana_transaction.d.ts.map +1 -0
  121. package/dist/solana/unsigned_solana_transaction.js +26 -0
  122. package/dist/solana/unsigned_solana_transaction.js.map +1 -0
  123. package/dist/token.d.ts +35 -0
  124. package/dist/token.d.ts.map +1 -0
  125. package/dist/token.js +62 -0
  126. package/dist/token.js.map +1 -0
  127. package/dist/ton/index.d.ts +2 -0
  128. package/dist/ton/index.d.ts.map +1 -0
  129. package/dist/ton/index.js +2 -0
  130. package/dist/ton/index.js.map +1 -0
  131. package/dist/ton/ton_address.d.ts +10 -0
  132. package/dist/ton/ton_address.d.ts.map +1 -0
  133. package/dist/ton/ton_address.js +53 -0
  134. package/dist/ton/ton_address.js.map +1 -0
  135. package/dist/transaction_status.d.ts +140 -0
  136. package/dist/transaction_status.d.ts.map +1 -0
  137. package/dist/transaction_status.js +271 -0
  138. package/dist/transaction_status.js.map +1 -0
  139. package/dist/unsigned_transaction.d.ts +7 -0
  140. package/dist/unsigned_transaction.d.ts.map +1 -0
  141. package/dist/unsigned_transaction.js +9 -0
  142. package/dist/unsigned_transaction.js.map +1 -0
  143. package/dist/utxo/btc/btc_address.d.ts +15 -0
  144. package/dist/utxo/btc/btc_address.d.ts.map +1 -0
  145. package/dist/utxo/btc/btc_address.js +39 -0
  146. package/dist/utxo/btc/btc_address.js.map +1 -0
  147. package/dist/utxo/btc/btc_chain.d.ts +34 -0
  148. package/dist/utxo/btc/btc_chain.d.ts.map +1 -0
  149. package/dist/utxo/btc/btc_chain.js +62 -0
  150. package/dist/utxo/btc/btc_chain.js.map +1 -0
  151. package/dist/utxo/btc/btc_chains.d.ts +25 -0
  152. package/dist/utxo/btc/btc_chains.d.ts.map +1 -0
  153. package/dist/utxo/btc/btc_chains.js +43 -0
  154. package/dist/utxo/btc/btc_chains.js.map +1 -0
  155. package/dist/utxo/btc/btc_token.d.ts +6 -0
  156. package/dist/utxo/btc/btc_token.d.ts.map +1 -0
  157. package/dist/utxo/btc/btc_token.js +11 -0
  158. package/dist/utxo/btc/btc_token.js.map +1 -0
  159. package/dist/utxo/btc/index.d.ts +14 -0
  160. package/dist/utxo/btc/index.d.ts.map +1 -0
  161. package/dist/utxo/btc/index.js +14 -0
  162. package/dist/utxo/btc/index.js.map +1 -0
  163. package/dist/utxo/btc/network_params.d.ts +50 -0
  164. package/dist/utxo/btc/network_params.d.ts.map +1 -0
  165. package/dist/utxo/btc/network_params.js +188 -0
  166. package/dist/utxo/btc/network_params.js.map +1 -0
  167. package/dist/utxo/btc/tools/asset_outpoint.d.ts +7 -0
  168. package/dist/utxo/btc/tools/asset_outpoint.d.ts.map +1 -0
  169. package/dist/utxo/btc/tools/asset_outpoint.js +12 -0
  170. package/dist/utxo/btc/tools/asset_outpoint.js.map +1 -0
  171. package/dist/utxo/btc/tools/bitcoin_core.tool.d.ts +41 -0
  172. package/dist/utxo/btc/tools/bitcoin_core.tool.d.ts.map +1 -0
  173. package/dist/utxo/btc/tools/bitcoin_core.tool.js +172 -0
  174. package/dist/utxo/btc/tools/bitcoin_core.tool.js.map +1 -0
  175. package/dist/utxo/btc/tools/esplora.tool.d.ts +27 -0
  176. package/dist/utxo/btc/tools/esplora.tool.d.ts.map +1 -0
  177. package/dist/utxo/btc/tools/esplora.tool.js +113 -0
  178. package/dist/utxo/btc/tools/esplora.tool.js.map +1 -0
  179. package/dist/utxo/btc/tools/inscription_index.d.ts +6 -0
  180. package/dist/utxo/btc/tools/inscription_index.d.ts.map +1 -0
  181. package/dist/utxo/btc/tools/inscription_index.js +2 -0
  182. package/dist/utxo/btc/tools/inscription_index.js.map +1 -0
  183. package/dist/utxo/btc/tools/ordiscan.tool.d.ts +19 -0
  184. package/dist/utxo/btc/tools/ordiscan.tool.d.ts.map +1 -0
  185. package/dist/utxo/btc/tools/ordiscan.tool.js +53 -0
  186. package/dist/utxo/btc/tools/ordiscan.tool.js.map +1 -0
  187. package/dist/utxo/btc/tools/rare_sat_index.d.ts +6 -0
  188. package/dist/utxo/btc/tools/rare_sat_index.d.ts.map +1 -0
  189. package/dist/utxo/btc/tools/rare_sat_index.js +2 -0
  190. package/dist/utxo/btc/tools/rare_sat_index.js.map +1 -0
  191. package/dist/utxo/btc/tools/rune_index.d.ts +6 -0
  192. package/dist/utxo/btc/tools/rune_index.d.ts.map +1 -0
  193. package/dist/utxo/btc/tools/rune_index.js +2 -0
  194. package/dist/utxo/btc/tools/rune_index.js.map +1 -0
  195. package/dist/utxo/btc/tools/unisat.tool.d.ts +18 -0
  196. package/dist/utxo/btc/tools/unisat.tool.d.ts.map +1 -0
  197. package/dist/utxo/btc/tools/unisat.tool.js +49 -0
  198. package/dist/utxo/btc/tools/unisat.tool.js.map +1 -0
  199. package/dist/utxo/coin_selection.d.ts +29 -0
  200. package/dist/utxo/coin_selection.d.ts.map +1 -0
  201. package/dist/utxo/coin_selection.js +156 -0
  202. package/dist/utxo/coin_selection.js.map +1 -0
  203. package/dist/utxo/doge/doge_chains.d.ts +17 -0
  204. package/dist/utxo/doge/doge_chains.d.ts.map +1 -0
  205. package/dist/utxo/doge/doge_chains.js +28 -0
  206. package/dist/utxo/doge/doge_chains.js.map +1 -0
  207. package/dist/utxo/doge/index.d.ts +3 -0
  208. package/dist/utxo/doge/index.d.ts.map +1 -0
  209. package/dist/utxo/doge/index.js +3 -0
  210. package/dist/utxo/doge/index.js.map +1 -0
  211. package/dist/utxo/doge/network_params.d.ts +4 -0
  212. package/dist/utxo/doge/network_params.d.ts.map +1 -0
  213. package/dist/utxo/doge/network_params.js +24 -0
  214. package/dist/utxo/doge/network_params.js.map +1 -0
  215. package/dist/utxo/ecc.d.ts +2 -0
  216. package/dist/utxo/ecc.d.ts.map +1 -0
  217. package/dist/utxo/ecc.js +11 -0
  218. package/dist/utxo/ecc.js.map +1 -0
  219. package/dist/utxo/fee.d.ts +12 -0
  220. package/dist/utxo/fee.d.ts.map +1 -0
  221. package/dist/utxo/fee.js +51 -0
  222. package/dist/utxo/fee.js.map +1 -0
  223. package/dist/utxo/index.d.ts +20 -0
  224. package/dist/utxo/index.d.ts.map +1 -0
  225. package/dist/utxo/index.js +20 -0
  226. package/dist/utxo/index.js.map +1 -0
  227. package/dist/utxo/ltc/index.d.ts +3 -0
  228. package/dist/utxo/ltc/index.d.ts.map +1 -0
  229. package/dist/utxo/ltc/index.js +3 -0
  230. package/dist/utxo/ltc/index.js.map +1 -0
  231. package/dist/utxo/ltc/ltc_chains.d.ts +17 -0
  232. package/dist/utxo/ltc/ltc_chains.d.ts.map +1 -0
  233. package/dist/utxo/ltc/ltc_chains.js +28 -0
  234. package/dist/utxo/ltc/ltc_chains.js.map +1 -0
  235. package/dist/utxo/ltc/network_params.d.ts +4 -0
  236. package/dist/utxo/ltc/network_params.d.ts.map +1 -0
  237. package/dist/utxo/ltc/network_params.js +27 -0
  238. package/dist/utxo/ltc/network_params.js.map +1 -0
  239. package/dist/utxo/script.d.ts +16 -0
  240. package/dist/utxo/script.d.ts.map +1 -0
  241. package/dist/utxo/script.js +52 -0
  242. package/dist/utxo/script.js.map +1 -0
  243. package/dist/utxo/tools/broadcaster.d.ts +6 -0
  244. package/dist/utxo/tools/broadcaster.d.ts.map +1 -0
  245. package/dist/utxo/tools/broadcaster.js +2 -0
  246. package/dist/utxo/tools/broadcaster.js.map +1 -0
  247. package/dist/utxo/tools/chain_tip_provider.d.ts +5 -0
  248. package/dist/utxo/tools/chain_tip_provider.d.ts.map +1 -0
  249. package/dist/utxo/tools/chain_tip_provider.js +2 -0
  250. package/dist/utxo/tools/chain_tip_provider.js.map +1 -0
  251. package/dist/utxo/tools/fee_estimator.d.ts +6 -0
  252. package/dist/utxo/tools/fee_estimator.d.ts.map +1 -0
  253. package/dist/utxo/tools/fee_estimator.js +2 -0
  254. package/dist/utxo/tools/fee_estimator.js.map +1 -0
  255. package/dist/utxo/tools/raw_transaction_provider.d.ts +8 -0
  256. package/dist/utxo/tools/raw_transaction_provider.d.ts.map +1 -0
  257. package/dist/utxo/tools/raw_transaction_provider.js +2 -0
  258. package/dist/utxo/tools/raw_transaction_provider.js.map +1 -0
  259. package/dist/utxo/tools/utxo_provider.d.ts +7 -0
  260. package/dist/utxo/tools/utxo_provider.d.ts.map +1 -0
  261. package/dist/utxo/tools/utxo_provider.js +2 -0
  262. package/dist/utxo/tools/utxo_provider.js.map +1 -0
  263. package/dist/utxo/unsigned_utxo_transaction.d.ts +32 -0
  264. package/dist/utxo/unsigned_utxo_transaction.d.ts.map +1 -0
  265. package/dist/utxo/unsigned_utxo_transaction.js +32 -0
  266. package/dist/utxo/unsigned_utxo_transaction.js.map +1 -0
  267. package/dist/utxo/utxo.d.ts +43 -0
  268. package/dist/utxo/utxo.d.ts.map +1 -0
  269. package/dist/utxo/utxo.js +2 -0
  270. package/dist/utxo/utxo.js.map +1 -0
  271. package/dist/utxo/utxo_chain.d.ts +102 -0
  272. package/dist/utxo/utxo_chain.d.ts.map +1 -0
  273. package/dist/utxo/utxo_chain.js +524 -0
  274. package/dist/utxo/utxo_chain.js.map +1 -0
  275. package/dist/utxo/utxo_chains.d.ts +29 -0
  276. package/dist/utxo/utxo_chains.d.ts.map +1 -0
  277. package/dist/utxo/utxo_chains.js +63 -0
  278. package/dist/utxo/utxo_chains.js.map +1 -0
  279. package/dist/utxo/utxo_gas_pricing.d.ts +22 -0
  280. package/dist/utxo/utxo_gas_pricing.d.ts.map +1 -0
  281. package/dist/utxo/utxo_gas_pricing.js +20 -0
  282. package/dist/utxo/utxo_gas_pricing.js.map +1 -0
  283. package/dist/utxo/utxo_network_params.d.ts +25 -0
  284. package/dist/utxo/utxo_network_params.d.ts.map +1 -0
  285. package/dist/utxo/utxo_network_params.js +15 -0
  286. package/dist/utxo/utxo_network_params.js.map +1 -0
  287. package/dist/utxo/utxo_transaction_status.d.ts +73 -0
  288. package/dist/utxo/utxo_transaction_status.d.ts.map +1 -0
  289. package/dist/utxo/utxo_transaction_status.js +89 -0
  290. package/dist/utxo/utxo_transaction_status.js.map +1 -0
  291. package/docs/PUBLISHING.md +119 -0
  292. package/docs/README.md +273 -0
  293. package/docs/evm.md +205 -0
  294. package/docs/solana.md +220 -0
  295. package/docs/utxo.md +364 -0
  296. package/package.json +94 -0
package/docs/solana.md ADDED
@@ -0,0 +1,220 @@
1
+ # Solana for EVM developers
2
+
3
+ A focused guide for someone who already knows EVM well. We map every concept to its EVM
4
+ counterpart, then explain what's different.
5
+
6
+ ## TL;DR mapping
7
+
8
+ | EVM concept | Solana counterpart | Note |
9
+ |---|---|---|
10
+ | Account (address) | Account (Pubkey, 32-byte ed25519) | Base58 string, no checksum |
11
+ | ETH balance on account | Lamports owned by the *system program* | `connection.getBalance(pubkey)` |
12
+ | ERC-20 token | SPL Token (mint) | Mint is a Solana address |
13
+ | ERC-20 balance | Associated Token Account (**ATA**) holding the balance | Wallet does NOT hold token balances directly |
14
+ | Transaction | Transaction (legacy or **VersionedTransaction**) | A bundle of *instructions* |
15
+ | Calldata | Instructions calling Programs | One tx may contain N instructions targeting different programs |
16
+ | `msg.sender` | Account signers list | Multiple signers per tx; first signer is the fee payer |
17
+ | Nonce | Recent blockhash + last valid block height | No per-account monotonic nonce — blockhash expiry replaces it |
18
+ | Gas units | Compute Units (CU), default 200k per ix | `ComputeBudgetProgram.setComputeUnitLimit(...)` |
19
+ | Gas price | Base fee (5,000 lamports per signature) + **priority fee** in microlamports/CU | Set via `ComputeBudgetProgram.setComputeUnitPrice(...)` |
20
+ | EIP-1559 priority fee | Same idea — `setComputeUnitPrice` is the lever | No `maxFeePerGas` cap; the price you set IS the price |
21
+ | Replace-by-fee (RBF) | Not supported | Once submitted, you can't bump. Just sign a higher-fee tx with the *same* blockhash within its validity window. |
22
+ | Mempool | Leader's local mempool | Not consensus mempool; tx flows directly to current/next leader |
23
+ | `eth_chainId` | Genesis block hash (CAIP-2 `solana:<first-32-of-hash>`) | Synthetic negative chainIds: mainnet `-2000`, testnet `-2001`, devnet `-2002` (matches omnichain-py `chain_ids.py`). The pre-v0 `-100/-101/-102` scheme is **NOT** registered as aliases — `networkTypeOf(-100)` throws `ChainError(ChainNotSupported)` at validation. Consumers must run `migrateLegacySolanaChainId(id)` on persisted rows *before* the value enters the SDK. |
24
+ | Finality | `processed → confirmed → finalized` | We use `confirmed` for status reads (~12 slots ≈ 5 s) |
25
+ | Block time | ~12 s | ~0.4 s |
26
+
27
+ ## Accounts are everything
28
+
29
+ The single biggest difference: **all state lives in accounts**. There is no "contract storage
30
+ slot" model. Every piece of mutable data — token balances, NFT metadata, user preferences,
31
+ swap pools — is its own Solana account, owned by some program.
32
+
33
+ ```
34
+ Account {
35
+ pubkey: Pubkey // base58 32-byte ed25519 public key
36
+ lamports: u64 // native SOL balance (in 1e-9 units)
37
+ owner: Pubkey // the program that controls this account's data
38
+ data: bytes // the program decides the layout
39
+ executable: bool // true for programs themselves
40
+ rent_epoch: u64 // legacy
41
+ }
42
+ ```
43
+
44
+ `owner` is the killer concept. A "user wallet" is owned by the **System Program**
45
+ (`11111111111111111111111111111111`). When the System Program owns an account, *only* it can
46
+ move that account's lamports. That's why `SystemProgram.transfer({fromPubkey, toPubkey,
47
+ lamports})` is the only way to move SOL between wallets — the System Program is the program
48
+ that knows how to debit `from` and credit `to`.
49
+
50
+ For SPL tokens, the **SPL Token Program** (`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) owns
51
+ the token accounts. Two flavours: classic SPL Token (above) and **Token-2022**
52
+ (`TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`). They're separate program IDs with mostly
53
+ overlapping instruction layouts. **You must use the right program ID** for the mint at hand.
54
+ The SDK reads the mint account's `owner` field to figure out which.
55
+
56
+ ## Transactions are instruction lists
57
+
58
+ A Solana transaction is `Vec<Instruction>` plus signers and a recent blockhash. Each
59
+ instruction is `{ program_id, accounts: Vec<Account>, data: Vec<u8> }`. The program_id field
60
+ routes execution to a specific program; `data` is the instruction-specific calldata; `accounts`
61
+ declares every account the instruction reads or writes (Solana is **explicit about side effects**
62
+ — if you don't list an account in `accounts`, the instruction can't touch it).
63
+
64
+ ```ts
65
+ // EVM mental model — one call per tx:
66
+ contract.transfer(to, amount);
67
+
68
+ // Solana mental model — N instructions, each calling a program:
69
+ new TransactionBuilder([
70
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
71
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000 }), // priority fee
72
+ createAssociatedTokenAccountInstruction(payer, ata, owner, mint, programId), // create-ATA if missing
73
+ createTransferCheckedInstruction(sourceAta, mint, destAta, owner, amount, decimals, [], programId),
74
+ ]);
75
+ ```
76
+
77
+ Multiple instructions execute **atomically**: if any one fails (program error, account
78
+ constraint violation, missing signer), the whole transaction is rolled back. Solana's
79
+ equivalent of "revert" is `InstructionError` with a program-defined code.
80
+
81
+ ### MessageV0 (VersionedTransaction)
82
+
83
+ Legacy transactions allow up to ~35 unique accounts before exceeding the 1232-byte tx limit.
84
+ **MessageV0** (versioned) supports Address Lookup Tables (ALTs) that pack repeated accounts
85
+ into 1-byte indices, raising the practical limit to hundreds. The depositron chain SDK
86
+ always emits MessageV0; legacy is essentially deprecated.
87
+
88
+ ## Native SOL transfer
89
+
90
+ ```ts
91
+ import { SystemProgram, VersionedTransaction, MessageV0, Connection } from '@solana/web3.js';
92
+
93
+ const connection = new Connection(rpcUrl, 'confirmed');
94
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('finalized');
95
+ const message = MessageV0.compile({
96
+ payerKey: senderPk,
97
+ instructions: [SystemProgram.transfer({ fromPubkey: senderPk, toPubkey: receiverPk, lamports: 1_000_000n })],
98
+ recentBlockhash: blockhash,
99
+ });
100
+ const tx = new VersionedTransaction(message);
101
+ tx.sign([senderKeypair]);
102
+ const signature = await connection.sendRawTransaction(tx.serialize());
103
+ ```
104
+
105
+ That `signature` IS the transaction hash. Solana txs are uniquely identified by the
106
+ first signature (base58, 88 chars).
107
+
108
+ ## SPL token transfer — the ATA gotcha
109
+
110
+ A wallet `Owner` doesn't hold token X directly. Instead, the wallet has one **Associated
111
+ Token Account** (ATA) per (mint, program_id) pair. The ATA address is a deterministic PDA
112
+ (Program-Derived Address) of `(owner, mint, token_program_id)`. You always have to:
113
+
114
+ 1. Derive `source_ata = ATA(sender, mint, program)`.
115
+ 2. Derive `dest_ata = ATA(recipient, mint, program)`.
116
+ 3. If `dest_ata` doesn't exist on chain, **prepend** a `createAssociatedTokenAccount` ix to
117
+ the tx (the sender pays ~0.00203 SOL rent-exempt deposit, locked in the ATA forever).
118
+ 4. Add `transferChecked(source_ata, mint, dest_ata, sender, amount, decimals, programId)`.
119
+
120
+ `transferChecked` is preferred over `transfer` because it includes the mint and decimals in
121
+ the instruction — the program verifies them, defeating "wrong-decimals" UI attacks. Use
122
+ `transferChecked` everywhere.
123
+
124
+ ## Priority fees
125
+
126
+ There is no `maxPriorityFeePerGas` / `maxFeePerGas` split. Two knobs:
127
+
128
+ - `setComputeUnitLimit(units)` — your declared budget. Charged for whichever is smaller:
129
+ actual CU used or this limit. Default cap is 200k per ix, max 1.4M per tx.
130
+ - `setComputeUnitPrice(microLamports)` — price per CU. **This is the priority fee.** A value
131
+ of 1000 microlamports/CU at a 200k limit costs `0.0002 SOL = 200k × 1000 / 1e9`.
132
+
133
+ Depositron maps its `Priority` enum:
134
+
135
+ | Priority | microLamports per CU |
136
+ |---|---|
137
+ | `SLOW` | 0 |
138
+ | `NORMAL` | 100 |
139
+ | `FAST` | 1000 |
140
+
141
+ ## No nonce — blockhash expiry instead
142
+
143
+ You include a **recent blockhash** in every transaction. The validator accepts the tx only if
144
+ the blockhash is still within ~150 slots (~1 minute) of the current tip. After that, the tx is
145
+ permanently invalid; you can't re-submit. To "retry" you rebuild with a fresh blockhash.
146
+
147
+ This is depositron's "PSBT-replay-impossible" property for free — once a Solana tx expires, it
148
+ can't be revived. The trade-off: you can't pre-sign + delay submission for >1 minute.
149
+
150
+ ## Status polling
151
+
152
+ `getSignatureStatus(sig)` returns `{ confirmationStatus: 'processed'|'confirmed'|'finalized',
153
+ err: null|object }`. Depositron treats `confirmed | finalized` as terminal success
154
+ (or terminal failure if `err` is set). `processed` means "leader saw it" but not yet voted on
155
+ by the cluster — we keep polling.
156
+
157
+ `getTransaction(sig, { commitment: 'confirmed', maxSupportedTransactionVersion: 0 })` returns
158
+ the full receipt: slot, meta.fee (in lamports), meta.preBalances / postBalances (per account
159
+ SOL deltas) and meta.preTokenBalances / postTokenBalances (per ATA token deltas). The SDK
160
+ decodes these into a `NestedBalanceChanges` — the same nested Map shape EVM emits —
161
+ and returns a `SolanaTransactionStatus` (extends `TransactionStatus`) with `fees:
162
+ SolanaTransactionFees` alongside. Wallet keys are the raw base58 (case-sensitive).
163
+
164
+ Fallback path when `getTransaction` returns null:
165
+ - `getSignatureStatus` yields no value at all → `NotFound`.
166
+ - `getSignatureStatus` reports `finalized`/`confirmed` with `err` set →
167
+ `SolanaTransactionStatus.failed` with `fees: null` (settled-but-
168
+ unfetchable — fees can't be reconstructed from sig-status alone).
169
+ - `getSignatureStatus` reports `processed` or is otherwise settled
170
+ without `err` → `Pending` so consumers keep polling rather than
171
+ treating a settled deposit as NotFound.
172
+ - If `getTransaction` returns a body but `tx.meta === null` → `Pending`
173
+ as well.
174
+
175
+ Consumers should treat Solana `NotFound` as retryable, not terminal —
176
+ signature-status can lag full-tx availability on a heavily load-
177
+ balanced RPC.
178
+
179
+ The `balanceChangesExcludingFees(nativeAsset)` helper strips Solana's fee_payer debit
180
+ from the emitted changes for callers who want gross-of-fee movements. It validates that
181
+ `nativeAsset` matches this chain's native token (`chainId` match + empty `identifier`)
182
+ and throws `ChainError(InvalidArgument)` otherwise.
183
+
184
+ ## Wallet derivation (BIP44 ed25519)
185
+
186
+ Depositron derives Solana keypairs via SLIP-0010 `m/44'/501'/{account}'/0'`. `501` is the
187
+ SLIP-44 coin type for SOL. **Unlike EVM, Solana uses ed25519, not secp256k1**, so we use the
188
+ `ed25519-hd-key` package — `ethers.HDNodeWallet` won't work (it produces secp256k1 keys).
189
+
190
+ ```ts
191
+ import { derivePath } from 'ed25519-hd-key';
192
+ import { Keypair } from '@solana/web3.js';
193
+ import * as bip39 from 'bip39';
194
+
195
+ const seed = await bip39.mnemonicToSeed(mnemonic);
196
+ const { key } = derivePath(`m/44'/501'/${index}'/0'`, seed.toString('hex'));
197
+ const kp = Keypair.fromSeed(key); // 32-byte ed25519 seed → keypair
198
+ const address = kp.publicKey.toBase58();
199
+ ```
200
+
201
+ Phantom and Solflare default to `m/44'/501'/0'/0'` for "account 0", incrementing the third
202
+ component for "account 1", "account 2", etc. (NOT the address_index slot — Solana wallets
203
+ typically expose ACCOUNT index, not address index).
204
+
205
+ ## Common attacks / footguns
206
+
207
+ - **Wrong token program**: SPL Token vs Token-2022 — using the wrong program ID for a mint
208
+ causes `IncorrectProgramId`. Always read the mint account's `owner` to decide.
209
+ - **ATA already exists but wrong type**: a recipient may have a Token account that ISN'T the
210
+ associated one (legacy or another wallet's). `transferChecked` to a non-ATA still works as
211
+ long as it's owned by the recipient and the correct mint, but depositron always uses ATAs.
212
+ - **Dust attacks via SPL Token-2022 hooks**: Token-2022 supports "transfer hooks" — programs
213
+ that run on every transfer of that mint. A malicious mint can do near-arbitrary work in the
214
+ hook. We don't inspect mints for hooks; treat each mint as an opaque external program.
215
+ - **Rent**: every account on Solana pays ~0.00089 SOL per 128 bytes per epoch unless it has
216
+ enough lamports to be "rent-exempt". ATAs are 165 bytes → ~2039 lamports rent-exempt
217
+ deposit. When you create an ATA, the payer pays this deposit; it's recoverable by closing
218
+ the ATA later.
219
+ - **No revert reason strings**: Solana program errors are codes (`u32`), not strings. Programs
220
+ document them; depositron passes `code: 'REVERTED'` for failed status.
package/docs/utxo.md ADDED
@@ -0,0 +1,364 @@
1
+ # UTXO chains (Bitcoin / Litecoin / Dogecoin)
2
+
3
+ `UtxoChain` is the generic base for any Bitcoin-derived UTXO network. It
4
+ handles the whole transfer pipeline — UTXO fetch, branch-and-bound coin
5
+ selection, PSBT construction with `nonWitnessUtxo` on every input,
6
+ RBF sequencing, OP_RETURN memos.
7
+
8
+ `BtcChain extends UtxoChain` adds Bitcoin-only concerns (inscription /
9
+ rune / rare-sat filtering).
10
+
11
+ ## Files
12
+
13
+ ### Generic UTXO layer
14
+
15
+ - [utxo_chain.ts](../utxo/utxo_chain.ts) — `UtxoChain extends Chain`
16
+ - [utxo_network_params.ts](../utxo/utxo_network_params.ts) — `UtxoNetworkParams`, SLIP-44 coin ids, derivation purposes, dust default, RBF sequence
17
+ - [unsigned_utxo_transaction.ts](../utxo/unsigned_utxo_transaction.ts) — `UnsignedUtxoTransaction { psbtBase64, selectedInputs, feeSats, inputsToSign, … }`
18
+ - [utxo.ts](../utxo/utxo.ts) — `UnspentTransactionOutput`, `RawTransactionView`, `FeeEstimate`, `AddressBalance`
19
+ - [script.ts](../utxo/script.ts) — `detectScriptType`, `buildOpReturnScript`, `scriptTypeForAddress`
20
+ - [fee.ts](../utxo/fee.ts) — per-script-type vByte tables, fee math
21
+ - [coin_selection.ts](../utxo/coin_selection.ts) — branch-and-bound + accumulator fallback over effective-value
22
+ - [ecc.ts](../utxo/ecc.ts) — one-time `initEccLib(tinySecp256k1)` for Taproot
23
+ - [tools/](../utxo/tools) — 5 role interfaces (see below)
24
+ - [utxo_chains.ts](../utxo/utxo_chains.ts) — `utxoChainFromCoin` / `utxoChainFromSlip44` dispatchers
25
+
26
+ ### Per-coin implementations
27
+
28
+ - [btc/](../utxo/btc) — Bitcoin (mainnet/testnet/signet/regtest) + asset indexes
29
+ - [ltc/](../utxo/ltc) — Litecoin (mainnet/testnet) via `coininfo`
30
+ - [doge/](../utxo/doge) — Dogecoin (mainnet/testnet) via `coininfo`
31
+
32
+ ## Role-provider interfaces
33
+
34
+ `UtxoChain` doesn't talk to any one provider — it depends on five small
35
+ interfaces. A single concrete tool (like `BitcoinCoreTool`) can implement
36
+ all five, or you can wire different providers per role:
37
+
38
+ | Interface | Method | Default impls |
39
+ |---|---|---|
40
+ | [`UtxoProvider`](../utxo/tools/utxo_provider.ts) | `getUtxos`, `getAddressBalance` | `BitcoinCoreTool`, `EsploraTool` |
41
+ | [`UtxoRawTransactionProvider`](../utxo/tools/raw_transaction_provider.ts) | `getRawTransactionHex`, `getRawTransactionHexBatch`, `getTransaction` | `BitcoinCoreTool`, `EsploraTool` |
42
+ | [`UtxoFeeEstimator`](../utxo/tools/fee_estimator.ts) | `getFeeEstimate(targetBlocks)` | `BitcoinCoreTool` (estimatesmartfee), `EsploraTool` (Esplora `/fee-estimates`) |
43
+ | [`UtxoBroadcaster`](../utxo/tools/broadcaster.ts) | `broadcast(rawHex)` | `BitcoinCoreTool` (sendrawtransaction), `EsploraTool` (POST `/tx`) |
44
+ | [`UtxoChainTipProvider`](../utxo/tools/chain_tip_provider.ts) | `getChainTipHeight()` | `BitcoinCoreTool` (getblockcount), `EsploraTool` (`/blocks/tip/height`) |
45
+
46
+ Mix-and-match example:
47
+
48
+ ```ts
49
+ const core = new BitcoinCoreTool({ /* … */ });
50
+ const esplora = new EsploraTool({
51
+ baseUrl: 'https://mempool.space/api',
52
+ params: BITCOIN_MAINNET_PARAMS,
53
+ });
54
+
55
+ const chain = bitcoinMainnetChain({
56
+ chainId: -1n,
57
+ utxoProvider: esplora, // Esplora handles arbitrary addresses without import
58
+ rawTxProvider: core, // Core's batch RPC fetches multiple parent txs in one round-trip
59
+ feeEstimator: esplora, // mempool.space has good fee estimates
60
+ broadcaster: core, // broadcast locally
61
+ chainTipProvider: esplora,
62
+ });
63
+ ```
64
+
65
+ ## Constructing a Bitcoin chain
66
+
67
+ ```ts
68
+ import {
69
+ bitcoinMainnetChain,
70
+ bitcoinTestnetChain,
71
+ bitcoinSignetChain,
72
+ bitcoinRegtestChain,
73
+ BitcoinCoreTool,
74
+ EsploraTool,
75
+ BITCOIN_MAINNET_PARAMS,
76
+ } from 'src/modules/chain';
77
+
78
+ const core = new BitcoinCoreTool({
79
+ baseUrl: 'http://localhost:8332',
80
+ user: 'rpcuser',
81
+ password: 'rpcpassword',
82
+ params: BITCOIN_MAINNET_PARAMS,
83
+ wallet: 'hot', // RPC routes to `/wallet/hot/`
84
+ importTimestamp: 'now', // descriptor import without rescan (default)
85
+ watchOnlyLabel: 'pluton', // label applied to imported descriptors
86
+ });
87
+
88
+ const btc = bitcoinMainnetChain({
89
+ chainId: -1n,
90
+ utxoProvider: core,
91
+ rawTxProvider: core,
92
+ feeEstimator: core,
93
+ broadcaster: core,
94
+ chainTipProvider: core,
95
+ });
96
+ ```
97
+
98
+ The chain registers itself with `NetworkType.BTC` in the global network-type
99
+ registry when constructed, so `addressFor(-1, 'bc1q…')` will return a
100
+ `BtcAddress` afterwards.
101
+
102
+ ### Litecoin
103
+
104
+ ```ts
105
+ import { litecoinMainnetChain, litecoinTestnetChain } from 'src/modules/chain';
106
+
107
+ const ltc = litecoinMainnetChain({
108
+ chainId: -10n,
109
+ utxoProvider: ltcCore,
110
+ rawTxProvider: ltcCore,
111
+ feeEstimator: ltcCore,
112
+ broadcaster: ltcCore,
113
+ chainTipProvider: ltcCore,
114
+ });
115
+ ```
116
+
117
+ LTC supports BIP-44/49/84 (no Taproot). Network params come from
118
+ `coininfo.litecoin.main.toBitcoinJS()`.
119
+
120
+ ### Dogecoin
121
+
122
+ ```ts
123
+ import { dogecoinMainnetChain } from 'src/modules/chain';
124
+
125
+ const doge = dogecoinMainnetChain({
126
+ chainId: -20n,
127
+ // … same 5 role providers …
128
+ });
129
+ ```
130
+
131
+ DOGE supports only BIP-44 (no segwit). Dust is `1_000_000` sats — DOGE's
132
+ network floor, much higher than BTC's 546.
133
+
134
+ ### Generic dispatcher
135
+
136
+ If you'd rather not import the per-coin factory:
137
+
138
+ ```ts
139
+ import { utxoChainFromCoin } from 'src/modules/chain';
140
+
141
+ const chain = utxoChainFromCoin({
142
+ coin: 'bitcoin', // 'bitcoin-testnet' | 'bitcoin-signet' | 'bitcoin-regtest'
143
+ // 'litecoin' | 'litecoin-testnet'
144
+ // 'dogecoin' | 'dogecoin-testnet'
145
+ chainId: -1n,
146
+ utxoProvider, rawTxProvider, feeEstimator, broadcaster, chainTipProvider,
147
+ });
148
+ ```
149
+
150
+ `utxoChainFromSlip44(coinId, isMainnet, opts)` exists too if you've got
151
+ SLIP-44 coin IDs to dispatch on.
152
+
153
+ ## Building a transfer
154
+
155
+ The `Chain` API works the same as EVM, with the BTC subclass accepting
156
+ extra options (`feeRateSatsPerVByte`, `excludeInscriptions`, etc.):
157
+
158
+ ```ts
159
+ const unsigned = await btc.createTransferUnsignedTransaction({
160
+ from: senderAddr,
161
+ to: recipientAddr,
162
+ tokenIdentifier: 'NATIVE', // BTC has no non-native tokens
163
+ amount: 50_000_000n, // 0.5 BTC in sats
164
+ feeRateSatsPerVByte: 10, // override; otherwise asks feeEstimator
165
+ rbfEnabled: true, // default true → sequence = 0xFFFFFFFD
166
+ memo: 'invoice-#123', // optional, emits an OP_RETURN output
167
+ excludeInscriptions: true, // optional, requires inscriptionIndex configured
168
+ });
169
+
170
+ // unsigned is an UnsignedUtxoTransaction:
171
+ unsigned.psbtBase64 // base64-encoded PSBT (BIP-174)
172
+ unsigned.selectedInputs // UTXOs picked by coin selection
173
+ unsigned.feeSats // computed fee
174
+ unsigned.feeRateSatsPerVByte // rate used
175
+ unsigned.estimatedVBytes
176
+ unsigned.totalInputSats
177
+ unsigned.totalOutputSats
178
+ unsigned.changeAddress // null if change was absorbed into the fee
179
+ unsigned.inputsToSign // Record<address, inputIndex[]> for non-HD signers
180
+ ```
181
+
182
+ The PSBT carries `witnessUtxo` *and* `nonWitnessUtxo` on every input —
183
+ this is intentional. Legacy inputs need `nonWitnessUtxo` for correctness;
184
+ SegWit/Taproot inputs technically don't, but hardware wallets like Trezor
185
+ and Phantom demand it. Populating both is the safe default.
186
+
187
+ ## Signing
188
+
189
+ The chain module never holds keys. You hand the PSBT to whatever signs
190
+ it — a user's wallet, a hardware device, an external service. For
191
+ testing or for protocol-controlled wallets that talk to Bitcoin Core:
192
+
193
+ ```ts
194
+ // Sign via Bitcoin Core's wallet (it has the keys for sender's address):
195
+ const processed = await rpc('walletprocesspsbt', [unsigned.psbtBase64]);
196
+ // processed.complete === true if Core signed every input
197
+
198
+ // Finalize → raw transaction hex:
199
+ const finalized = await rpc('finalizepsbt', [processed.psbt]);
200
+ // finalized.complete === true if all inputs are now scriptSig/witness-ready
201
+ // finalized.hex is broadcast-ready
202
+ ```
203
+
204
+ ## Broadcasting
205
+
206
+ ```ts
207
+ const result = await btc.broadcaster.broadcast(finalized.hex);
208
+ // result.txid: '<64-hex>'
209
+ ```
210
+
211
+ ## Address validation
212
+
213
+ ```ts
214
+ btc.validateWalletAddress('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'); // true
215
+ btc.validateWalletAddress('tb1q…'); // false on mainnet
216
+ btc.validateTokenIdentifier('NATIVE'); // true
217
+ ```
218
+
219
+ All five address types are supported per chain: P2PKH (`1…`), P2SH
220
+ (`3…`), P2WPKH (`bc1q…` 42-char bech32), P2WSH (`bc1q…` 62-char bech32),
221
+ P2TR (`bc1p…` bech32m).
222
+
223
+ ## Asset filtering (Bitcoin only)
224
+
225
+ To avoid accidentally spending UTXOs that carry inscriptions, runes, or
226
+ rare sats, wire one or more asset indexes into the chain:
227
+
228
+ ```ts
229
+ import { OrdiscanIndex, UnisatIndex } from 'src/modules/chain';
230
+
231
+ const ordiscan = new OrdiscanIndex({ apiKey: process.env.ORDISCAN_API_KEY! });
232
+ const unisat = new UnisatIndex({ apiKey: process.env.UNISAT_API_KEY! });
233
+
234
+ const btc = bitcoinMainnetChain({
235
+ chainId: -1n,
236
+ utxoProvider: core, rawTxProvider: core, feeEstimator: core,
237
+ broadcaster: core, chainTipProvider: core,
238
+ inscriptionIndex: unisat, // UniSat is inscription-only
239
+ runeIndex: ordiscan, // Ordiscan covers runes
240
+ rareSatIndex: ordiscan, // Ordiscan covers rare sats
241
+ });
242
+ ```
243
+
244
+ Then, per call, the caller opts into the filter:
245
+
246
+ ```ts
247
+ const unsigned = await btc.createTransferUnsignedTransaction({
248
+ from: senderAddr,
249
+ to: recipientAddr,
250
+ tokenIdentifier: 'NATIVE',
251
+ amount: 50_000_000n,
252
+ excludeInscriptions: true,
253
+ excludeRunes: true,
254
+ excludeRareSats: true,
255
+ });
256
+ ```
257
+
258
+ Each `excludeXxx: true` is validated against the configured index **before**
259
+ any HTTP call — explicit request without the corresponding index throws
260
+ fail-fast with a clear message:
261
+
262
+ ```
263
+ BtcChain ...: excludeRunes requested but no runeIndex was configured
264
+ ```
265
+
266
+ If all three flags are off / omitted (default), the chain doesn't consult
267
+ any index. If all three are on, the chain queries each index once per
268
+ sender address (parallel) and unions the tainted outpoint set before
269
+ coin selection sees the UTXOs.
270
+
271
+ ### Picking an asset-index provider
272
+
273
+ - **`OrdiscanIndex`** ([ordiscan.tool.ts](../utxo/btc/tools/ordiscan.tool.ts)) — implements all three asset interfaces. Three separate endpoints per address, fanned out in parallel.
274
+ - **`UnisatIndex`** ([unisat.tool.ts](../utxo/btc/tools/unisat.tool.ts)) — implements only `BtcInscriptionIndex`. Paginated.
275
+ - Both require an API key.
276
+ - For mainnet, prefer Ordiscan if you need rune/rare-sat coverage; for inscription-only filtering, UniSat is cheaper.
277
+
278
+ You can split per-concern: inscriptions from UniSat (cheaper), runes from
279
+ Ordiscan, etc. The interface system enforces that you can't accidentally
280
+ plug UniSat into a rune slot — `UnisatIndex` doesn't implement `BtcRuneIndex`.
281
+
282
+ ## Coin selection
283
+
284
+ Branch-and-bound first (exact-match within `cost_of_change`, no change
285
+ output emitted) → accumulator (largest-first) fallback. Effective value
286
+ math: each UTXO's worth is `valueSats − inputVBytes * feeRate`, so the
287
+ selector doesn't pick UTXOs that cost more to spend than they're worth.
288
+
289
+ If change after fee would be under dust (546 sats by default), the
290
+ selector absorbs the surplus into the fee instead of emitting a sub-dust
291
+ change output that the network would reject.
292
+
293
+ Missing vs Bitcoin Core's full algorithm: knapsack fallback,
294
+ `OutputGroup` privacy (treating multi-UTXO same-address as one atomic
295
+ spend). See the [bitcoin-coin-selection][bcs] Python port for what a
296
+ faithful re-implementation looks like.
297
+
298
+ [bcs]: https://github.com/jamesob/bitcoin-coin-selection
299
+
300
+ ## Bitcoin Core wallet integration
301
+
302
+ `BitcoinCoreTool` uses `listunspent` against a loaded wallet (faster than
303
+ `scantxoutset`, but the wallet has to "know" each address):
304
+
305
+ - On first `getUtxos(address)`, calls `getaddressinfo` to check if Core
306
+ already knows the address (`ismine` / `iswatchonly`).
307
+ - If not, runs `importdescriptors` with `timestamp: 'now'` (no rescan;
308
+ configurable via `importTimestamp`) and the `watchOnlyLabel` label.
309
+ - Caches the watched-address set in-process so subsequent calls skip
310
+ the check.
311
+
312
+ For `getrawtransaction` fetches (parent-tx hydration during PSBT
313
+ construction), the tool sends a **single array-body POST** with all
314
+ txids, taking advantage of Bitcoin Core's JSON-RPC batch protocol. One
315
+ round-trip regardless of how many parents the tx has.
316
+
317
+ ## Esplora integration
318
+
319
+ `EsploraTool` works against Blockstream's Esplora API or any compatible
320
+ implementation (mempool.space is a fork). No wallet, no rescan, no
321
+ import — anyone-can-query against arbitrary addresses. Trade-off:
322
+ public Esplora caps `/address/{addr}/utxo` at 500 UTXOs.
323
+
324
+ ## Testing
325
+
326
+ | Spec | Verifies |
327
+ |---|---|
328
+ | `btc_address.spec.ts` | Address validation across all 5 types × 4 networks |
329
+ | `script.spec.ts` | Script-type detection + OP_RETURN builder |
330
+ | `fee.spec.ts` | Per-script vByte tables, effective-value math |
331
+ | `coin_selection.spec.ts` | BnB + accumulator + change-absorbed-into-fee + multi-UTXO |
332
+ | `btc_chain.spec.ts` | Full PSBT round-trip with `FakeBtcTool` (in-memory provider) |
333
+ | `ordinal_filter.spec.ts` | Asset filter on/off + explicit-error semantics |
334
+ | `bitcoin_core_batch.spec.ts` | JSON-RPC array-body batching shape |
335
+ | `bitcoin_core_listunspent.spec.ts` | listunspent + auto-import flow |
336
+ | `unisat.spec.ts` | UniSat pagination + error handling |
337
+ | `test/integration/btc_regtest.e2e-spec.ts` | **Real Bitcoin Core regtest** — builds → signs → broadcasts a PSBT round-trip, including an OP_RETURN memo case and an insufficient-funds case |
338
+
339
+ ### Running the regtest e2e
340
+
341
+ ```bash
342
+ npm test -- test/integration/btc_regtest.e2e-spec.ts \
343
+ --config ./test/jest-e2e.json --runInBand
344
+ ```
345
+
346
+ Spins up `ruimarinho/bitcoin-core:24-alpine` via testcontainers (no host
347
+ config needed beyond Docker being installed). First run pulls the image
348
+ (~50 MB); subsequent runs are ~38 s total.
349
+
350
+ ## Adding another UTXO chain (e.g. Bitcoin Cash)
351
+
352
+ 1. Add the coin to SLIP-44 mapping in [utxo_network_params.ts](../utxo/utxo_network_params.ts).
353
+ 2. Create `chain/utxo/bch/`:
354
+ - `network_params.ts` — pull `coininfo.bitcoincash.main.toBitcoinJS()`,
355
+ set `supportedDerivationPurposes` (BCH typically only BIP-44),
356
+ define wallet-address regex.
357
+ - `bch_chains.ts` — factory functions taking `chainId` + role
358
+ providers, calling `new UtxoChain({ params: BCH_MAINNET_PARAMS, … })`.
359
+ 3. Add `export * from './bch';` to [utxo/index.ts](../utxo/index.ts).
360
+ 4. Extend the [utxoChainFromCoin](../utxo/utxo_chains.ts) dispatcher.
361
+
362
+ For chains where you need extra concerns (BCH doesn't have inscriptions
363
+ but has e.g. SLP tokens), follow the BTC pattern — extend `UtxoChain`
364
+ with a chain-specific subclass that adds the extra abstractions.
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@getomnichain/omnichain",
3
+ "version": "0.2.0",
4
+ "description": "Multi-chain SDK for EVM, Solana, UTXO (BTC/LTC/DOGE/DASH/ZEC/BCH), TON, and Tron. TypeScript parity port of omnichain-py.",
5
+ "keywords": [
6
+ "omnichain",
7
+ "blockchain",
8
+ "evm",
9
+ "ethereum",
10
+ "solana",
11
+ "bitcoin",
12
+ "utxo",
13
+ "ton",
14
+ "tron",
15
+ "sdk",
16
+ "multichain",
17
+ "web3"
18
+ ],
19
+ "homepage": "https://github.com/getomnichain/omnichain#readme",
20
+ "bugs": "https://github.com/getomnichain/omnichain/issues",
21
+ "license": "MIT",
22
+ "author": "getomnichain",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/getomnichain/omnichain.git"
26
+ },
27
+ "type": "module",
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./evm": {
36
+ "types": "./dist/evm/index.d.ts",
37
+ "default": "./dist/evm/index.js"
38
+ },
39
+ "./solana": {
40
+ "types": "./dist/solana/index.d.ts",
41
+ "default": "./dist/solana/index.js"
42
+ },
43
+ "./utxo": {
44
+ "types": "./dist/utxo/index.d.ts",
45
+ "default": "./dist/utxo/index.js"
46
+ },
47
+ "./ton": {
48
+ "types": "./dist/ton/index.d.ts",
49
+ "default": "./dist/ton/index.js"
50
+ },
51
+ "./package.json": "./package.json"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "docs",
56
+ "README.md",
57
+ "LICENSE",
58
+ "CHANGELOG.md"
59
+ ],
60
+ "scripts": {
61
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
62
+ "typecheck": "tsc -p tsconfig.json --noEmit",
63
+ "test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js",
64
+ "release": "bash scripts/release.sh",
65
+ "prepublishOnly": "npm run build"
66
+ },
67
+ "dependencies": {
68
+ "@solana/spl-token": "^0.4.14",
69
+ "@solana/web3.js": "^1.98.0",
70
+ "axios": "^1.7.0",
71
+ "bitcoinjs-lib": "^7.0.1",
72
+ "class-transformer": "^0.5.1",
73
+ "class-validator": "^0.15.1",
74
+ "coininfo": "^5.2.1",
75
+ "decimal.js": "^10.4.3",
76
+ "ethers": "^6.13.0",
77
+ "js-sha3": "^0.9.3",
78
+ "tiny-secp256k1": "^2.2.3"
79
+ },
80
+ "devDependencies": {
81
+ "@jest/globals": "^29.7.0",
82
+ "@types/jest": "^29.5.12",
83
+ "@types/node": "^22.13.1",
84
+ "jest": "^29.7.0",
85
+ "ts-jest": "^29.2.0",
86
+ "typescript": "^5.7.0"
87
+ },
88
+ "engines": {
89
+ "node": ">=20.0.0"
90
+ },
91
+ "publishConfig": {
92
+ "access": "public"
93
+ }
94
+ }