@arkade-os/swap 0.0.5 → 0.0.7
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 +79 -10
- package/dist/chunk-WGRU2DBF.js +38 -0
- package/dist/{chunk-Q4FAYBXS.js → chunk-ZDTRQZE2.js} +49 -15
- package/dist/index.cjs +103 -14
- package/dist/index.d.cts +46 -231
- package/dist/index.d.ts +46 -231
- package/dist/index.js +62 -35
- package/dist/nostr.cjs +15 -9
- package/dist/nostr.d.cts +4 -3
- package/dist/nostr.d.ts +4 -3
- package/dist/nostr.js +4 -11
- package/dist/repositories/realm/index.cjs +138 -0
- package/dist/repositories/realm/index.d.cts +103 -0
- package/dist/repositories/realm/index.d.ts +103 -0
- package/dist/repositories/realm/index.js +108 -0
- package/dist/repositories/sqlite/index.cjs +163 -0
- package/dist/repositories/sqlite/index.d.cts +63 -0
- package/dist/repositories/sqlite/index.d.ts +63 -0
- package/dist/repositories/sqlite/index.js +138 -0
- package/dist/repository-BwnZ8N62.d.cts +236 -0
- package/dist/repository-BwnZ8N62.d.ts +236 -0
- package/dist/{rfq-BH2yvo3O.d.cts → rfq-DfT9dAss.d.cts} +24 -11
- package/dist/{rfq-BH2yvo3O.d.ts → rfq-DfT9dAss.d.ts} +24 -11
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Client-side [Arkade Intents](https://arkade.money) asset swaps: discover markets, quote and
|
|
4
4
|
validate, create offers, track them, cancel them, and rebuild the whole record set from chain after
|
|
5
|
-
a wallet restore. Framework-free TypeScript over `@arkade-os/sdk`: the core API and
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
a wallet restore. Framework-free TypeScript over `@arkade-os/sdk`: the core API uses no DOM and no
|
|
6
|
+
Node-specific APIs, so it runs in Node, the browser, and React Native alike. Four storage backends
|
|
7
|
+
ship — in-memory (anywhere, nothing outlives the process), IndexedDB (browser), SQLite and Realm
|
|
8
|
+
(React Native, on subpath entry points) — see "Storage backends" below.
|
|
9
9
|
|
|
10
10
|
## Roles
|
|
11
11
|
|
|
@@ -100,11 +100,78 @@ arkade:BTC|asset` (quote, then take by funding an offer from layer 1).
|
|
|
100
100
|
|
|
101
101
|
Everything the package persists — swap records, the restore-scan cursor, and the markets cache —
|
|
102
102
|
goes through a single `AssetSwapRepository`, following the Arkade repository convention
|
|
103
|
-
(versioned interface, `AsyncDisposable`, one backend per platform).
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
(versioned interface, `AsyncDisposable`, one backend per platform). Construct one and pass it
|
|
104
|
+
wherever the package asks for a repository; `discoverMarkets` also accepts none, for a one-shot
|
|
105
|
+
uncached discovery.
|
|
106
|
+
|
|
107
|
+
## Storage backends
|
|
108
|
+
|
|
109
|
+
| Backend | Import from | For |
|
|
110
|
+
| ------------------------------ | ------------------------------------- | ----------------------------------------------- |
|
|
111
|
+
| `InMemoryAssetSwapRepository` | `@arkade-os/swap` | tests, one-shot scripts — nothing survives exit |
|
|
112
|
+
| `IndexedDbAssetSwapRepository` | `@arkade-os/swap` | the browser (or a polyfilled IndexedDB) |
|
|
113
|
+
| `SQLiteAssetSwapRepository` | `@arkade-os/swap/repositories/sqlite` | React Native, over your SQLite driver |
|
|
114
|
+
| `RealmAssetSwapRepository` | `@arkade-os/swap/repositories/realm` | React Native, over your Realm instance |
|
|
115
|
+
|
|
116
|
+
Neither subpath adds a dependency: they take the SDK's structural `SQLExecutor` / `RealmLike`
|
|
117
|
+
handles, so you pass the database you already opened.
|
|
118
|
+
|
|
119
|
+
**Records are stored whole.** The SQLite and Realm backends serialize each record to **JSON** in a
|
|
120
|
+
`data` column, with only `status` / `createdAt` mapped out for querying — so a field they do not
|
|
121
|
+
know about survives, which is what the `quote`-shaped extension in `MIGRATION.md` relies on. JSON is
|
|
122
|
+
the boundary, though, and it is narrower than IndexedDB's structured clone: a `Date` in a
|
|
123
|
+
consumer-added field comes back an ISO **string**, a `Set` or `Map` comes back empty, and a `bigint`
|
|
124
|
+
makes `saveSwap` **throw**. `AssetSwap` itself is JSON-safe by design (amounts are strings); keep
|
|
125
|
+
your own added fields that way too.
|
|
126
|
+
|
|
127
|
+
### SQLite
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { SQLiteAssetSwapRepository } from "@arkade-os/swap/repositories/sqlite";
|
|
131
|
+
import { SQLiteWalletRepository, type SQLExecutor } from "@arkade-os/sdk/repositories/sqlite";
|
|
132
|
+
|
|
133
|
+
const db = await SQLite.openDatabaseAsync("wallet.db"); // expo-sqlite
|
|
134
|
+
// Build the executor ONCE and hand this same instance to every repository on
|
|
135
|
+
// the database: the SDK serializes transactions in a chain keyed by this
|
|
136
|
+
// object, so a per-repository literal splits the chain and two BEGIN
|
|
137
|
+
// IMMEDIATEs can interleave.
|
|
138
|
+
const executor: SQLExecutor = {
|
|
139
|
+
run: (sql, params) => db.runAsync(sql, params ?? []),
|
|
140
|
+
get: (sql, params) => db.getFirstAsync(sql, params ?? []),
|
|
141
|
+
all: (sql, params) => db.getAllAsync(sql, params ?? []),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const swaps = new SQLiteAssetSwapRepository(executor);
|
|
145
|
+
const wallet = new SQLiteWalletRepository(executor); // same instance
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Sharing the executor is **necessary** for that serialization, not sufficient for atomicity across
|
|
149
|
+
all wallet storage: it disciplines the repositories that enter the chain — this one,
|
|
150
|
+
`SQLiteIntentRepository`, `SQLiteVirtualTxRepository`, and the wallet repository's migration path —
|
|
151
|
+
and nothing else. `SQLiteWalletRepository` and `SQLiteContractRepository` still write raw, so their
|
|
152
|
+
writes can land inside whatever transaction happens to be open.
|
|
153
|
+
|
|
154
|
+
Three tables land in your database, prefixed `arkade_`: `arkade_asset_swaps`,
|
|
155
|
+
`arkade_asset_swap_scanned_txids`, `arkade_asset_swap_markets`. Pass `{ prefix: "myapp_" }` if your
|
|
156
|
+
app already owns those names.
|
|
157
|
+
|
|
158
|
+
### Realm
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import Realm from "realm";
|
|
162
|
+
import { AssetSwapRealmSchemas, RealmAssetSwapRepository } from "@arkade-os/swap/repositories/realm";
|
|
163
|
+
import { ArkRealmSchemas } from "@arkade-os/sdk/repositories/realm";
|
|
164
|
+
|
|
165
|
+
const realm = await Realm.open({
|
|
166
|
+
schema: [...ArkRealmSchemas, ...AssetSwapRealmSchemas, ...yourOwnSchemas],
|
|
167
|
+
schemaVersion: YOUR_VERSION, // these schemas are new: bump yours when adding them
|
|
168
|
+
});
|
|
169
|
+
const swaps = new RealmAssetSwapRepository(realm);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Three classes land in your Realm namespace: `ArkadeAssetSwap`, `ArkadeAssetSwapScannedTxid`,
|
|
173
|
+
`ArkadeAssetSwapMarketsCache`. Unlike SQLite there is no prefix option — a Realm schema name is
|
|
174
|
+
baked into the schema objects you register — so reconcile against your own models by name.
|
|
108
175
|
|
|
109
176
|
## Creating an offer
|
|
110
177
|
|
|
@@ -223,7 +290,9 @@ message anywhere: **acceptance is funding**.
|
|
|
223
290
|
offline. The solver observes the funding on-chain, pays the invoice, and claims with the
|
|
224
291
|
preimage — which lands publicly in the claim witness as the receipt. A failed swap refunds by
|
|
225
292
|
covenant to the trader's address, pushable by anyone, no trader keys or state.
|
|
226
|
-
- **Arkade ↔ arkade** (BTC↔asset, asset↔asset):
|
|
293
|
+
- **Arkade ↔ arkade** (BTC↔asset, asset↔asset): an arkade asset leg names the asset id itself —
|
|
294
|
+
`arkade:<68-hex>`, built with `arkadeAssetLeg` (the deprecated coarse `ARKADE_ASSET` is served by
|
|
295
|
+
no solver). The trader accepts a quote by creating and funding
|
|
227
296
|
an **offer** (layer 1) bound to the quoted terms before `valid_until`. The offer covenant only
|
|
228
297
|
releases the deposit to a fill that delivers the quoted amount, so the solver fills or nothing
|
|
229
298
|
moves; an unfilled offer is cancelled cooperatively. The quote wire shape ships here; the
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/repository.ts
|
|
2
|
+
var marketsCacheKey = (network, registry) => `arkade-intents-markets-${network}-${registry}`;
|
|
3
|
+
var InMemoryAssetSwapRepository = class {
|
|
4
|
+
version = 2;
|
|
5
|
+
swaps = /* @__PURE__ */ new Map();
|
|
6
|
+
scanned = /* @__PURE__ */ new Set();
|
|
7
|
+
markets = /* @__PURE__ */ new Map();
|
|
8
|
+
async saveSwap(swap) {
|
|
9
|
+
this.swaps.set(swap.id, swap);
|
|
10
|
+
}
|
|
11
|
+
async getAllSwaps() {
|
|
12
|
+
return [...this.swaps.values()];
|
|
13
|
+
}
|
|
14
|
+
async getScannedTxids() {
|
|
15
|
+
return new Set(this.scanned);
|
|
16
|
+
}
|
|
17
|
+
async markTxidsScanned(txids) {
|
|
18
|
+
for (const txid of txids) this.scanned.add(txid);
|
|
19
|
+
}
|
|
20
|
+
async getCachedMarkets(network, registry) {
|
|
21
|
+
return this.markets.get(marketsCacheKey(network, registry));
|
|
22
|
+
}
|
|
23
|
+
async saveCachedMarkets(network, registry, entry) {
|
|
24
|
+
this.markets.set(marketsCacheKey(network, registry), entry);
|
|
25
|
+
}
|
|
26
|
+
async clear() {
|
|
27
|
+
this.swaps.clear();
|
|
28
|
+
this.scanned.clear();
|
|
29
|
+
this.markets.clear();
|
|
30
|
+
}
|
|
31
|
+
async [Symbol.asyncDispose]() {
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
marketsCacheKey,
|
|
37
|
+
InMemoryAssetSwapRepository
|
|
38
|
+
};
|
|
@@ -311,9 +311,10 @@ var solverHex = (value, field) => {
|
|
|
311
311
|
}
|
|
312
312
|
};
|
|
313
313
|
var ARKADE_BTC = "arkade:BTC";
|
|
314
|
-
var ARKADE_ASSET = "arkade:ASSET";
|
|
315
314
|
var LIGHTNING_BTC = "lightning:BTC";
|
|
316
315
|
var ONCHAIN_BTC = "onchain:BTC";
|
|
316
|
+
var arkadeAssetLeg = (id) => `arkade:${id.toString()}`;
|
|
317
|
+
var ARKADE_ASSET = "arkade:ASSET";
|
|
317
318
|
var rfqPair = (from, to) => `${from}->${to}`;
|
|
318
319
|
var LIGHTNING_SEND_PAIR = rfqPair(ARKADE_BTC, LIGHTNING_BTC);
|
|
319
320
|
var LIGHTNING_RECEIVE_PAIR = rfqPair(LIGHTNING_BTC, ARKADE_BTC);
|
|
@@ -354,23 +355,34 @@ var lightningSendRequest = (input) => ({
|
|
|
354
355
|
}
|
|
355
356
|
});
|
|
356
357
|
var arkadeSwapRequest = (input) => {
|
|
357
|
-
if (
|
|
358
|
-
throw new Error(
|
|
358
|
+
if (!input.wantAsset && !input.offerAsset) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
"set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC) \u2014 with neither set both legs are BTC, which is not a swap"
|
|
361
|
+
);
|
|
359
362
|
}
|
|
363
|
+
if (input.wantAsset && input.offerAsset) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
"set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC) \u2014 asset->asset is nameable on the wire but no solver quotes it yet"
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
const pair = rfqPair(
|
|
369
|
+
input.offerAsset ? arkadeAssetLeg(input.offerAsset) : ARKADE_BTC,
|
|
370
|
+
input.wantAsset ? arkadeAssetLeg(input.wantAsset) : ARKADE_BTC
|
|
371
|
+
);
|
|
372
|
+
assertPairLength(pair);
|
|
360
373
|
return {
|
|
361
374
|
v: 1,
|
|
362
375
|
type: "rfq_request",
|
|
363
376
|
rfq_id: input.rfqId,
|
|
364
|
-
pair
|
|
365
|
-
input.offerAsset ? ARKADE_ASSET : ARKADE_BTC,
|
|
366
|
-
input.wantAsset ? ARKADE_ASSET : ARKADE_BTC
|
|
367
|
-
),
|
|
377
|
+
pair,
|
|
368
378
|
amount_side: input.amountSide,
|
|
369
379
|
amount: input.amount,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
380
|
+
// The pair is the only place the asset ids appear. Repeating them here
|
|
381
|
+
// would be a key the solver's `.strict()` profile schema does not
|
|
382
|
+
// declare, and an undeclared key is `unsupported_payload` — a refusal,
|
|
383
|
+
// not an ignored extra. Empty, not absent: `profile` is required on
|
|
384
|
+
// every other request shape this wire carries.
|
|
385
|
+
profile: {}
|
|
374
386
|
};
|
|
375
387
|
};
|
|
376
388
|
var MIN_HEADROOM_SECONDS = 90 * 60;
|
|
@@ -384,6 +396,14 @@ var assertFinite = (value, reason, label) => {
|
|
|
384
396
|
throw gateError(reason, `${label} is not a finite number (${String(value)})`);
|
|
385
397
|
}
|
|
386
398
|
};
|
|
399
|
+
var MAX_PAIR_LENGTH = 158;
|
|
400
|
+
var assertPairLength = (pair) => {
|
|
401
|
+
if (pair.length > MAX_PAIR_LENGTH) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`pair is ${pair.length} characters, over the wire's ${MAX_PAIR_LENGTH}-character limit`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
387
407
|
var verifyLockupAddress = (quote, derivedAddress) => {
|
|
388
408
|
const quoted = quote.profile?.lockup_address;
|
|
389
409
|
if (derivedAddress !== quoted) throw new AddressMismatch(derivedAddress, quoted);
|
|
@@ -423,14 +443,20 @@ var assertFundable = (input) => {
|
|
|
423
443
|
}
|
|
424
444
|
}
|
|
425
445
|
};
|
|
426
|
-
var expectQuote = (payload, rfqId) => {
|
|
446
|
+
var expectQuote = (payload, rfqId, requestedPair) => {
|
|
427
447
|
const p = payload;
|
|
428
448
|
if (p?.type === "rfq_refusal") throw new SwapRefusal(p.reason ?? "unknown", p.rfq_id ?? rfqId);
|
|
429
449
|
if (p?.type !== "rfq_quote" || p.rfq_id !== rfqId) {
|
|
430
450
|
throw new Error(`unexpected reply: ${p?.type ?? "no payload"}`);
|
|
431
451
|
}
|
|
452
|
+
if (requestedPair !== void 0 && p.pair !== requestedPair) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`solver quoted ${JSON.stringify(p.pair)}, not the requested ${requestedPair}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
432
457
|
return payload;
|
|
433
458
|
};
|
|
459
|
+
var pairOf = (payload) => typeof payload.pair === "string" ? payload.pair : void 0;
|
|
434
460
|
var httpTransport = (baseUrl, options = {}) => {
|
|
435
461
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
436
462
|
const readJson = async (response, what) => {
|
|
@@ -450,7 +476,11 @@ var httpTransport = (baseUrl, options = {}) => {
|
|
|
450
476
|
headers: { "content-type": "application/json" },
|
|
451
477
|
body: JSON.stringify(payload)
|
|
452
478
|
});
|
|
453
|
-
return expectQuote(
|
|
479
|
+
return expectQuote(
|
|
480
|
+
await readJson(response, "quote request"),
|
|
481
|
+
String(payload.rfq_id),
|
|
482
|
+
pairOf(payload)
|
|
483
|
+
);
|
|
454
484
|
},
|
|
455
485
|
async status(rfqId) {
|
|
456
486
|
const response = await fetchImpl(`${baseUrl}/v1/rfq/${rfqId}`, { method: "GET" });
|
|
@@ -527,7 +557,8 @@ var relayTransport = (relayUrl, options) => {
|
|
|
527
557
|
async requestQuote(payload) {
|
|
528
558
|
return expectQuote(
|
|
529
559
|
await roundTrip(payload, String(payload.rfq_id)),
|
|
530
|
-
String(payload.rfq_id)
|
|
560
|
+
String(payload.rfq_id),
|
|
561
|
+
pairOf(payload)
|
|
531
562
|
);
|
|
532
563
|
},
|
|
533
564
|
async status(rfqId) {
|
|
@@ -1151,9 +1182,10 @@ export {
|
|
|
1151
1182
|
LockupRegistrationFailed,
|
|
1152
1183
|
registerLockupContract,
|
|
1153
1184
|
ARKADE_BTC,
|
|
1154
|
-
ARKADE_ASSET,
|
|
1155
1185
|
LIGHTNING_BTC,
|
|
1156
1186
|
ONCHAIN_BTC,
|
|
1187
|
+
arkadeAssetLeg,
|
|
1188
|
+
ARKADE_ASSET,
|
|
1157
1189
|
rfqPair,
|
|
1158
1190
|
LIGHTNING_SEND_PAIR,
|
|
1159
1191
|
LIGHTNING_RECEIVE_PAIR,
|
|
@@ -1168,6 +1200,8 @@ export {
|
|
|
1168
1200
|
MIN_HEADROOM_SECONDS,
|
|
1169
1201
|
verifyLockupAddress,
|
|
1170
1202
|
assertFundable,
|
|
1203
|
+
expectQuote,
|
|
1204
|
+
pairOf,
|
|
1171
1205
|
httpTransport,
|
|
1172
1206
|
relayTransport,
|
|
1173
1207
|
SOLO_REFUND_HEADROOM_SECONDS,
|
package/dist/index.cjs
CHANGED
|
@@ -67,6 +67,7 @@ __export(index_exports, {
|
|
|
67
67
|
SWAP_LOCKUP_CONTRACT_TYPE: () => SWAP_LOCKUP_CONTRACT_TYPE,
|
|
68
68
|
SwapRefusal: () => SwapRefusal,
|
|
69
69
|
addAssetSwap: () => addAssetSwap,
|
|
70
|
+
arkadeAssetLeg: () => arkadeAssetLeg,
|
|
70
71
|
arkadeSwapRequest: () => arkadeSwapRequest,
|
|
71
72
|
assertFundable: () => assertFundable,
|
|
72
73
|
assertReceivable: () => assertReceivable,
|
|
@@ -128,6 +129,7 @@ __export(index_exports, {
|
|
|
128
129
|
senderIdentityForSwapRecord: () => senderIdentityForSwapRecord,
|
|
129
130
|
spendTxidsOf: () => spendTxidsOf,
|
|
130
131
|
spendUpdate: () => spendUpdate,
|
|
132
|
+
swapActivityResolver: () => swapActivityResolver,
|
|
131
133
|
swapPrograms: () => swapPrograms,
|
|
132
134
|
swapSecretsToRecord: () => swapSecretsToRecord,
|
|
133
135
|
unilateralClaimDelay: () => unilateralClaimDelay,
|
|
@@ -1445,9 +1447,10 @@ var solverHex = (value, field) => {
|
|
|
1445
1447
|
}
|
|
1446
1448
|
};
|
|
1447
1449
|
var ARKADE_BTC = "arkade:BTC";
|
|
1448
|
-
var ARKADE_ASSET = "arkade:ASSET";
|
|
1449
1450
|
var LIGHTNING_BTC = "lightning:BTC";
|
|
1450
1451
|
var ONCHAIN_BTC = "onchain:BTC";
|
|
1452
|
+
var arkadeAssetLeg = (id) => `arkade:${id.toString()}`;
|
|
1453
|
+
var ARKADE_ASSET = "arkade:ASSET";
|
|
1451
1454
|
var rfqPair = (from, to) => `${from}->${to}`;
|
|
1452
1455
|
var LIGHTNING_SEND_PAIR = rfqPair(ARKADE_BTC, LIGHTNING_BTC);
|
|
1453
1456
|
var LIGHTNING_RECEIVE_PAIR = rfqPair(LIGHTNING_BTC, ARKADE_BTC);
|
|
@@ -1488,23 +1491,34 @@ var lightningSendRequest = (input) => ({
|
|
|
1488
1491
|
}
|
|
1489
1492
|
});
|
|
1490
1493
|
var arkadeSwapRequest = (input) => {
|
|
1491
|
-
if (
|
|
1492
|
-
throw new Error(
|
|
1494
|
+
if (!input.wantAsset && !input.offerAsset) {
|
|
1495
|
+
throw new Error(
|
|
1496
|
+
"set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC) \u2014 with neither set both legs are BTC, which is not a swap"
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
if (input.wantAsset && input.offerAsset) {
|
|
1500
|
+
throw new Error(
|
|
1501
|
+
"set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC) \u2014 asset->asset is nameable on the wire but no solver quotes it yet"
|
|
1502
|
+
);
|
|
1493
1503
|
}
|
|
1504
|
+
const pair = rfqPair(
|
|
1505
|
+
input.offerAsset ? arkadeAssetLeg(input.offerAsset) : ARKADE_BTC,
|
|
1506
|
+
input.wantAsset ? arkadeAssetLeg(input.wantAsset) : ARKADE_BTC
|
|
1507
|
+
);
|
|
1508
|
+
assertPairLength(pair);
|
|
1494
1509
|
return {
|
|
1495
1510
|
v: 1,
|
|
1496
1511
|
type: "rfq_request",
|
|
1497
1512
|
rfq_id: input.rfqId,
|
|
1498
|
-
pair
|
|
1499
|
-
input.offerAsset ? ARKADE_ASSET : ARKADE_BTC,
|
|
1500
|
-
input.wantAsset ? ARKADE_ASSET : ARKADE_BTC
|
|
1501
|
-
),
|
|
1513
|
+
pair,
|
|
1502
1514
|
amount_side: input.amountSide,
|
|
1503
1515
|
amount: input.amount,
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1516
|
+
// The pair is the only place the asset ids appear. Repeating them here
|
|
1517
|
+
// would be a key the solver's `.strict()` profile schema does not
|
|
1518
|
+
// declare, and an undeclared key is `unsupported_payload` — a refusal,
|
|
1519
|
+
// not an ignored extra. Empty, not absent: `profile` is required on
|
|
1520
|
+
// every other request shape this wire carries.
|
|
1521
|
+
profile: {}
|
|
1508
1522
|
};
|
|
1509
1523
|
};
|
|
1510
1524
|
var MIN_HEADROOM_SECONDS = 90 * 60;
|
|
@@ -1518,6 +1532,14 @@ var assertFinite = (value, reason, label) => {
|
|
|
1518
1532
|
throw gateError(reason, `${label} is not a finite number (${String(value)})`);
|
|
1519
1533
|
}
|
|
1520
1534
|
};
|
|
1535
|
+
var MAX_PAIR_LENGTH = 158;
|
|
1536
|
+
var assertPairLength = (pair) => {
|
|
1537
|
+
if (pair.length > MAX_PAIR_LENGTH) {
|
|
1538
|
+
throw new Error(
|
|
1539
|
+
`pair is ${pair.length} characters, over the wire's ${MAX_PAIR_LENGTH}-character limit`
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1521
1543
|
var verifyLockupAddress = (quote, derivedAddress) => {
|
|
1522
1544
|
const quoted = quote.profile?.lockup_address;
|
|
1523
1545
|
if (derivedAddress !== quoted) throw new AddressMismatch(derivedAddress, quoted);
|
|
@@ -1557,14 +1579,20 @@ var assertFundable = (input) => {
|
|
|
1557
1579
|
}
|
|
1558
1580
|
}
|
|
1559
1581
|
};
|
|
1560
|
-
var expectQuote = (payload, rfqId) => {
|
|
1582
|
+
var expectQuote = (payload, rfqId, requestedPair) => {
|
|
1561
1583
|
const p = payload;
|
|
1562
1584
|
if (p?.type === "rfq_refusal") throw new SwapRefusal(p.reason ?? "unknown", p.rfq_id ?? rfqId);
|
|
1563
1585
|
if (p?.type !== "rfq_quote" || p.rfq_id !== rfqId) {
|
|
1564
1586
|
throw new Error(`unexpected reply: ${p?.type ?? "no payload"}`);
|
|
1565
1587
|
}
|
|
1588
|
+
if (requestedPair !== void 0 && p.pair !== requestedPair) {
|
|
1589
|
+
throw new Error(
|
|
1590
|
+
`solver quoted ${JSON.stringify(p.pair)}, not the requested ${requestedPair}`
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1566
1593
|
return payload;
|
|
1567
1594
|
};
|
|
1595
|
+
var pairOf = (payload) => typeof payload.pair === "string" ? payload.pair : void 0;
|
|
1568
1596
|
var httpTransport = (baseUrl, options = {}) => {
|
|
1569
1597
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1570
1598
|
const readJson = async (response, what) => {
|
|
@@ -1584,7 +1612,11 @@ var httpTransport = (baseUrl, options = {}) => {
|
|
|
1584
1612
|
headers: { "content-type": "application/json" },
|
|
1585
1613
|
body: JSON.stringify(payload)
|
|
1586
1614
|
});
|
|
1587
|
-
return expectQuote(
|
|
1615
|
+
return expectQuote(
|
|
1616
|
+
await readJson(response, "quote request"),
|
|
1617
|
+
String(payload.rfq_id),
|
|
1618
|
+
pairOf(payload)
|
|
1619
|
+
);
|
|
1588
1620
|
},
|
|
1589
1621
|
async status(rfqId) {
|
|
1590
1622
|
const response = await fetchImpl(`${baseUrl}/v1/rfq/${rfqId}`, { method: "GET" });
|
|
@@ -1661,7 +1693,8 @@ var relayTransport = (relayUrl, options) => {
|
|
|
1661
1693
|
async requestQuote(payload) {
|
|
1662
1694
|
return expectQuote(
|
|
1663
1695
|
await roundTrip(payload, String(payload.rfq_id)),
|
|
1664
|
-
String(payload.rfq_id)
|
|
1696
|
+
String(payload.rfq_id),
|
|
1697
|
+
pairOf(payload)
|
|
1665
1698
|
);
|
|
1666
1699
|
},
|
|
1667
1700
|
async status(rfqId) {
|
|
@@ -3376,6 +3409,60 @@ var outcomeOf = (swap) => {
|
|
|
3376
3409
|
};
|
|
3377
3410
|
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
3378
3411
|
var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
|
|
3412
|
+
|
|
3413
|
+
// src/activity.ts
|
|
3414
|
+
var LABELS = {
|
|
3415
|
+
lightning_send: "Lightning send",
|
|
3416
|
+
lightning_receive: "Lightning receive",
|
|
3417
|
+
onchain_send: "Onchain send"
|
|
3418
|
+
};
|
|
3419
|
+
var OUTCOME = {
|
|
3420
|
+
pending: "pending",
|
|
3421
|
+
// `claimable` and `claimed` are both in-progress states with no
|
|
3422
|
+
// user-visible phase distinct from "pending". `needs_counterparty` is
|
|
3423
|
+
// different in kind — the swap is BLOCKED, not merely in flight, since no
|
|
3424
|
+
// unilateral trader move exists (see `RfqSwapState`). Collapsing it into
|
|
3425
|
+
// `pending` here is a deliberate choice the opaque-token design permits —
|
|
3426
|
+
// apps map tokens themselves — but a future reader weighing a `"blocked"`
|
|
3427
|
+
// or `"stuck"` token should know this was already considered.
|
|
3428
|
+
claimable: "pending",
|
|
3429
|
+
claimed: "pending",
|
|
3430
|
+
needs_counterparty: "pending",
|
|
3431
|
+
settled: "settled",
|
|
3432
|
+
refunded: "refunded",
|
|
3433
|
+
failed: "failed"
|
|
3434
|
+
};
|
|
3435
|
+
function swapActivityResolver(deps) {
|
|
3436
|
+
let byTxid = /* @__PURE__ */ new Map();
|
|
3437
|
+
return {
|
|
3438
|
+
id: "arkade:swap",
|
|
3439
|
+
async prepare() {
|
|
3440
|
+
const swaps = await deps.listSwaps();
|
|
3441
|
+
const index = /* @__PURE__ */ new Map();
|
|
3442
|
+
for (const swap of swaps) {
|
|
3443
|
+
for (const txid of swap.txids) {
|
|
3444
|
+
if (txid) index.set(txid, swap);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
byTxid = index;
|
|
3448
|
+
},
|
|
3449
|
+
resolve(tx) {
|
|
3450
|
+
const key = tx.key.arkTxid || tx.key.commitmentTxid || tx.key.boardingTxid;
|
|
3451
|
+
const swap = key ? byTxid.get(key) : void 0;
|
|
3452
|
+
if (!swap) return void 0;
|
|
3453
|
+
const lostReceive = swap.kind === "lightning_receive" && swap.state === "refunded";
|
|
3454
|
+
return [
|
|
3455
|
+
{
|
|
3456
|
+
groupId: `swap:${swap.rfqId}`,
|
|
3457
|
+
label: LABELS[swap.kind],
|
|
3458
|
+
kind: "swap",
|
|
3459
|
+
outcome: lostReceive ? "lost" : OUTCOME[swap.state],
|
|
3460
|
+
metadata: { rfqId: swap.rfqId, swapKind: swap.kind }
|
|
3461
|
+
}
|
|
3462
|
+
];
|
|
3463
|
+
}
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3379
3466
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3380
3467
|
0 && (module.exports = {
|
|
3381
3468
|
ARKADE_ASSET,
|
|
@@ -3415,6 +3502,7 @@ var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
|
|
|
3415
3502
|
SWAP_LOCKUP_CONTRACT_TYPE,
|
|
3416
3503
|
SwapRefusal,
|
|
3417
3504
|
addAssetSwap,
|
|
3505
|
+
arkadeAssetLeg,
|
|
3418
3506
|
arkadeSwapRequest,
|
|
3419
3507
|
assertFundable,
|
|
3420
3508
|
assertReceivable,
|
|
@@ -3476,6 +3564,7 @@ var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
|
|
|
3476
3564
|
senderIdentityForSwapRecord,
|
|
3477
3565
|
spendTxidsOf,
|
|
3478
3566
|
spendUpdate,
|
|
3567
|
+
swapActivityResolver,
|
|
3479
3568
|
swapPrograms,
|
|
3480
3569
|
swapSecretsToRecord,
|
|
3481
3570
|
unilateralClaimDelay,
|