@arkade-os/swap 0.0.11 → 0.0.13

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/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  ARKADE_ASSET,
3
3
  ARKADE_BTC,
4
4
  AddressMismatch,
5
+ L1_NETWORKS,
5
6
  LIGHTNING_BTC,
6
7
  LIGHTNING_RECEIVE_PAIR,
7
8
  LIGHTNING_SEND_PAIR,
@@ -38,6 +39,7 @@ import {
38
39
  deriveOnchainSend,
39
40
  extractPreimage,
40
41
  httpTransport,
42
+ l1ScriptForAddress,
41
43
  lightningReceiveRequest,
42
44
  lightningSendRequest,
43
45
  lightningSendVtxoScript,
@@ -63,7 +65,7 @@ import {
63
65
  unilateralRefundWithoutReceiverDelay,
64
66
  verifyLockupAddress,
65
67
  verifyReceiveInvoice
66
- } from "./chunk-RIC5ZTZK.js";
68
+ } from "./chunk-IFZOBTTG.js";
67
69
  import {
68
70
  InMemoryAssetSwapRepository,
69
71
  marketsCacheKey
@@ -1061,7 +1063,8 @@ function onchainSendProfile(result) {
1061
1063
  htlcLocktime: result.htlcParams.refundLocktime,
1062
1064
  network: result.l1Network,
1063
1065
  htlcAddress: result.htlc.address,
1064
- minConfirmations: result.minConfirmations
1066
+ minConfirmations: result.minConfirmations,
1067
+ payoutPkScript: hex3.encode(result.payoutPkScript)
1065
1068
  };
1066
1069
  }
