recon-crypto-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/abis/aave-pool.d.ts +174 -0
  4. package/dist/abis/aave-pool.js +101 -0
  5. package/dist/abis/aave-pool.js.map +1 -0
  6. package/dist/abis/aave-ui-pool-data-provider.d.ts +232 -0
  7. package/dist/abis/aave-ui-pool-data-provider.js +107 -0
  8. package/dist/abis/aave-ui-pool-data-provider.js.map +1 -0
  9. package/dist/abis/access-control.d.ts +94 -0
  10. package/dist/abis/access-control.js +51 -0
  11. package/dist/abis/access-control.js.map +1 -0
  12. package/dist/abis/compound-comet.d.ts +120 -0
  13. package/dist/abis/compound-comet.js +84 -0
  14. package/dist/abis/compound-comet.js.map +1 -0
  15. package/dist/abis/eigenlayer-delegation-manager.d.ts +23 -0
  16. package/dist/abis/eigenlayer-delegation-manager.js +18 -0
  17. package/dist/abis/eigenlayer-delegation-manager.js.map +1 -0
  18. package/dist/abis/eigenlayer-strategy-manager.d.ts +53 -0
  19. package/dist/abis/eigenlayer-strategy-manager.js +47 -0
  20. package/dist/abis/eigenlayer-strategy-manager.js.map +1 -0
  21. package/dist/abis/erc20.d.ts +78 -0
  22. package/dist/abis/erc20.js +10 -0
  23. package/dist/abis/erc20.js.map +1 -0
  24. package/dist/abis/lido.d.ts +80 -0
  25. package/dist/abis/lido.js +60 -0
  26. package/dist/abis/lido.js.map +1 -0
  27. package/dist/abis/morpho-blue.d.ts +321 -0
  28. package/dist/abis/morpho-blue.js +193 -0
  29. package/dist/abis/morpho-blue.js.map +1 -0
  30. package/dist/abis/uniswap-pool.d.ts +70 -0
  31. package/dist/abis/uniswap-pool.js +53 -0
  32. package/dist/abis/uniswap-pool.js.map +1 -0
  33. package/dist/abis/uniswap-position-manager.d.ts +71 -0
  34. package/dist/abis/uniswap-position-manager.js +41 -0
  35. package/dist/abis/uniswap-position-manager.js.map +1 -0
  36. package/dist/config/cache.d.ts +12 -0
  37. package/dist/config/cache.js +12 -0
  38. package/dist/config/cache.js.map +1 -0
  39. package/dist/config/chains.d.ts +24 -0
  40. package/dist/config/chains.js +158 -0
  41. package/dist/config/chains.js.map +1 -0
  42. package/dist/config/contracts.d.ts +107 -0
  43. package/dist/config/contracts.js +123 -0
  44. package/dist/config/contracts.js.map +1 -0
  45. package/dist/config/user-config.d.ts +15 -0
  46. package/dist/config/user-config.js +93 -0
  47. package/dist/config/user-config.js.map +1 -0
  48. package/dist/data/apis/etherscan.d.ts +30 -0
  49. package/dist/data/apis/etherscan.js +109 -0
  50. package/dist/data/apis/etherscan.js.map +1 -0
  51. package/dist/data/cache.d.ts +18 -0
  52. package/dist/data/cache.js +47 -0
  53. package/dist/data/cache.js.map +1 -0
  54. package/dist/data/format.d.ts +6 -0
  55. package/dist/data/format.js +44 -0
  56. package/dist/data/format.js.map +1 -0
  57. package/dist/data/prices.d.ts +12 -0
  58. package/dist/data/prices.js +81 -0
  59. package/dist/data/prices.js.map +1 -0
  60. package/dist/data/rpc.d.ts +17 -0
  61. package/dist/data/rpc.js +68 -0
  62. package/dist/data/rpc.js.map +1 -0
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.js +344 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/modules/balances/index.d.ts +20 -0
  67. package/dist/modules/balances/index.js +49 -0
  68. package/dist/modules/balances/index.js.map +1 -0
  69. package/dist/modules/balances/schemas.d.ts +32 -0
  70. package/dist/modules/balances/schemas.js +21 -0
  71. package/dist/modules/balances/schemas.js.map +1 -0
  72. package/dist/modules/bitcoin/index.d.ts +26 -0
  73. package/dist/modules/bitcoin/index.js +128 -0
  74. package/dist/modules/bitcoin/index.js.map +1 -0
  75. package/dist/modules/bitcoin/schemas.d.ts +49 -0
  76. package/dist/modules/bitcoin/schemas.js +51 -0
  77. package/dist/modules/bitcoin/schemas.js.map +1 -0
  78. package/dist/modules/bitcoin/send.d.ts +52 -0
  79. package/dist/modules/bitcoin/send.js +158 -0
  80. package/dist/modules/bitcoin/send.js.map +1 -0
  81. package/dist/modules/bitcoin/utxo.d.ts +99 -0
  82. package/dist/modules/bitcoin/utxo.js +116 -0
  83. package/dist/modules/bitcoin/utxo.js.map +1 -0
  84. package/dist/modules/compound/actions.d.ts +8 -0
  85. package/dist/modules/compound/actions.js +154 -0
  86. package/dist/modules/compound/actions.js.map +1 -0
  87. package/dist/modules/compound/index.d.ts +26 -0
  88. package/dist/modules/compound/index.js +141 -0
  89. package/dist/modules/compound/index.js.map +1 -0
  90. package/dist/modules/compound/schemas.d.ts +95 -0
  91. package/dist/modules/compound/schemas.js +37 -0
  92. package/dist/modules/compound/schemas.js.map +1 -0
  93. package/dist/modules/execution/index.d.ts +51 -0
  94. package/dist/modules/execution/index.js +271 -0
  95. package/dist/modules/execution/index.js.map +1 -0
  96. package/dist/modules/execution/schemas.d.ts +183 -0
  97. package/dist/modules/execution/schemas.js +88 -0
  98. package/dist/modules/execution/schemas.js.map +1 -0
  99. package/dist/modules/feedback/index.d.ts +28 -0
  100. package/dist/modules/feedback/index.js +161 -0
  101. package/dist/modules/feedback/index.js.map +1 -0
  102. package/dist/modules/feedback/rate-limit.d.ts +15 -0
  103. package/dist/modules/feedback/rate-limit.js +110 -0
  104. package/dist/modules/feedback/rate-limit.js.map +1 -0
  105. package/dist/modules/feedback/schemas.d.ts +41 -0
  106. package/dist/modules/feedback/schemas.js +40 -0
  107. package/dist/modules/feedback/schemas.js.map +1 -0
  108. package/dist/modules/morpho/actions.d.ts +8 -0
  109. package/dist/modules/morpho/actions.js +265 -0
  110. package/dist/modules/morpho/actions.js.map +1 -0
  111. package/dist/modules/morpho/index.d.ts +26 -0
  112. package/dist/modules/morpho/index.js +94 -0
  113. package/dist/modules/morpho/index.js.map +1 -0
  114. package/dist/modules/morpho/schemas.d.ts +130 -0
  115. package/dist/modules/morpho/schemas.js +34 -0
  116. package/dist/modules/morpho/schemas.js.map +1 -0
  117. package/dist/modules/portfolio/index.d.ts +3 -0
  118. package/dist/modules/portfolio/index.js +197 -0
  119. package/dist/modules/portfolio/index.js.map +1 -0
  120. package/dist/modules/portfolio/schemas.d.ts +20 -0
  121. package/dist/modules/portfolio/schemas.js +15 -0
  122. package/dist/modules/portfolio/schemas.js.map +1 -0
  123. package/dist/modules/positions/aave.d.ts +22 -0
  124. package/dist/modules/positions/aave.js +197 -0
  125. package/dist/modules/positions/aave.js.map +1 -0
  126. package/dist/modules/positions/actions.d.ts +18 -0
  127. package/dist/modules/positions/actions.js +205 -0
  128. package/dist/modules/positions/actions.js.map +1 -0
  129. package/dist/modules/positions/index.d.ts +37 -0
  130. package/dist/modules/positions/index.js +59 -0
  131. package/dist/modules/positions/index.js.map +1 -0
  132. package/dist/modules/positions/schemas.d.ts +55 -0
  133. package/dist/modules/positions/schemas.js +25 -0
  134. package/dist/modules/positions/schemas.js.map +1 -0
  135. package/dist/modules/positions/uniswap.d.ts +4 -0
  136. package/dist/modules/positions/uniswap.js +181 -0
  137. package/dist/modules/positions/uniswap.js.map +1 -0
  138. package/dist/modules/prices/index.d.ts +19 -0
  139. package/dist/modules/prices/index.js +22 -0
  140. package/dist/modules/prices/index.js.map +1 -0
  141. package/dist/modules/security/index.d.ts +26 -0
  142. package/dist/modules/security/index.js +13 -0
  143. package/dist/modules/security/index.js.map +1 -0
  144. package/dist/modules/security/permissions.d.ts +8 -0
  145. package/dist/modules/security/permissions.js +96 -0
  146. package/dist/modules/security/permissions.js.map +1 -0
  147. package/dist/modules/security/risk-score.d.ts +18 -0
  148. package/dist/modules/security/risk-score.js +116 -0
  149. package/dist/modules/security/risk-score.js.map +1 -0
  150. package/dist/modules/security/schemas.d.ts +31 -0
  151. package/dist/modules/security/schemas.js +16 -0
  152. package/dist/modules/security/schemas.js.map +1 -0
  153. package/dist/modules/security/verification.d.ts +4 -0
  154. package/dist/modules/security/verification.js +82 -0
  155. package/dist/modules/security/verification.js.map +1 -0
  156. package/dist/modules/shared/approval.d.ts +71 -0
  157. package/dist/modules/shared/approval.js +130 -0
  158. package/dist/modules/shared/approval.js.map +1 -0
  159. package/dist/modules/staking/actions.d.ts +22 -0
  160. package/dist/modules/staking/actions.js +100 -0
  161. package/dist/modules/staking/actions.js.map +1 -0
  162. package/dist/modules/staking/eigenlayer.d.ts +14 -0
  163. package/dist/modules/staking/eigenlayer.js +105 -0
  164. package/dist/modules/staking/eigenlayer.js.map +1 -0
  165. package/dist/modules/staking/index.d.ts +24 -0
  166. package/dist/modules/staking/index.js +59 -0
  167. package/dist/modules/staking/index.js.map +1 -0
  168. package/dist/modules/staking/lido.d.ts +14 -0
  169. package/dist/modules/staking/lido.js +113 -0
  170. package/dist/modules/staking/lido.js.map +1 -0
  171. package/dist/modules/staking/schemas.d.ts +34 -0
  172. package/dist/modules/staking/schemas.js +20 -0
  173. package/dist/modules/staking/schemas.js.map +1 -0
  174. package/dist/modules/swap/index.d.ts +47 -0
  175. package/dist/modules/swap/index.js +376 -0
  176. package/dist/modules/swap/index.js.map +1 -0
  177. package/dist/modules/swap/lifi.d.ts +17 -0
  178. package/dist/modules/swap/lifi.js +44 -0
  179. package/dist/modules/swap/lifi.js.map +1 -0
  180. package/dist/modules/swap/oneinch.d.ts +26 -0
  181. package/dist/modules/swap/oneinch.js +33 -0
  182. package/dist/modules/swap/oneinch.js.map +1 -0
  183. package/dist/modules/swap/schemas.d.ts +65 -0
  184. package/dist/modules/swap/schemas.js +46 -0
  185. package/dist/modules/swap/schemas.js.map +1 -0
  186. package/dist/setup.d.ts +2 -0
  187. package/dist/setup.js +257 -0
  188. package/dist/setup.js.map +1 -0
  189. package/dist/signing/pre-sign-check.d.ts +8 -0
  190. package/dist/signing/pre-sign-check.js +231 -0
  191. package/dist/signing/pre-sign-check.js.map +1 -0
  192. package/dist/signing/session.d.ts +28 -0
  193. package/dist/signing/session.js +26 -0
  194. package/dist/signing/session.js.map +1 -0
  195. package/dist/signing/tx-store.d.ts +29 -0
  196. package/dist/signing/tx-store.js +85 -0
  197. package/dist/signing/tx-store.js.map +1 -0
  198. package/dist/signing/walletconnect.d.ts +33 -0
  199. package/dist/signing/walletconnect.js +226 -0
  200. package/dist/signing/walletconnect.js.map +1 -0
  201. package/dist/types/index.d.ts +222 -0
  202. package/dist/types/index.js +18 -0
  203. package/dist/types/index.js.map +1 -0
  204. package/package.json +134 -0
