@satora/swap 1.5.0 → 1.6.1

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.
package/README.md CHANGED
@@ -18,7 +18,7 @@ npm install @satora/swap
18
18
 
19
19
  ## Compatibility headers
20
20
 
21
- API requests include `X-Lendaswap-Client: <sdk-name>/<semver>` and `x-satora-server-version: <server-api-semver>`. The server-version header declares the server/API version this SDK was built against; the API logs it for diagnostics and does not enforce compatibility yet.
21
+ API requests include `X-Lendaswap-Client: <sdk-name>/<semver>` and one `x-satora-<component>-version: <epoch>` header per protocol component (`bitcoin-htlc`, `arkade-vhtlc`, `lightning`, `evm-erc20-htlc`, `evm-native-htlc`). The server rejects a client whose epoch for a component the swap uses is too old.
22
22
 
23
23
  ## Supported swaps
24
24
 
@@ -0,0 +1,8 @@
1
+ import {
2
+ electrumReader
3
+ } from "./chunk-3ZMSZSAV.js";
4
+ import "./chunk-LSCRTQOS.js";
5
+ export {
6
+ electrumReader
7
+ };
8
+ //# sourceMappingURL=bitcoin-reader-electrum-KEI7VWW6.js.map
@@ -29,9 +29,11 @@ function scriptHashOf(script) {
29
29
  function electrumReader(client, opts) {
30
30
  const network = opts?.network ?? "mainnet";
31
31
  const fallback = opts?.fallback;
32
- const scriptFor = (address) => btc.OutScript.encode(
33
- btc.Address(toBtcSignerNetwork(network)).decode(address)
34
- );
32
+ const scriptFor = (address) => {
33
+ const decoded = btc.Address(toBtcSignerNetwork(network)).decode(address);
34
+ if (!decoded) throw new Error(`Unrecognised Bitcoin address: ${address}`);
35
+ return btc.OutScript.encode(decoded);
36
+ };
35
37
  async function getHtlcFactsElectrum(address, required) {
36
38
  const script = scriptFor(address);
37
39
  const scripthash = scriptHashOf(script);
@@ -152,4 +154,4 @@ function electrumReader(client, opts) {
152
154
  export {
153
155
  electrumReader
154
156
  };
155
- //# sourceMappingURL=chunk-INWVLX4A.js.map
157
+ //# sourceMappingURL=chunk-3ZMSZSAV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contracts/bitcoin-reader-electrum.ts"],"sourcesContent":["/**\n * An Electrum-backed {@link BitcoinChainReader} — reads HTLC facts from our own\n * Fulcrum over WebSocket instead of public esplora REST endpoints.\n *\n * Two advantages over the esplora reader:\n *\n * - **Freshness**: our Fulcrum sees a transaction the moment its bitcoind\n * accepts it, while public explorers index with a lag — the exact lag that\n * made a server-funding hint's targeted verify come back \"absent\" and park\n * the claim on the 60s at-risk poller.\n * - **Push**: implements the reader's optional `subscribe` capability via\n * `blockchain.scripthash.subscribe`, so the manager reconciles the moment\n * an HTLC address's history changes instead of waiting for a poll tick.\n *\n * Reads fall back to a wrapped reader (typically the esplora one) whenever the\n * Electrum server errors, so a Fulcrum outage degrades to today's behavior\n * rather than blinding the tracker.\n */\n\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { hex } from \"@scure/base\";\nimport * as btc from \"@scure/btc-signer\";\nimport type { BitcoinHtlcFacts } from \"./bitcoin.js\";\nimport type { BitcoinChainReader } from \"./bitcoin-manager.js\";\nimport {\n type BitcoinReaderPolicy,\n resolveMinConfirmations,\n} from \"./bitcoin-reader-esplora.js\";\n\n/** One entry of `blockchain.scripthash.get_history`. */\ntype ElectrumHistoryEntry = {\n tx_hash: string;\n /** Block height; 0 for mempool, -1 for mempool with unconfirmed parents. */\n height: number;\n};\n\n/**\n * The Electrum client surface this reader needs. The pure SDK's\n * `ElectrumWsClient` satisfies it; tests inject a fake.\n */\nexport type ElectrumRpc = {\n request<T>(method: string, params: unknown[]): Promise<T>;\n subscribeScriptHash(\n scripthash: string,\n onChange: (status: string | null) => void,\n ): Promise<string | null>;\n unsubscribeScriptHash(\n scripthash: string,\n onChange: (status: string | null) => void,\n ): void;\n};\n\nexport type ElectrumReaderNetwork =\n | \"mainnet\"\n | \"testnet\"\n | \"signet\"\n | \"regtest\";\n\n/** btc-signer only ships mainnet/testnet; regtest differs in bech32 prefix. */\nconst REGTEST_NETWORK = {\n bech32: \"bcrt\",\n pubKeyHash: 0x6f,\n scriptHash: 0xc4,\n wif: 0xef,\n} as const;\n\nfunction toBtcSignerNetwork(network: ElectrumReaderNetwork) {\n switch (network) {\n case \"mainnet\":\n return btc.NETWORK;\n case \"testnet\":\n case \"signet\":\n return btc.TEST_NETWORK;\n case \"regtest\":\n return REGTEST_NETWORK;\n }\n}\n\n/** Electrum scripthash: sha256 of the output script, reversed, hex. */\nfunction scriptHashOf(script: Uint8Array): string {\n return hex.encode(sha256(script).reverse());\n}\n\n/**\n * Build a {@link BitcoinChainReader} over an Electrum connection, with push\n * support and an optional fallback reader for outages.\n */\nexport function electrumReader(\n client: ElectrumRpc,\n opts?: BitcoinReaderPolicy & {\n /** Network the HTLC addresses live on; defaults to mainnet. */\n network?: ElectrumReaderNetwork;\n /** Reader consulted when the Electrum server errors (e.g. esplora). */\n fallback?: BitcoinChainReader;\n },\n): BitcoinChainReader {\n const network = opts?.network ?? \"mainnet\";\n const fallback = opts?.fallback;\n\n const scriptFor = (address: string): Uint8Array => {\n const decoded = btc.Address(toBtcSignerNetwork(network)).decode(address);\n if (!decoded) throw new Error(`Unrecognised Bitcoin address: ${address}`);\n return btc.OutScript.encode(decoded);\n };\n\n async function getHtlcFactsElectrum(\n address: string,\n required: number,\n ): Promise<BitcoinHtlcFacts> {\n const script = scriptFor(address);\n const scripthash = scriptHashOf(script);\n const history = await client.request<ElectrumHistoryEntry[]>(\n \"blockchain.scripthash.get_history\",\n [scripthash],\n );\n if (!history || history.length === 0)\n return { funding: \"absent\", fundedSats: 0 };\n\n // HTLC addresses see at most a handful of txs (fund + resolve), so\n // fetching each raw tx is cheap.\n const txs = await Promise.all(\n history.map(async (entry) => {\n const raw = await client.request<string>(\"blockchain.transaction.get\", [\n entry.tx_hash,\n ]);\n return {\n entry,\n tx: btc.Transaction.fromRaw(hex.decode(raw), {\n allowUnknownOutputs: true,\n allowUnknownInputs: true,\n disableScriptCheck: true,\n }),\n };\n }),\n );\n\n const scriptHex = hex.encode(script);\n const outputIndicesPaying = (tx: btc.Transaction): number[] => {\n const indices: number[] = [];\n for (let i = 0; i < tx.outputsLength; i++) {\n const out = tx.getOutput(i);\n if (out.script && hex.encode(out.script) === scriptHex) indices.push(i);\n }\n return indices;\n };\n\n // Among all txs paying the address, the one paying the MOST is the\n // funding candidate — same rule as the pure SDK's UTXO lookup. The\n // address is public, so a stray dust payment (whether it landed before\n // or after the real funding) must not be mistaken for the funding and\n // observe the swap as underfunded/invalid.\n let funding: (typeof txs)[number] | undefined;\n let fundedSats = 0;\n for (const candidate of txs) {\n let paid = 0;\n for (const i of outputIndicesPaying(candidate.tx))\n paid += Number(candidate.tx.getOutput(i).amount ?? 0n);\n if (paid > fundedSats) {\n funding = candidate;\n fundedSats = paid;\n }\n }\n if (!funding) return { funding: \"absent\", fundedSats: 0 };\n const fundedVouts = outputIndicesPaying(funding.tx);\n\n // A tx spending one of the funding outputs is the resolving spend; its\n // witness classifies claim vs refund.\n for (const { tx } of txs) {\n for (let i = 0; i < tx.inputsLength; i++) {\n const input = tx.getInput(i);\n if (\n input.txid &&\n hex.encode(input.txid) === funding.entry.tx_hash &&\n input.index !== undefined &&\n fundedVouts.includes(input.index)\n ) {\n return {\n funding: \"confirmed\",\n fundedSats: 0, // already spent — the amount no longer matters\n spendWitness: (input.finalScriptWitness ?? []).map((item) =>\n hex.encode(item),\n ),\n };\n }\n }\n }\n\n // Same depth policy as the esplora reader: 0/1 read straight off the\n // confirmed flag; deeper policies need the tip to compute depth.\n const height = funding.entry.height;\n const isConfirmed = height > 0;\n let deepEnough: boolean;\n if (isConfirmed) {\n if (required <= 1) {\n deepEnough = true;\n } else {\n const tip = await client.request<{ height: number }>(\n \"blockchain.headers.subscribe\",\n [],\n );\n deepEnough = tip.height - height + 1 >= required;\n }\n } else {\n deepEnough = required === 0;\n }\n return { funding: deepEnough ? \"confirmed\" : \"mempool\", fundedSats };\n }\n\n return {\n async getHtlcFacts(address, minConfirmations) {\n const required =\n minConfirmations ?? resolveMinConfirmations(opts?.minConfirmations);\n try {\n return await getHtlcFactsElectrum(address, required);\n } catch (error) {\n if (!fallback) throw error;\n console.warn(\n \"electrumReader: falling back to secondary reader:\",\n error instanceof Error ? error.message : error,\n );\n return fallback.getHtlcFacts(address, minConfirmations);\n }\n },\n\n subscribe(address, onChange) {\n // An address the configured network can't decode (e.g. a signet address\n // under a mainnet-configured reader) must not throw out of register()\n // and abort tracking — degrade to the poll cadence instead.\n let scripthash: string;\n try {\n scripthash = scriptHashOf(scriptFor(address));\n } catch (error) {\n console.warn(\n \"electrumReader: subscribe skipped (address decode failed):\",\n error instanceof Error ? error.message : error,\n );\n return () => {};\n }\n const handler = () => onChange();\n // Best-effort: if the subscription fails the tracker's poll cadence\n // remains the backstop, so the error is logged, not thrown.\n client.subscribeScriptHash(scripthash, handler).catch((error) => {\n console.warn(\n \"electrumReader: scripthash subscribe failed:\",\n error instanceof Error ? error.message : error,\n );\n });\n return () => client.unsubscribeScriptHash(scripthash, handler);\n },\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -149,9 +149,11 @@ function scriptHashOf(script) {
149
149
  function electrumReader(client, opts) {
150
150
  const network = opts?.network ?? "mainnet";
151
151
  const fallback2 = opts?.fallback;
152
- const scriptFor = (address) => btc.OutScript.encode(
153
- btc.Address(toBtcSignerNetwork(network)).decode(address)
154
- );
152
+ const scriptFor = (address) => {
153
+ const decoded = btc.Address(toBtcSignerNetwork(network)).decode(address);
154
+ if (!decoded) throw new Error(`Unrecognised Bitcoin address: ${address}`);
155
+ return btc.OutScript.encode(decoded);
156
+ };
155
157
  async function getHtlcFactsElectrum(address, required) {
156
158
  const script = scriptFor(address);
157
159
  const scripthash = scriptHashOf(script);
@@ -671,7 +673,7 @@ function payOnLightningStatus(serverHtlc) {
671
673
  }
672
674
 
673
675
  // src/client.ts
674
- var import_lendaswap_sdk_pure2 = require("@lendasat/lendaswap-sdk-pure");
676
+ var import_lendaswap_sdk_pure3 = require("@lendasat/lendaswap-sdk-pure");
675
677
 
676
678
  // src/contracts/arkade-manager.ts
677
679
  var import_sdk2 = require("@arkade-os/sdk");
@@ -1117,7 +1119,9 @@ var AVG_BLOCK_MS = {
1117
1119
  1: 12e3,
1118
1120
  137: 2100,
1119
1121
  42161: 250,
1120
- 30: 3e4
1122
+ 30: 3e4,
1123
+ 31: 3e4
1124
+ // Rootstock testnet: same merge-mined cadence
1121
1125
  };
1122
1126
  var SCAN_MARGIN_BLOCKS = 1000n;
1123
1127
  var EvmContractManager = class _EvmContractManager {
@@ -1298,7 +1302,10 @@ var EvmContractManager = class _EvmContractManager {
1298
1302
  for (const ref of refs) {
1299
1303
  const { observation, preimage } = evmObservation(
1300
1304
  events.get(refQueryKey(ref)) ?? [],
1301
- { amount: ref.expectedAmount, token: ref.expectedToken }
1305
+ {
1306
+ amount: ref.minAmount ?? ref.expectedAmount,
1307
+ token: ref.expectedToken
1308
+ }
1302
1309
  );
1303
1310
  const key = htlcKey(ref);
1304
1311
  if (preimage) this.#preimages.set(key, preimage);
@@ -1375,7 +1382,8 @@ var DEFAULT_EVM_RPCS = {
1375
1382
  "https://arbitrum-one-rpc.publicnode.com"
1376
1383
  ],
1377
1384
  // Rootstock: the public node, HTTP only (no WebSocket endpoint exists).
1378
- 30: ["https://public-node.rsk.co"]
1385
+ 30: ["https://public-node.rsk.co"],
1386
+ 31: ["https://public-node.testnet.rsk.co"]
1379
1387
  };
1380
1388
  var SWAP_CREATED = (0, import_viem.parseAbiItem)(
1381
1389
  "event SwapCreated(bytes32 indexed preimageHash, address indexed refundAddress, address indexed claimAddress, address token, uint256 amount, uint256 timelock, bytes32 key)"
@@ -1576,7 +1584,8 @@ function createEvmRpcReader(rpcUrls, options = {}) {
1576
1584
  return evmReaderFromClient(client, options);
1577
1585
  }
1578
1586
  var EVM_LOG_RANGE_LIMITS = {
1579
- 30: 2000n
1587
+ 30: 2000n,
1588
+ 31: 2000n
1580
1589
  };
1581
1590
  function defaultEvmReaders(overrides) {
1582
1591
  const chainIds = /* @__PURE__ */ new Set([
@@ -2049,6 +2058,9 @@ function defaultWebSocketFactory(url) {
2049
2058
  return new WS(url);
2050
2059
  }
2051
2060
 
2061
+ // src/tracker/from-swap.ts
2062
+ var import_lendaswap_sdk_pure2 = require("@lendasat/lendaswap-sdk-pure");
2063
+
2052
2064
  // src/contracts/arkade-vhtlc.ts
2053
2065
  var import_sdk3 = require("@arkade-os/sdk");
2054
2066
  var import_lendaswap_sdk_pure = require("@lendasat/lendaswap-sdk-pure");
@@ -2134,6 +2146,7 @@ function evmLeg(args) {
2134
2146
  preimageHash: ensure0x(args.hashLock),
2135
2147
  claimAddress: ensure0x(args.claimAddress),
2136
2148
  expectedAmount: BigInt(args.expectedSats),
2149
+ minAmount: args.minAmount,
2137
2150
  expectedToken: args.token ? ensure0x(args.token) : void 0,
2138
2151
  sender: args.sender ? ensure0x(args.sender) : void 0,
2139
2152
  timelockSec: args.timelockSec,
@@ -2141,6 +2154,13 @@ function evmLeg(args) {
2141
2154
  };
2142
2155
  }
2143
2156
  var CLIENT_FUNDING_MIN_CONFIRMATIONS = 1;
2157
+ var FEE_ROUNDING_SLACK_SATS = 2;
2158
+ function minServerPayout(r) {
2159
+ return Math.max(
2160
+ 0,
2161
+ Number(r.target_amount) - r.fee_sats - FEE_ROUNDING_SLACK_SATS
2162
+ );
2163
+ }
2144
2164
  function bitcoinLeg(address, hashLock, expectedSats, minConfirmations) {
2145
2165
  return {
2146
2166
  ledger: "bitcoin",
@@ -2185,15 +2205,19 @@ function swapToTracked(stored) {
2185
2205
  claimAddress: r.server_evm_address,
2186
2206
  // the server claims the client's EVM HTLC
2187
2207
  expectedSats: r.evm_expected_sats,
2188
- // evm_to_arkade doesn't expose the locked token — fall back to the
2189
- // per-chain mainnet constant so the isActive tuple is complete.
2190
- token: lockedTokenFallback(r.evm_chain_id, r.network),
2191
- sender: r.client_evm_address,
2192
- // the client funded it
2208
+ minAmount: 0n,
2209
+ // see SHORT_LOCK_RULE
2210
+ // A native lock's asset word is zero; an ERC20 lock's token is not
2211
+ // on this response yet, so fall back to the per-chain mainnet
2212
+ // constant to complete the isActive tuple.
2213
+ token: r.evm_htlc_kind === "native" ? import_lendaswap_sdk_pure2.NATIVE_TOKEN_ADDRESS : lockedTokenFallback(r.evm_chain_id, r.network),
2214
+ // The coordinator created the HTLC on the client's behalf, so it
2215
+ // is the lock's sender (refund address), as on evm_to_lightning.
2216
+ sender: r.evm_coordinator_address,
2193
2217
  timelockSec: r.evm_refund_locktime,
2194
2218
  createdAt: r.created_at
2195
2219
  }),
2196
- serverHtlc: arkadeLeg(r, r.btc_vhtlc_address, Number(r.target_amount)),
2220
+ serverHtlc: arkadeLeg(r, r.btc_vhtlc_address, minServerPayout(r)),
2197
2221
  clientRefundLocktime: ms(r.evm_refund_locktime),
2198
2222
  serverRefundLocktime: ms(r.vhtlc_refund_locktime)
2199
2223
  };
@@ -2234,16 +2258,19 @@ function swapToTracked(stored) {
2234
2258
  claimAddress: r.server_evm_address,
2235
2259
  // the server claims the client's EVM HTLC
2236
2260
  expectedSats: r.evm_expected_sats,
2261
+ minAmount: 0n,
2262
+ // see SHORT_LOCK_RULE
2237
2263
  token: r.wbtc_address,
2238
- sender: r.client_evm_address,
2239
- // the client funded it
2264
+ // The coordinator created the HTLC on the client's behalf, so it
2265
+ // is the lock's sender (refund address).
2266
+ sender: r.evm_coordinator_address,
2240
2267
  timelockSec: r.evm_refund_locktime,
2241
2268
  createdAt: r.created_at
2242
2269
  }),
2243
2270
  serverHtlc: bitcoinLeg(
2244
2271
  r.btc_htlc_address,
2245
2272
  r.evm_hash_lock,
2246
- Number(r.target_amount)
2273
+ minServerPayout(r)
2247
2274
  ),
2248
2275
  clientRefundLocktime: ms(r.evm_refund_locktime),
2249
2276
  serverRefundLocktime: ms(r.btc_refund_locktime)
@@ -2729,7 +2756,7 @@ var Client = class {
2729
2756
  /** Read per chain read, so it can change without rebuilding the client. */
2730
2757
  #bitcoinMinConfirmations;
2731
2758
  constructor(...args) {
2732
- if (args[0] instanceof import_lendaswap_sdk_pure2.Client) {
2759
+ if (args[0] instanceof import_lendaswap_sdk_pure3.Client) {
2733
2760
  this.#legacy = args[0];
2734
2761
  this.#tracking = args[1] ?? {
2735
2762
  enabled: false
@@ -2737,7 +2764,7 @@ var Client = class {
2737
2764
  assertMinConfirmations(this.#tracking.bitcoinMinConfirmations);
2738
2765
  this.#bitcoinMinConfirmations = this.#tracking.bitcoinMinConfirmations;
2739
2766
  } else {
2740
- this.#legacy = new import_lendaswap_sdk_pure2.Client(
2767
+ this.#legacy = new import_lendaswap_sdk_pure3.Client(
2741
2768
  ...args
2742
2769
  );
2743
2770
  this.#tracking = { enabled: false };
@@ -2918,6 +2945,10 @@ var Client = class {
2918
2945
  return this.#legacy.claim(...args);
2919
2946
  }
2920
2947
  /** Delegated to the legacy client (migration checkpoint). */
2948
+ claimEvmWithSigner(...args) {
2949
+ return this.#legacy.claimEvmWithSigner(...args);
2950
+ }
2951
+ /** Delegated to the legacy client (migration checkpoint). */
2921
2952
  claimViaGasless(...args) {
2922
2953
  return this.#legacy.claimViaGasless(...args);
2923
2954
  }
@@ -3305,7 +3336,7 @@ var Client = class {
3305
3336
  "bitcoin",
3306
3337
  await BitcoinContractManager.create({
3307
3338
  esploraUrl: esploraUrl ? [esploraUrl] : DEFAULT_ESPLORA_URLS,
3308
- electrumWsUrl: electrumWsUrl ?? import_lendaswap_sdk_pure2.DEFAULT_ELECTRUM_WS_URLS[network],
3339
+ electrumWsUrl: electrumWsUrl ?? import_lendaswap_sdk_pure3.DEFAULT_ELECTRUM_WS_URLS[network],
3309
3340
  network,
3310
3341
  chainTime,
3311
3342
  minConfirmations: () => this.#bitcoinMinConfirmations
@@ -3316,7 +3347,7 @@ var Client = class {
3316
3347
  }
3317
3348
  };
3318
3349
  var ClientBuilder = class {
3319
- #inner = import_lendaswap_sdk_pure2.Client.builder();
3350
+ #inner = import_lendaswap_sdk_pure3.Client.builder();
3320
3351
  // Observe-mode tracking is on by default; the built client auto-builds its
3321
3352
  // managers from config unless disabled or given an explicit override.
3322
3353
  #trackingEnabled = true;