@arkade-os/swap 0.0.12 → 0.1.0-rc.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 +67 -22
- package/dist/{chunk-XXWOODUE.js → chunk-OEKSNYJW.js} +107 -76
- package/dist/{chunk-6ZUS47GA.js → chunk-U7VWWTL6.js} +15 -1
- package/dist/index.cjs +1457 -312
- package/dist/index.d.cts +542 -46
- package/dist/index.d.ts +542 -46
- package/dist/index.js +1312 -230
- package/dist/node/index.cjs +322 -0
- package/dist/node/index.d.cts +1055 -0
- package/dist/node/index.d.ts +1055 -0
- package/dist/node/index.js +295 -0
- package/dist/nostr.cjs +4 -1
- 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.cjs +50 -2
- package/dist/repositories/realm/index.d.cts +55 -10
- package/dist/repositories/realm/index.d.ts +55 -10
- package/dist/repositories/realm/index.js +49 -3
- package/dist/repositories/sqlite/index.cjs +42 -1
- package/dist/repositories/sqlite/index.d.cts +8 -3
- package/dist/repositories/sqlite/index.d.ts +8 -3
- package/dist/repositories/sqlite/index.js +43 -2
- package/dist/{repository-DaK1RzRq.d.cts → repository-DoR-ahHc.d.ts} +1081 -84
- package/dist/{repository-DlLvj_y6.d.ts → repository-oeW8KZo1.d.cts} +1081 -84
- package/dist/{rfq-DzsmhXX3.d.cts → rfq-D0KGjmnn.d.cts} +42 -32
- package/dist/{rfq-DzsmhXX3.d.ts → rfq-D0KGjmnn.d.ts} +42 -32
- package/package.json +22 -6
package/README.md
CHANGED
|
@@ -12,6 +12,33 @@ React Native does not, so install `react-native-get-random-values` (or `expo-cry
|
|
|
12
12
|
it before this package. `crypto.subtle` is not used. `EventSource` and `WebSocket` are needed only
|
|
13
13
|
by the watch and relay transports, both of which take an injected implementation.
|
|
14
14
|
|
|
15
|
+
The v2 swap client API is tracked in [V2_API.md](./V2_API.md). That document is
|
|
16
|
+
the package-level developer UX note for the new client surface as it lands; the
|
|
17
|
+
current README still documents the existing package exports and protocol
|
|
18
|
+
building blocks.
|
|
19
|
+
|
|
20
|
+
**The covenant-deriving entry points need a wallet and nothing else.** `createOffer`,
|
|
21
|
+
`requestLightningSend`, `requestLightningReceive`, `requestOnchainSend` and
|
|
22
|
+
`requestOnchainReceive` take their server facts from `wallet.getArkadeInfo()`: the network and
|
|
23
|
+
signer key, plus the unilateral-exit delay for the four `request*` calls. The wallet is the single
|
|
24
|
+
place that knows which server it speaks to, so there is no URL to thread through and no second
|
|
25
|
+
`/v1/info` round-trip *per call*. Each entrypoint still performs its own live read (deliberately —
|
|
26
|
+
covenant derivation requires live info and fails closed offline); a session creating many offers
|
|
27
|
+
pays one read per offer until the SDK grows a `CachingClientTransport`-style memo (the NArk
|
|
28
|
+
reference's answer), noted as follow-up on `ArkadeInfo`.
|
|
29
|
+
|
|
30
|
+
No offer entrypoint here takes a server URL. `cancelOffer` and `watchOfferSwaps` need more than
|
|
31
|
+
server info — cancel broadcasts the refund and falls back to the indexer for a deposit made
|
|
32
|
+
before contract registration existed, and the watcher reads spending transactions — so they
|
|
33
|
+
ask the wallet for those too: `wallet.getArkadeReader()` for chain reads and
|
|
34
|
+
`wallet.getArkadeBroadcaster()` for `submitTx`/`finalizeTx`. On a service-worker wallet both
|
|
35
|
+
are proxied to the worker, so these reads stay on the wallet's own connection.
|
|
36
|
+
The RFQ restore/refund/claim helpers still take provider instances. An `ArkadeReader`
|
|
37
|
+
satisfies their *indexer* parameter structurally — `restoreAssetSwaps` can be fed
|
|
38
|
+
`await wallet.getArkadeReader()` today — while `arkadeRefunder` and `claim`/`refund` also
|
|
39
|
+
want an ark provider (`getInfo` plus the broadcast pair), buildable from
|
|
40
|
+
`wallet.getArkadeInfo()` and `wallet.getArkadeBroadcaster()`.
|
|
41
|
+
|
|
15
42
|
## Roles
|
|
16
43
|
|
|
17
44
|
Arkade Intents names two participants:
|
|
@@ -76,7 +103,7 @@ funds an offer should keep cancelling within reach.
|
|
|
76
103
|
|
|
77
104
|
1. **`offer`** — the swap covenant itself. Two program JSONs (want-BTC / want-asset), the
|
|
78
105
|
`Offer` type, the TLV wire codec (`encodeOffer`/`decodeOffer`, `OFFER_PACKET_TYPE`), address
|
|
79
|
-
derivation (`
|
|
106
|
+
derivation (`offerContract`), and the user-side operations `createOffer`/`cancelOffer`. Identical
|
|
80
107
|
offers always derive identical swap addresses — the program JSONs are hashed into the address,
|
|
81
108
|
so their bytes are frozen (guarded by a golden test).
|
|
82
109
|
2. **`markets`** — solver discovery and pricing guardrails: `discoverMarkets` (1-hour cached
|
|
@@ -117,9 +144,25 @@ uncached discovery.
|
|
|
117
144
|
| `IndexedDbAssetSwapRepository` | `@arkade-os/swap` | the browser (or a polyfilled IndexedDB) |
|
|
118
145
|
| `SQLiteAssetSwapRepository` | `@arkade-os/swap/repositories/sqlite` | React Native, over your SQLite driver |
|
|
119
146
|
| `RealmAssetSwapRepository` | `@arkade-os/swap/repositories/realm` | React Native, over your Realm instance |
|
|
147
|
+
| `nodeSwapRepository()` | `@arkade-os/swap/node` | Node — file-backed SQLite, opened for you |
|
|
148
|
+
|
|
149
|
+
Neither React Native subpath adds a dependency: they take the SDK's structural `SQLExecutor` /
|
|
150
|
+
`RealmLike` handles, so you pass the database you already opened.
|
|
151
|
+
|
|
152
|
+
`@arkade-os/swap/node` is the exception, and the only entry point that imports `node:` builtins —
|
|
153
|
+
which is why it is a separate subpath rather than something the main entry falls back to. It opens
|
|
154
|
+
the database itself, under the platform config directory (XDG / `~/Library/Application Support` /
|
|
155
|
+
`%APPDATA%`) at `arkade/swaps/swaps-<network>.sqlite`, and it is the one backend whose disposal
|
|
156
|
+
closes a connection:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { nodeSwapRepository } from "@arkade-os/swap/node";
|
|
160
|
+
|
|
161
|
+
await using swaps = nodeSwapRepository({ network: "mainnet" }); // or { path } to choose the file
|
|
162
|
+
```
|
|
120
163
|
|
|
121
|
-
|
|
122
|
-
|
|
164
|
+
Every other backend's `[Symbol.asyncDispose]` is a no-op, because you opened the handle and it is
|
|
165
|
+
yours to close. This one opened it, so it closes it.
|
|
123
166
|
|
|
124
167
|
All four carry both record types: asset swaps and the monitored RFQ swaps
|
|
125
168
|
(`saveRfqSwap` / `getRfqSwap` / `getAllRfqSwaps` / `removeRfqSwap`). Each keeps them in a store of their own — a
|
|
@@ -128,8 +171,8 @@ Realm — since the two record types have different keys and no consumer wants t
|
|
|
128
171
|
|
|
129
172
|
**Records are stored whole.** The SQLite and Realm backends serialize each record to **JSON** in a
|
|
130
173
|
`data` column, with only `status` / `createdAt` (and an RFQ record's `state` / `updatedAt`) mapped
|
|
131
|
-
out for querying — so a field they do not know about survives, which is what
|
|
132
|
-
|
|
174
|
+
out for querying — so a field they do not know about survives, which is what a consumer's
|
|
175
|
+
cast-extended record relies on. It is also what keeps an RFQ record's corridor `profile`
|
|
133
176
|
intact: `profile.hashlock` is a nested object holding the payment hash and any preimage material, and
|
|
134
177
|
a field-mapped backend is exactly what would lose it. JSON is
|
|
135
178
|
the boundary, though, and it is narrower than IndexedDB's structured clone: a `Date` in a
|
|
@@ -183,15 +226,17 @@ const realm = await Realm.open({
|
|
|
183
226
|
const swaps = new RealmAssetSwapRepository(realm);
|
|
184
227
|
```
|
|
185
228
|
|
|
186
|
-
|
|
229
|
+
Five classes land in your Realm namespace: `ArkadeAssetSwap`, `ArkadeRfqSwap`, `ArkadeSwapRecord`,
|
|
187
230
|
`ArkadeAssetSwapScannedTxid`, `ArkadeAssetSwapMarketsCache`. Unlike SQLite there is no prefix option
|
|
188
231
|
— a Realm schema name is baked into the schema objects you register — so reconcile against your own
|
|
189
232
|
models by name.
|
|
190
233
|
|
|
191
|
-
`ArkadeRfqSwap` arrived after the
|
|
192
|
-
`schemaVersion` again**: Realm creates schemas at open, so a
|
|
193
|
-
|
|
194
|
-
|
|
234
|
+
`ArkadeRfqSwap` and `ArkadeSwapRecord` arrived after the first three. **If you already shipped an
|
|
235
|
+
earlier set, add the new ones and bump `schemaVersion` again**: Realm creates schemas at open, so a
|
|
236
|
+
config listing fewer fails on the first read of the missing one rather than at open. Spreading
|
|
237
|
+
`AssetSwapRealmSchemas` rather than listing names by hand is what keeps that from happening again.
|
|
238
|
+
SQLite needs nothing — its DDL runs `CREATE TABLE IF NOT EXISTS` on every init, so a new table
|
|
239
|
+
appears on the next operation.
|
|
195
240
|
|
|
196
241
|
## Creating an offer
|
|
197
242
|
|
|
@@ -200,11 +245,11 @@ the rest:
|
|
|
200
245
|
|
|
201
246
|
```ts
|
|
202
247
|
// BTC -> asset
|
|
203
|
-
const o = await createOffer(wallet,
|
|
248
|
+
const o = await createOffer(wallet, { wantAmount: 1000n, wantAsset });
|
|
204
249
|
await wallet.send({ address: o.address, amount: 1000, extensions: [o.extension] });
|
|
205
250
|
|
|
206
251
|
// asset -> BTC (the sats are the VTXO carrier for the asset)
|
|
207
|
-
const o = await createOffer(wallet,
|
|
252
|
+
const o = await createOffer(wallet, { wantAmount: 1000n, offerAsset });
|
|
208
253
|
await wallet.send({
|
|
209
254
|
address: o.address,
|
|
210
255
|
amount: 500,
|
|
@@ -238,7 +283,7 @@ offer bytes themselves are recoverable from the funding tx if the record is lost
|
|
|
238
283
|
## Live status
|
|
239
284
|
|
|
240
285
|
```ts
|
|
241
|
-
const watcher = await watchOfferSwaps({ wallet,
|
|
286
|
+
const watcher = await watchOfferSwaps({ wallet, repository, onUpdate: render });
|
|
242
287
|
// later
|
|
243
288
|
watcher.stop();
|
|
244
289
|
```
|
|
@@ -260,7 +305,7 @@ repository.
|
|
|
260
305
|
## Cancelling: the refund path
|
|
261
306
|
|
|
262
307
|
```ts
|
|
263
|
-
const txid = await cancelOffer(wallet,
|
|
308
|
+
const txid = await cancelOffer(wallet, swap.offerHex, {
|
|
264
309
|
repository,
|
|
265
310
|
fundingTxid: swap.fundingTxid,
|
|
266
311
|
swapAddress: swap.swapAddress,
|
|
@@ -322,7 +367,7 @@ message anywhere: **acceptance is funding**.
|
|
|
322
367
|
import { httpTransport, requestLightningSend } from "@arkade-os/swap";
|
|
323
368
|
|
|
324
369
|
// invoice facts from YOUR OWN decoder — the module takes facts, not a decoder
|
|
325
|
-
const swap = await requestLightningSend(wallet,
|
|
370
|
+
const swap = await requestLightningSend(wallet, httpTransport(solverUrl), {
|
|
326
371
|
invoice: { raw: bolt11, paymentHash, amountSats, expiresAt },
|
|
327
372
|
});
|
|
328
373
|
// quote verified against the LOCAL derivation and gated; now fund and go offline:
|
|
@@ -395,7 +440,7 @@ import {
|
|
|
395
440
|
swapSecretsToRecord,
|
|
396
441
|
} from "@arkade-os/swap";
|
|
397
442
|
|
|
398
|
-
const swap = await requestOnchainSend(wallet,
|
|
443
|
+
const swap = await requestOnchainSend(wallet, httpTransport(solverUrl), {
|
|
399
444
|
amount: 100_000,
|
|
400
445
|
amountSide: "to",
|
|
401
446
|
payoutPubkey,
|
|
@@ -829,7 +874,7 @@ Notes from before 0.0.1, kept for consumers who tracked the branch.
|
|
|
829
874
|
silently overwritten.
|
|
830
875
|
- **`readLockupFate` names the spends it observed.** `claimed` and `returned` now carry
|
|
831
876
|
`spends: readonly LockupSpend[]`, one per spent lockup output, with the `checkpointTxid` that
|
|
832
|
-
`spentBy` names and the `
|
|
877
|
+
`spentBy` names and the `txid` that rode it. History correlation wants `txid`; the
|
|
833
878
|
checkpoint txid is the wrong value to correlate on alone. `unknown` and `open` claim no spend.
|
|
834
879
|
|
|
835
880
|
- **Every derived address changed again, in both corridors — the unilateral ladder was re-spaced.**
|
|
@@ -901,8 +946,8 @@ stored?)`. The returned `ProvisionedKey` / `ProvisionedClaimSecret` replace `Swa
|
|
|
901
946
|
quote at `verifyLockupAddress`. Upgrade both sides before expecting fills.
|
|
902
947
|
- **`cancelOffer` and `restoreAssetSwaps` take an options object.** `cancelOffer(wallet, url,
|
|
903
948
|
offerHex, { repository, fundingTxid?, swapAddress? })` — the repository is required because the
|
|
904
|
-
call now records its own outcome. `restoreAssetSwaps(indexer, txs, existingIds, {
|
|
905
|
-
scanned? })` — the
|
|
949
|
+
call now records its own outcome. `restoreAssetSwaps(indexer, txs, existingIds, { operatorPubkey,
|
|
950
|
+
scanned? })` — the operator key is required because a spend is classified by rebuilding the
|
|
906
951
|
covenant and matching the leaf it took.
|
|
907
952
|
- **`isCancelSpend` is gone**, replaced by `classifySpend`, and `Tx.assets` with it. The old test
|
|
908
953
|
read what a transaction moved, which a wallet reports as a _net_ delta: once the deposit is a
|
|
@@ -944,7 +989,7 @@ scanned? })` — the server key is required because a spend is classified by reb
|
|
|
944
989
|
kind: "lightning_receive",
|
|
945
990
|
lockupAddress: result.address,
|
|
946
991
|
profile: {
|
|
947
|
-
...rfqSecretsProfile(result.secrets, result.
|
|
992
|
+
...rfqSecretsProfile(result.secrets, result.contractParams.paymentHash),
|
|
948
993
|
expectedAmount: result.expectedAmount,
|
|
949
994
|
payoutAddress: result.payoutAddress,
|
|
950
995
|
},
|
|
@@ -1014,7 +1059,7 @@ scanned? })` — the server key is required because a spend is classified by reb
|
|
|
1014
1059
|
when `persisted` is true — the callback is documented as following a persisted change, and a
|
|
1015
1060
|
consumer caching from it must not run ahead of the store.
|
|
1016
1061
|
- **`lightningSendProgram` and `htlcSendProgram` are gone** along with the program-artifact layer
|
|
1017
|
-
they compiled. Derive scripts through `
|
|
1062
|
+
they compiled. Derive scripts through `lightningSendContract` / `onchainHtlcScript`.
|
|
1018
1063
|
- **The receive corridors are wired, and the wire shape settled.** `lightningReceiveRequest` is
|
|
1019
1064
|
new; `onchainReceiveRequest`'s profile now matches the shipped solver schema (`payment_hash`,
|
|
1020
1065
|
`claim_packet`, `refund_pubkey`, `payout_address`, `payout_pubkey` — the earlier
|
|
@@ -1025,7 +1070,7 @@ scanned? })` — the server key is required because a spend is classified by reb
|
|
|
1025
1070
|
corridor's fee — and refuses quotes whose `to_amount` reprices the invoice; solvers charge
|
|
1026
1071
|
per-corridor fees on all four pairs, and funding the bare invoice amount underfunds by exactly
|
|
1027
1072
|
the fee.
|
|
1028
|
-
- **`
|
|
1073
|
+
- **`lightningSendContract` takes two new required fields**: `senderPubkey` (the trader's VHTLC
|
|
1029
1074
|
sender key — generate, persist, see `requestLightningSend`) and `receiverPkScript` (the solver's
|
|
1030
1075
|
claim destination, from `profile.receiver_pk_script`). Callers that built the lockup directly
|
|
1031
1076
|
must supply both; callers going through `requestLightningSend` are unaffected.
|
|
@@ -9,6 +9,7 @@ var MAX_MIN_CONFIRMATIONS = 6;
|
|
|
9
9
|
var LOCKTIME_THRESHOLD = 5e8;
|
|
10
10
|
var ONCHAIN_SECONDS_PER_BLOCK = 600;
|
|
11
11
|
var ONCHAIN_DUST_SATS = BigInt(330);
|
|
12
|
+
var ONCHAIN_CLAIM_VSIZE = 152;
|
|
12
13
|
var newPreimage = () => crypto.getRandomValues(new Uint8Array(32));
|
|
13
14
|
var paymentHashOf = (preimage) => hex.encode(sha256(preimage));
|
|
14
15
|
var h160FromPaymentHash = (paymentHash) => ripemd160(hex.decode(paymentHash));
|
|
@@ -314,9 +315,8 @@ import { hex as hex3 } from "@scure/base";
|
|
|
314
315
|
import { ripemd160 as ripemd1602 } from "@noble/hashes/legacy.js";
|
|
315
316
|
import {
|
|
316
317
|
ArkAddress as ArkAddress2,
|
|
317
|
-
RestArkProvider,
|
|
318
318
|
VHTLC,
|
|
319
|
-
|
|
319
|
+
networkFromArkadeInfo,
|
|
320
320
|
resolveEmulatorPubkey,
|
|
321
321
|
toXOnly
|
|
322
322
|
} from "@arkade-os/sdk";
|
|
@@ -343,11 +343,14 @@ var ONCHAIN_SEND_PAIR = rfqPair(ARKADE_BTC, ONCHAIN_BTC);
|
|
|
343
343
|
var ONCHAIN_RECEIVE_PAIR = rfqPair(ONCHAIN_BTC, ARKADE_BTC);
|
|
344
344
|
var RFQ_TERMINAL_STATES = ["settled", "refused", "expired", "refunded", "stuck"];
|
|
345
345
|
var SwapRefusal = class extends Error {
|
|
346
|
+
/** Literal-typed so the v2 error taxonomy's union discriminates on `name`
|
|
347
|
+
* — a `string` here collapses the discriminant for every member. Same value
|
|
348
|
+
* the constructor has always set, moved to a field initializer. */
|
|
349
|
+
name = "SwapRefusal";
|
|
346
350
|
reason;
|
|
347
351
|
rfqId;
|
|
348
352
|
constructor(reason, rfqId) {
|
|
349
353
|
super(`solver refused: ${reason}`);
|
|
350
|
-
this.name = "SwapRefusal";
|
|
351
354
|
this.reason = reason;
|
|
352
355
|
this.rfqId = rfqId;
|
|
353
356
|
}
|
|
@@ -448,6 +451,10 @@ var assertFundable = (input) => {
|
|
|
448
451
|
const fail = (reason, message) => {
|
|
449
452
|
throw gateError(reason, message);
|
|
450
453
|
};
|
|
454
|
+
if (input.quote.valid_until === void 0) {
|
|
455
|
+
fail("quote_malformed", "quote carries no valid_until");
|
|
456
|
+
}
|
|
457
|
+
assertFinite(input.quote.valid_until, "quote_malformed", "quote valid_until");
|
|
451
458
|
if (input.invoiceExpiresAt !== void 0 && input.now >= input.invoiceExpiresAt) {
|
|
452
459
|
fail("invoice_expired", "invoice expired");
|
|
453
460
|
}
|
|
@@ -650,22 +657,22 @@ var relayTransport = (relayUrl, options) => {
|
|
|
650
657
|
};
|
|
651
658
|
var SEQUENCE_GRANULARITY_SECONDS = 512;
|
|
652
659
|
var SOLO_REFUND_HEADROOM_SECONDS = 8 * SEQUENCE_GRANULARITY_SECONDS;
|
|
653
|
-
var unilateralClaimDelay = (
|
|
654
|
-
if (!Number.isFinite(
|
|
660
|
+
var unilateralClaimDelay = (operatorExitDelaySeconds) => {
|
|
661
|
+
if (!Number.isFinite(operatorExitDelaySeconds) || operatorExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
|
|
655
662
|
throw new Error(
|
|
656
|
-
`
|
|
663
|
+
`operator exit delay must be at least ${SEQUENCE_GRANULARITY_SECONDS}s of seconds, got ${operatorExitDelaySeconds}`
|
|
657
664
|
);
|
|
658
665
|
}
|
|
659
|
-
if (
|
|
666
|
+
if (operatorExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
|
|
660
667
|
throw new Error(
|
|
661
|
-
`
|
|
668
|
+
`operator exit delay ${operatorExitDelaySeconds}s exceeds what BIP68 can encode once the solo refund's headroom is stacked above it`
|
|
662
669
|
);
|
|
663
670
|
}
|
|
664
|
-
return Math.ceil(
|
|
671
|
+
return Math.ceil(operatorExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
|
|
665
672
|
};
|
|
666
673
|
var unilateralRefundDelay = (claimDelay) => claimDelay;
|
|
667
674
|
var unilateralRefundWithoutReceiverDelay = (claimDelay) => claimDelay + SOLO_REFUND_HEADROOM_SECONDS;
|
|
668
|
-
function
|
|
675
|
+
function lightningSendContract(params) {
|
|
669
676
|
const seconds = (value) => ({
|
|
670
677
|
type: "seconds",
|
|
671
678
|
value: BigInt(value)
|
|
@@ -673,7 +680,7 @@ function lightningSendVtxoScript(params) {
|
|
|
673
680
|
return new VHTLC.ScriptV2({
|
|
674
681
|
sender: params.senderPubkey,
|
|
675
682
|
receiver: params.solverPubkey,
|
|
676
|
-
server: params.
|
|
683
|
+
server: params.operatorPubkey,
|
|
677
684
|
preimageHash: ripemd1602(hex3.decode(params.paymentHash)),
|
|
678
685
|
refundLocktime: BigInt(params.refundLocktime),
|
|
679
686
|
unilateralClaimDelay: seconds(params.claimDelay),
|
|
@@ -689,15 +696,8 @@ function lightningSendVtxoScript(params) {
|
|
|
689
696
|
}
|
|
690
697
|
});
|
|
691
698
|
}
|
|
692
|
-
|
|
693
|
-
const
|
|
694
|
-
const secrets = await provisionRefundKey(wallet);
|
|
695
|
-
const senderPubkey = secrets.pubkey;
|
|
696
|
-
const refundAddress = secrets.address;
|
|
697
|
-
const info = await new RestArkProvider(arkServerUrl).getInfo();
|
|
698
|
-
const quote = await transport.requestQuote(
|
|
699
|
-
lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
|
|
700
|
-
);
|
|
699
|
+
function deriveLightningSend(input) {
|
|
700
|
+
const { quote } = input;
|
|
701
701
|
if (quote.refund_locktime === void 0) {
|
|
702
702
|
throw new Error("lightning-send quote is missing refund_locktime");
|
|
703
703
|
}
|
|
@@ -705,6 +705,43 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
705
705
|
if (receiverPkScriptHex === void 0) {
|
|
706
706
|
throw new Error("lightning-send quote is missing profile.receiver_pk_script");
|
|
707
707
|
}
|
|
708
|
+
const contractParams = {
|
|
709
|
+
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
710
|
+
refundLocktime: quote.refund_locktime,
|
|
711
|
+
operatorPubkey: input.operatorPubkey,
|
|
712
|
+
paymentHash: input.paymentHash,
|
|
713
|
+
claimDelay: input.claimDelay,
|
|
714
|
+
emulatorPubkey: input.emulatorPubkey,
|
|
715
|
+
senderPubkey: input.senderPubkey,
|
|
716
|
+
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
717
|
+
refundPkScript: input.refundPkScript
|
|
718
|
+
};
|
|
719
|
+
const matched = matchQuotedLockup(
|
|
720
|
+
quote,
|
|
721
|
+
input.hrp,
|
|
722
|
+
input.operatorPubkey,
|
|
723
|
+
(legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
|
|
724
|
+
);
|
|
725
|
+
return {
|
|
726
|
+
address: matched.address,
|
|
727
|
+
swapPkScript: matched.script.pkScript,
|
|
728
|
+
script: matched.script,
|
|
729
|
+
contractParams: {
|
|
730
|
+
...contractParams,
|
|
731
|
+
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
732
|
+
},
|
|
733
|
+
refundLocktime: quote.refund_locktime
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
async function requestLightningSend(wallet, transport, params) {
|
|
737
|
+
const rfqId = params.rfqId ?? newRfqId();
|
|
738
|
+
const secrets = await provisionRefundKey(wallet);
|
|
739
|
+
const senderPubkey = secrets.pubkey;
|
|
740
|
+
const refundAddress = secrets.address;
|
|
741
|
+
const info = await wallet.getArkadeInfo({ requireLive: true });
|
|
742
|
+
const quote = await transport.requestQuote(
|
|
743
|
+
lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
|
|
744
|
+
);
|
|
708
745
|
if (quote.to_amount !== params.invoice.amountSats) {
|
|
709
746
|
throw new Error(
|
|
710
747
|
`quote to_amount ${quote.to_amount} does not match the invoice's ${params.invoice.amountSats}`
|
|
@@ -715,34 +752,22 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
715
752
|
`quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
|
|
716
753
|
);
|
|
717
754
|
}
|
|
718
|
-
const
|
|
719
|
-
const network =
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
refundLocktime: quote.refund_locktime,
|
|
723
|
-
serverPubkey,
|
|
755
|
+
const operatorPubkey = toXOnly(hex3.decode(info.signerPubkey), "ark signer key");
|
|
756
|
+
const network = networkFromArkadeInfo(info);
|
|
757
|
+
const derived = deriveLightningSend({
|
|
758
|
+
quote,
|
|
724
759
|
paymentHash: params.invoice.paymentHash,
|
|
725
|
-
|
|
760
|
+
senderPubkey,
|
|
761
|
+
refundPkScript: secrets.pkScript,
|
|
762
|
+
operatorPubkey,
|
|
726
763
|
emulatorPubkey: toXOnly(
|
|
727
764
|
hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
|
|
728
765
|
"emulator signer key"
|
|
729
766
|
),
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
};
|
|
734
|
-
const matched = matchQuotedLockup(
|
|
735
|
-
quote,
|
|
736
|
-
network.hrp,
|
|
737
|
-
serverPubkey,
|
|
738
|
-
(legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
739
|
-
);
|
|
740
|
-
const script = matched.script;
|
|
741
|
-
const address = matched.address;
|
|
742
|
-
const matchedTreeParams = {
|
|
743
|
-
...treeParams,
|
|
744
|
-
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
745
|
-
};
|
|
767
|
+
claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
|
|
768
|
+
hrp: network.hrp
|
|
769
|
+
});
|
|
770
|
+
const { address, script, contractParams } = derived;
|
|
746
771
|
assertFundable({
|
|
747
772
|
quote,
|
|
748
773
|
invoiceExpiresAt: params.invoice.expiresAt,
|
|
@@ -756,12 +781,12 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
756
781
|
// What the lockup must carry: the quote's `from_amount` — the invoice
|
|
757
782
|
// PLUS the corridor's fee, never the bare invoice amount.
|
|
758
783
|
fundAmount: quote.from_amount,
|
|
759
|
-
swapPkScript:
|
|
784
|
+
swapPkScript: derived.swapPkScript,
|
|
760
785
|
script,
|
|
761
786
|
refundAddress,
|
|
762
787
|
senderPubkey,
|
|
763
788
|
secrets,
|
|
764
|
-
|
|
789
|
+
contractParams
|
|
765
790
|
};
|
|
766
791
|
}
|
|
767
792
|
var offerTermsFromQuote = (quote, assets) => {
|
|
@@ -826,10 +851,10 @@ function deriveOnchainSend(input) {
|
|
|
826
851
|
if (refundLocktime === void 0 || htlcPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || receiverPkScriptHex === void 0) {
|
|
827
852
|
throw new Error("onchain-send quote is missing a binding field");
|
|
828
853
|
}
|
|
829
|
-
const
|
|
854
|
+
const contractParams = {
|
|
830
855
|
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
831
856
|
refundLocktime,
|
|
832
|
-
|
|
857
|
+
operatorPubkey: input.operatorPubkey,
|
|
833
858
|
paymentHash: input.paymentHash,
|
|
834
859
|
claimDelay: input.claimDelay,
|
|
835
860
|
emulatorPubkey: input.emulatorPubkey,
|
|
@@ -840,8 +865,8 @@ function deriveOnchainSend(input) {
|
|
|
840
865
|
const { script, address } = matchQuotedLockup(
|
|
841
866
|
quote,
|
|
842
867
|
input.hrp,
|
|
843
|
-
input.
|
|
844
|
-
(legacy) =>
|
|
868
|
+
input.operatorPubkey,
|
|
869
|
+
(legacy) => lightningSendContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
|
|
845
870
|
);
|
|
846
871
|
const htlcParams = {
|
|
847
872
|
paymentHash: input.paymentHash,
|
|
@@ -863,7 +888,7 @@ function deriveOnchainSend(input) {
|
|
|
863
888
|
minConfirmations
|
|
864
889
|
};
|
|
865
890
|
}
|
|
866
|
-
async function requestOnchainSend(wallet,
|
|
891
|
+
async function requestOnchainSend(wallet, transport, params) {
|
|
867
892
|
const rfqId = params.rfqId ?? newRfqId();
|
|
868
893
|
const secrets = await provisionClaimSecret(wallet, { preimage: params.preimage });
|
|
869
894
|
if (secrets.mustPersistPreimage) {
|
|
@@ -874,7 +899,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
|
|
|
874
899
|
const paymentHash = hex3.encode(secrets.paymentHash);
|
|
875
900
|
const senderPubkey = secrets.pubkey;
|
|
876
901
|
const [info, refundAddress] = await Promise.all([
|
|
877
|
-
|
|
902
|
+
wallet.getArkadeInfo({ requireLive: true }),
|
|
878
903
|
wallet.getAddress()
|
|
879
904
|
]);
|
|
880
905
|
const quote = await transport.requestQuote(
|
|
@@ -889,12 +914,12 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
|
|
|
889
914
|
})
|
|
890
915
|
);
|
|
891
916
|
assertQuotedAmount(quote, params.amountSide, params.amount);
|
|
892
|
-
const network =
|
|
917
|
+
const network = networkFromArkadeInfo(info);
|
|
893
918
|
const derived = deriveOnchainSend({
|
|
894
919
|
quote,
|
|
895
920
|
paymentHash,
|
|
896
921
|
payoutPubkey: params.payoutPubkey,
|
|
897
|
-
|
|
922
|
+
operatorPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
|
|
898
923
|
emulatorPubkey: toXOnly(
|
|
899
924
|
hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
|
|
900
925
|
"emulator signer key"
|
|
@@ -931,6 +956,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
|
|
|
931
956
|
htlcParams: derived.htlcParams,
|
|
932
957
|
l1Network: derived.l1Network,
|
|
933
958
|
minConfirmations: derived.minConfirmations,
|
|
959
|
+
refundLocktime: derived.refundLocktime,
|
|
934
960
|
senderPubkey,
|
|
935
961
|
secrets
|
|
936
962
|
};
|
|
@@ -1002,7 +1028,7 @@ var assertReceivable = (input) => {
|
|
|
1002
1028
|
);
|
|
1003
1029
|
}
|
|
1004
1030
|
};
|
|
1005
|
-
function
|
|
1031
|
+
function lightningReceiveContract(params) {
|
|
1006
1032
|
const seconds = (value) => ({
|
|
1007
1033
|
type: "seconds",
|
|
1008
1034
|
value: BigInt(value)
|
|
@@ -1010,7 +1036,7 @@ function receiveVtxoScript(params) {
|
|
|
1010
1036
|
return new VHTLC.ScriptV2({
|
|
1011
1037
|
sender: params.solverPubkey,
|
|
1012
1038
|
receiver: params.payoutPubkey,
|
|
1013
|
-
server: params.
|
|
1039
|
+
server: params.operatorPubkey,
|
|
1014
1040
|
preimageHash: ripemd1602(hex3.decode(params.paymentHash)),
|
|
1015
1041
|
refundLocktime: BigInt(params.refundLocktime),
|
|
1016
1042
|
unilateralClaimDelay: seconds(params.claimDelay),
|
|
@@ -1035,10 +1061,10 @@ function deriveLightningReceive(input) {
|
|
|
1035
1061
|
if (refundLocktime === void 0 || invoice === void 0 || solverRefundPkScriptHex === void 0) {
|
|
1036
1062
|
throw new Error("lightning-receive quote is missing a binding field");
|
|
1037
1063
|
}
|
|
1038
|
-
const
|
|
1064
|
+
const contractParams = {
|
|
1039
1065
|
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
1040
1066
|
refundLocktime,
|
|
1041
|
-
|
|
1067
|
+
operatorPubkey: input.operatorPubkey,
|
|
1042
1068
|
paymentHash: input.paymentHash,
|
|
1043
1069
|
claimDelay: input.claimDelay,
|
|
1044
1070
|
emulatorPubkey: input.emulatorPubkey,
|
|
@@ -1049,8 +1075,8 @@ function deriveLightningReceive(input) {
|
|
|
1049
1075
|
const matched = matchQuotedLockup(
|
|
1050
1076
|
quote,
|
|
1051
1077
|
input.hrp,
|
|
1052
|
-
input.
|
|
1053
|
-
(legacy) =>
|
|
1078
|
+
input.operatorPubkey,
|
|
1079
|
+
(legacy) => lightningReceiveContract({ ...contractParams, ...legacy !== void 0 && { legacy } })
|
|
1054
1080
|
);
|
|
1055
1081
|
return {
|
|
1056
1082
|
address: matched.address,
|
|
@@ -1058,13 +1084,13 @@ function deriveLightningReceive(input) {
|
|
|
1058
1084
|
script: matched.script,
|
|
1059
1085
|
invoice,
|
|
1060
1086
|
refundLocktime,
|
|
1061
|
-
|
|
1062
|
-
...
|
|
1087
|
+
contractParams: {
|
|
1088
|
+
...contractParams,
|
|
1063
1089
|
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
1064
1090
|
}
|
|
1065
1091
|
};
|
|
1066
1092
|
}
|
|
1067
|
-
async function requestLightningReceive(wallet,
|
|
1093
|
+
async function requestLightningReceive(wallet, transport, params) {
|
|
1068
1094
|
const rfqId = params.rfqId ?? newRfqId();
|
|
1069
1095
|
const secrets = await provisionClaimSecret(wallet);
|
|
1070
1096
|
if (secrets.mustPersistPreimage) {
|
|
@@ -1076,7 +1102,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
|
|
|
1076
1102
|
const paymentHash = hex3.encode(secrets.paymentHash);
|
|
1077
1103
|
const payoutPubkey = secrets.pubkey;
|
|
1078
1104
|
const [info, payoutAddress] = await Promise.all([
|
|
1079
|
-
|
|
1105
|
+
wallet.getArkadeInfo({ requireLive: true }),
|
|
1080
1106
|
wallet.getAddress()
|
|
1081
1107
|
]);
|
|
1082
1108
|
const claimPacket = await sealClaimPacket({
|
|
@@ -1095,13 +1121,13 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
|
|
|
1095
1121
|
})
|
|
1096
1122
|
);
|
|
1097
1123
|
assertQuotedAmount(quote, params.amountSide, params.amount);
|
|
1098
|
-
const network =
|
|
1124
|
+
const network = networkFromArkadeInfo(info);
|
|
1099
1125
|
const derived = deriveLightningReceive({
|
|
1100
1126
|
quote,
|
|
1101
1127
|
paymentHash,
|
|
1102
1128
|
payoutPubkey,
|
|
1103
1129
|
payoutAddress,
|
|
1104
|
-
|
|
1130
|
+
operatorPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
|
|
1105
1131
|
emulatorPubkey: toXOnly(
|
|
1106
1132
|
hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
|
|
1107
1133
|
"emulator signer key"
|
|
@@ -1135,7 +1161,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
|
|
|
1135
1161
|
payoutAddress,
|
|
1136
1162
|
payoutPubkey,
|
|
1137
1163
|
secrets,
|
|
1138
|
-
|
|
1164
|
+
contractParams: derived.contractParams
|
|
1139
1165
|
};
|
|
1140
1166
|
}
|
|
1141
1167
|
function deriveOnchainReceive(input) {
|
|
@@ -1150,10 +1176,10 @@ function deriveOnchainReceive(input) {
|
|
|
1150
1176
|
if (refundLocktime === void 0 || claimPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || solverRefundPkScriptHex === void 0) {
|
|
1151
1177
|
throw new Error("onchain-receive quote is missing a binding field");
|
|
1152
1178
|
}
|
|
1153
|
-
const
|
|
1179
|
+
const contractParams = {
|
|
1154
1180
|
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
1155
1181
|
refundLocktime,
|
|
1156
|
-
|
|
1182
|
+
operatorPubkey: input.operatorPubkey,
|
|
1157
1183
|
paymentHash: input.paymentHash,
|
|
1158
1184
|
claimDelay: input.claimDelay,
|
|
1159
1185
|
emulatorPubkey: input.emulatorPubkey,
|
|
@@ -1164,8 +1190,11 @@ function deriveOnchainReceive(input) {
|
|
|
1164
1190
|
const { script, address } = matchQuotedLockup(
|
|
1165
1191
|
quote,
|
|
1166
1192
|
input.hrp,
|
|
1167
|
-
input.
|
|
1168
|
-
(legacy) =>
|
|
1193
|
+
input.operatorPubkey,
|
|
1194
|
+
(legacy) => lightningReceiveContract({
|
|
1195
|
+
...contractParams,
|
|
1196
|
+
...legacy !== void 0 && { legacy }
|
|
1197
|
+
})
|
|
1169
1198
|
);
|
|
1170
1199
|
const htlc = onchainHtlcScript(
|
|
1171
1200
|
{
|
|
@@ -1187,7 +1216,7 @@ function deriveOnchainReceive(input) {
|
|
|
1187
1216
|
minConfirmations
|
|
1188
1217
|
};
|
|
1189
1218
|
}
|
|
1190
|
-
async function requestOnchainReceive(wallet,
|
|
1219
|
+
async function requestOnchainReceive(wallet, transport, params) {
|
|
1191
1220
|
const rfqId = params.rfqId ?? newRfqId();
|
|
1192
1221
|
const secrets = await provisionClaimSecret(wallet);
|
|
1193
1222
|
if (secrets.mustPersistPreimage) {
|
|
@@ -1199,7 +1228,7 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
|
|
|
1199
1228
|
const paymentHash = hex3.encode(secrets.paymentHash);
|
|
1200
1229
|
const payoutPubkey = secrets.pubkey;
|
|
1201
1230
|
const [info, payoutAddress] = await Promise.all([
|
|
1202
|
-
|
|
1231
|
+
wallet.getArkadeInfo({ requireLive: true }),
|
|
1203
1232
|
wallet.getAddress()
|
|
1204
1233
|
]);
|
|
1205
1234
|
const claimPacket = await sealClaimPacket({
|
|
@@ -1219,14 +1248,14 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
|
|
|
1219
1248
|
})
|
|
1220
1249
|
);
|
|
1221
1250
|
assertQuotedAmount(quote, params.amountSide, params.amount);
|
|
1222
|
-
const network =
|
|
1251
|
+
const network = networkFromArkadeInfo(info);
|
|
1223
1252
|
const derived = deriveOnchainReceive({
|
|
1224
1253
|
quote,
|
|
1225
1254
|
paymentHash,
|
|
1226
1255
|
payoutPubkey,
|
|
1227
1256
|
payoutAddress,
|
|
1228
1257
|
refundPubkey: params.refundPubkey,
|
|
1229
|
-
|
|
1258
|
+
operatorPubkey: toXOnly(hex3.decode(info.signerPubkey), "ark signer key"),
|
|
1230
1259
|
emulatorPubkey: toXOnly(
|
|
1231
1260
|
hex3.decode(resolveEmulatorPubkey(network, params.emulatorPubkey)),
|
|
1232
1261
|
"emulator signer key"
|
|
@@ -1271,6 +1300,7 @@ export {
|
|
|
1271
1300
|
LOCKTIME_THRESHOLD,
|
|
1272
1301
|
ONCHAIN_SECONDS_PER_BLOCK,
|
|
1273
1302
|
ONCHAIN_DUST_SATS,
|
|
1303
|
+
ONCHAIN_CLAIM_VSIZE,
|
|
1274
1304
|
newPreimage,
|
|
1275
1305
|
paymentHashOf,
|
|
1276
1306
|
L1_NETWORKS,
|
|
@@ -1317,9 +1347,10 @@ export {
|
|
|
1317
1347
|
unilateralClaimDelay,
|
|
1318
1348
|
unilateralRefundDelay,
|
|
1319
1349
|
unilateralRefundWithoutReceiverDelay,
|
|
1320
|
-
|
|
1350
|
+
lightningSendContract,
|
|
1321
1351
|
requestLightningSend,
|
|
1322
1352
|
offerTermsFromQuote,
|
|
1353
|
+
l1NetworkFromArk,
|
|
1323
1354
|
onchainSendRequest,
|
|
1324
1355
|
lightningReceiveRequest,
|
|
1325
1356
|
onchainReceiveRequest,
|
|
@@ -1328,7 +1359,7 @@ export {
|
|
|
1328
1359
|
MIN_CLAIM_WINDOW_SECONDS,
|
|
1329
1360
|
verifyReceiveInvoice,
|
|
1330
1361
|
assertReceivable,
|
|
1331
|
-
|
|
1362
|
+
lightningReceiveContract,
|
|
1332
1363
|
deriveLightningReceive,
|
|
1333
1364
|
requestLightningReceive,
|
|
1334
1365
|
deriveOnchainReceive,
|