@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/{chunk-RIC5ZTZK.js → chunk-IFZOBTTG.js} +54 -9
- package/dist/index.cjs +661 -50
- package/dist/index.d.cts +240 -6
- package/dist/index.d.ts +240 -6
- package/dist/index.js +596 -28
- package/dist/nostr.d.cts +1 -1
- package/dist/nostr.d.ts +1 -1
- package/dist/nostr.js +1 -1
- package/dist/repositories/realm/index.d.cts +2 -2
- package/dist/repositories/realm/index.d.ts +2 -2
- package/dist/repositories/sqlite/index.d.cts +2 -2
- package/dist/repositories/sqlite/index.d.ts +2 -2
- package/dist/{repository-B02vsgcV.d.cts → repository-D5JoeqwB.d.ts} +4 -1
- package/dist/{repository-CfE18Fif.d.ts → repository-Vfx65uF1.d.cts} +4 -1
- package/dist/{rfq-DkckzRKK.d.ts → rfq-CYfKvAPp.d.cts} +45 -12
- package/dist/{rfq-DkckzRKK.d.cts → rfq-CYfKvAPp.d.ts} +45 -12
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,8 @@ __export(index_exports, {
|
|
|
34
34
|
ARKADE_BTC: () => ARKADE_BTC,
|
|
35
35
|
AddressMismatch: () => AddressMismatch,
|
|
36
36
|
BTC_ASSET_ID: () => BTC_ASSET_ID,
|
|
37
|
+
CROSS_ASSET_RAIL: () => CROSS_ASSET_RAIL,
|
|
38
|
+
CovclaimdRevealError: () => CovclaimdRevealError,
|
|
37
39
|
InMemoryAssetSwapRepository: () => InMemoryAssetSwapRepository,
|
|
38
40
|
IndexedDbAssetSwapRepository: () => IndexedDbAssetSwapRepository,
|
|
39
41
|
LIGHTNING_BTC: () => LIGHTNING_BTC,
|
|
@@ -66,6 +68,8 @@ __export(index_exports, {
|
|
|
66
68
|
RfqSwapManager: () => RfqSwapManager,
|
|
67
69
|
RfqSwapOriginRequired: () => RfqSwapOriginRequired,
|
|
68
70
|
SOLO_REFUND_HEADROOM_SECONDS: () => SOLO_REFUND_HEADROOM_SECONDS,
|
|
71
|
+
SOLVER_LIGHTNING_RAIL: () => SOLVER_LIGHTNING_RAIL,
|
|
72
|
+
SOLVER_ONCHAIN_RAIL: () => SOLVER_ONCHAIN_RAIL,
|
|
69
73
|
SWAP_LOCKUP_CONTRACT_KIND: () => SWAP_LOCKUP_CONTRACT_KIND,
|
|
70
74
|
SWAP_LOCKUP_CONTRACT_LABEL: () => SWAP_LOCKUP_CONTRACT_LABEL,
|
|
71
75
|
SWAP_LOCKUP_CONTRACT_TYPE: () => SWAP_LOCKUP_CONTRACT_TYPE,
|
|
@@ -82,13 +86,16 @@ __export(index_exports, {
|
|
|
82
86
|
buildHtlcClaim: () => buildHtlcClaim,
|
|
83
87
|
buildHtlcRefund: () => buildHtlcRefund,
|
|
84
88
|
cancelOffer: () => cancelOffer,
|
|
89
|
+
chainSourceFrom: () => chainSourceFrom,
|
|
85
90
|
claimOnchainFill: () => claimOnchainFill,
|
|
86
91
|
claimReceiveLockup: () => claimReceiveLockup,
|
|
87
92
|
classifyDepositSpend: () => classifyDepositSpend,
|
|
88
93
|
classifyOnchainHtlc: () => classifyOnchainHtlc,
|
|
89
94
|
classifySpend: () => classifySpend,
|
|
95
|
+
covclaimdClient: () => covclaimdClient,
|
|
90
96
|
createOffer: () => createOffer,
|
|
91
97
|
createRfqSwapRecord: () => createRfqSwapRecord,
|
|
98
|
+
crossAssetRail: () => crossAssetRail,
|
|
92
99
|
decodeOffer: () => decodeOffer,
|
|
93
100
|
deriveLightningReceive: () => deriveLightningReceive,
|
|
94
101
|
deriveOnchainReceive: () => deriveOnchainReceive,
|
|
@@ -103,6 +110,7 @@ __export(index_exports, {
|
|
|
103
110
|
httpTransport: () => httpTransport,
|
|
104
111
|
isRfqSwapTerminal: () => isRfqSwapTerminal,
|
|
105
112
|
isRfqTerminal: () => isRfqTerminal,
|
|
113
|
+
l1ScriptForAddress: () => l1ScriptForAddress,
|
|
106
114
|
lightningReceiveRequest: () => lightningReceiveRequest,
|
|
107
115
|
lightningSendRequest: () => lightningSendRequest,
|
|
108
116
|
lightningSendVtxoScript: () => lightningSendVtxoScript,
|
|
@@ -133,6 +141,8 @@ __export(index_exports, {
|
|
|
133
141
|
requestOnchainSend: () => requestOnchainSend,
|
|
134
142
|
restoreAssetSwaps: () => restoreAssetSwaps,
|
|
135
143
|
retireSettledOfferContracts: () => retireSettledOfferContracts,
|
|
144
|
+
revealClaimPacket: () => revealClaimPacket,
|
|
145
|
+
revealFieldsFromScript: () => revealFieldsFromScript,
|
|
136
146
|
rfqClaimSecretOf: () => rfqClaimSecretOf,
|
|
137
147
|
rfqPair: () => rfqPair,
|
|
138
148
|
rfqSecretsProfile: () => rfqSecretsProfile,
|
|
@@ -142,6 +152,11 @@ __export(index_exports, {
|
|
|
142
152
|
sealClaimPacket: () => sealClaimPacket,
|
|
143
153
|
senderIdentityForSwapRecord: () => senderIdentityForSwapRecord,
|
|
144
154
|
shouldRetainRfqSwap: () => shouldRetainRfqSwap,
|
|
155
|
+
solverLightningRail: () => solverLightningRail,
|
|
156
|
+
solverLightningRendezvous: () => solverLightningRendezvous,
|
|
157
|
+
solverOnchainRail: () => solverOnchainRail,
|
|
158
|
+
solverOnchainRendezvous: () => solverOnchainRendezvous,
|
|
159
|
+
solverRendezvous: () => solverRendezvous,
|
|
145
160
|
spendTxidsOf: () => spendTxidsOf,
|
|
146
161
|
spendUpdate: () => spendUpdate,
|
|
147
162
|
swapActivityResolver: () => swapActivityResolver,
|
|
@@ -1158,6 +1173,7 @@ var L1_NETWORKS = {
|
|
|
1158
1173
|
testnet: btc.TEST_NETWORK,
|
|
1159
1174
|
regtest: { ...btc.TEST_NETWORK, bech32: "bcrt" }
|
|
1160
1175
|
};
|
|
1176
|
+
var l1ScriptForAddress = (address, network) => btc.OutScript.encode(btc.Address(L1_NETWORKS[network]).decode(address));
|
|
1161
1177
|
function onchainHtlcScript(params, network) {
|
|
1162
1178
|
if (params.claimKey.length !== 32 || params.refundKey.length !== 32) {
|
|
1163
1179
|
throw new Error("claimKey and refundKey must be 32-byte x-only keys");
|
|
@@ -1340,7 +1356,11 @@ async function classifyOnchainHtlc(chain, input) {
|
|
|
1340
1356
|
const best = utxos.sort((a, b) => b.amount > a.amount ? 1 : -1)[0];
|
|
1341
1357
|
if (!best) {
|
|
1342
1358
|
if (!input.funding) return { phase: "unfunded" };
|
|
1343
|
-
const spend = await chain.getSpendingTx(
|
|
1359
|
+
const spend = await chain.getSpendingTx(
|
|
1360
|
+
input.funding.txid,
|
|
1361
|
+
input.funding.vout,
|
|
1362
|
+
input.htlc.pkScript
|
|
1363
|
+
);
|
|
1344
1364
|
if (!spend) return { phase: "unfunded" };
|
|
1345
1365
|
const preimage = extractPreimage(spend.txHex, input.htlc.paymentHash);
|
|
1346
1366
|
const txid = btc.Transaction.fromRaw(import_base3.hex.decode(spend.txHex), {
|
|
@@ -1399,7 +1419,8 @@ function onchainSendProfile(result) {
|
|
|
1399
1419
|
htlcLocktime: result.htlcParams.refundLocktime,
|
|
1400
1420
|
network: result.l1Network,
|
|
1401
1421
|
htlcAddress: result.htlc.address,
|
|
1402
|
-
minConfirmations: result.minConfirmations
|
|
1422
|
+
minConfirmations: result.minConfirmations,
|
|
1423
|
+
payoutPkScript: import_base4.hex.encode(result.payoutPkScript)
|
|
1403
1424
|
};
|
|
1404
1425
|
}
|
|
1405
1426
|
var OnchainSendCorridor = {
|
|
@@ -1448,6 +1469,9 @@ var OnchainSendCorridor = {
|
|
|
1448
1469
|
paymentHash,
|
|
1449
1470
|
htlc,
|
|
1450
1471
|
minConfirmations: profile.minConfirmations,
|
|
1472
|
+
// Optional here, required at the write: throwing on an older
|
|
1473
|
+
// record would strand the refund it is still owed.
|
|
1474
|
+
...profile.payoutPkScript ? { payoutPkScript: import_base4.hex.decode(profile.payoutPkScript) } : {},
|
|
1451
1475
|
...profile.funding ? { funding: profile.funding } : {},
|
|
1452
1476
|
...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
|
|
1453
1477
|
};
|
|
@@ -2045,6 +2069,43 @@ var assertFundable = (input) => {
|
|
|
2045
2069
|
if (input.quote.refund_locktime !== void 0 && input.quote.refund_locktime - input.now < MIN_HEADROOM_SECONDS) {
|
|
2046
2070
|
fail("insufficient_headroom", "refund deadline headroom below 90 minutes");
|
|
2047
2071
|
}
|
|
2072
|
+
if (input.maxFee) {
|
|
2073
|
+
const { bps, sats, referenceRate } = input.maxFee;
|
|
2074
|
+
if (bps === void 0 && sats === void 0) {
|
|
2075
|
+
fail("max_fee_unbounded", "maxFee names neither bps nor sats");
|
|
2076
|
+
}
|
|
2077
|
+
if (bps !== void 0 && (!Number.isInteger(bps) || bps < 0 || bps > 1e4)) {
|
|
2078
|
+
fail("max_fee_out_of_range", `maxFee.bps must be an integer in 0..10000, got ${bps}`);
|
|
2079
|
+
}
|
|
2080
|
+
if (sats !== void 0 && (!Number.isInteger(sats) || sats < 0)) {
|
|
2081
|
+
fail("max_fee_out_of_range", `maxFee.sats must be a non-negative integer, got ${sats}`);
|
|
2082
|
+
}
|
|
2083
|
+
const legs = input.quote.pair.split("->");
|
|
2084
|
+
const assetOf = (leg) => leg.slice(leg.indexOf(":") + 1);
|
|
2085
|
+
const sameAsset = legs.length === 2 && assetOf(legs[0]) === assetOf(legs[1]);
|
|
2086
|
+
if (!sameAsset && referenceRate === void 0) {
|
|
2087
|
+
fail(
|
|
2088
|
+
"fee_gate_unavailable",
|
|
2089
|
+
`maxFee cannot gate ${input.quote.pair}: its legs name different assets, so from_amount - to_amount is not a fee. Supply maxFee.referenceRate (to-units per from-unit) from a source of your OWN \u2014 reading it off the solver's published feed would check the solver against its own number`
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
if (!sameAsset && (!Number.isFinite(referenceRate) || referenceRate <= 0)) {
|
|
2093
|
+
fail(
|
|
2094
|
+
"max_fee_out_of_range",
|
|
2095
|
+
`maxFee.referenceRate must be a positive finite number, got ${referenceRate}`
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
const fee = sameAsset ? input.quote.from_amount - input.quote.to_amount : Math.ceil(
|
|
2099
|
+
(input.quote.from_amount * referenceRate - input.quote.to_amount) / referenceRate
|
|
2100
|
+
);
|
|
2101
|
+
const allowed = Math.max(
|
|
2102
|
+
sats ?? 0,
|
|
2103
|
+
Math.floor(input.quote.from_amount * (bps ?? 0) / 1e4)
|
|
2104
|
+
);
|
|
2105
|
+
if (fee > allowed) {
|
|
2106
|
+
fail("fee_too_high", `fee ${fee} exceeds the ${allowed} this client allows`);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2048
2109
|
if (input.onchain) {
|
|
2049
2110
|
const { htlcLocktime, minConfirmations, direction } = input.onchain;
|
|
2050
2111
|
if (!Number.isInteger(minConfirmations) || minConfirmations < 1 || minConfirmations > MAX_MIN_CONFIRMATIONS) {
|
|
@@ -2348,7 +2409,7 @@ var lightningReceiveRequest = (input) => ({
|
|
|
2348
2409
|
payment_hash: input.paymentHash,
|
|
2349
2410
|
payout_address: input.payoutAddress,
|
|
2350
2411
|
payout_pubkey: import_base10.hex.encode(input.payoutPubkey),
|
|
2351
|
-
claim_packet: input.claimPacket
|
|
2412
|
+
...input.claimPacket === void 0 ? {} : { claim_packet: input.claimPacket }
|
|
2352
2413
|
}
|
|
2353
2414
|
});
|
|
2354
2415
|
var onchainReceiveRequest = (input) => ({
|
|
@@ -2360,7 +2421,7 @@ var onchainReceiveRequest = (input) => ({
|
|
|
2360
2421
|
amount: input.amount,
|
|
2361
2422
|
profile: {
|
|
2362
2423
|
payment_hash: input.paymentHash,
|
|
2363
|
-
claim_packet: input.claimPacket,
|
|
2424
|
+
...input.claimPacket === void 0 ? {} : { claim_packet: input.claimPacket },
|
|
2364
2425
|
refund_pubkey: import_base10.hex.encode(input.refundPubkey),
|
|
2365
2426
|
payout_address: input.payoutAddress,
|
|
2366
2427
|
payout_pubkey: import_base10.hex.encode(input.payoutPubkey)
|
|
@@ -2440,6 +2501,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
|
|
|
2440
2501
|
amountSide: params.amountSide
|
|
2441
2502
|
})
|
|
2442
2503
|
);
|
|
2504
|
+
assertQuotedAmount(quote, params.amountSide, params.amount);
|
|
2443
2505
|
const network = (0, import_sdk9.getNetwork)(info.network);
|
|
2444
2506
|
const derived = deriveOnchainSend({
|
|
2445
2507
|
quote,
|
|
@@ -2630,17 +2692,17 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
|
|
|
2630
2692
|
new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
|
|
2631
2693
|
wallet.getAddress()
|
|
2632
2694
|
]);
|
|
2633
|
-
const claimPacket = await sealClaimPacket({
|
|
2695
|
+
const claimPacket = params.covclaimdPubkey ? await sealClaimPacket({
|
|
2634
2696
|
preimage,
|
|
2635
2697
|
covclaimdPubkey: params.covclaimdPubkey
|
|
2636
|
-
});
|
|
2698
|
+
}) : void 0;
|
|
2637
2699
|
const quote = await transport.requestQuote(
|
|
2638
2700
|
lightningReceiveRequest({
|
|
2639
2701
|
rfqId,
|
|
2640
2702
|
paymentHash,
|
|
2641
2703
|
payoutAddress,
|
|
2642
2704
|
payoutPubkey,
|
|
2643
|
-
claimPacket: claimPacket
|
|
2705
|
+
claimPacket: claimPacket?.ciphertext,
|
|
2644
2706
|
amount: params.amount,
|
|
2645
2707
|
amountSide: params.amountSide
|
|
2646
2708
|
})
|
|
@@ -2753,10 +2815,10 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
|
|
|
2753
2815
|
new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
|
|
2754
2816
|
wallet.getAddress()
|
|
2755
2817
|
]);
|
|
2756
|
-
const claimPacket = await sealClaimPacket({
|
|
2818
|
+
const claimPacket = params.covclaimdPubkey ? await sealClaimPacket({
|
|
2757
2819
|
preimage,
|
|
2758
2820
|
covclaimdPubkey: params.covclaimdPubkey
|
|
2759
|
-
});
|
|
2821
|
+
}) : void 0;
|
|
2760
2822
|
const quote = await transport.requestQuote(
|
|
2761
2823
|
onchainReceiveRequest({
|
|
2762
2824
|
rfqId,
|
|
@@ -2764,7 +2826,7 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
|
|
|
2764
2826
|
payoutAddress,
|
|
2765
2827
|
payoutPubkey,
|
|
2766
2828
|
refundPubkey: params.refundPubkey,
|
|
2767
|
-
claimPacket: claimPacket
|
|
2829
|
+
claimPacket: claimPacket?.ciphertext,
|
|
2768
2830
|
amount: params.amount,
|
|
2769
2831
|
amountSide: params.amountSide
|
|
2770
2832
|
})
|
|
@@ -2815,16 +2877,550 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
|
|
|
2815
2877
|
};
|
|
2816
2878
|
}
|
|
2817
2879
|
|
|
2818
|
-
// src/
|
|
2880
|
+
// src/chainSource.ts
|
|
2881
|
+
var btc2 = __toESM(require("@scure/btc-signer"), 1);
|
|
2882
|
+
var import_base11 = require("@scure/base");
|
|
2883
|
+
var addressOf = (pkScript, network) => btc2.Address(L1_NETWORKS[network]).encode(btc2.OutScript.decode(pkScript));
|
|
2884
|
+
var chainSourceFrom = (provider, network) => {
|
|
2885
|
+
const spenderFromHistory = async (txid, vout, pkScript) => {
|
|
2886
|
+
const txs = await provider.getTransactions(addressOf(pkScript, network));
|
|
2887
|
+
return txs.find((tx) => tx.vin?.some((i) => i.txid === txid && i.vout === vout))?.txid;
|
|
2888
|
+
};
|
|
2889
|
+
return {
|
|
2890
|
+
async getScriptUtxos(pkScript) {
|
|
2891
|
+
const address = addressOf(pkScript, network);
|
|
2892
|
+
const [coins, tip] = await Promise.all([
|
|
2893
|
+
provider.getCoins(address),
|
|
2894
|
+
provider.getChainTip()
|
|
2895
|
+
]);
|
|
2896
|
+
return coins.map((coin) => ({
|
|
2897
|
+
txid: coin.txid,
|
|
2898
|
+
vout: coin.vout,
|
|
2899
|
+
amount: BigInt(coin.value),
|
|
2900
|
+
// Zero, not one: calling a mempool output "1 deep" would let a
|
|
2901
|
+
// 1-confirmation policy claim against a replaceable transaction.
|
|
2902
|
+
confirmations: coin.status.confirmed && typeof coin.status.block_height === "number" ? Math.max(0, tip.height - coin.status.block_height + 1) : 0
|
|
2903
|
+
}));
|
|
2904
|
+
},
|
|
2905
|
+
async getSpendingTx(txid, vout, pkScript) {
|
|
2906
|
+
const outspends = await provider.getTxOutspends(txid);
|
|
2907
|
+
const outspend = outspends[vout];
|
|
2908
|
+
if (!outspend?.spent) return null;
|
|
2909
|
+
const spender = outspend.txid || await spenderFromHistory(txid, vout, pkScript);
|
|
2910
|
+
if (!spender) return null;
|
|
2911
|
+
const raw = await provider.getRawTransaction(spender);
|
|
2912
|
+
return { txHex: import_base11.hex.encode(raw) };
|
|
2913
|
+
},
|
|
2914
|
+
broadcast(txHex) {
|
|
2915
|
+
return provider.broadcastTransaction(txHex);
|
|
2916
|
+
},
|
|
2917
|
+
async getMtp() {
|
|
2918
|
+
return (await provider.getChainTip()).time;
|
|
2919
|
+
}
|
|
2920
|
+
};
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
// src/payment/rendezvous.ts
|
|
2924
|
+
var import_solver_discovery2 = require("@arkade-os/solver-discovery");
|
|
2819
2925
|
var import_base12 = require("@scure/base");
|
|
2820
|
-
var
|
|
2821
|
-
var
|
|
2926
|
+
var XONLY_HEX = /^[0-9a-f]{64}$/;
|
|
2927
|
+
var rendezvousOf = (market, pinned) => {
|
|
2928
|
+
const transports = { nostr: { relays: market.transports?.nostr?.relays ?? [] } };
|
|
2929
|
+
if (!market.discovery_pubkey || !transports.nostr.relays.length) return void 0;
|
|
2930
|
+
const advertised = market.emulator_pubkey;
|
|
2931
|
+
const emulatorPubkey = advertised === void 0 || advertised === null || advertised === "" ? pinned : typeof advertised === "string" && XONLY_HEX.test(advertised) ? advertised : void 0;
|
|
2932
|
+
if (!emulatorPubkey) return void 0;
|
|
2933
|
+
if (pinned && emulatorPubkey !== pinned) return void 0;
|
|
2934
|
+
const bounds = (0, import_solver_discovery2.sideLimits)(market, "quote");
|
|
2935
|
+
if (!bounds) return void 0;
|
|
2936
|
+
return {
|
|
2937
|
+
solverPubkey: market.discovery_pubkey,
|
|
2938
|
+
transports,
|
|
2939
|
+
minSats: Number(bounds.min),
|
|
2940
|
+
maxSats: Number(bounds.max)
|
|
2941
|
+
};
|
|
2942
|
+
};
|
|
2943
|
+
var solverRendezvous = (markets, payoutCorridor, amountSats, fallbackEmulatorPubkey) => {
|
|
2944
|
+
const encoded = fallbackEmulatorPubkey ? import_base12.hex.encode(fallbackEmulatorPubkey) : void 0;
|
|
2945
|
+
if (encoded !== void 0 && !XONLY_HEX.test(encoded)) return void 0;
|
|
2946
|
+
const pinned = encoded;
|
|
2947
|
+
const candidates = (0, import_solver_discovery2.selectMarkets)(markets, {
|
|
2948
|
+
baseId: BTC_ASSET_ID,
|
|
2949
|
+
quoteId: BTC_ASSET_ID,
|
|
2950
|
+
baseCorridor: "arkade",
|
|
2951
|
+
quoteCorridor: payoutCorridor
|
|
2952
|
+
});
|
|
2953
|
+
for (const market of candidates) {
|
|
2954
|
+
const rendezvous = rendezvousOf(market, pinned);
|
|
2955
|
+
if (!rendezvous) continue;
|
|
2956
|
+
if (amountSats >= rendezvous.minSats && amountSats <= rendezvous.maxSats) {
|
|
2957
|
+
return rendezvous;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
return void 0;
|
|
2961
|
+
};
|
|
2962
|
+
|
|
2963
|
+
// src/payment/solverOnchain.ts
|
|
2964
|
+
var import_sdk11 = require("@arkade-os/sdk");
|
|
2965
|
+
var SOLVER_ONCHAIN_RAIL = "solver-onchain";
|
|
2966
|
+
var solverOnchainRendezvous = (markets, amountSats, fallbackEmulatorPubkey) => solverRendezvous(markets, "onchain", amountSats, fallbackEmulatorPubkey);
|
|
2967
|
+
function solverOnchainRail(deps) {
|
|
2968
|
+
const rendezvousFor = async (amount) => {
|
|
2969
|
+
if (amount === void 0) return void 0;
|
|
2970
|
+
const markets = await deps.discover();
|
|
2971
|
+
return solverOnchainRendezvous(markets, amount, deps.fallbackEmulatorPubkey);
|
|
2972
|
+
};
|
|
2973
|
+
return {
|
|
2974
|
+
id: SOLVER_ONCHAIN_RAIL,
|
|
2975
|
+
match: (req) => (0, import_sdk11.btcTarget)(req.raw) !== void 0,
|
|
2976
|
+
available: async (req) => {
|
|
2977
|
+
if ((0, import_sdk11.assetsOf)(req).length > 0) return false;
|
|
2978
|
+
const address = (0, import_sdk11.btcTarget)(req.raw);
|
|
2979
|
+
if (!address) return false;
|
|
2980
|
+
try {
|
|
2981
|
+
l1ScriptForAddress(address, deps.l1Network);
|
|
2982
|
+
} catch {
|
|
2983
|
+
return false;
|
|
2984
|
+
}
|
|
2985
|
+
const amount = (0, import_sdk11.tryResolveSendAmount)(req.raw, req.amount);
|
|
2986
|
+
if (amount === void 0) return false;
|
|
2987
|
+
return await rendezvousFor(amount) !== void 0;
|
|
2988
|
+
},
|
|
2989
|
+
quote: async (req, ctx) => {
|
|
2990
|
+
const address = (0, import_sdk11.btcTarget)(req.raw);
|
|
2991
|
+
(0, import_sdk11.assertNoAssets)(SOLVER_ONCHAIN_RAIL, req);
|
|
2992
|
+
const amount = (0, import_sdk11.resolveSendAmount)(SOLVER_ONCHAIN_RAIL, req.raw, req.amount);
|
|
2993
|
+
const payoutPkScript = l1ScriptForAddress(address, deps.l1Network);
|
|
2994
|
+
const rendezvous = await rendezvousFor(amount);
|
|
2995
|
+
if (!rendezvous) {
|
|
2996
|
+
throw new Error(
|
|
2997
|
+
`${SOLVER_ONCHAIN_RAIL}: no solver serves arkade:BTC -> onchain:BTC at ${amount} sats`
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
const negotiated = await deps.connect(
|
|
3001
|
+
rendezvous,
|
|
3002
|
+
(transport) => requestOnchainSend(ctx.wallet, deps.arkServerUrl, transport, {
|
|
3003
|
+
amount,
|
|
3004
|
+
amountSide: "to",
|
|
3005
|
+
payoutPubkey: deps.payoutPubkey,
|
|
3006
|
+
...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
|
|
3007
|
+
})
|
|
3008
|
+
);
|
|
3009
|
+
if (negotiated.l1Network !== deps.l1Network) {
|
|
3010
|
+
throw new Error(
|
|
3011
|
+
`${SOLVER_ONCHAIN_RAIL}: rail built for ${deps.l1Network} but the swap was negotiated on ${negotiated.l1Network}`
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
const swap = { ...negotiated, rendezvous, payoutPkScript };
|
|
3015
|
+
return {
|
|
3016
|
+
railId: SOLVER_ONCHAIN_RAIL,
|
|
3017
|
+
amount,
|
|
3018
|
+
fee: swap.fundAmount - amount,
|
|
3019
|
+
total: swap.fundAmount,
|
|
3020
|
+
meta: {
|
|
3021
|
+
rfqId: swap.rfqId,
|
|
3022
|
+
validUntil: swap.quote.valid_until,
|
|
3023
|
+
htlcAddress: swap.htlc.address,
|
|
3024
|
+
minConfirmations: swap.minConfirmations,
|
|
3025
|
+
solverPubkey: rendezvous.solverPubkey,
|
|
3026
|
+
// The claim tx's fee comes out of the HTLC output at a rate
|
|
3027
|
+
// not knowable now, so `amount` is the payout, not the net.
|
|
3028
|
+
claimFeeDeductedFromPayout: true
|
|
3029
|
+
},
|
|
3030
|
+
send: async () => (0, import_sdk11.makeHandle)(SOLVER_ONCHAIN_RAIL, async (emit) => {
|
|
3031
|
+
assertFundable({
|
|
3032
|
+
quote: swap.quote,
|
|
3033
|
+
now: Math.floor(Date.now() / 1e3),
|
|
3034
|
+
onchain: {
|
|
3035
|
+
htlcLocktime: swap.htlcParams.refundLocktime,
|
|
3036
|
+
minConfirmations: swap.minConfirmations,
|
|
3037
|
+
direction: "send"
|
|
3038
|
+
}
|
|
3039
|
+
});
|
|
3040
|
+
await deps.persist(swap);
|
|
3041
|
+
await ctx.wallet.send({
|
|
3042
|
+
address: swap.address,
|
|
3043
|
+
amount: swap.fundAmount
|
|
3044
|
+
});
|
|
3045
|
+
emit({ status: "sent" });
|
|
3046
|
+
const result = { railId: SOLVER_ONCHAIN_RAIL, swapId: swap.rfqId };
|
|
3047
|
+
if (!deps.awaitSettlement) return result;
|
|
3048
|
+
let txid;
|
|
3049
|
+
try {
|
|
3050
|
+
({ txid } = await deps.awaitSettlement(swap));
|
|
3051
|
+
} catch (e) {
|
|
3052
|
+
console.warn(
|
|
3053
|
+
`${SOLVER_ONCHAIN_RAIL}: settlement watch failed; the payment is sent`,
|
|
3054
|
+
e
|
|
3055
|
+
);
|
|
3056
|
+
return result;
|
|
3057
|
+
}
|
|
3058
|
+
const settled = { ...result, txid };
|
|
3059
|
+
emit({ status: "settled", result: settled });
|
|
3060
|
+
return settled;
|
|
3061
|
+
})
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
// src/payment/solverLightning.ts
|
|
2822
3068
|
var import_sdk12 = require("@arkade-os/sdk");
|
|
3069
|
+
var SOLVER_LIGHTNING_RAIL = "solver-lightning";
|
|
3070
|
+
var factsOf = (raw, decode, now) => {
|
|
3071
|
+
const invoice = (0, import_sdk12.invoiceTarget)(raw);
|
|
3072
|
+
if (!invoice) return void 0;
|
|
3073
|
+
let facts;
|
|
3074
|
+
try {
|
|
3075
|
+
facts = decode(invoice);
|
|
3076
|
+
} catch {
|
|
3077
|
+
return void 0;
|
|
3078
|
+
}
|
|
3079
|
+
if (!Number.isInteger(facts.amountSats) || facts.amountSats <= 0) return void 0;
|
|
3080
|
+
if (facts.expiresAt <= now) return void 0;
|
|
3081
|
+
return facts;
|
|
3082
|
+
};
|
|
3083
|
+
var solverLightningRendezvous = (markets, amountSats, fallbackEmulatorPubkey) => solverRendezvous(markets, "lightning", amountSats, fallbackEmulatorPubkey);
|
|
3084
|
+
function solverLightningRail(deps) {
|
|
3085
|
+
const rendezvousFor = async (amountSats) => solverLightningRendezvous(await deps.discover(), amountSats, deps.fallbackEmulatorPubkey);
|
|
3086
|
+
return {
|
|
3087
|
+
id: SOLVER_LIGHTNING_RAIL,
|
|
3088
|
+
match: (req) => (0, import_sdk12.invoiceTarget)(req.raw) !== void 0,
|
|
3089
|
+
available: async (req) => {
|
|
3090
|
+
if ((0, import_sdk12.assetsOf)(req).length > 0) return false;
|
|
3091
|
+
const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
|
|
3092
|
+
if (!facts) return false;
|
|
3093
|
+
if (req.amount !== void 0 && req.amount !== facts.amountSats) return false;
|
|
3094
|
+
return await rendezvousFor(facts.amountSats) !== void 0;
|
|
3095
|
+
},
|
|
3096
|
+
quote: async (req, ctx) => {
|
|
3097
|
+
(0, import_sdk12.assertNoAssets)(SOLVER_LIGHTNING_RAIL, req);
|
|
3098
|
+
const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
|
|
3099
|
+
if (!facts) {
|
|
3100
|
+
throw new Error(
|
|
3101
|
+
`${SOLVER_LIGHTNING_RAIL}: the request carries no payable BOLT11 invoice (amountless, expired, or undecodable)`
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
if (req.amount !== void 0 && req.amount !== facts.amountSats) {
|
|
3105
|
+
throw new Error(
|
|
3106
|
+
`${SOLVER_LIGHTNING_RAIL}: the request names ${req.amount} sats but the invoice is for ${facts.amountSats} \u2014 the payee is paid the invoice`
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
const rendezvous = await rendezvousFor(facts.amountSats);
|
|
3110
|
+
if (!rendezvous) {
|
|
3111
|
+
throw new Error(
|
|
3112
|
+
`${SOLVER_LIGHTNING_RAIL}: no solver serves arkade:BTC -> lightning:BTC at ${facts.amountSats} sats`
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
const negotiated = await deps.connect(
|
|
3116
|
+
rendezvous,
|
|
3117
|
+
(transport) => requestLightningSend(ctx.wallet, deps.arkServerUrl, transport, {
|
|
3118
|
+
invoice: facts,
|
|
3119
|
+
...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
|
|
3120
|
+
})
|
|
3121
|
+
);
|
|
3122
|
+
const swap = { ...negotiated, invoice: facts, rendezvous };
|
|
3123
|
+
return {
|
|
3124
|
+
railId: SOLVER_LIGHTNING_RAIL,
|
|
3125
|
+
// `requestLightningSend` refuses a quote that reprices the
|
|
3126
|
+
// invoice, so the spread is a fee on top.
|
|
3127
|
+
amount: facts.amountSats,
|
|
3128
|
+
fee: swap.fundAmount - facts.amountSats,
|
|
3129
|
+
total: swap.fundAmount,
|
|
3130
|
+
meta: {
|
|
3131
|
+
rfqId: swap.rfqId,
|
|
3132
|
+
validUntil: swap.quote.valid_until,
|
|
3133
|
+
paymentHash: facts.paymentHash,
|
|
3134
|
+
invoiceExpiresAt: facts.expiresAt,
|
|
3135
|
+
solverPubkey: rendezvous.solverPubkey
|
|
3136
|
+
},
|
|
3137
|
+
send: async () => (0, import_sdk12.makeHandle)(SOLVER_LIGHTNING_RAIL, async (emit) => {
|
|
3138
|
+
assertFundable({
|
|
3139
|
+
quote: swap.quote,
|
|
3140
|
+
invoiceExpiresAt: facts.expiresAt,
|
|
3141
|
+
now: Math.floor(Date.now() / 1e3)
|
|
3142
|
+
});
|
|
3143
|
+
await deps.persist(swap);
|
|
3144
|
+
await ctx.wallet.send({
|
|
3145
|
+
address: swap.address,
|
|
3146
|
+
amount: swap.fundAmount
|
|
3147
|
+
});
|
|
3148
|
+
emit({ status: "sent" });
|
|
3149
|
+
const result = { railId: SOLVER_LIGHTNING_RAIL, swapId: swap.rfqId };
|
|
3150
|
+
if (!deps.awaitSettlement) return result;
|
|
3151
|
+
let preimage;
|
|
3152
|
+
try {
|
|
3153
|
+
({ preimage } = await deps.awaitSettlement(swap));
|
|
3154
|
+
} catch (e) {
|
|
3155
|
+
console.warn(
|
|
3156
|
+
`${SOLVER_LIGHTNING_RAIL}: settlement watch failed; the payment is sent`,
|
|
3157
|
+
e
|
|
3158
|
+
);
|
|
3159
|
+
return result;
|
|
3160
|
+
}
|
|
3161
|
+
const settled = { ...result, ...preimage !== void 0 && { preimage } };
|
|
3162
|
+
emit({ status: "settled", result: settled });
|
|
3163
|
+
return settled;
|
|
3164
|
+
})
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
2823
3169
|
|
|
2824
|
-
// src/
|
|
2825
|
-
var
|
|
3170
|
+
// src/payment/crossAsset.ts
|
|
3171
|
+
var import_sdk13 = require("@arkade-os/sdk");
|
|
3172
|
+
var CROSS_ASSET_RAIL = "cross-asset";
|
|
3173
|
+
var DEFAULT_CARRIER_SATS = 330;
|
|
3174
|
+
var parseAssetId = (assetId) => {
|
|
3175
|
+
try {
|
|
3176
|
+
return import_sdk13.asset.AssetId.fromString(assetId);
|
|
3177
|
+
} catch {
|
|
3178
|
+
return void 0;
|
|
3179
|
+
}
|
|
3180
|
+
};
|
|
3181
|
+
function crossAssetRail(deps) {
|
|
3182
|
+
const planFor = async (asset3) => {
|
|
3183
|
+
const markets = await deps.discover();
|
|
3184
|
+
const found = findMarket(markets, BTC_ASSET_ID, asset3.assetId);
|
|
3185
|
+
const market = found?.market;
|
|
3186
|
+
if (!market) return void 0;
|
|
3187
|
+
const plan = await deps.quote(market, found.give, asset3.amount);
|
|
3188
|
+
const [balance, dust] = await Promise.all([deps.btcBalance(), deps.dust()]);
|
|
3189
|
+
return { plan, error: validatePlan(plan, balance, dust) };
|
|
3190
|
+
};
|
|
3191
|
+
return {
|
|
3192
|
+
id: CROSS_ASSET_RAIL,
|
|
3193
|
+
match: (req) => (0, import_sdk13.arkTarget)(req.raw) !== void 0,
|
|
3194
|
+
available: async (req) => {
|
|
3195
|
+
const assets = (0, import_sdk13.assetsOf)(req);
|
|
3196
|
+
if (assets.length !== 1) return false;
|
|
3197
|
+
const [asset3] = assets;
|
|
3198
|
+
if (asset3.assetId === BTC_ASSET_ID) return false;
|
|
3199
|
+
if (typeof asset3.amount !== "bigint" || asset3.amount <= 0n) return false;
|
|
3200
|
+
if (!parseAssetId(asset3.assetId)) return false;
|
|
3201
|
+
const planned = await planFor(asset3);
|
|
3202
|
+
return planned !== void 0 && planned.error === void 0;
|
|
3203
|
+
},
|
|
3204
|
+
quote: async (req, ctx) => {
|
|
3205
|
+
const payTo = (0, import_sdk13.arkTarget)(req.raw);
|
|
3206
|
+
const asset3 = (0, import_sdk13.resolveAssetAmount)(CROSS_ASSET_RAIL, req);
|
|
3207
|
+
const wantAsset = parseAssetId(asset3.assetId);
|
|
3208
|
+
if (!wantAsset) {
|
|
3209
|
+
throw new Error(
|
|
3210
|
+
`${CROSS_ASSET_RAIL}: ${asset3.assetId} is not an asset id (expected 34 bytes of hex)`
|
|
3211
|
+
);
|
|
3212
|
+
}
|
|
3213
|
+
const planned = await planFor(asset3);
|
|
3214
|
+
if (!planned) {
|
|
3215
|
+
throw new Error(`${CROSS_ASSET_RAIL}: no market swaps BTC into ${asset3.assetId}`);
|
|
3216
|
+
}
|
|
3217
|
+
if (planned.error) {
|
|
3218
|
+
throw new Error(
|
|
3219
|
+
`${CROSS_ASSET_RAIL}: cannot swap into ${asset3.assetId} \u2014 ${planned.error}`
|
|
3220
|
+
);
|
|
3221
|
+
}
|
|
3222
|
+
const { plan } = planned;
|
|
3223
|
+
if (!Number.isSafeInteger(Number(plan.deposit.atomic))) {
|
|
3224
|
+
throw new Error(
|
|
3225
|
+
`${CROSS_ASSET_RAIL}: deposit of ${plan.deposit.atomic} does not fit a JS number`
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
const depositSats = Number(plan.deposit.atomic);
|
|
3229
|
+
const carrier = deps.carrierSats ?? DEFAULT_CARRIER_SATS;
|
|
3230
|
+
return {
|
|
3231
|
+
railId: CROSS_ASSET_RAIL,
|
|
3232
|
+
// OPEN QUESTION for review: `total = amount + fee` forces the
|
|
3233
|
+
// purchase price into `fee`, which reads as a service charge.
|
|
3234
|
+
// A third field instead? `assets.spent` labels it correctly.
|
|
3235
|
+
amount: carrier,
|
|
3236
|
+
fee: depositSats,
|
|
3237
|
+
total: depositSats + carrier,
|
|
3238
|
+
assets: {
|
|
3239
|
+
delivered: asset3,
|
|
3240
|
+
spent: { assetId: BTC_ASSET_ID, amount: plan.deposit.atomic }
|
|
3241
|
+
},
|
|
3242
|
+
meta: {
|
|
3243
|
+
market: plan.market.pair,
|
|
3244
|
+
priceDisplay: plan.priceDisplay,
|
|
3245
|
+
give: plan.give
|
|
3246
|
+
},
|
|
3247
|
+
send: async () => (0, import_sdk13.makeHandle)(CROSS_ASSET_RAIL, async (emit) => {
|
|
3248
|
+
const offer = await createOffer(ctx.wallet, deps.arkServerUrl, {
|
|
3249
|
+
wantAmount: asset3.amount,
|
|
3250
|
+
wantAsset,
|
|
3251
|
+
...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
|
|
3252
|
+
});
|
|
3253
|
+
const swap = {
|
|
3254
|
+
phase: "quoted",
|
|
3255
|
+
offerHex: offer.offerHex,
|
|
3256
|
+
address: offer.address,
|
|
3257
|
+
swapPkScript: offer.swapPkScript,
|
|
3258
|
+
depositSats,
|
|
3259
|
+
asset: asset3,
|
|
3260
|
+
payTo,
|
|
3261
|
+
plan
|
|
3262
|
+
};
|
|
3263
|
+
await deps.persist(swap);
|
|
3264
|
+
await ctx.wallet.send({
|
|
3265
|
+
address: offer.address,
|
|
3266
|
+
amount: depositSats,
|
|
3267
|
+
extensions: [offer.extension]
|
|
3268
|
+
});
|
|
3269
|
+
emit({ status: "sent" });
|
|
3270
|
+
await deps.awaitFill(swap);
|
|
3271
|
+
await deps.persist({ ...swap, phase: "filled" });
|
|
3272
|
+
await deps.persist({ ...swap, phase: "paying" });
|
|
3273
|
+
const txid = await ctx.wallet.send({
|
|
3274
|
+
address: payTo,
|
|
3275
|
+
amount: carrier,
|
|
3276
|
+
assets: [asset3]
|
|
3277
|
+
});
|
|
3278
|
+
await deps.persist({ ...swap, phase: "settled", txid });
|
|
3279
|
+
const result = {
|
|
3280
|
+
railId: CROSS_ASSET_RAIL,
|
|
3281
|
+
txid,
|
|
3282
|
+
swapId: offer.offerHex
|
|
3283
|
+
};
|
|
3284
|
+
emit({ status: "settled", result });
|
|
3285
|
+
return result;
|
|
3286
|
+
})
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
// src/reveal.ts
|
|
3293
|
+
var import_base13 = require("@scure/base");
|
|
3294
|
+
var import_legacy3 = require("@noble/hashes/legacy.js");
|
|
2826
3295
|
var import_sha24 = require("@noble/hashes/sha2.js");
|
|
2827
|
-
var
|
|
3296
|
+
var import_sdk14 = require("@arkade-os/sdk");
|
|
3297
|
+
var CovclaimdRevealError = class extends Error {
|
|
3298
|
+
name = "CovclaimdRevealError";
|
|
3299
|
+
status;
|
|
3300
|
+
detail;
|
|
3301
|
+
retryable;
|
|
3302
|
+
constructor(what, status, detail) {
|
|
3303
|
+
super(`covclaimd ${what} failed: ${status || "no response"} ${detail}`.trimEnd());
|
|
3304
|
+
this.status = status;
|
|
3305
|
+
this.detail = detail;
|
|
3306
|
+
this.retryable = status === 0 || status === 429 || status >= 500;
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
var decodeKey = (value, field) => {
|
|
3310
|
+
if (typeof value !== "string") throw new Error(`covclaimd info: ${field} is not a string`);
|
|
3311
|
+
let bytes;
|
|
3312
|
+
try {
|
|
3313
|
+
bytes = import_base13.hex.decode(value);
|
|
3314
|
+
} catch {
|
|
3315
|
+
throw new Error(`covclaimd info: ${field} is not hex`);
|
|
3316
|
+
}
|
|
3317
|
+
if (bytes.length !== 33) {
|
|
3318
|
+
throw new Error(`covclaimd info: ${field} must be 33-byte compressed, got ${bytes.length}`);
|
|
3319
|
+
}
|
|
3320
|
+
return bytes;
|
|
3321
|
+
};
|
|
3322
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
3323
|
+
var assertTransportSecure = (baseUrl, allowInsecureHttp) => {
|
|
3324
|
+
const { protocol, hostname } = new URL(baseUrl);
|
|
3325
|
+
if (protocol === "https:" || allowInsecureHttp) return;
|
|
3326
|
+
if (protocol === "http:" && LOOPBACK.has(hostname)) return;
|
|
3327
|
+
throw new Error(
|
|
3328
|
+
`covclaimd URL must be https (got ${protocol}//${hostname}): the key it serves is the one the preimage gets sealed to. Pass allowInsecureHttp to override.`
|
|
3329
|
+
);
|
|
3330
|
+
};
|
|
3331
|
+
var covclaimdClient = (baseUrl, options = {}) => {
|
|
3332
|
+
assertTransportSecure(baseUrl, options.allowInsecureHttp === true);
|
|
3333
|
+
const url = baseUrl.replace(/\/+$/, "");
|
|
3334
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
3335
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
3336
|
+
const send = async (what, path, init) => {
|
|
3337
|
+
let response;
|
|
3338
|
+
try {
|
|
3339
|
+
response = await fetchImpl(`${url}${path}`, {
|
|
3340
|
+
...init,
|
|
3341
|
+
// a redirect would move the key fetch off the vetted origin
|
|
3342
|
+
redirect: "error",
|
|
3343
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
3344
|
+
});
|
|
3345
|
+
} catch (error) {
|
|
3346
|
+
throw new CovclaimdRevealError(what, 0, String(error));
|
|
3347
|
+
}
|
|
3348
|
+
if (!response.ok) {
|
|
3349
|
+
throw new CovclaimdRevealError(what, response.status, await response.text());
|
|
3350
|
+
}
|
|
3351
|
+
return response;
|
|
3352
|
+
};
|
|
3353
|
+
const readJson = async (what, response) => {
|
|
3354
|
+
const body = await response.text();
|
|
3355
|
+
try {
|
|
3356
|
+
return JSON.parse(body);
|
|
3357
|
+
} catch {
|
|
3358
|
+
throw new CovclaimdRevealError(
|
|
3359
|
+
what,
|
|
3360
|
+
response.status,
|
|
3361
|
+
`non-JSON body: ${body.slice(0, 200)}`
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
};
|
|
3365
|
+
return {
|
|
3366
|
+
async info() {
|
|
3367
|
+
const response = await send("info", "/v1/preimage/covclaimd-pubkey", {
|
|
3368
|
+
method: "GET"
|
|
3369
|
+
});
|
|
3370
|
+
const body = await readJson("info", response);
|
|
3371
|
+
return {
|
|
3372
|
+
covclaimdPubkey: decodeKey(body?.covclaimd_pub_key, "covclaimd_pub_key"),
|
|
3373
|
+
emulatorPubkey: import_base13.hex.encode(decodeKey(body?.emulator_pub_key, "emulator_pub_key"))
|
|
3374
|
+
};
|
|
3375
|
+
},
|
|
3376
|
+
async reveal(params) {
|
|
3377
|
+
await send("reveal", "/v1/reveal", {
|
|
3378
|
+
method: "POST",
|
|
3379
|
+
headers: { "content-type": "application/json" },
|
|
3380
|
+
body: JSON.stringify({
|
|
3381
|
+
swap_address: params.swapAddress,
|
|
3382
|
+
packet: { ciphertext: params.ciphertext, arkade_script: params.arkadeScript },
|
|
3383
|
+
taptree: params.taptree
|
|
3384
|
+
})
|
|
3385
|
+
});
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
};
|
|
3389
|
+
function revealFieldsFromScript(script) {
|
|
3390
|
+
if (!script.nonInteractiveClaimArkadeScript) {
|
|
3391
|
+
throw new Error("this covenant has no non-interactive claim leaf to reveal against");
|
|
3392
|
+
}
|
|
3393
|
+
return {
|
|
3394
|
+
arkadeScript: import_base13.base64.encode(script.nonInteractiveClaimArkadeScript),
|
|
3395
|
+
taptree: import_base13.hex.encode(script.encode())
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
async function revealClaimPacket(client, input) {
|
|
3399
|
+
const fields = revealFieldsFromScript(input.script);
|
|
3400
|
+
if (import_base13.hex.encode(import_sdk14.ArkAddress.decode(input.address).pkScript) !== import_base13.hex.encode(input.script.pkScript)) {
|
|
3401
|
+
throw new Error("address does not belong to this covenant");
|
|
3402
|
+
}
|
|
3403
|
+
if (import_base13.hex.encode((0, import_legacy3.ripemd160)((0, import_sha24.sha256)(input.preimage))) !== import_base13.hex.encode(input.script.options.preimageHash)) {
|
|
3404
|
+
throw new Error("preimage does not match the covenant's payment hash");
|
|
3405
|
+
}
|
|
3406
|
+
const { ciphertext } = await sealClaimPacket({
|
|
3407
|
+
preimage: input.preimage,
|
|
3408
|
+
covclaimdPubkey: (await client.info()).covclaimdPubkey
|
|
3409
|
+
});
|
|
3410
|
+
await client.reveal({ swapAddress: input.address, ciphertext, ...fields });
|
|
3411
|
+
return { ciphertext };
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
// src/claim.ts
|
|
3415
|
+
var import_base15 = require("@scure/base");
|
|
3416
|
+
var import_legacy4 = require("@noble/hashes/legacy.js");
|
|
3417
|
+
var import_sha26 = require("@noble/hashes/sha2.js");
|
|
3418
|
+
var import_sdk16 = require("@arkade-os/sdk");
|
|
3419
|
+
|
|
3420
|
+
// src/refund.ts
|
|
3421
|
+
var import_base14 = require("@scure/base");
|
|
3422
|
+
var import_sha25 = require("@noble/hashes/sha2.js");
|
|
3423
|
+
var import_sdk15 = require("@arkade-os/sdk");
|
|
2828
3424
|
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2829
3425
|
var isRfqTerminal = (state) => RFQ_TERMINAL_STATES.includes(state);
|
|
2830
3426
|
var RFQ_RESOLVED_STATES = ["settled", "refunded"];
|
|
@@ -2871,7 +3467,7 @@ var LockupNeedsRecoveryError = class extends Error {
|
|
|
2871
3467
|
}
|
|
2872
3468
|
};
|
|
2873
3469
|
async function findLockupVtxos(indexer, swapPkScript) {
|
|
2874
|
-
const scripts = [
|
|
3470
|
+
const scripts = [import_base14.hex.encode(swapPkScript)];
|
|
2875
3471
|
const [spendable, recoverable] = await Promise.all([
|
|
2876
3472
|
indexer.getVtxos({ scripts, spendableOnly: true }),
|
|
2877
3473
|
indexer.getVtxos({ scripts, recoverableOnly: true })
|
|
@@ -2897,16 +3493,16 @@ async function findLockupVtxos(indexer, swapPkScript) {
|
|
|
2897
3493
|
}
|
|
2898
3494
|
return out;
|
|
2899
3495
|
}
|
|
2900
|
-
var hashesTo = (candidate, paymentHash) =>
|
|
3496
|
+
var hashesTo = (candidate, paymentHash) => import_base14.hex.encode((0, import_sha25.sha256)(candidate)) === paymentHash;
|
|
2901
3497
|
var candidateWitnessItems = (tx, inputIndex) => [
|
|
2902
|
-
...(0,
|
|
3498
|
+
...(0, import_sdk15.getArkPsbtFields)(tx, inputIndex, import_sdk15.ConditionWitness).flat(),
|
|
2903
3499
|
...tx.getInput(inputIndex).finalScriptWitness ?? []
|
|
2904
3500
|
];
|
|
2905
3501
|
async function readLockupFate(indexer, input) {
|
|
2906
|
-
const { vtxos } = await indexer.getVtxos({ scripts: [
|
|
3502
|
+
const { vtxos } = await indexer.getVtxos({ scripts: [import_base14.hex.encode(input.swapPkScript)] });
|
|
2907
3503
|
const all = vtxos ?? [];
|
|
2908
3504
|
if (all.length === 0) return { fate: "unknown" };
|
|
2909
|
-
const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0,
|
|
3505
|
+
const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0, import_sdk15.hasTerminalSpend)(vtxo));
|
|
2910
3506
|
if (exited.length > 0) {
|
|
2911
3507
|
return {
|
|
2912
3508
|
fate: "exited",
|
|
@@ -2916,7 +3512,7 @@ async function readLockupFate(indexer, input) {
|
|
|
2916
3512
|
const spentBy = /* @__PURE__ */ new Map();
|
|
2917
3513
|
let everySpendNamed = true;
|
|
2918
3514
|
for (const vtxo of all) {
|
|
2919
|
-
if (!(0,
|
|
3515
|
+
if (!(0, import_sdk15.hasTerminalSpend)(vtxo)) return { fate: "open" };
|
|
2920
3516
|
if (vtxo.spentBy)
|
|
2921
3517
|
spentBy.set(vtxo.spentBy, {
|
|
2922
3518
|
checkpointTxid: vtxo.spentBy,
|
|
@@ -2930,7 +3526,7 @@ async function readLockupFate(indexer, input) {
|
|
|
2930
3526
|
for (const raw of txs) {
|
|
2931
3527
|
let tx;
|
|
2932
3528
|
try {
|
|
2933
|
-
tx =
|
|
3529
|
+
tx = import_sdk15.Transaction.fromPSBT(import_base14.base64.decode(raw));
|
|
2934
3530
|
} catch {
|
|
2935
3531
|
continue;
|
|
2936
3532
|
}
|
|
@@ -2938,7 +3534,7 @@ async function readLockupFate(indexer, input) {
|
|
|
2938
3534
|
for (let i = 0; i < tx.inputsLength; i++) {
|
|
2939
3535
|
const spent = tx.getInput(i);
|
|
2940
3536
|
if (!spent.txid) continue;
|
|
2941
|
-
const txid =
|
|
3537
|
+
const txid = import_base14.hex.encode(spent.txid);
|
|
2942
3538
|
if (!all.some((vtxo) => vtxo.txid === txid && vtxo.vout === spent.index)) continue;
|
|
2943
3539
|
for (const candidate of candidateWitnessItems(tx, i)) {
|
|
2944
3540
|
if (hashesTo(candidate, input.paymentHash)) {
|
|
@@ -2967,14 +3563,14 @@ async function pushRefundWithoutReceiver(ark, input) {
|
|
|
2967
3563
|
const info = await ark.getInfo();
|
|
2968
3564
|
let serverUnrollScript;
|
|
2969
3565
|
try {
|
|
2970
|
-
serverUnrollScript =
|
|
3566
|
+
serverUnrollScript = import_sdk15.CSVMultisigTapscript.decode(import_base14.hex.decode(info.checkpointTapscript));
|
|
2971
3567
|
} catch {
|
|
2972
3568
|
throw new Error("invalid checkpointTapscript from the Arkade server");
|
|
2973
3569
|
}
|
|
2974
3570
|
const leaf = input.script.refundWithoutReceiver();
|
|
2975
3571
|
const tapTree = input.script.encode();
|
|
2976
3572
|
const amount = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
|
|
2977
|
-
const { arkTx, checkpoints } = (0,
|
|
3573
|
+
const { arkTx, checkpoints } = (0, import_sdk15.buildOffchainTx)(
|
|
2978
3574
|
input.vtxos.map((vtxo) => ({
|
|
2979
3575
|
txid: vtxo.txid,
|
|
2980
3576
|
vout: vtxo.vout,
|
|
@@ -2987,18 +3583,18 @@ async function pushRefundWithoutReceiver(ark, input) {
|
|
|
2987
3583
|
);
|
|
2988
3584
|
const signedArkTx = await input.sender.sign(arkTx);
|
|
2989
3585
|
const submitted = await ark.submitTx(
|
|
2990
|
-
|
|
2991
|
-
checkpoints.map((c) =>
|
|
3586
|
+
import_base14.base64.encode(signedArkTx.toPSBT()),
|
|
3587
|
+
checkpoints.map((c) => import_base14.base64.encode(c.toPSBT()))
|
|
2992
3588
|
);
|
|
2993
|
-
(0,
|
|
2994
|
-
const matched = (0,
|
|
3589
|
+
(0, import_sdk15.assertSubmittedArkTxid)(submitted, signedArkTx, "refundWithoutReceiver");
|
|
3590
|
+
const matched = (0, import_sdk15.matchServerCheckpoints)(
|
|
2995
3591
|
submitted.signedCheckpointTxs,
|
|
2996
3592
|
checkpoints,
|
|
2997
3593
|
"refundWithoutReceiver"
|
|
2998
3594
|
);
|
|
2999
3595
|
const finalCheckpoints = await Promise.all(
|
|
3000
3596
|
matched.map(
|
|
3001
|
-
async ({ server }) =>
|
|
3597
|
+
async ({ server }) => import_base14.base64.encode((await input.sender.sign(server, [0])).toPSBT())
|
|
3002
3598
|
)
|
|
3003
3599
|
);
|
|
3004
3600
|
await ark.finalizeTx(submitted.arkTxid, finalCheckpoints);
|
|
@@ -3093,20 +3689,20 @@ async function pushClaim(ark, input) {
|
|
|
3093
3689
|
}
|
|
3094
3690
|
}
|
|
3095
3691
|
const committed = input.script.options.preimageHash;
|
|
3096
|
-
if (
|
|
3692
|
+
if (import_base15.hex.encode((0, import_legacy4.ripemd160)((0, import_sha26.sha256)(input.preimage))) !== import_base15.hex.encode(committed)) {
|
|
3097
3693
|
throw new Error("preimage does not match the covenant's payment hash");
|
|
3098
3694
|
}
|
|
3099
3695
|
const info = await ark.getInfo();
|
|
3100
3696
|
let serverUnrollScript;
|
|
3101
3697
|
try {
|
|
3102
|
-
serverUnrollScript =
|
|
3698
|
+
serverUnrollScript = import_sdk16.CSVMultisigTapscript.decode(import_base15.hex.decode(info.checkpointTapscript));
|
|
3103
3699
|
} catch {
|
|
3104
3700
|
throw new Error("invalid checkpointTapscript from the Arkade server");
|
|
3105
3701
|
}
|
|
3106
3702
|
const leaf = input.script.claim();
|
|
3107
3703
|
const tapTree = input.script.encode();
|
|
3108
|
-
const arkTxid = await (0,
|
|
3109
|
-
identity: (0,
|
|
3704
|
+
const arkTxid = await (0, import_sdk16.signAndSubmitOffchainTx)({
|
|
3705
|
+
identity: (0, import_sdk16.claimWithPreimageIdentity)(input.receiver, input.preimage),
|
|
3110
3706
|
provider: ark,
|
|
3111
3707
|
inputs: input.vtxos.map((vtxo) => ({
|
|
3112
3708
|
txid: vtxo.txid,
|
|
@@ -3153,7 +3749,7 @@ async function claimReceiveLockup(indexer, ark, input) {
|
|
|
3153
3749
|
}
|
|
3154
3750
|
|
|
3155
3751
|
// src/refundBlocked.ts
|
|
3156
|
-
var
|
|
3752
|
+
var import_sdk17 = require("@arkade-os/sdk");
|
|
3157
3753
|
var RefundNotLocallyPossibleError = class extends Error {
|
|
3158
3754
|
constructor(reason, message, options) {
|
|
3159
3755
|
super(message, options);
|
|
@@ -3170,16 +3766,16 @@ async function senderIdentityForSwapRecord(wallet, record) {
|
|
|
3170
3766
|
);
|
|
3171
3767
|
}
|
|
3172
3768
|
try {
|
|
3173
|
-
return await (0,
|
|
3769
|
+
return await (0, import_sdk17.contractSigner)(wallet, record.signingDescriptor);
|
|
3174
3770
|
} catch (cause) {
|
|
3175
|
-
if (cause instanceof
|
|
3771
|
+
if (cause instanceof import_sdk17.WalletCannotSignError) {
|
|
3176
3772
|
throw new RefundNotLocallyPossibleError(
|
|
3177
3773
|
"unsignable-wallet",
|
|
3178
3774
|
`this wallet holds ${record.signingDescriptor} but cannot sign with it; attach its signer`,
|
|
3179
3775
|
{ cause }
|
|
3180
3776
|
);
|
|
3181
3777
|
}
|
|
3182
|
-
if (cause instanceof
|
|
3778
|
+
if (cause instanceof import_sdk17.ForeignDescriptorError) {
|
|
3183
3779
|
throw new RefundNotLocallyPossibleError(
|
|
3184
3780
|
"foreign-descriptor",
|
|
3185
3781
|
`this wallet cannot derive ${record.signingDescriptor}; the swap was created on another wallet`,
|
|
@@ -3214,7 +3810,7 @@ function arkadeRefunder(deps) {
|
|
|
3214
3810
|
}
|
|
3215
3811
|
|
|
3216
3812
|
// src/swapManager.ts
|
|
3217
|
-
var
|
|
3813
|
+
var import_base16 = require("@scure/base");
|
|
3218
3814
|
function nextOnchainAction(input) {
|
|
3219
3815
|
switch (input.phase.phase) {
|
|
3220
3816
|
case "unfunded":
|
|
@@ -3662,7 +4258,7 @@ var RfqSwapManager = class {
|
|
|
3662
4258
|
// ── internals ────────────────────────────────────────────────────────────
|
|
3663
4259
|
track(swap) {
|
|
3664
4260
|
this.monitored.set(swap.rfqId, swap);
|
|
3665
|
-
this.byLockupScript.set(
|
|
4261
|
+
this.byLockupScript.set(import_base16.hex.encode(swap.lockupPkScript), swap);
|
|
3666
4262
|
}
|
|
3667
4263
|
/** Drops the swap from BOTH indexes. The event index is the one that stops
|
|
3668
4264
|
* a late event finding a swap that is gone; `pollSwap`'s own
|
|
@@ -3671,7 +4267,7 @@ var RfqSwapManager = class {
|
|
|
3671
4267
|
* them from silently re-driving a cancelled swap. */
|
|
3672
4268
|
untrack(rfqId) {
|
|
3673
4269
|
const swap = this.monitored.get(rfqId);
|
|
3674
|
-
if (swap) this.byLockupScript.delete(
|
|
4270
|
+
if (swap) this.byLockupScript.delete(import_base16.hex.encode(swap.lockupPkScript));
|
|
3675
4271
|
this.monitored.delete(rfqId);
|
|
3676
4272
|
this.refundRefused.delete(rfqId);
|
|
3677
4273
|
this.lastClaimError.delete(rfqId);
|
|
@@ -3733,7 +4329,7 @@ var RfqSwapManager = class {
|
|
|
3733
4329
|
if (!lockup) {
|
|
3734
4330
|
try {
|
|
3735
4331
|
const [existing] = await contracts.getContracts({
|
|
3736
|
-
script:
|
|
4332
|
+
script: import_base16.hex.encode(swap.lockupPkScript)
|
|
3737
4333
|
});
|
|
3738
4334
|
if (existing) {
|
|
3739
4335
|
this.registered.set(swap.rfqId, true);
|
|
@@ -3752,13 +4348,13 @@ var RfqSwapManager = class {
|
|
|
3752
4348
|
);
|
|
3753
4349
|
return;
|
|
3754
4350
|
}
|
|
3755
|
-
const script =
|
|
3756
|
-
if (script !==
|
|
4351
|
+
const script = import_base16.hex.encode(lockup.script.pkScript);
|
|
4352
|
+
if (script !== import_base16.hex.encode(swap.lockupPkScript)) {
|
|
3757
4353
|
this.registered.set(swap.rfqId, false);
|
|
3758
4354
|
this.emitFailed(
|
|
3759
4355
|
swap,
|
|
3760
4356
|
new Error(
|
|
3761
|
-
`swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${
|
|
4357
|
+
`swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${import_base16.hex.encode(swap.lockupPkScript)}`
|
|
3762
4358
|
)
|
|
3763
4359
|
);
|
|
3764
4360
|
return;
|
|
@@ -3777,7 +4373,7 @@ var RfqSwapManager = class {
|
|
|
3777
4373
|
* script for its whole life. Best-effort — the swap is over either way. */
|
|
3778
4374
|
retireContract(swap) {
|
|
3779
4375
|
if (!this.deps.contracts || !this.registered.get(swap.rfqId)) return;
|
|
3780
|
-
void this.deps.contracts.setContractWatchState(
|
|
4376
|
+
void this.deps.contracts.setContractWatchState(import_base16.hex.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
|
|
3781
4377
|
}
|
|
3782
4378
|
arm() {
|
|
3783
4379
|
if (!this.running) return;
|
|
@@ -4250,8 +4846,8 @@ var errorMessage = (error) => error instanceof Error ? error.message : String(er
|
|
|
4250
4846
|
var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
|
|
4251
4847
|
|
|
4252
4848
|
// src/activity.ts
|
|
4253
|
-
var
|
|
4254
|
-
var
|
|
4849
|
+
var import_sdk18 = require("@arkade-os/sdk");
|
|
4850
|
+
var import_base17 = require("@scure/base");
|
|
4255
4851
|
var LABELS = {
|
|
4256
4852
|
lightning_send: "Lightning send",
|
|
4257
4853
|
lightning_receive: "Lightning receive",
|
|
@@ -4326,7 +4922,7 @@ async function activityInputOf(record, indexer) {
|
|
|
4326
4922
|
async function lockupTxids(indexer, record, wantFunding) {
|
|
4327
4923
|
let script;
|
|
4328
4924
|
try {
|
|
4329
|
-
script =
|
|
4925
|
+
script = import_base17.hex.encode(import_sdk18.ArkAddress.decode(record.lockupAddress).pkScript);
|
|
4330
4926
|
} catch {
|
|
4331
4927
|
return [];
|
|
4332
4928
|
}
|
|
@@ -4348,6 +4944,8 @@ async function lockupTxids(indexer, record, wantFunding) {
|
|
|
4348
4944
|
ARKADE_BTC,
|
|
4349
4945
|
AddressMismatch,
|
|
4350
4946
|
BTC_ASSET_ID,
|
|
4947
|
+
CROSS_ASSET_RAIL,
|
|
4948
|
+
CovclaimdRevealError,
|
|
4351
4949
|
InMemoryAssetSwapRepository,
|
|
4352
4950
|
IndexedDbAssetSwapRepository,
|
|
4353
4951
|
LIGHTNING_BTC,
|
|
@@ -4380,6 +4978,8 @@ async function lockupTxids(indexer, record, wantFunding) {
|
|
|
4380
4978
|
RfqSwapManager,
|
|
4381
4979
|
RfqSwapOriginRequired,
|
|
4382
4980
|
SOLO_REFUND_HEADROOM_SECONDS,
|
|
4981
|
+
SOLVER_LIGHTNING_RAIL,
|
|
4982
|
+
SOLVER_ONCHAIN_RAIL,
|
|
4383
4983
|
SWAP_LOCKUP_CONTRACT_KIND,
|
|
4384
4984
|
SWAP_LOCKUP_CONTRACT_LABEL,
|
|
4385
4985
|
SWAP_LOCKUP_CONTRACT_TYPE,
|
|
@@ -4396,13 +4996,16 @@ async function lockupTxids(indexer, record, wantFunding) {
|
|
|
4396
4996
|
buildHtlcClaim,
|
|
4397
4997
|
buildHtlcRefund,
|
|
4398
4998
|
cancelOffer,
|
|
4999
|
+
chainSourceFrom,
|
|
4399
5000
|
claimOnchainFill,
|
|
4400
5001
|
claimReceiveLockup,
|
|
4401
5002
|
classifyDepositSpend,
|
|
4402
5003
|
classifyOnchainHtlc,
|
|
4403
5004
|
classifySpend,
|
|
5005
|
+
covclaimdClient,
|
|
4404
5006
|
createOffer,
|
|
4405
5007
|
createRfqSwapRecord,
|
|
5008
|
+
crossAssetRail,
|
|
4406
5009
|
decodeOffer,
|
|
4407
5010
|
deriveLightningReceive,
|
|
4408
5011
|
deriveOnchainReceive,
|
|
@@ -4417,6 +5020,7 @@ async function lockupTxids(indexer, record, wantFunding) {
|
|
|
4417
5020
|
httpTransport,
|
|
4418
5021
|
isRfqSwapTerminal,
|
|
4419
5022
|
isRfqTerminal,
|
|
5023
|
+
l1ScriptForAddress,
|
|
4420
5024
|
lightningReceiveRequest,
|
|
4421
5025
|
lightningSendRequest,
|
|
4422
5026
|
lightningSendVtxoScript,
|
|
@@ -4447,6 +5051,8 @@ async function lockupTxids(indexer, record, wantFunding) {
|
|
|
4447
5051
|
requestOnchainSend,
|
|
4448
5052
|
restoreAssetSwaps,
|
|
4449
5053
|
retireSettledOfferContracts,
|
|
5054
|
+
revealClaimPacket,
|
|
5055
|
+
revealFieldsFromScript,
|
|
4450
5056
|
rfqClaimSecretOf,
|
|
4451
5057
|
rfqPair,
|
|
4452
5058
|
rfqSecretsProfile,
|
|
@@ -4456,6 +5062,11 @@ async function lockupTxids(indexer, record, wantFunding) {
|
|
|
4456
5062
|
sealClaimPacket,
|
|
4457
5063
|
senderIdentityForSwapRecord,
|
|
4458
5064
|
shouldRetainRfqSwap,
|
|
5065
|
+
solverLightningRail,
|
|
5066
|
+
solverLightningRendezvous,
|
|
5067
|
+
solverOnchainRail,
|
|
5068
|
+
solverOnchainRendezvous,
|
|
5069
|
+
solverRendezvous,
|
|
4459
5070
|
spendTxidsOf,
|
|
4460
5071
|
spendUpdate,
|
|
4461
5072
|
swapActivityResolver,
|