@arkade-os/sdk 0.3.4 → 0.3.5
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 +7 -1
- package/dist/cjs/providers/ark.js +8 -1
- package/dist/cjs/utils/transactionHistory.js +29 -13
- package/dist/cjs/wallet/ramps.js +7 -1
- package/dist/cjs/wallet/wallet.js +75 -4
- package/dist/esm/providers/ark.js +8 -1
- package/dist/esm/utils/transactionHistory.js +29 -13
- package/dist/esm/wallet/ramps.js +7 -1
- package/dist/esm/wallet/wallet.js +75 -4
- package/dist/types/index.d.ts +2 -2
- package/dist/types/providers/ark.d.ts +2 -2
- package/dist/types/wallet/ramps.d.ts +3 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -171,7 +171,13 @@ Collaborative exit or "offboarding" allows you to withdraw your virtual funds to
|
|
|
171
171
|
```typescript
|
|
172
172
|
import { Ramps } from '@arkade-os/sdk'
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
// Get fee information from the server
|
|
175
|
+
const info = await wallet.arkProvider.getInfo();
|
|
176
|
+
|
|
177
|
+
const exitTxid = await new Ramps(wallet).offboard(
|
|
178
|
+
onchainAddress,
|
|
179
|
+
info.fees
|
|
180
|
+
);
|
|
175
181
|
```
|
|
176
182
|
|
|
177
183
|
### Unilateral Exit
|
|
@@ -46,7 +46,14 @@ class RestArkProvider {
|
|
|
46
46
|
})) ?? [],
|
|
47
47
|
digest: fromServer.digest ?? "",
|
|
48
48
|
dust: BigInt(fromServer.dust ?? 0),
|
|
49
|
-
fees:
|
|
49
|
+
fees: {
|
|
50
|
+
intentFee: {
|
|
51
|
+
...fromServer.fees?.intentFee,
|
|
52
|
+
onchainInput: BigInt(fromServer.fees?.intentFee?.onchainInput ?? 0),
|
|
53
|
+
onchainOutput: BigInt(fromServer.fees?.intentFee?.onchainOutput ?? 0),
|
|
54
|
+
},
|
|
55
|
+
txFeeRate: fromServer?.fees?.txFeeRate ?? "",
|
|
56
|
+
},
|
|
50
57
|
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
51
58
|
forfeitPubkey: fromServer.forfeitPubkey ?? "",
|
|
52
59
|
network: fromServer.network ?? "",
|
|
@@ -14,6 +14,10 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
14
14
|
// All vtxos are received unless:
|
|
15
15
|
// - they resulted from a settlement (either boarding or refresh)
|
|
16
16
|
// - they are the change of a spend tx
|
|
17
|
+
// - they were spent in a payment (have arkTxId set)
|
|
18
|
+
// - they resulted from a payment (their txid matches an arkTxId of a spent vtxo)
|
|
19
|
+
// First, collect all arkTxIds from spent vtxos to identify payment transactions
|
|
20
|
+
const paymentArkTxIds = new Set(spent.filter((v) => v.arkTxId).map((v) => v.arkTxId));
|
|
17
21
|
let vtxosLeftToCheck = [...spent];
|
|
18
22
|
for (const vtxo of [...spendable, ...spent]) {
|
|
19
23
|
if (vtxo.virtualStatus.state !== "preconfirmed" &&
|
|
@@ -21,6 +25,16 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
21
25
|
vtxo.virtualStatus.commitmentTxIds.some((txid) => boardingBatchTxids.has(txid))) {
|
|
22
26
|
continue;
|
|
23
27
|
}
|
|
28
|
+
// Skip vtxos that were spent in a payment transaction
|
|
29
|
+
// These will be handled in the sent transaction section below
|
|
30
|
+
if (vtxo.arkTxId) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// Skip vtxos that resulted from a payment transaction
|
|
34
|
+
// (their txid matches an arkTxId from a spent vtxo)
|
|
35
|
+
if (paymentArkTxIds.has(vtxo.txid)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
24
38
|
const settleVtxos = findVtxosSpentInSettlement(vtxosLeftToCheck, vtxo);
|
|
25
39
|
vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, settleVtxos);
|
|
26
40
|
const settleAmount = reduceVtxosAmount(settleVtxos);
|
|
@@ -56,21 +70,17 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
56
70
|
// vtxos by settled by or ark txid
|
|
57
71
|
const vtxosByTxid = new Map();
|
|
58
72
|
for (const v of spent) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const currentVtxos = vtxosByTxid.get(v.settledBy);
|
|
64
|
-
vtxosByTxid.set(v.settledBy, [...currentVtxos, v]);
|
|
65
|
-
}
|
|
66
|
-
if (!v.arkTxId) {
|
|
73
|
+
// Prefer arkTxId over settledBy to avoid duplicates
|
|
74
|
+
// A vtxo should only be grouped once
|
|
75
|
+
const groupKey = v.arkTxId || v.settledBy;
|
|
76
|
+
if (!groupKey) {
|
|
67
77
|
continue;
|
|
68
78
|
}
|
|
69
|
-
if (!vtxosByTxid.has(
|
|
70
|
-
vtxosByTxid.set(
|
|
79
|
+
if (!vtxosByTxid.has(groupKey)) {
|
|
80
|
+
vtxosByTxid.set(groupKey, []);
|
|
71
81
|
}
|
|
72
|
-
const currentVtxos = vtxosByTxid.get(
|
|
73
|
-
vtxosByTxid.set(
|
|
82
|
+
const currentVtxos = vtxosByTxid.get(groupKey);
|
|
83
|
+
vtxosByTxid.set(groupKey, [...currentVtxos, v]);
|
|
74
84
|
}
|
|
75
85
|
for (const [sb, vtxos] of vtxosByTxid) {
|
|
76
86
|
const resultedVtxos = findVtxosResultedFromTxid([...spendable, ...spent], sb);
|
|
@@ -85,7 +95,13 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
85
95
|
boardingTxid: "",
|
|
86
96
|
arkTxid: "",
|
|
87
97
|
};
|
|
88
|
-
|
|
98
|
+
// Use the grouping key (sb) as arkTxid if it looks like an arkTxId
|
|
99
|
+
// (i.e., if the spent vtxos had arkTxId set, use that instead of result vtxo's txid)
|
|
100
|
+
const isArkTxId = vtxos.some((v) => v.arkTxId === sb);
|
|
101
|
+
if (isArkTxId) {
|
|
102
|
+
txKey.arkTxid = sb;
|
|
103
|
+
}
|
|
104
|
+
else if (vtxo.virtualStatus.state === "preconfirmed") {
|
|
89
105
|
txKey.arkTxid = vtxo.txid;
|
|
90
106
|
}
|
|
91
107
|
txs.push({
|
package/dist/cjs/wallet/ramps.js
CHANGED
|
@@ -56,10 +56,11 @@ class Ramps {
|
|
|
56
56
|
* Offboard vtxos, or "collaborative exit" vtxos to onchain address.
|
|
57
57
|
*
|
|
58
58
|
* @param destinationAddress - The destination address to offboard to.
|
|
59
|
+
* @param feeInfo - The fee info to deduct from the offboard amount.
|
|
59
60
|
* @param amount - The amount to offboard. If not provided, the total amount of vtxos will be offboarded.
|
|
60
61
|
* @param eventCallback - The callback to receive settlement events. optional.
|
|
61
62
|
*/
|
|
62
|
-
async offboard(destinationAddress, amount, eventCallback) {
|
|
63
|
+
async offboard(destinationAddress, feeInfo, amount, eventCallback) {
|
|
63
64
|
const vtxos = await this.wallet.getVtxos({
|
|
64
65
|
withRecoverable: true,
|
|
65
66
|
withUnrolled: false,
|
|
@@ -73,6 +74,11 @@ class Ramps {
|
|
|
73
74
|
change = totalAmount - amount;
|
|
74
75
|
}
|
|
75
76
|
amount = amount ?? totalAmount;
|
|
77
|
+
const fees = feeInfo.intentFee.onchainOutput;
|
|
78
|
+
if (fees > amount) {
|
|
79
|
+
throw new Error(`can't deduct fees from offboard amount (${fees} > ${amount})`);
|
|
80
|
+
}
|
|
81
|
+
amount -= fees;
|
|
76
82
|
const outputs = [
|
|
77
83
|
{
|
|
78
84
|
address: destinationAddress,
|
|
@@ -431,14 +431,13 @@ class Wallet {
|
|
|
431
431
|
});
|
|
432
432
|
}
|
|
433
433
|
const tapTree = this.offchainTapscript.encode();
|
|
434
|
-
|
|
434
|
+
const offchainTx = (0, arkTransaction_1.buildOffchainTx)(selected.inputs.map((input) => ({
|
|
435
435
|
...input,
|
|
436
436
|
tapLeafScript: selectedLeaf,
|
|
437
437
|
tapTree,
|
|
438
438
|
})), outputs, this.serverUnrollScript);
|
|
439
439
|
const signedVirtualTx = await this.identity.sign(offchainTx.arkTx);
|
|
440
440
|
const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base_1.base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base_1.base64.encode(c.toPSBT())));
|
|
441
|
-
// TODO persist final virtual tx and checkpoints to repository
|
|
442
441
|
// sign the checkpoints
|
|
443
442
|
const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
|
|
444
443
|
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
|
|
@@ -446,7 +445,78 @@ class Wallet {
|
|
|
446
445
|
return base_1.base64.encode(signedCheckpoint.toPSBT());
|
|
447
446
|
}));
|
|
448
447
|
await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
|
|
449
|
-
|
|
448
|
+
try {
|
|
449
|
+
// mark VTXOs as spent and optionally add the change VTXO
|
|
450
|
+
const spentVtxos = [];
|
|
451
|
+
const commitmentTxIds = new Set();
|
|
452
|
+
let batchExpiry = Number.MAX_SAFE_INTEGER;
|
|
453
|
+
for (const [inputIndex, input] of selected.inputs.entries()) {
|
|
454
|
+
const vtxo = (0, utils_1.extendVirtualCoin)(this, input);
|
|
455
|
+
const checkpointB64 = signedCheckpointTxs[inputIndex];
|
|
456
|
+
const checkpoint = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(checkpointB64));
|
|
457
|
+
spentVtxos.push({
|
|
458
|
+
...vtxo,
|
|
459
|
+
virtualStatus: { ...vtxo.virtualStatus, state: "spent" },
|
|
460
|
+
spentBy: checkpoint.id,
|
|
461
|
+
arkTxId: arkTxid,
|
|
462
|
+
isSpent: true,
|
|
463
|
+
});
|
|
464
|
+
if (vtxo.virtualStatus.commitmentTxIds) {
|
|
465
|
+
for (const commitmentTxId of vtxo.virtualStatus
|
|
466
|
+
.commitmentTxIds) {
|
|
467
|
+
commitmentTxIds.add(commitmentTxId);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (vtxo.virtualStatus.batchExpiry) {
|
|
471
|
+
batchExpiry = Math.min(batchExpiry, vtxo.virtualStatus.batchExpiry);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const createdAt = Date.now();
|
|
475
|
+
const addr = this.arkAddress.encode();
|
|
476
|
+
if (selected.changeAmount > 0n &&
|
|
477
|
+
batchExpiry !== Number.MAX_SAFE_INTEGER) {
|
|
478
|
+
const changeVtxo = {
|
|
479
|
+
txid: arkTxid,
|
|
480
|
+
vout: outputs.length - 1,
|
|
481
|
+
createdAt: new Date(createdAt),
|
|
482
|
+
forfeitTapLeafScript: this.offchainTapscript.forfeit(),
|
|
483
|
+
intentTapLeafScript: this.offchainTapscript.exit(),
|
|
484
|
+
isUnrolled: false,
|
|
485
|
+
isSpent: false,
|
|
486
|
+
tapTree: this.offchainTapscript.encode(),
|
|
487
|
+
value: Number(selected.changeAmount),
|
|
488
|
+
virtualStatus: {
|
|
489
|
+
state: "preconfirmed",
|
|
490
|
+
commitmentTxIds: Array.from(commitmentTxIds),
|
|
491
|
+
batchExpiry,
|
|
492
|
+
},
|
|
493
|
+
status: {
|
|
494
|
+
confirmed: false,
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
await this.walletRepository.saveVtxos(addr, [changeVtxo]);
|
|
498
|
+
}
|
|
499
|
+
await this.walletRepository.saveVtxos(addr, spentVtxos);
|
|
500
|
+
await this.walletRepository.saveTransactions(addr, [
|
|
501
|
+
{
|
|
502
|
+
key: {
|
|
503
|
+
boardingTxid: "",
|
|
504
|
+
commitmentTxid: "",
|
|
505
|
+
arkTxid: arkTxid,
|
|
506
|
+
},
|
|
507
|
+
amount: params.amount,
|
|
508
|
+
type: _1.TxType.TxSent,
|
|
509
|
+
settled: false,
|
|
510
|
+
createdAt: Date.now(),
|
|
511
|
+
},
|
|
512
|
+
]);
|
|
513
|
+
}
|
|
514
|
+
catch (e) {
|
|
515
|
+
console.warn("error saving offchain tx to repository", e);
|
|
516
|
+
}
|
|
517
|
+
finally {
|
|
518
|
+
return arkTxid;
|
|
519
|
+
}
|
|
450
520
|
}
|
|
451
521
|
async settle(params, eventCallback) {
|
|
452
522
|
if (params?.inputs) {
|
|
@@ -719,7 +789,8 @@ class Wallet {
|
|
|
719
789
|
(async () => {
|
|
720
790
|
try {
|
|
721
791
|
for await (const update of subscription) {
|
|
722
|
-
if (update.newVtxos?.length > 0
|
|
792
|
+
if (update.newVtxos?.length > 0 ||
|
|
793
|
+
update.spentVtxos?.length > 0) {
|
|
723
794
|
eventCallback({
|
|
724
795
|
type: "vtxo",
|
|
725
796
|
newVtxos: update.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
@@ -42,7 +42,14 @@ export class RestArkProvider {
|
|
|
42
42
|
})) ?? [],
|
|
43
43
|
digest: fromServer.digest ?? "",
|
|
44
44
|
dust: BigInt(fromServer.dust ?? 0),
|
|
45
|
-
fees:
|
|
45
|
+
fees: {
|
|
46
|
+
intentFee: {
|
|
47
|
+
...fromServer.fees?.intentFee,
|
|
48
|
+
onchainInput: BigInt(fromServer.fees?.intentFee?.onchainInput ?? 0),
|
|
49
|
+
onchainOutput: BigInt(fromServer.fees?.intentFee?.onchainOutput ?? 0),
|
|
50
|
+
},
|
|
51
|
+
txFeeRate: fromServer?.fees?.txFeeRate ?? "",
|
|
52
|
+
},
|
|
46
53
|
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
47
54
|
forfeitPubkey: fromServer.forfeitPubkey ?? "",
|
|
48
55
|
network: fromServer.network ?? "",
|
|
@@ -11,6 +11,10 @@ export function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
11
11
|
// All vtxos are received unless:
|
|
12
12
|
// - they resulted from a settlement (either boarding or refresh)
|
|
13
13
|
// - they are the change of a spend tx
|
|
14
|
+
// - they were spent in a payment (have arkTxId set)
|
|
15
|
+
// - they resulted from a payment (their txid matches an arkTxId of a spent vtxo)
|
|
16
|
+
// First, collect all arkTxIds from spent vtxos to identify payment transactions
|
|
17
|
+
const paymentArkTxIds = new Set(spent.filter((v) => v.arkTxId).map((v) => v.arkTxId));
|
|
14
18
|
let vtxosLeftToCheck = [...spent];
|
|
15
19
|
for (const vtxo of [...spendable, ...spent]) {
|
|
16
20
|
if (vtxo.virtualStatus.state !== "preconfirmed" &&
|
|
@@ -18,6 +22,16 @@ export function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
18
22
|
vtxo.virtualStatus.commitmentTxIds.some((txid) => boardingBatchTxids.has(txid))) {
|
|
19
23
|
continue;
|
|
20
24
|
}
|
|
25
|
+
// Skip vtxos that were spent in a payment transaction
|
|
26
|
+
// These will be handled in the sent transaction section below
|
|
27
|
+
if (vtxo.arkTxId) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Skip vtxos that resulted from a payment transaction
|
|
31
|
+
// (their txid matches an arkTxId from a spent vtxo)
|
|
32
|
+
if (paymentArkTxIds.has(vtxo.txid)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
21
35
|
const settleVtxos = findVtxosSpentInSettlement(vtxosLeftToCheck, vtxo);
|
|
22
36
|
vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, settleVtxos);
|
|
23
37
|
const settleAmount = reduceVtxosAmount(settleVtxos);
|
|
@@ -53,21 +67,17 @@ export function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
53
67
|
// vtxos by settled by or ark txid
|
|
54
68
|
const vtxosByTxid = new Map();
|
|
55
69
|
for (const v of spent) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const currentVtxos = vtxosByTxid.get(v.settledBy);
|
|
61
|
-
vtxosByTxid.set(v.settledBy, [...currentVtxos, v]);
|
|
62
|
-
}
|
|
63
|
-
if (!v.arkTxId) {
|
|
70
|
+
// Prefer arkTxId over settledBy to avoid duplicates
|
|
71
|
+
// A vtxo should only be grouped once
|
|
72
|
+
const groupKey = v.arkTxId || v.settledBy;
|
|
73
|
+
if (!groupKey) {
|
|
64
74
|
continue;
|
|
65
75
|
}
|
|
66
|
-
if (!vtxosByTxid.has(
|
|
67
|
-
vtxosByTxid.set(
|
|
76
|
+
if (!vtxosByTxid.has(groupKey)) {
|
|
77
|
+
vtxosByTxid.set(groupKey, []);
|
|
68
78
|
}
|
|
69
|
-
const currentVtxos = vtxosByTxid.get(
|
|
70
|
-
vtxosByTxid.set(
|
|
79
|
+
const currentVtxos = vtxosByTxid.get(groupKey);
|
|
80
|
+
vtxosByTxid.set(groupKey, [...currentVtxos, v]);
|
|
71
81
|
}
|
|
72
82
|
for (const [sb, vtxos] of vtxosByTxid) {
|
|
73
83
|
const resultedVtxos = findVtxosResultedFromTxid([...spendable, ...spent], sb);
|
|
@@ -82,7 +92,13 @@ export function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
82
92
|
boardingTxid: "",
|
|
83
93
|
arkTxid: "",
|
|
84
94
|
};
|
|
85
|
-
|
|
95
|
+
// Use the grouping key (sb) as arkTxid if it looks like an arkTxId
|
|
96
|
+
// (i.e., if the spent vtxos had arkTxId set, use that instead of result vtxo's txid)
|
|
97
|
+
const isArkTxId = vtxos.some((v) => v.arkTxId === sb);
|
|
98
|
+
if (isArkTxId) {
|
|
99
|
+
txKey.arkTxid = sb;
|
|
100
|
+
}
|
|
101
|
+
else if (vtxo.virtualStatus.state === "preconfirmed") {
|
|
86
102
|
txKey.arkTxid = vtxo.txid;
|
|
87
103
|
}
|
|
88
104
|
txs.push({
|
package/dist/esm/wallet/ramps.js
CHANGED
|
@@ -53,10 +53,11 @@ export class Ramps {
|
|
|
53
53
|
* Offboard vtxos, or "collaborative exit" vtxos to onchain address.
|
|
54
54
|
*
|
|
55
55
|
* @param destinationAddress - The destination address to offboard to.
|
|
56
|
+
* @param feeInfo - The fee info to deduct from the offboard amount.
|
|
56
57
|
* @param amount - The amount to offboard. If not provided, the total amount of vtxos will be offboarded.
|
|
57
58
|
* @param eventCallback - The callback to receive settlement events. optional.
|
|
58
59
|
*/
|
|
59
|
-
async offboard(destinationAddress, amount, eventCallback) {
|
|
60
|
+
async offboard(destinationAddress, feeInfo, amount, eventCallback) {
|
|
60
61
|
const vtxos = await this.wallet.getVtxos({
|
|
61
62
|
withRecoverable: true,
|
|
62
63
|
withUnrolled: false,
|
|
@@ -70,6 +71,11 @@ export class Ramps {
|
|
|
70
71
|
change = totalAmount - amount;
|
|
71
72
|
}
|
|
72
73
|
amount = amount ?? totalAmount;
|
|
74
|
+
const fees = feeInfo.intentFee.onchainOutput;
|
|
75
|
+
if (fees > amount) {
|
|
76
|
+
throw new Error(`can't deduct fees from offboard amount (${fees} > ${amount})`);
|
|
77
|
+
}
|
|
78
|
+
amount -= fees;
|
|
73
79
|
const outputs = [
|
|
74
80
|
{
|
|
75
81
|
address: destinationAddress,
|
|
@@ -394,14 +394,13 @@ export class Wallet {
|
|
|
394
394
|
});
|
|
395
395
|
}
|
|
396
396
|
const tapTree = this.offchainTapscript.encode();
|
|
397
|
-
|
|
397
|
+
const offchainTx = buildOffchainTx(selected.inputs.map((input) => ({
|
|
398
398
|
...input,
|
|
399
399
|
tapLeafScript: selectedLeaf,
|
|
400
400
|
tapTree,
|
|
401
401
|
})), outputs, this.serverUnrollScript);
|
|
402
402
|
const signedVirtualTx = await this.identity.sign(offchainTx.arkTx);
|
|
403
403
|
const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT())));
|
|
404
|
-
// TODO persist final virtual tx and checkpoints to repository
|
|
405
404
|
// sign the checkpoints
|
|
406
405
|
const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
|
|
407
406
|
const tx = Transaction.fromPSBT(base64.decode(c));
|
|
@@ -409,7 +408,78 @@ export class Wallet {
|
|
|
409
408
|
return base64.encode(signedCheckpoint.toPSBT());
|
|
410
409
|
}));
|
|
411
410
|
await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
|
|
412
|
-
|
|
411
|
+
try {
|
|
412
|
+
// mark VTXOs as spent and optionally add the change VTXO
|
|
413
|
+
const spentVtxos = [];
|
|
414
|
+
const commitmentTxIds = new Set();
|
|
415
|
+
let batchExpiry = Number.MAX_SAFE_INTEGER;
|
|
416
|
+
for (const [inputIndex, input] of selected.inputs.entries()) {
|
|
417
|
+
const vtxo = extendVirtualCoin(this, input);
|
|
418
|
+
const checkpointB64 = signedCheckpointTxs[inputIndex];
|
|
419
|
+
const checkpoint = Transaction.fromPSBT(base64.decode(checkpointB64));
|
|
420
|
+
spentVtxos.push({
|
|
421
|
+
...vtxo,
|
|
422
|
+
virtualStatus: { ...vtxo.virtualStatus, state: "spent" },
|
|
423
|
+
spentBy: checkpoint.id,
|
|
424
|
+
arkTxId: arkTxid,
|
|
425
|
+
isSpent: true,
|
|
426
|
+
});
|
|
427
|
+
if (vtxo.virtualStatus.commitmentTxIds) {
|
|
428
|
+
for (const commitmentTxId of vtxo.virtualStatus
|
|
429
|
+
.commitmentTxIds) {
|
|
430
|
+
commitmentTxIds.add(commitmentTxId);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (vtxo.virtualStatus.batchExpiry) {
|
|
434
|
+
batchExpiry = Math.min(batchExpiry, vtxo.virtualStatus.batchExpiry);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const createdAt = Date.now();
|
|
438
|
+
const addr = this.arkAddress.encode();
|
|
439
|
+
if (selected.changeAmount > 0n &&
|
|
440
|
+
batchExpiry !== Number.MAX_SAFE_INTEGER) {
|
|
441
|
+
const changeVtxo = {
|
|
442
|
+
txid: arkTxid,
|
|
443
|
+
vout: outputs.length - 1,
|
|
444
|
+
createdAt: new Date(createdAt),
|
|
445
|
+
forfeitTapLeafScript: this.offchainTapscript.forfeit(),
|
|
446
|
+
intentTapLeafScript: this.offchainTapscript.exit(),
|
|
447
|
+
isUnrolled: false,
|
|
448
|
+
isSpent: false,
|
|
449
|
+
tapTree: this.offchainTapscript.encode(),
|
|
450
|
+
value: Number(selected.changeAmount),
|
|
451
|
+
virtualStatus: {
|
|
452
|
+
state: "preconfirmed",
|
|
453
|
+
commitmentTxIds: Array.from(commitmentTxIds),
|
|
454
|
+
batchExpiry,
|
|
455
|
+
},
|
|
456
|
+
status: {
|
|
457
|
+
confirmed: false,
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
await this.walletRepository.saveVtxos(addr, [changeVtxo]);
|
|
461
|
+
}
|
|
462
|
+
await this.walletRepository.saveVtxos(addr, spentVtxos);
|
|
463
|
+
await this.walletRepository.saveTransactions(addr, [
|
|
464
|
+
{
|
|
465
|
+
key: {
|
|
466
|
+
boardingTxid: "",
|
|
467
|
+
commitmentTxid: "",
|
|
468
|
+
arkTxid: arkTxid,
|
|
469
|
+
},
|
|
470
|
+
amount: params.amount,
|
|
471
|
+
type: TxType.TxSent,
|
|
472
|
+
settled: false,
|
|
473
|
+
createdAt: Date.now(),
|
|
474
|
+
},
|
|
475
|
+
]);
|
|
476
|
+
}
|
|
477
|
+
catch (e) {
|
|
478
|
+
console.warn("error saving offchain tx to repository", e);
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
return arkTxid;
|
|
482
|
+
}
|
|
413
483
|
}
|
|
414
484
|
async settle(params, eventCallback) {
|
|
415
485
|
if (params?.inputs) {
|
|
@@ -682,7 +752,8 @@ export class Wallet {
|
|
|
682
752
|
(async () => {
|
|
683
753
|
try {
|
|
684
754
|
for await (const update of subscription) {
|
|
685
|
-
if (update.newVtxos?.length > 0
|
|
755
|
+
if (update.newVtxos?.length > 0 ||
|
|
756
|
+
update.spentVtxos?.length > 0) {
|
|
686
757
|
eventCallback({
|
|
687
758
|
type: "vtxo",
|
|
688
759
|
newVtxos: update.newVtxos.map((vtxo) => extendVirtualCoin(this, vtxo)),
|
package/dist/types/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { Worker } from "./wallet/serviceWorker/worker";
|
|
|
18
18
|
import { Request } from "./wallet/serviceWorker/request";
|
|
19
19
|
import { Response } from "./wallet/serviceWorker/response";
|
|
20
20
|
import { ESPLORA_URL, EsploraProvider, OnchainProvider, ExplorerTransaction } from "./providers/onchain";
|
|
21
|
-
import { RestArkProvider, ArkProvider, SettlementEvent, SettlementEventType, ArkInfo, SignedIntent, Output, TxNotification, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession } from "./providers/ark";
|
|
21
|
+
import { RestArkProvider, ArkProvider, SettlementEvent, SettlementEventType, ArkInfo, SignedIntent, Output, TxNotification, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession, FeeInfo } from "./providers/ark";
|
|
22
22
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, TapscriptType, ArkTapscript, RelativeTimelock } from "./script/tapscript";
|
|
23
23
|
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, ArkTxInput, OffchainTx, combineTapscriptSigs } from "./utils/arkTransaction";
|
|
24
24
|
import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldCoder, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry } from "./utils/unknownFields";
|
|
@@ -34,4 +34,4 @@ import { WalletRepositoryImpl } from "./repositories/walletRepository";
|
|
|
34
34
|
import { ContractRepositoryImpl } from "./repositories/contractRepository";
|
|
35
35
|
import { ArkError, maybeArkError } from "./providers/errors";
|
|
36
36
|
export { Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager, ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, IndexerTxType, ChainTxType, SettlementEventType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, TapTreeCoder, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, combineTapscriptSigs, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, ArkError, maybeArkError, };
|
|
37
|
-
export type { Identity, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, ArkTxInput, OffchainTx, TapLeaves, IncomingFunds, IndexerProvider, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, Vtxo, VtxoChain, Tx, OnchainProvider, ArkProvider, SettlementEvent, ArkInfo, SignedIntent, Output, TxNotification, ExplorerTransaction, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent, Network, NetworkName, ArkTapscript, RelativeTimelock, EncodedVtxoScript, TapLeafScript, SignerSession, TreeNonces, TreePartialSigs, GetVtxosFilter, Nonces, PartialSig, ArkPsbtFieldCoder, TxTreeNode, AnchorBumper, };
|
|
37
|
+
export type { Identity, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, ArkTxInput, OffchainTx, TapLeaves, IncomingFunds, IndexerProvider, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, Vtxo, VtxoChain, Tx, OnchainProvider, ArkProvider, SettlementEvent, FeeInfo, ArkInfo, SignedIntent, Output, TxNotification, ExplorerTransaction, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent, Network, NetworkName, ArkTapscript, RelativeTimelock, EncodedVtxoScript, TapLeafScript, SignerSession, TreeNonces, TreePartialSigs, GetVtxosFilter, Nonces, PartialSig, ArkPsbtFieldCoder, TxTreeNode, AnchorBumper, };
|
|
@@ -75,8 +75,8 @@ export interface ScheduledSession {
|
|
|
75
75
|
export interface IntentFeeInfo {
|
|
76
76
|
offchainInput: string;
|
|
77
77
|
offchainOutput: string;
|
|
78
|
-
onchainInput:
|
|
79
|
-
onchainOutput:
|
|
78
|
+
onchainInput: bigint;
|
|
79
|
+
onchainOutput: bigint;
|
|
80
80
|
}
|
|
81
81
|
export interface FeeInfo {
|
|
82
82
|
intentFee: IntentFeeInfo;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExtendedCoin, IWallet } from ".";
|
|
2
|
-
import { SettlementEvent } from "../providers/ark";
|
|
2
|
+
import { FeeInfo, SettlementEvent } from "../providers/ark";
|
|
3
3
|
/**
|
|
4
4
|
* Ramps is a class wrapping IWallet.settle method to provide a more convenient interface for onboarding and offboarding operations.
|
|
5
5
|
*
|
|
@@ -25,8 +25,9 @@ export declare class Ramps {
|
|
|
25
25
|
* Offboard vtxos, or "collaborative exit" vtxos to onchain address.
|
|
26
26
|
*
|
|
27
27
|
* @param destinationAddress - The destination address to offboard to.
|
|
28
|
+
* @param feeInfo - The fee info to deduct from the offboard amount.
|
|
28
29
|
* @param amount - The amount to offboard. If not provided, the total amount of vtxos will be offboarded.
|
|
29
30
|
* @param eventCallback - The callback to receive settlement events. optional.
|
|
30
31
|
*/
|
|
31
|
-
offboard(destinationAddress: string, amount?: bigint, eventCallback?: (event: SettlementEvent) => void): ReturnType<IWallet["settle"]>;
|
|
32
|
+
offboard(destinationAddress: string, feeInfo: FeeInfo, amount?: bigint, eventCallback?: (event: SettlementEvent) => void): ReturnType<IWallet["settle"]>;
|
|
32
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkade-os/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Bitcoin wallet SDK with Taproot and Ark integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"@types/node": "24.3.1",
|
|
64
64
|
"@vitest/coverage-v8": "3.2.4",
|
|
65
65
|
"esbuild": "^0.25.9",
|
|
66
|
-
"expo": "~52.0.47",
|
|
67
66
|
"eventsource": "4.0.0",
|
|
67
|
+
"expo": "~52.0.47",
|
|
68
68
|
"glob": "11.0.3",
|
|
69
69
|
"husky": "9.1.7",
|
|
70
70
|
"prettier": "3.6.2",
|