@@ -0,0 +1,128 @@
1
+ import { cache } from "../../data/cache.js";
2
+ import { CACHE_TTL } from "../../config/cache.js";
3
+ /**
4
+ * Bitcoin mainnet portfolio reads.
5
+ *
6
+ * Data source: mempool.space REST API (no key required, no rate-limit issues for
7
+ * occasional lookups). We only fetch the address-summary endpoint — no block or
8
+ * tx history, since portfolio integration only needs the confirmed balance.
9
+ *
10
+ * Scope is intentionally read-only. Tx construction (UTXO selection, fee-rate,
11
+ * PSBT assembly) and signing via WalletConnect `bip122:` are phase-2 work — a
12
+ * separate module when we wire Ledger Live's Bitcoin app.
13
+ *
14
+ * Prices come from DefiLlama's `coingecko:bitcoin` key to stay consistent with
15
+ * the EVM pricing path; cached for the same TTL as ERC-20 prices.
16
+ */
17
+ const MEMPOOL_API = "https://mempool.space/api";
18
+ const SATS_PER_BTC = 100000000n;
19
+ function satsToBtcString(sats) {
20
+ const whole = sats / SATS_PER_BTC;
21
+ const frac = sats % SATS_PER_BTC;
22
+ if (frac === 0n)
23
+ return whole.toString();
24
+ const fracStr = frac.toString().padStart(8, "0").replace(/0+$/, "");
25
+ return `${whole.toString()}.${fracStr}`;
26
+ }
27
+ async function fetchBitcoinPrice() {
28
+ const cacheKey = "price:coingecko:bitcoin";
29
+ const hit = cache.get(cacheKey);
30
+ if (hit !== undefined)
31
+ return hit;
32
+ try {
33
+ const res = await fetch("https://coins.llama.fi/prices/current/coingecko:bitcoin");
34
+ if (!res.ok)
35
+ return undefined;
36
+ const body = (await res.json());
37
+ const price = body.coins["coingecko:bitcoin"]?.price;
38
+ if (typeof price === "number") {
39
+ cache.set(cacheKey, price, CACHE_TTL.PRICE);
40
+ return price;
41
+ }
42
+ }
43
+ catch {
44
+ // Swallow — degrade to undefined USD so callers can still return sats.
45
+ }
46
+ return undefined;
47
+ }
48
+ async function fetchAddressSummary(address) {
49
+ const res = await fetch(`${MEMPOOL_API}/address/${encodeURIComponent(address)}`);
50
+ if (!res.ok) {
51
+ const body = await res.text().catch(() => "");
52
+ throw new Error(`mempool.space ${res.status}: ${body.slice(0, 200)}`);
53
+ }
54
+ return (await res.json());
55
+ }
56
+ export async function getBitcoinBalance(args) {
57
+ const [summary, priceUsd] = await Promise.all([
58
+ fetchAddressSummary(args.address),
59
+ fetchBitcoinPrice(),
60
+ ]);
61
+ return toBalance(args.address, summary, priceUsd);
62
+ }
63
+ export async function getBitcoinPortfolio(args) {
64
+ const priceUsd = await fetchBitcoinPrice();
65
+ // Fetch each address in parallel; an individual failure shouldn't kill the whole report.
66
+ const balances = await Promise.all(args.addresses.map(async (addr) => {
67
+ try {
68
+ const summary = await fetchAddressSummary(addr);
69
+ return toBalance(addr, summary, priceUsd);
70
+ }
71
+ catch {
72
+ return toBalance(addr, emptySummary(addr), priceUsd);
73
+ }
74
+ }));
75
+ const totalSats = balances.reduce((sum, b) => sum + BigInt(b.amountSats), 0n);
76
+ const totalBtc = satsToBtcString(totalSats);
77
+ const totalUsd = priceUsd !== undefined
78
+ ? Number(totalBtc) * priceUsd
79
+ : undefined;
80
+ return {
81
+ chain: "bitcoin",
82
+ addresses: args.addresses,
83
+ balances,
84
+ totalSats: totalSats.toString(),
85
+ totalBtc,
86
+ totalUsd,
87
+ priceUsd,
88
+ };
89
+ }
90
+ function toBalance(address, summary, priceUsd) {
91
+ const confirmedSats = BigInt(summary.chain_stats.funded_txo_sum) -
92
+ BigInt(summary.chain_stats.spent_txo_sum);
93
+ const unconfirmedSats = BigInt(summary.mempool_stats.funded_txo_sum) -
94
+ BigInt(summary.mempool_stats.spent_txo_sum);
95
+ const formattedBtc = satsToBtcString(confirmedSats);
96
+ const valueUsd = priceUsd !== undefined ? Number(formattedBtc) * priceUsd : undefined;
97
+ return {
98
+ chain: "bitcoin",
99
+ address,
100
+ amountSats: confirmedSats.toString(),
101
+ unconfirmedSats: unconfirmedSats.toString(),
102
+ formattedBtc,
103
+ symbol: "BTC",
104
+ decimals: 8,
105
+ priceUsd,
106
+ valueUsd,
107
+ };
108
+ }
109
+ function emptySummary(address) {
110
+ return {
111
+ address,
112
+ chain_stats: {
113
+ funded_txo_count: 0,
114
+ funded_txo_sum: 0,
115
+ spent_txo_count: 0,
116
+ spent_txo_sum: 0,
117
+ tx_count: 0,
118
+ },
119
+ mempool_stats: {
120
+ funded_txo_count: 0,
121
+ funded_txo_sum: 0,
122
+ spent_txo_count: 0,
123
+ spent_txo_sum: 0,
124
+ tx_count: 0,
125
+ },
126
+ };
127
+ }
128
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/bitcoin/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ export declare const bitcoinAddressSchema: z.ZodString;
3
+ export declare const getBitcoinBalanceInput: z.ZodObject<{
4
+ address: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ address: string;
7
+ }, {
8
+ address: string;
9
+ }>;
10
+ export declare const getBitcoinPortfolioInput: z.ZodObject<{
11
+ addresses: z.ZodArray<z.ZodString, "many">;
12
+ }, "strip", z.ZodTypeAny, {
13
+ addresses: string[];
14
+ }, {
15
+ addresses: string[];
16
+ }>;
17
+ export type GetBitcoinBalanceArgs = z.infer<typeof getBitcoinBalanceInput>;
18
+ export type GetBitcoinPortfolioArgs = z.infer<typeof getBitcoinPortfolioInput>;
19
+ export declare const prepareBitcoinSendInput: z.ZodObject<{
20
+ from: z.ZodString;
21
+ to: z.ZodString;
22
+ /** Amount to send to the recipient, in satoshis. Use a string to avoid JS precision issues for > 2^53. */
23
+ amountSats: z.ZodString;
24
+ feeRate: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"fastest">, z.ZodLiteral<"halfhour">, z.ZodLiteral<"hour">, z.ZodLiteral<"economy">, z.ZodLiteral<"minimum">, z.ZodNumber]>>;
25
+ /** Include unconfirmed (mempool) UTXOs as spendable. Default false. */
26
+ includeUnconfirmed: z.ZodOptional<z.ZodBoolean>;
27
+ }, "strip", z.ZodTypeAny, {
28
+ from: string;
29
+ to: string;
30
+ amountSats: string;
31
+ feeRate?: number | "minimum" | "fastest" | "halfhour" | "hour" | "economy" | undefined;
32
+ includeUnconfirmed?: boolean | undefined;
33
+ }, {
34
+ from: string;
35
+ to: string;
36
+ amountSats: string;
37
+ feeRate?: number | "minimum" | "fastest" | "halfhour" | "hour" | "economy" | undefined;
38
+ includeUnconfirmed?: boolean | undefined;
39
+ }>;
40
+ export declare const broadcastBitcoinTxInput: z.ZodObject<{
41
+ /** Fully signed raw Bitcoin transaction as lowercase hex (no 0x prefix). */
42
+ hex: z.ZodString;
43
+ }, "strip", z.ZodTypeAny, {
44
+ hex: string;
45
+ }, {
46
+ hex: string;
47
+ }>;
48
+ export type PrepareBitcoinSendArgs = z.infer<typeof prepareBitcoinSendInput>;
49
+ export type BroadcastBitcoinTxArgs = z.infer<typeof broadcastBitcoinTxInput>;
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Mainnet-only. Covers:
4
+ * - P2PKH (legacy, starts with "1")
5
+ * - P2SH (starts with "3")
6
+ * - P2WPKH / P2WSH (bech32, starts with "bc1q" / "bc1Q")
7
+ * - P2TR (taproot, bech32m, starts with "bc1p")
8
+ *
9
+ * Testnet ("tb1…") and regtest are intentionally excluded — this is a portfolio tool.
10
+ */
11
+ const BITCOIN_ADDRESS_REGEX = /^(bc1[a-zA-HJ-NP-Z0-9]{25,87}|[13][a-km-zA-HJ-NP-Z1-9]{25,34})$/;
12
+ export const bitcoinAddressSchema = z
13
+ .string()
14
+ .regex(BITCOIN_ADDRESS_REGEX, "Not a valid Bitcoin mainnet address");
15
+ export const getBitcoinBalanceInput = z.object({
16
+ address: bitcoinAddressSchema,
17
+ });
18
+ export const getBitcoinPortfolioInput = z.object({
19
+ addresses: z.array(bitcoinAddressSchema).min(1).max(20),
20
+ });
21
+ /**
22
+ * Fee rate hint. Either a specific sat/vB number, or a named tier that maps to
23
+ * mempool.space's recommended-fees endpoint. Default is "hour" (conservative).
24
+ */
25
+ const feeRateSchema = z.union([
26
+ z.literal("fastest"),
27
+ z.literal("halfhour"),
28
+ z.literal("hour"),
29
+ z.literal("economy"),
30
+ z.literal("minimum"),
31
+ z.number().positive().max(1000),
32
+ ]);
33
+ export const prepareBitcoinSendInput = z.object({
34
+ from: bitcoinAddressSchema,
35
+ to: bitcoinAddressSchema,
36
+ /** Amount to send to the recipient, in satoshis. Use a string to avoid JS precision issues for > 2^53. */
37
+ amountSats: z
38
+ .string()
39
+ .regex(/^\d+$/, "amountSats must be a positive integer string"),
40
+ feeRate: feeRateSchema.optional(),
41
+ /** Include unconfirmed (mempool) UTXOs as spendable. Default false. */
42
+ includeUnconfirmed: z.boolean().optional(),
43
+ });
44
+ export const broadcastBitcoinTxInput = z.object({
45
+ /** Fully signed raw Bitcoin transaction as lowercase hex (no 0x prefix). */
46
+ hex: z
47
+ .string()
48
+ .regex(/^[0-9a-fA-F]+$/, "hex must contain only hex characters")
49
+ .min(20),
50
+ });
51
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../../src/modules/bitcoin/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AACH,MAAM,qBAAqB,GACzB,iEAAiE,CAAC;AAEpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,EAAE;KACR,KAAK,CAAC,qBAAqB,EAAE,qCAAqC,CAAC,CAAC;AAEvE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,OAAO,EAAE,oBAAoB;CAC9B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;CACxD,CAAC,CAAC;AAKH;;;GAGG;AACH,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC;IAC5B,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IACpB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;IACrB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACjB,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IACpB,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IACpB,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;CAChC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,IAAI,EAAE,oBAAoB;IAC1B,EAAE,EAAE,oBAAoB;IACxB,0GAA0G;IAC1G,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,KAAK,CAAC,OAAO,EAAE,8CAA8C,CAAC;IACjE,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE;IACjC,uEAAuE;IACvE,kBAAkB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CAC3C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,4EAA4E;IAC5E,GAAG,EAAE,CAAC;SACH,MAAM,EAAE;SACR,KAAK,CAAC,gBAAgB,EAAE,sCAAsC,CAAC;SAC/D,GAAG,CAAC,EAAE,CAAC;CACX,CAAC,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { type BitcoinScriptType } from "./utxo.js";
2
+ import type { PrepareBitcoinSendArgs, BroadcastBitcoinTxArgs } from "./schemas.js";
3
+ export interface PreparedBitcoinSend {
4
+ chain: "bitcoin";
5
+ from: string;
6
+ to: string;
7
+ amountSats: string;
8
+ amountBtc: string;
9
+ inputs: {
10
+ txid: string;
11
+ vout: number;
12
+ valueSats: string;
13
+ confirmed: boolean;
14
+ }[];
15
+ outputs: ({
16
+ role: "recipient";
17
+ address: string;
18
+ valueSats: string;
19
+ valueBtc: string;
20
+ } | {
21
+ role: "change";
22
+ address: string;
23
+ valueSats: string;
24
+ valueBtc: string;
25
+ })[];
26
+ fee: {
27
+ sats: string;
28
+ btc: string;
29
+ rateSatVb: number;
30
+ rateSource: string;
31
+ effectiveRateSatVb: number;
32
+ vsize: number;
33
+ };
34
+ /** Script type of the source — determines input witness sizing. */
35
+ sourceScriptType: BitcoinScriptType;
36
+ /**
37
+ * Plain-English summary of what the user will sign. Shown by default so the
38
+ * agent has something human-readable to relay before any external signer sees
39
+ * the tx.
40
+ */
41
+ description: string;
42
+ }
43
+ export declare function prepareBitcoinSend(args: PrepareBitcoinSendArgs): Promise<PreparedBitcoinSend>;
44
+ /**
45
+ * POST a signed raw Bitcoin transaction hex to mempool.space, which forwards it
46
+ * into the mempool. Returns the txid on success. The caller is responsible for
47
+ * producing a correctly signed tx (this tool doesn't validate it).
48
+ */
49
+ export declare function broadcastBitcoinTx(args: BroadcastBitcoinTxArgs): Promise<{
50
+ txid: string;
51
+ chain: "bitcoin";
52
+ }>;
@@ -0,0 +1,158 @@
1
+ import { SEGWIT_OVERHEAD_VBYTES, LEGACY_OVERHEAD_VBYTES, VBYTES, detectScriptType, dustThreshold, selectUtxos, } from "./utxo.js";
2
+ /**
3
+ * Prepare an unsigned Bitcoin send: fetch UTXOs, run consolidation selection,
4
+ * and return a structured plan (not a PSBT, not a serialized tx). The caller
5
+ * signs it with their preferred tool (Sparrow, Electrum, hardware wallet) and
6
+ * broadcasts the resulting hex via `broadcast_bitcoin_tx`.
7
+ *
8
+ * Why no PSBT / no serialized unsigned hex:
9
+ * - PSBT requires pulling in bitcoinjs-lib and carries complexity we don't
10
+ * need for a dev-facing MCP tool.
11
+ * - Returning a pure selection plan + vsize + fee lets any wallet build the
12
+ * actual tx. It's the data contract that matters, not the binary layout.
13
+ *
14
+ * Selection strategy lives in ./utxo.ts — spend every spendable UTXO to
15
+ * minimize the post-confirmation UTXO count in the source wallet.
16
+ */
17
+ const MEMPOOL_API = "https://mempool.space/api";
18
+ async function fetchUtxos(address) {
19
+ const res = await fetch(`${MEMPOOL_API}/address/${encodeURIComponent(address)}/utxo`);
20
+ if (!res.ok) {
21
+ const body = await res.text().catch(() => "");
22
+ throw new Error(`mempool.space UTXO ${res.status}: ${body.slice(0, 200)}`);
23
+ }
24
+ return (await res.json());
25
+ }
26
+ async function fetchFeeRecommendations() {
27
+ const res = await fetch(`${MEMPOOL_API}/v1/fees/recommended`);
28
+ if (!res.ok) {
29
+ throw new Error(`mempool.space fee rec ${res.status}`);
30
+ }
31
+ return (await res.json());
32
+ }
33
+ function resolveFeeRate(input, recs) {
34
+ if (typeof input === "number")
35
+ return { satVb: input, source: "user" };
36
+ switch (input) {
37
+ case "fastest":
38
+ return { satVb: recs.fastestFee, source: "fastest (mempool.space)" };
39
+ case "halfhour":
40
+ return { satVb: recs.halfHourFee, source: "halfhour (mempool.space)" };
41
+ case "hour":
42
+ case undefined:
43
+ return { satVb: recs.hourFee, source: "hour (mempool.space)" };
44
+ case "economy":
45
+ return { satVb: recs.economyFee, source: "economy (mempool.space)" };
46
+ case "minimum":
47
+ return { satVb: recs.minimumFee, source: "minimum (mempool.space)" };
48
+ }
49
+ }
50
+ function satsToBtc(sats) {
51
+ const whole = sats / 100000000n;
52
+ const frac = sats % 100000000n;
53
+ if (frac === 0n)
54
+ return whole.toString();
55
+ const fracStr = frac.toString().padStart(8, "0").replace(/0+$/, "");
56
+ return `${whole.toString()}.${fracStr}`;
57
+ }
58
+ export async function prepareBitcoinSend(args) {
59
+ const sourceScriptType = detectScriptType(args.from);
60
+ const recipientScriptType = detectScriptType(args.to);
61
+ const changeScriptType = sourceScriptType; // Always return change to the sender.
62
+ const isSegwitSource = sourceScriptType === "p2wpkh" ||
63
+ sourceScriptType === "p2wsh" ||
64
+ sourceScriptType === "p2tr" ||
65
+ sourceScriptType === "p2sh"; // P2SH-P2WPKH is the practical case.
66
+ const overhead = isSegwitSource ? SEGWIT_OVERHEAD_VBYTES : LEGACY_OVERHEAD_VBYTES;
67
+ const [utxos, feeRecs] = await Promise.all([
68
+ fetchUtxos(args.from),
69
+ fetchFeeRecommendations(),
70
+ ]);
71
+ if (utxos.length === 0) {
72
+ throw new Error(`Address ${args.from} has no UTXOs.`);
73
+ }
74
+ const feeRate = resolveFeeRate(args.feeRate, feeRecs);
75
+ const targetSats = BigInt(args.amountSats);
76
+ if (targetSats <= 0n) {
77
+ throw new Error("amountSats must be a positive integer.");
78
+ }
79
+ const selection = selectUtxos({
80
+ utxos: utxos.map((u) => ({
81
+ txid: u.txid,
82
+ vout: u.vout,
83
+ value: u.value,
84
+ confirmed: u.status.confirmed,
85
+ })),
86
+ targetSats,
87
+ feeRateSatVb: feeRate.satVb,
88
+ inputVbytes: VBYTES[sourceScriptType].input,
89
+ outputVbytesRecipient: VBYTES[recipientScriptType].output,
90
+ outputVbytesChange: VBYTES[changeScriptType].output,
91
+ overheadVbytes: overhead,
92
+ dustSats: dustThreshold(changeScriptType),
93
+ includeUnconfirmed: args.includeUnconfirmed ?? false,
94
+ });
95
+ const outputs = [
96
+ {
97
+ role: "recipient",
98
+ address: args.to,
99
+ valueSats: targetSats.toString(),
100
+ valueBtc: satsToBtc(targetSats),
101
+ },
102
+ ];
103
+ if (selection.changeSats > 0n) {
104
+ outputs.push({
105
+ role: "change",
106
+ address: args.from,
107
+ valueSats: selection.changeSats.toString(),
108
+ valueBtc: satsToBtc(selection.changeSats),
109
+ });
110
+ }
111
+ return {
112
+ chain: "bitcoin",
113
+ from: args.from,
114
+ to: args.to,
115
+ amountSats: targetSats.toString(),
116
+ amountBtc: satsToBtc(targetSats),
117
+ inputs: selection.chosen.map((u) => ({
118
+ txid: u.txid,
119
+ vout: u.vout,
120
+ valueSats: u.value.toString(),
121
+ confirmed: u.confirmed,
122
+ })),
123
+ outputs,
124
+ fee: {
125
+ sats: selection.feeSats.toString(),
126
+ btc: satsToBtc(selection.feeSats),
127
+ rateSatVb: feeRate.satVb,
128
+ rateSource: feeRate.source,
129
+ effectiveRateSatVb: Math.round(selection.effectiveFeeRateSatVb * 100) / 100,
130
+ vsize: selection.vbytes,
131
+ },
132
+ sourceScriptType,
133
+ description: `Send ${satsToBtc(targetSats)} BTC from ${args.from} to ${args.to}. ` +
134
+ `Consumes ${selection.chosen.length} UTXO(s) totaling ${satsToBtc(selection.totalInSats)} BTC. ` +
135
+ `Fee ${selection.feeSats} sats (${satsToBtc(selection.feeSats)} BTC) at ${feeRate.satVb} sat/vB (${feeRate.source}). ` +
136
+ (selection.changeSats > 0n
137
+ ? `Change ${satsToBtc(selection.changeSats)} BTC returned to sender.`
138
+ : `No change output — dust absorbed into fee.`),
139
+ };
140
+ }
141
+ /**
142
+ * POST a signed raw Bitcoin transaction hex to mempool.space, which forwards it
143
+ * into the mempool. Returns the txid on success. The caller is responsible for
144
+ * producing a correctly signed tx (this tool doesn't validate it).
145
+ */
146
+ export async function broadcastBitcoinTx(args) {
147
+ const res = await fetch(`${MEMPOOL_API}/tx`, {
148
+ method: "POST",
149
+ headers: { "Content-Type": "text/plain" },
150
+ body: args.hex,
151
+ });
152
+ const body = await res.text().catch(() => "");
153
+ if (!res.ok) {
154
+ throw new Error(`Broadcast failed (${res.status}): ${body.slice(0, 300)}`);
155
+ }
156
+ return { txid: body.trim(), chain: "bitcoin" };
157
+ }
158
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../../src/modules/bitcoin/send.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,WAAW,GAGZ,MAAM,WAAW,CAAC;AAMnB;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAsBhD,KAAK,UAAU,UAAU,CAAC,OAAe;IACvC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,YAAY,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAkB,CAAC;AAC7C,CAAC;AAED,KAAK,UAAU,uBAAuB;IACpC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,sBAAsB,CAAC,CAAC;IAC9D,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAkB,CAAC;AAC7C,CAAC;AAED,SAAS,cAAc,CACrB,KAAwC,EACxC,IAAmB;IAEnB,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACvE,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,SAAS;YACZ,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;QACvE,KAAK,UAAU;YACb,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;QACzE,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS;YACZ,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;QACjE,KAAK,SAAS;YACZ,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;QACvE,KAAK,SAAS;YACZ,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;IACzE,CAAC;AACH,CAAC;AAoCD,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,KAAK,GAAG,IAAI,GAAG,UAAY,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,GAAG,UAAY,CAAC;IACjC,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpE,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,IAAI,OAAO,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAA4B;IAE5B,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtD,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,CAAC,sCAAsC;IACjF,MAAM,cAAc,GAClB,gBAAgB,KAAK,QAAQ;QAC7B,gBAAgB,KAAK,OAAO;QAC5B,gBAAgB,KAAK,MAAM;QAC3B,gBAAgB,KAAK,MAAM,CAAC,CAAC,qCAAqC;IACpE,MAAM,QAAQ,GAAG,cAAc,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAElF,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QACrB,uBAAuB,EAAE;KAC1B,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,CAAC,IAAI,gBAAgB,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEtD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,UAAU,IAAI,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC;QAC5B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS;SAC9B,CAAC,CAAC;QACH,UAAU;QACV,YAAY,EAAE,OAAO,CAAC,KAAK;QAC3B,WAAW,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,KAAK;QAC3C,qBAAqB,EAAE,MAAM,CAAC,mBAAmB,CAAC,CAAC,MAAM;QACzD,kBAAkB,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,MAAM;QACnD,cAAc,EAAE,QAAQ;QACxB,QAAQ,EAAE,aAAa,CAAC,gBAAgB,CAAC;QACzC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,IAAI,KAAK;KACrD,CAAC,CAAC;IAEH,MAAM,OAAO,GAAmC;QAC9C;YACE,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,SAAS,EAAE,UAAU,CAAC,QAAQ,EAAE;YAChC,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC;SAChC;KACF,CAAC;IACF,IAAI,SAAS,CAAC,UAAU,GAAG,EAAE,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,IAAI,CAAC,IAAI;YAClB,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE;YAC1C,QAAQ,EAAE,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC;SAC1C,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;QACjC,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC;QAChC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE;YAC7B,SAAS,EAAE,CAAC,CAAC,SAAS;SACvB,CAAC,CAAC;QACH,OAAO;QACP,GAAG,EAAE;YACH,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE;YAClC,GAAG,EAAE,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC;YACjC,SAAS,EAAE,OAAO,CAAC,KAAK;YACxB,UAAU,EAAE,OAAO,CAAC,MAAM;YAC1B,kBAAkB,EAChB,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,qBAAqB,GAAG,GAAG,CAAC,GAAG,GAAG;YACzD,KAAK,EAAE,SAAS,CAAC,MAAM;SACxB;QACD,gBAAgB;QAChB,WAAW,EACT,QAAQ,SAAS,CAAC,UAAU,CAAC,aAAa,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,IAAI;YACrE,YAAY,SAAS,CAAC,MAAM,CAAC,MAAM,qBAAqB,SAAS,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ;YAChG,OAAO,SAAS,CAAC,OAAO,UAAU,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,MAAM,KAAK;YACtH,CAAC,SAAS,CAAC,UAAU,GAAG,EAAE;gBACxB,CAAC,CAAC,UAAU,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,0BAA0B;gBACrE,CAAC,CAAC,4CAA4C,CAAC;KACpD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAA4B;IAE5B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,KAAK,EAAE;QAC3C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE;QACzC,IAAI,EAAE,IAAI,CAAC,GAAG;KACf,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Pure UTXO-selection logic, independent of any network I/O. Kept here so the
3
+ * coin-selection algorithm can be unit-tested without mocking the HTTP client.
4
+ *
5
+ * Objective: minimize the number of UTXOs remaining in the wallet *after* the
6
+ * transaction confirms — i.e., consolidate. Post-tx UTXO count equals (pool −
7
+ * chosen) + (1 if change else 0), so this is minimized by spending every
8
+ * spendable UTXO and, when possible, absorbing the residue into fee so no
9
+ * change output is created.
10
+ *
11
+ * Strategy: always spend the entire spendable pool. Then:
12
+ * - If totalIn < target + fee(no-change): insufficient funds.
13
+ * - Else if change (= totalIn − target − fee-with-change) ≥ dust: emit a
14
+ * change output. Post-tx wallet has 1 UTXO (the change).
15
+ * - Else: absorb the residue into fee and emit no change output. Post-tx
16
+ * wallet has 0 UTXOs.
17
+ *
18
+ * Tradeoff: consolidation increases the fee on *this* transaction roughly
19
+ * linearly in input count, but eliminates the ongoing cost of carrying — and
20
+ * eventually spending — every small UTXO. The user asked explicitly for this
21
+ * objective, so we take the one-time fee hit.
22
+ */
23
+ export interface Utxo {
24
+ txid: string;
25
+ vout: number;
26
+ /** Output value in satoshis. */
27
+ value: number;
28
+ confirmed: boolean;
29
+ }
30
+ export interface SelectionInput {
31
+ utxos: Utxo[];
32
+ /** Amount the recipient should receive, in satoshis. */
33
+ targetSats: bigint;
34
+ /** Fee rate in sat/vB. */
35
+ feeRateSatVb: number;
36
+ /** vsize cost of each input (depends on script type of the source address). */
37
+ inputVbytes: number;
38
+ /** vsize cost of the recipient output. */
39
+ outputVbytesRecipient: number;
40
+ /** vsize cost of the change output (if any). */
41
+ outputVbytesChange: number;
42
+ /** Fixed overhead: version, locktime, witness marker/flag, input/output counts. */
43
+ overheadVbytes: number;
44
+ /** Outputs below this value are absorbed into fee instead of created. */
45
+ dustSats: number;
46
+ /** If true, include unconfirmed (mempool) UTXOs as spendable. */
47
+ includeUnconfirmed?: boolean;
48
+ }
49
+ export interface SelectionResult {
50
+ chosen: Utxo[];
51
+ totalInSats: bigint;
52
+ /** Fee that will actually be paid (totalIn − target − change). */
53
+ feeSats: bigint;
54
+ /** Change amount in sats (0 if absorbed). */
55
+ changeSats: bigint;
56
+ /** Estimated vsize of the final transaction. */
57
+ vbytes: number;
58
+ /** Effective fee rate actually paid (may exceed feeRateSatVb when change is absorbed). */
59
+ effectiveFeeRateSatVb: number;
60
+ }
61
+ export declare function selectUtxos(input: SelectionInput): SelectionResult;
62
+ /**
63
+ * vsize constants by Bitcoin script type. Numbers come from BIP-141/BIP-341
64
+ * worst-case witness sizes; close enough for fee estimation.
65
+ */
66
+ export declare const VBYTES: {
67
+ readonly p2pkh: {
68
+ readonly input: 148;
69
+ readonly output: 34;
70
+ };
71
+ readonly p2sh: {
72
+ readonly input: 91;
73
+ readonly output: 32;
74
+ };
75
+ readonly p2wpkh: {
76
+ readonly input: 68;
77
+ readonly output: 31;
78
+ };
79
+ readonly p2wsh: {
80
+ readonly input: 104;
81
+ readonly output: 43;
82
+ };
83
+ readonly p2tr: {
84
+ readonly input: 58;
85
+ readonly output: 43;
86
+ };
87
+ };
88
+ export type BitcoinScriptType = keyof typeof VBYTES;
89
+ /** Detect script type from a mainnet address prefix. */
90
+ export declare function detectScriptType(address: string): BitcoinScriptType;
91
+ /** Dust threshold per script type (sats). From Bitcoin Core policy. */
92
+ export declare function dustThreshold(scriptType: BitcoinScriptType): number;
93
+ /**
94
+ * Overhead vbytes for a SegWit-eligible transaction (inputs-from-segwit-address
95
+ * paths). For a pure-legacy spend this is slightly smaller (~10 vB), but
96
+ * overestimating by a couple of bytes just overpays fee by pennies.
97
+ */
98
+ export declare const SEGWIT_OVERHEAD_VBYTES = 11;
99
+ export declare const LEGACY_OVERHEAD_VBYTES = 10;