1067
1070
  var OnchainSendCorridor = {
@@ -1110,6 +1113,9 @@ var OnchainSendCorridor = {
1110
1113
  paymentHash,
1111
1114
  htlc,
1112
1115
  minConfirmations: profile.minConfirmations,
1116
+ // Optional here, required at the write: throwing on an older
1117
+ // record would strand the refund it is still owed.
1118
+ ...profile.payoutPkScript ? { payoutPkScript: hex3.decode(profile.payoutPkScript) } : {},
1113
1119
  ...profile.funding ? { funding: profile.funding } : {},
1114
1120
  ...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
1115
1121
  };
@@ -1490,10 +1496,557 @@ async function watchOfferSwaps({
1490
1496
  };
1491
1497
  }
1492
1498
 
1493
- // src/claim.ts
1499
+ // src/chainSource.ts
1500
+ import * as btc from "@scure/btc-signer";
1501
+ import { hex as hex7 } from "@scure/base";
1502
+ var addressOf = (pkScript, network) => btc.Address(L1_NETWORKS[network]).encode(btc.OutScript.decode(pkScript));
1503
+ var chainSourceFrom = (provider, network) => {
1504
+ const spenderFromHistory = async (txid, vout, pkScript) => {
1505
+ const txs = await provider.getTransactions(addressOf(pkScript, network));
1506
+ return txs.find((tx) => tx.vin?.some((i) => i.txid === txid && i.vout === vout))?.txid;
1507
+ };
1508
+ return {
1509
+ async getScriptUtxos(pkScript) {
1510
+ const address = addressOf(pkScript, network);
1511
+ const [coins, tip] = await Promise.all([
1512
+ provider.getCoins(address),
1513
+ provider.getChainTip()
1514
+ ]);
1515
+ return coins.map((coin) => ({
1516
+ txid: coin.txid,
1517
+ vout: coin.vout,
1518
+ amount: BigInt(coin.value),
1519
+ // Zero, not one: calling a mempool output "1 deep" would let a
1520
+ // 1-confirmation policy claim against a replaceable transaction.
1521
+ confirmations: coin.status.confirmed && typeof coin.status.block_height === "number" ? Math.max(0, tip.height - coin.status.block_height + 1) : 0
1522
+ }));
1523
+ },
1524
+ async getSpendingTx(txid, vout, pkScript) {
1525
+ const outspends = await provider.getTxOutspends(txid);
1526
+ const outspend = outspends[vout];
1527
+ if (!outspend?.spent) return null;
1528
+ const spender = outspend.txid || await spenderFromHistory(txid, vout, pkScript);
1529
+ if (!spender) return null;
1530
+ const raw = await provider.getRawTransaction(spender);
1531
+ return { txHex: hex7.encode(raw) };
1532
+ },
1533
+ broadcast(txHex) {
1534
+ return provider.broadcastTransaction(txHex);
1535
+ },
1536
+ async getMtp() {
1537
+ return (await provider.getChainTip()).time;
1538
+ }
1539
+ };
1540
+ };
1541
+
1542
+ // src/payment/rendezvous.ts
1543
+ import { selectMarkets, sideLimits as sideLimits2 } from "@arkade-os/solver-discovery";
1494
1544
  import { hex as hex8 } from "@scure/base";
1545
+ var XONLY_HEX = /^[0-9a-f]{64}$/;
1546
+ var rendezvousOf = (market, pinned) => {
1547
+ const transports = { nostr: { relays: market.transports?.nostr?.relays ?? [] } };
1548
+ if (!market.discovery_pubkey || !transports.nostr.relays.length) return void 0;
1549
+ const advertised = market.emulator_pubkey;
1550
+ const emulatorPubkey = advertised === void 0 || advertised === null || advertised === "" ? pinned : typeof advertised === "string" && XONLY_HEX.test(advertised) ? advertised : void 0;
1551
+ if (!emulatorPubkey) return void 0;
1552
+ if (pinned && emulatorPubkey !== pinned) return void 0;
1553
+ const bounds = sideLimits2(market, "quote");
1554
+ if (!bounds) return void 0;
1555
+ return {
1556
+ solverPubkey: market.discovery_pubkey,
1557
+ transports,
1558
+ minSats: Number(bounds.min),
1559
+ maxSats: Number(bounds.max)
1560
+ };
1561
+ };
1562
+ var solverRendezvous = (markets, payoutCorridor, amountSats, fallbackEmulatorPubkey) => {
1563
+ const encoded = fallbackEmulatorPubkey ? hex8.encode(fallbackEmulatorPubkey) : void 0;
1564
+ if (encoded !== void 0 && !XONLY_HEX.test(encoded)) return void 0;
1565
+ const pinned = encoded;
1566
+ const candidates = selectMarkets(markets, {
1567
+ baseId: BTC_ASSET_ID,
1568
+ quoteId: BTC_ASSET_ID,
1569
+ baseCorridor: "arkade",
1570
+ quoteCorridor: payoutCorridor
1571
+ });
1572
+ for (const market of candidates) {
1573
+ const rendezvous = rendezvousOf(market, pinned);
1574
+ if (!rendezvous) continue;
1575
+ if (amountSats >= rendezvous.minSats && amountSats <= rendezvous.maxSats) {
1576
+ return rendezvous;
1577
+ }
1578
+ }
1579
+ return void 0;
1580
+ };
1581
+
1582
+ // src/payment/solverOnchain.ts
1583
+ import {
1584
+ assertNoAssets,
1585
+ assetsOf,
1586
+ btcTarget,
1587
+ makeHandle,
1588
+ resolveSendAmount,
1589
+ tryResolveSendAmount
1590
+ } from "@arkade-os/sdk";
1591
+ var SOLVER_ONCHAIN_RAIL = "solver-onchain";
1592
+ var solverOnchainRendezvous = (markets, amountSats, fallbackEmulatorPubkey) => solverRendezvous(markets, "onchain", amountSats, fallbackEmulatorPubkey);
1593
+ function solverOnchainRail(deps) {
1594
+ const rendezvousFor = async (amount) => {
1595
+ if (amount === void 0) return void 0;
1596
+ const markets = await deps.discover();
1597
+ return solverOnchainRendezvous(markets, amount, deps.fallbackEmulatorPubkey);
1598
+ };
1599
+ return {
1600
+ id: SOLVER_ONCHAIN_RAIL,
1601
+ match: (req) => btcTarget(req.raw) !== void 0,
1602
+ available: async (req) => {
1603
+ if (assetsOf(req).length > 0) return false;
1604
+ const address = btcTarget(req.raw);
1605
+ if (!address) return false;
1606
+ try {
1607
+ l1ScriptForAddress(address, deps.l1Network);
1608
+ } catch {
1609
+ return false;
1610
+ }
1611
+ const amount = tryResolveSendAmount(req.raw, req.amount);
1612
+ if (amount === void 0) return false;
1613
+ return await rendezvousFor(amount) !== void 0;
1614
+ },
1615
+ quote: async (req, ctx) => {
1616
+ const address = btcTarget(req.raw);
1617
+ assertNoAssets(SOLVER_ONCHAIN_RAIL, req);
1618
+ const amount = resolveSendAmount(SOLVER_ONCHAIN_RAIL, req.raw, req.amount);
1619
+ const payoutPkScript = l1ScriptForAddress(address, deps.l1Network);
1620
+ const rendezvous = await rendezvousFor(amount);
1621
+ if (!rendezvous) {
1622
+ throw new Error(
1623
+ `${SOLVER_ONCHAIN_RAIL}: no solver serves arkade:BTC -> onchain:BTC at ${amount} sats`
1624
+ );
1625
+ }
1626
+ const negotiated = await deps.connect(
1627
+ rendezvous,
1628
+ (transport) => requestOnchainSend(ctx.wallet, deps.arkServerUrl, transport, {
1629
+ amount,
1630
+ amountSide: "to",
1631
+ payoutPubkey: deps.payoutPubkey,
1632
+ ...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
1633
+ })
1634
+ );
1635
+ if (negotiated.l1Network !== deps.l1Network) {
1636
+ throw new Error(
1637
+ `${SOLVER_ONCHAIN_RAIL}: rail built for ${deps.l1Network} but the swap was negotiated on ${negotiated.l1Network}`
1638
+ );
1639
+ }
1640
+ const swap = { ...negotiated, rendezvous, payoutPkScript };
1641
+ return {
1642
+ railId: SOLVER_ONCHAIN_RAIL,
1643
+ amount,
1644
+ fee: swap.fundAmount - amount,
1645
+ total: swap.fundAmount,
1646
+ meta: {
1647
+ rfqId: swap.rfqId,
1648
+ validUntil: swap.quote.valid_until,
1649
+ htlcAddress: swap.htlc.address,
1650
+ minConfirmations: swap.minConfirmations,
1651
+ solverPubkey: rendezvous.solverPubkey,
1652
+ // The claim tx's fee comes out of the HTLC output at a rate
1653
+ // not knowable now, so `amount` is the payout, not the net.
1654
+ claimFeeDeductedFromPayout: true
1655
+ },
1656
+ send: async () => makeHandle(SOLVER_ONCHAIN_RAIL, async (emit) => {
1657
+ assertFundable({
1658
+ quote: swap.quote,
1659
+ now: Math.floor(Date.now() / 1e3),
1660
+ onchain: {
1661
+ htlcLocktime: swap.htlcParams.refundLocktime,
1662
+ minConfirmations: swap.minConfirmations,
1663
+ direction: "send"
1664
+ }
1665
+ });
1666
+ await deps.persist(swap);
1667
+ await ctx.wallet.send({
1668
+ address: swap.address,
1669
+ amount: swap.fundAmount
1670
+ });
1671
+ emit({ status: "sent" });
1672
+ const result = { railId: SOLVER_ONCHAIN_RAIL, swapId: swap.rfqId };
1673
+ if (!deps.awaitSettlement) return result;
1674
+ let txid;
1675
+ try {
1676
+ ({ txid } = await deps.awaitSettlement(swap));
1677
+ } catch (e) {
1678
+ console.warn(
1679
+ `${SOLVER_ONCHAIN_RAIL}: settlement watch failed; the payment is sent`,
1680
+ e
1681
+ );
1682
+ return result;
1683
+ }
1684
+ const settled = { ...result, txid };
1685
+ emit({ status: "settled", result: settled });
1686
+ return settled;
1687
+ })
1688
+ };
1689
+ }
1690
+ };
1691
+ }
1692
+
1693
+ // src/payment/solverLightning.ts
1694
+ import { assertNoAssets as assertNoAssets2, assetsOf as assetsOf2, invoiceTarget, makeHandle as makeHandle2 } from "@arkade-os/sdk";
1695
+ var SOLVER_LIGHTNING_RAIL = "solver-lightning";
1696
+ var factsOf = (raw, decode, now) => {
1697
+ const invoice = invoiceTarget(raw);
1698
+ if (!invoice) return void 0;
1699
+ let facts;
1700
+ try {
1701
+ facts = decode(invoice);
1702
+ } catch {
1703
+ return void 0;
1704
+ }
1705
+ if (!Number.isInteger(facts.amountSats) || facts.amountSats <= 0) return void 0;
1706
+ if (facts.expiresAt <= now) return void 0;
1707
+ return facts;
1708
+ };
1709
+ var solverLightningRendezvous = (markets, amountSats, fallbackEmulatorPubkey) => solverRendezvous(markets, "lightning", amountSats, fallbackEmulatorPubkey);
1710
+ function solverLightningRail(deps) {
1711
+ const rendezvousFor = async (amountSats) => solverLightningRendezvous(await deps.discover(), amountSats, deps.fallbackEmulatorPubkey);
1712
+ return {
1713
+ id: SOLVER_LIGHTNING_RAIL,
1714
+ match: (req) => invoiceTarget(req.raw) !== void 0,
1715
+ available: async (req) => {
1716
+ if (assetsOf2(req).length > 0) return false;
1717
+ const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
1718
+ if (!facts) return false;
1719
+ if (req.amount !== void 0 && req.amount !== facts.amountSats) return false;
1720
+ return await rendezvousFor(facts.amountSats) !== void 0;
1721
+ },
1722
+ quote: async (req, ctx) => {
1723
+ assertNoAssets2(SOLVER_LIGHTNING_RAIL, req);
1724
+ const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
1725
+ if (!facts) {
1726
+ throw new Error(
1727
+ `${SOLVER_LIGHTNING_RAIL}: the request carries no payable BOLT11 invoice (amountless, expired, or undecodable)`
1728
+ );
1729
+ }
1730
+ if (req.amount !== void 0 && req.amount !== facts.amountSats) {
1731
+ throw new Error(
1732
+ `${SOLVER_LIGHTNING_RAIL}: the request names ${req.amount} sats but the invoice is for ${facts.amountSats} \u2014 the payee is paid the invoice`
1733
+ );
1734
+ }
1735
+ const rendezvous = await rendezvousFor(facts.amountSats);
1736
+ if (!rendezvous) {
1737
+ throw new Error(
1738
+ `${SOLVER_LIGHTNING_RAIL}: no solver serves arkade:BTC -> lightning:BTC at ${facts.amountSats} sats`
1739
+ );
1740
+ }
1741
+ const negotiated = await deps.connect(
1742
+ rendezvous,
1743
+ (transport) => requestLightningSend(ctx.wallet, deps.arkServerUrl, transport, {
1744
+ invoice: facts,
1745
+ ...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
1746
+ })
1747
+ );
1748
+ const swap = { ...negotiated, invoice: facts, rendezvous };
1749
+ return {
1750
+ railId: SOLVER_LIGHTNING_RAIL,
1751
+ // `requestLightningSend` refuses a quote that reprices the
1752
+ // invoice, so the spread is a fee on top.
1753
+ amount: facts.amountSats,
1754
+ fee: swap.fundAmount - facts.amountSats,
1755
+ total: swap.fundAmount,
1756
+ meta: {
1757
+ rfqId: swap.rfqId,
1758
+ validUntil: swap.quote.valid_until,
1759
+ paymentHash: facts.paymentHash,
1760
+ invoiceExpiresAt: facts.expiresAt,
1761
+ solverPubkey: rendezvous.solverPubkey
1762
+ },
1763
+ send: async () => makeHandle2(SOLVER_LIGHTNING_RAIL, async (emit) => {
1764
+ assertFundable({
1765
+ quote: swap.quote,
1766
+ invoiceExpiresAt: facts.expiresAt,
1767
+ now: Math.floor(Date.now() / 1e3)
1768
+ });
1769
+ await deps.persist(swap);
1770
+ await ctx.wallet.send({
1771
+ address: swap.address,
1772
+ amount: swap.fundAmount
1773
+ });
1774
+ emit({ status: "sent" });
1775
+ const result = { railId: SOLVER_LIGHTNING_RAIL, swapId: swap.rfqId };
1776
+ if (!deps.awaitSettlement) return result;
1777
+ let preimage;
1778
+ try {
1779
+ ({ preimage } = await deps.awaitSettlement(swap));
1780
+ } catch (e) {
1781
+ console.warn(
1782
+ `${SOLVER_LIGHTNING_RAIL}: settlement watch failed; the payment is sent`,
1783
+ e
1784
+ );
1785
+ return result;
1786
+ }
1787
+ const settled = { ...result, ...preimage !== void 0 && { preimage } };
1788
+ emit({ status: "settled", result: settled });
1789
+ return settled;
1790
+ })
1791
+ };
1792
+ }
1793
+ };
1794
+ }
1795
+
1796
+ // src/payment/crossAsset.ts
1797
+ import {
1798
+ arkTarget,
1799
+ asset as assetExt,
1800
+ assetsOf as assetsOf3,
1801
+ makeHandle as makeHandle3,
1802
+ resolveAssetAmount
1803
+ } from "@arkade-os/sdk";
1804
+ var CROSS_ASSET_RAIL = "cross-asset";
1805
+ var DEFAULT_CARRIER_SATS = 330;
1806
+ var parseAssetId = (assetId) => {
1807
+ try {
1808
+ return assetExt.AssetId.fromString(assetId);
1809
+ } catch {
1810
+ return void 0;
1811
+ }
1812
+ };
1813
+ function crossAssetRail(deps) {
1814
+ const planFor = async (asset2) => {
1815
+ const markets = await deps.discover();
1816
+ const found = findMarket(markets, BTC_ASSET_ID, asset2.assetId);
1817
+ const market = found?.market;
1818
+ if (!market) return void 0;
1819
+ const plan = await deps.quote(market, found.give, asset2.amount);
1820
+ const [balance, dust] = await Promise.all([deps.btcBalance(), deps.dust()]);
1821
+ return { plan, error: validatePlan(plan, balance, dust) };
1822
+ };
1823
+ return {
1824
+ id: CROSS_ASSET_RAIL,
1825
+ match: (req) => arkTarget(req.raw) !== void 0,
1826
+ available: async (req) => {
1827
+ const assets = assetsOf3(req);
1828
+ if (assets.length !== 1) return false;
1829
+ const [asset2] = assets;
1830
+ if (asset2.assetId === BTC_ASSET_ID) return false;
1831
+ if (typeof asset2.amount !== "bigint" || asset2.amount <= 0n) return false;
1832
+ if (!parseAssetId(asset2.assetId)) return false;
1833
+ const planned = await planFor(asset2);
1834
+ return planned !== void 0 && planned.error === void 0;
1835
+ },
1836
+ quote: async (req, ctx) => {
1837
+ const payTo = arkTarget(req.raw);
1838
+ const asset2 = resolveAssetAmount(CROSS_ASSET_RAIL, req);
1839
+ const wantAsset = parseAssetId(asset2.assetId);
1840
+ if (!wantAsset) {
1841
+ throw new Error(
1842
+ `${CROSS_ASSET_RAIL}: ${asset2.assetId} is not an asset id (expected 34 bytes of hex)`
1843
+ );
1844
+ }
1845
+ const planned = await planFor(asset2);
1846
+ if (!planned) {
1847
+ throw new Error(`${CROSS_ASSET_RAIL}: no market swaps BTC into ${asset2.assetId}`);
1848
+ }
1849
+ if (planned.error) {
1850
+ throw new Error(
1851
+ `${CROSS_ASSET_RAIL}: cannot swap into ${asset2.assetId} \u2014 ${planned.error}`
1852
+ );
1853
+ }
1854
+ const { plan } = planned;
1855
+ if (!Number.isSafeInteger(Number(plan.deposit.atomic))) {
1856
+ throw new Error(
1857
+ `${CROSS_ASSET_RAIL}: deposit of ${plan.deposit.atomic} does not fit a JS number`
1858
+ );
1859
+ }
1860
+ const depositSats = Number(plan.deposit.atomic);
1861
+ const carrier = deps.carrierSats ?? DEFAULT_CARRIER_SATS;
1862
+ return {
1863
+ railId: CROSS_ASSET_RAIL,
1864
+ // OPEN QUESTION for review: `total = amount + fee` forces the
1865
+ // purchase price into `fee`, which reads as a service charge.
1866
+ // A third field instead? `assets.spent` labels it correctly.
1867
+ amount: carrier,
1868
+ fee: depositSats,
1869
+ total: depositSats + carrier,
1870
+ assets: {
1871
+ delivered: asset2,
1872
+ spent: { assetId: BTC_ASSET_ID, amount: plan.deposit.atomic }
1873
+ },
1874
+ meta: {
1875
+ market: plan.market.pair,
1876
+ priceDisplay: plan.priceDisplay,
1877
+ give: plan.give
1878
+ },
1879
+ send: async () => makeHandle3(CROSS_ASSET_RAIL, async (emit) => {
1880
+ const offer = await createOffer(ctx.wallet, deps.arkServerUrl, {
1881
+ wantAmount: asset2.amount,
1882
+ wantAsset,
1883
+ ...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
1884
+ });
1885
+ const swap = {
1886
+ phase: "quoted",
1887
+ offerHex: offer.offerHex,
1888
+ address: offer.address,
1889
+ swapPkScript: offer.swapPkScript,
1890
+ depositSats,
1891
+ asset: asset2,
1892
+ payTo,
1893
+ plan
1894
+ };
1895
+ await deps.persist(swap);
1896
+ await ctx.wallet.send({
1897
+ address: offer.address,
1898
+ amount: depositSats,
1899
+ extensions: [offer.extension]
1900
+ });
1901
+ emit({ status: "sent" });
1902
+ await deps.awaitFill(swap);
1903
+ await deps.persist({ ...swap, phase: "filled" });
1904
+ await deps.persist({ ...swap, phase: "paying" });
1905
+ const txid = await ctx.wallet.send({
1906
+ address: payTo,
1907
+ amount: carrier,
1908
+ assets: [asset2]
1909
+ });
1910
+ await deps.persist({ ...swap, phase: "settled", txid });
1911
+ const result = {
1912
+ railId: CROSS_ASSET_RAIL,
1913
+ txid,
1914
+ swapId: offer.offerHex
1915
+ };
1916
+ emit({ status: "settled", result });
1917
+ return result;
1918
+ })
1919
+ };
1920
+ }
1921
+ };
1922
+ }
1923
+
1924
+ // src/reveal.ts
1925
+ import { base64 as base643, hex as hex9 } from "@scure/base";
1495
1926
  import { ripemd160 } from "@noble/hashes/legacy.js";
1496
- import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
1927
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
1928
+ import { ArkAddress as ArkAddress4 } from "@arkade-os/sdk";
1929
+ var CovclaimdRevealError = class extends Error {
1930
+ name = "CovclaimdRevealError";
1931
+ status;
1932
+ detail;
1933
+ retryable;
1934
+ constructor(what, status, detail) {
1935
+ super(`covclaimd ${what} failed: ${status || "no response"} ${detail}`.trimEnd());
1936
+ this.status = status;
1937
+ this.detail = detail;
1938
+ this.retryable = status === 0 || status === 429 || status >= 500;
1939
+ }
1940
+ };
1941
+ var decodeKey = (value, field) => {
1942
+ if (typeof value !== "string") throw new Error(`covclaimd info: ${field} is not a string`);
1943
+ let bytes;
1944
+ try {
1945
+ bytes = hex9.decode(value);
1946
+ } catch {
1947
+ throw new Error(`covclaimd info: ${field} is not hex`);
1948
+ }
1949
+ if (bytes.length !== 33) {
1950
+ throw new Error(`covclaimd info: ${field} must be 33-byte compressed, got ${bytes.length}`);
1951
+ }
1952
+ return bytes;
1953
+ };
1954
+ var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
1955
+ var assertTransportSecure = (baseUrl, allowInsecureHttp) => {
1956
+ const { protocol, hostname } = new URL(baseUrl);
1957
+ if (protocol === "https:" || allowInsecureHttp) return;
1958
+ if (protocol === "http:" && LOOPBACK.has(hostname)) return;
1959
+ throw new Error(
1960
+ `covclaimd URL must be https (got ${protocol}//${hostname}): the key it serves is the one the preimage gets sealed to. Pass allowInsecureHttp to override.`
1961
+ );
1962
+ };
1963
+ var covclaimdClient = (baseUrl, options = {}) => {
1964
+ assertTransportSecure(baseUrl, options.allowInsecureHttp === true);
1965
+ const url = baseUrl.replace(/\/+$/, "");
1966
+ const fetchImpl = options.fetchImpl ?? fetch;
1967
+ const timeoutMs = options.timeoutMs ?? 3e4;
1968
+ const send = async (what, path, init) => {
1969
+ let response;
1970
+ try {
1971
+ response = await fetchImpl(`${url}${path}`, {
1972
+ ...init,
1973
+ // a redirect would move the key fetch off the vetted origin
1974
+ redirect: "error",
1975
+ signal: AbortSignal.timeout(timeoutMs)
1976
+ });
1977
+ } catch (error) {
1978
+ throw new CovclaimdRevealError(what, 0, String(error));
1979
+ }
1980
+ if (!response.ok) {
1981
+ throw new CovclaimdRevealError(what, response.status, await response.text());
1982
+ }
1983
+ return response;
1984
+ };
1985
+ const readJson = async (what, response) => {
1986
+ const body = await response.text();
1987
+ try {
1988
+ return JSON.parse(body);
1989
+ } catch {
1990
+ throw new CovclaimdRevealError(
1991
+ what,
1992
+ response.status,
1993
+ `non-JSON body: ${body.slice(0, 200)}`
1994
+ );
1995
+ }
1996
+ };
1997
+ return {
1998
+ async info() {
1999
+ const response = await send("info", "/v1/preimage/covclaimd-pubkey", {
2000
+ method: "GET"
2001
+ });
2002
+ const body = await readJson("info", response);
2003
+ return {
2004
+ covclaimdPubkey: decodeKey(body?.covclaimd_pub_key, "covclaimd_pub_key"),
2005
+ emulatorPubkey: hex9.encode(decodeKey(body?.emulator_pub_key, "emulator_pub_key"))
2006
+ };
2007
+ },
2008
+ async reveal(params) {
2009
+ await send("reveal", "/v1/reveal", {
2010
+ method: "POST",
2011
+ headers: { "content-type": "application/json" },
2012
+ body: JSON.stringify({
2013
+ swap_address: params.swapAddress,
2014
+ packet: { ciphertext: params.ciphertext, arkade_script: params.arkadeScript },
2015
+ taptree: params.taptree
2016
+ })
2017
+ });
2018
+ }
2019
+ };
2020
+ };
2021
+ function revealFieldsFromScript(script) {
2022
+ if (!script.nonInteractiveClaimArkadeScript) {
2023
+ throw new Error("this covenant has no non-interactive claim leaf to reveal against");
2024
+ }
2025
+ return {
2026
+ arkadeScript: base643.encode(script.nonInteractiveClaimArkadeScript),
2027
+ taptree: hex9.encode(script.encode())
2028
+ };
2029
+ }
2030
+ async function revealClaimPacket(client, input) {
2031
+ const fields = revealFieldsFromScript(input.script);
2032
+ if (hex9.encode(ArkAddress4.decode(input.address).pkScript) !== hex9.encode(input.script.pkScript)) {
2033
+ throw new Error("address does not belong to this covenant");
2034
+ }
2035
+ if (hex9.encode(ripemd160(sha2562(input.preimage))) !== hex9.encode(input.script.options.preimageHash)) {
2036
+ throw new Error("preimage does not match the covenant's payment hash");
2037
+ }
2038
+ const { ciphertext } = await sealClaimPacket({
2039
+ preimage: input.preimage,
2040
+ covclaimdPubkey: (await client.info()).covclaimdPubkey
2041
+ });
2042
+ await client.reveal({ swapAddress: input.address, ciphertext, ...fields });
2043
+ return { ciphertext };
2044
+ }
2045
+
2046
+ // src/claim.ts
2047
+ import { hex as hex11 } from "@scure/base";
2048
+ import { ripemd160 as ripemd1602 } from "@noble/hashes/legacy.js";
2049
+ import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
1497
2050
  import {
1498
2051
  CSVMultisigTapscript as CSVMultisigTapscript2,
1499
2052
  claimWithPreimageIdentity,
@@ -1501,8 +2054,8 @@ import {
1501
2054
  } from "@arkade-os/sdk";
1502
2055
 
1503
2056
  // src/refund.ts
1504
- import { base64 as base643, hex as hex7 } from "@scure/base";
1505
- import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
2057
+ import { base64 as base644, hex as hex10 } from "@scure/base";
2058
+ import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
1506
2059
  import {
1507
2060
  CSVMultisigTapscript,
1508
2061
  ConditionWitness,
@@ -1559,7 +2112,7 @@ var LockupNeedsRecoveryError = class extends Error {
1559
2112
  }
1560
2113
  };
1561
2114
  async function findLockupVtxos(indexer, swapPkScript) {
1562
- const scripts = [hex7.encode(swapPkScript)];
2115
+ const scripts = [hex10.encode(swapPkScript)];
1563
2116
  const [spendable, recoverable] = await Promise.all([
1564
2117
  indexer.getVtxos({ scripts, spendableOnly: true }),
1565
2118
  indexer.getVtxos({ scripts, recoverableOnly: true })
@@ -1585,13 +2138,13 @@ async function findLockupVtxos(indexer, swapPkScript) {
1585
2138
  }
1586
2139
  return out;
1587
2140
  }
1588
- var hashesTo = (candidate, paymentHash) => hex7.encode(sha2562(candidate)) === paymentHash;
2141
+ var hashesTo = (candidate, paymentHash) => hex10.encode(sha2563(candidate)) === paymentHash;
1589
2142
  var candidateWitnessItems = (tx, inputIndex) => [
1590
2143
  ...getArkPsbtFields(tx, inputIndex, ConditionWitness).flat(),
1591
2144
  ...tx.getInput(inputIndex).finalScriptWitness ?? []
1592
2145
  ];
1593
2146
  async function readLockupFate(indexer, input) {
1594
- const { vtxos } = await indexer.getVtxos({ scripts: [hex7.encode(input.swapPkScript)] });
2147
+ const { vtxos } = await indexer.getVtxos({ scripts: [hex10.encode(input.swapPkScript)] });
1595
2148
  const all = vtxos ?? [];
1596
2149
  if (all.length === 0) return { fate: "unknown" };
1597
2150
  const exited = all.filter((vtxo) => vtxo.isUnrolled && !hasTerminalSpend(vtxo));
@@ -1618,7 +2171,7 @@ async function readLockupFate(indexer, input) {
1618
2171
  for (const raw of txs) {
1619
2172
  let tx;
1620
2173
  try {
1621
- tx = Transaction3.fromPSBT(base643.decode(raw));
2174
+ tx = Transaction3.fromPSBT(base644.decode(raw));
1622
2175
  } catch {
1623
2176
  continue;
1624
2177
  }
@@ -1626,7 +2179,7 @@ async function readLockupFate(indexer, input) {
1626
2179
  for (let i = 0; i < tx.inputsLength; i++) {
1627
2180
  const spent = tx.getInput(i);
1628
2181
  if (!spent.txid) continue;
1629
- const txid = hex7.encode(spent.txid);
2182
+ const txid = hex10.encode(spent.txid);
1630
2183
  if (!all.some((vtxo) => vtxo.txid === txid && vtxo.vout === spent.index)) continue;
1631
2184
  for (const candidate of candidateWitnessItems(tx, i)) {
1632
2185
  if (hashesTo(candidate, input.paymentHash)) {
@@ -1655,7 +2208,7 @@ async function pushRefundWithoutReceiver(ark, input) {
1655
2208
  const info = await ark.getInfo();
1656
2209
  let serverUnrollScript;
1657
2210
  try {
1658
- serverUnrollScript = CSVMultisigTapscript.decode(hex7.decode(info.checkpointTapscript));
2211
+ serverUnrollScript = CSVMultisigTapscript.decode(hex10.decode(info.checkpointTapscript));
1659
2212
  } catch {
1660
2213
  throw new Error("invalid checkpointTapscript from the Arkade server");
1661
2214
  }
@@ -1675,8 +2228,8 @@ async function pushRefundWithoutReceiver(ark, input) {
1675
2228
  );
1676
2229
  const signedArkTx = await input.sender.sign(arkTx);
1677
2230
  const submitted = await ark.submitTx(
1678
- base643.encode(signedArkTx.toPSBT()),
1679
- checkpoints.map((c) => base643.encode(c.toPSBT()))
2231
+ base644.encode(signedArkTx.toPSBT()),
2232
+ checkpoints.map((c) => base644.encode(c.toPSBT()))
1680
2233
  );
1681
2234
  assertSubmittedArkTxid(submitted, signedArkTx, "refundWithoutReceiver");
1682
2235
  const matched = matchServerCheckpoints(
@@ -1686,7 +2239,7 @@ async function pushRefundWithoutReceiver(ark, input) {
1686
2239
  );
1687
2240
  const finalCheckpoints = await Promise.all(
1688
2241
  matched.map(
1689
- async ({ server }) => base643.encode((await input.sender.sign(server, [0])).toPSBT())
2242
+ async ({ server }) => base644.encode((await input.sender.sign(server, [0])).toPSBT())
1690
2243
  )
1691
2244
  );
1692
2245
  await ark.finalizeTx(submitted.arkTxid, finalCheckpoints);
@@ -1781,13 +2334,13 @@ async function pushClaim(ark, input) {
1781
2334
  }
1782
2335
  }
1783
2336
  const committed = input.script.options.preimageHash;
1784
- if (hex8.encode(ripemd160(sha2563(input.preimage))) !== hex8.encode(committed)) {
2337
+ if (hex11.encode(ripemd1602(sha2564(input.preimage))) !== hex11.encode(committed)) {
1785
2338
  throw new Error("preimage does not match the covenant's payment hash");
1786
2339
  }
1787
2340
  const info = await ark.getInfo();
1788
2341
  let serverUnrollScript;
1789
2342
  try {
1790
- serverUnrollScript = CSVMultisigTapscript2.decode(hex8.decode(info.checkpointTapscript));
2343
+ serverUnrollScript = CSVMultisigTapscript2.decode(hex11.decode(info.checkpointTapscript));
1791
2344
  } catch {
1792
2345
  throw new Error("invalid checkpointTapscript from the Arkade server");
1793
2346
  }
@@ -1906,7 +2459,7 @@ function arkadeRefunder(deps) {
1906
2459
  }
1907
2460
 
1908
2461
  // src/swapManager.ts
1909
- import { hex as hex9 } from "@scure/base";
2462
+ import { hex as hex12 } from "@scure/base";
1910
2463
  function nextOnchainAction(input) {
1911
2464
  switch (input.phase.phase) {
1912
2465
  case "unfunded":
@@ -2354,7 +2907,7 @@ var RfqSwapManager = class {
2354
2907
  // ── internals ────────────────────────────────────────────────────────────
2355
2908
  track(swap) {
2356
2909
  this.monitored.set(swap.rfqId, swap);
2357
- this.byLockupScript.set(hex9.encode(swap.lockupPkScript), swap);
2910
+ this.byLockupScript.set(hex12.encode(swap.lockupPkScript), swap);
2358
2911
  }
2359
2912
  /** Drops the swap from BOTH indexes. The event index is the one that stops
2360
2913
  * a late event finding a swap that is gone; `pollSwap`'s own
@@ -2363,7 +2916,7 @@ var RfqSwapManager = class {
2363
2916
  * them from silently re-driving a cancelled swap. */
2364
2917
  untrack(rfqId) {
2365
2918
  const swap = this.monitored.get(rfqId);
2366
- if (swap) this.byLockupScript.delete(hex9.encode(swap.lockupPkScript));
2919
+ if (swap) this.byLockupScript.delete(hex12.encode(swap.lockupPkScript));
2367
2920
  this.monitored.delete(rfqId);
2368
2921
  this.refundRefused.delete(rfqId);
2369
2922
  this.lastClaimError.delete(rfqId);
@@ -2425,7 +2978,7 @@ var RfqSwapManager = class {
2425
2978
  if (!lockup) {
2426
2979
  try {
2427
2980
  const [existing] = await contracts.getContracts({
2428
- script: hex9.encode(swap.lockupPkScript)
2981
+ script: hex12.encode(swap.lockupPkScript)
2429
2982
  });
2430
2983
  if (existing) {
2431
2984
  this.registered.set(swap.rfqId, true);
@@ -2444,13 +2997,13 @@ var RfqSwapManager = class {
2444
2997
  );
2445
2998
  return;
2446
2999
  }
2447
- const script = hex9.encode(lockup.script.pkScript);
2448
- if (script !== hex9.encode(swap.lockupPkScript)) {
3000
+ const script = hex12.encode(lockup.script.pkScript);
3001
+ if (script !== hex12.encode(swap.lockupPkScript)) {
2449
3002
  this.registered.set(swap.rfqId, false);
2450
3003
  this.emitFailed(
2451
3004
  swap,
2452
3005
  new Error(
2453
- `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${hex9.encode(swap.lockupPkScript)}`
3006
+ `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${hex12.encode(swap.lockupPkScript)}`
2454
3007
  )
2455
3008
  );
2456
3009
  return;
@@ -2469,7 +3022,7 @@ var RfqSwapManager = class {
2469
3022
  * script for its whole life. Best-effort — the swap is over either way. */
2470
3023
  retireContract(swap) {
2471
3024
  if (!this.deps.contracts || !this.registered.get(swap.rfqId)) return;
2472
- void this.deps.contracts.setContractWatchState(hex9.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
3025
+ void this.deps.contracts.setContractWatchState(hex12.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
2473
3026
  }
2474
3027
  arm() {
2475
3028
  if (!this.running) return;
@@ -2943,9 +3496,9 @@ var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
2943
3496
 
2944
3497
  // src/activity.ts
2945
3498
  import {
2946
- ArkAddress as ArkAddress4
3499
+ ArkAddress as ArkAddress5
2947
3500
  } from "@arkade-os/sdk";
2948
- import { hex as hex10 } from "@scure/base";
3501
+ import { hex as hex13 } from "@scure/base";
2949
3502
  var LABELS = {
2950
3503
  lightning_send: "Lightning send",
2951
3504
  lightning_receive: "Lightning receive",
@@ -3020,7 +3573,7 @@ async function activityInputOf(record, indexer) {
3020
3573
  async function lockupTxids(indexer, record, wantFunding) {
3021
3574
  let script;
3022
3575
  try {
3023
- script = hex10.encode(ArkAddress4.decode(record.lockupAddress).pkScript);
3576
+ script = hex13.encode(ArkAddress5.decode(record.lockupAddress).pkScript);
3024
3577
  } catch {
3025
3578
  return [];
3026
3579
  }
@@ -3041,6 +3594,8 @@ export {
3041
3594
  ARKADE_BTC,
3042
3595
  AddressMismatch,
3043
3596
  BTC_ASSET_ID,
3597
+ CROSS_ASSET_RAIL,
3598
+ CovclaimdRevealError,
3044
3599
  InMemoryAssetSwapRepository,
3045
3600
  IndexedDbAssetSwapRepository,
3046
3601
  LIGHTNING_BTC,
@@ -3073,6 +3628,8 @@ export {
3073
3628
  RfqSwapManager,
3074
3629
  RfqSwapOriginRequired,
3075
3630
  SOLO_REFUND_HEADROOM_SECONDS,
3631
+ SOLVER_LIGHTNING_RAIL,
3632
+ SOLVER_ONCHAIN_RAIL,
3076
3633
  SWAP_LOCKUP_CONTRACT_KIND,
3077
3634
  SWAP_LOCKUP_CONTRACT_LABEL,
3078
3635
  SWAP_LOCKUP_CONTRACT_TYPE,
@@ -3089,13 +3646,16 @@ export {
3089
3646
  buildHtlcClaim,
3090
3647
  buildHtlcRefund,
3091
3648
  cancelOffer,
3649
+ chainSourceFrom,
3092
3650
  claimOnchainFill,
3093
3651
  claimReceiveLockup,
3094
3652
  classifyDepositSpend,
3095
3653
  classifyOnchainHtlc,
3096
3654
  classifySpend,
3655
+ covclaimdClient,
3097
3656
  createOffer,
3098
3657
  createRfqSwapRecord,
3658
+ crossAssetRail,
3099
3659
  decodeOffer,
3100
3660
  deriveLightningReceive,
3101
3661
  deriveOnchainReceive,
@@ -3110,6 +3670,7 @@ export {
3110
3670
  httpTransport,
3111
3671
  isRfqSwapTerminal,
3112
3672
  isRfqTerminal,
3673
+ l1ScriptForAddress,
3113
3674
  lightningReceiveRequest,
3114
3675
  lightningSendRequest,
3115
3676
  lightningSendVtxoScript,
@@ -3140,6 +3701,8 @@ export {
3140
3701
  requestOnchainSend,
3141
3702
  restoreAssetSwaps,
3142
3703
  retireSettledOfferContracts,
3704
+ revealClaimPacket,
3705
+ revealFieldsFromScript,
3143
3706
  rfqClaimSecretOf,
3144
3707
  rfqPair,
3145
3708
  rfqSecretsProfile,
@@ -3149,6 +3712,11 @@ export {
3149
3712
  sealClaimPacket,
3150
3713
  senderIdentityForSwapRecord,
3151
3714
  shouldRetainRfqSwap,
3715
+ solverLightningRail,
3716
+ solverLightningRendezvous,
3717
+ solverOnchainRail,
3718
+ solverOnchainRendezvous,
3719
+ solverRendezvous,
3152
3720
  spendTxidsOf,
3153
3721
  spendUpdate,
3154
3722
  swapActivityResolver,