@arkade-os/sdk 0.3.9 → 0.3.11
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/cjs/arkfee/celenv.js +43 -0
- package/dist/cjs/arkfee/estimator.js +143 -0
- package/dist/cjs/arkfee/index.js +5 -0
- package/dist/cjs/arkfee/types.js +25 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/intent/index.js +21 -0
- package/dist/cjs/providers/ark.js +2 -5
- package/dist/cjs/providers/indexer.js +1 -0
- package/dist/cjs/utils/transactionHistory.js +118 -156
- package/dist/cjs/wallet/ramps.js +96 -11
- package/dist/cjs/wallet/serviceWorker/worker.js +4 -31
- package/dist/cjs/wallet/wallet.js +68 -35
- package/dist/esm/arkfee/celenv.js +40 -0
- package/dist/esm/arkfee/estimator.js +139 -0
- package/dist/esm/arkfee/index.js +1 -0
- package/dist/esm/arkfee/types.js +21 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/intent/index.js +21 -0
- package/dist/esm/providers/ark.js +2 -5
- package/dist/esm/providers/indexer.js +1 -0
- package/dist/esm/utils/transactionHistory.js +117 -155
- package/dist/esm/wallet/ramps.js +96 -11
- package/dist/esm/wallet/serviceWorker/worker.js +4 -31
- package/dist/esm/wallet/wallet.js +68 -35
- package/dist/types/arkfee/celenv.d.ts +25 -0
- package/dist/types/arkfee/estimator.d.ts +49 -0
- package/dist/types/arkfee/index.d.ts +2 -0
- package/dist/types/arkfee/types.d.ts +37 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/intent/index.d.ts +1 -0
- package/dist/types/providers/ark.d.ts +3 -8
- package/dist/types/utils/transactionHistory.d.ts +12 -5
- package/dist/types/wallet/index.d.ts +2 -0
- package/dist/types/wallet/ramps.d.ts +2 -1
- package/dist/types/wallet/serviceWorker/worker.d.ts +0 -1
- package/package.json +114 -123
package/dist/cjs/wallet/ramps.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Ramps = void 0;
|
|
4
|
+
const arkfee_1 = require("../arkfee");
|
|
5
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
|
+
const base_1 = require("@scure/base");
|
|
7
|
+
const networks_1 = require("../networks");
|
|
8
|
+
const address_1 = require("../script/address");
|
|
4
9
|
/**
|
|
5
10
|
* Ramps is a class wrapping IWallet.settle method to provide a more convenient interface for onboarding and offboarding operations.
|
|
6
11
|
*
|
|
@@ -18,22 +23,51 @@ class Ramps {
|
|
|
18
23
|
/**
|
|
19
24
|
* Onboard boarding utxos.
|
|
20
25
|
*
|
|
26
|
+
* @param feeInfo - The fee info to deduct from the onboard amount.
|
|
21
27
|
* @param boardingUtxos - The boarding utxos to onboard. If not provided, all boarding utxos will be used.
|
|
22
28
|
* @param amount - The amount to onboard. If not provided, the total amount of boarding utxos will be onboarded.
|
|
23
29
|
* @param eventCallback - The callback to receive settlement events. optional.
|
|
24
30
|
*/
|
|
25
|
-
async onboard(boardingUtxos, amount, eventCallback) {
|
|
31
|
+
async onboard(feeInfo, boardingUtxos, amount, eventCallback) {
|
|
26
32
|
boardingUtxos = boardingUtxos ?? (await this.wallet.getBoardingUtxos());
|
|
27
|
-
|
|
33
|
+
// Calculate input fees and filter out utxos where fee >= value
|
|
34
|
+
const estimator = new arkfee_1.Estimator(feeInfo?.intentFee ?? {});
|
|
35
|
+
const filteredBoardingUtxos = [];
|
|
36
|
+
let totalAmount = 0n;
|
|
37
|
+
for (const utxo of boardingUtxos) {
|
|
38
|
+
const inputFee = estimator.evalOnchainInput({
|
|
39
|
+
amount: BigInt(utxo.value),
|
|
40
|
+
});
|
|
41
|
+
if (inputFee.satoshis >= utxo.value) {
|
|
42
|
+
// skip if fees are greater than or equal to the utxo value
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
filteredBoardingUtxos.push(utxo);
|
|
46
|
+
totalAmount += BigInt(utxo.value) - BigInt(inputFee.satoshis);
|
|
47
|
+
}
|
|
48
|
+
if (filteredBoardingUtxos.length === 0) {
|
|
49
|
+
throw new Error("No boarding utxos available after deducting fees");
|
|
50
|
+
}
|
|
28
51
|
let change = 0n;
|
|
29
52
|
if (amount) {
|
|
30
53
|
if (amount > totalAmount) {
|
|
31
|
-
throw new Error("Amount is greater than total amount of boarding utxos");
|
|
54
|
+
throw new Error("Amount is greater than total amount of boarding utxos after fees");
|
|
32
55
|
}
|
|
33
56
|
change = totalAmount - amount;
|
|
34
57
|
}
|
|
35
58
|
amount = amount ?? totalAmount;
|
|
59
|
+
// Calculate offchain output fee using Estimator
|
|
36
60
|
const offchainAddress = await this.wallet.getAddress();
|
|
61
|
+
const offchainAddr = address_1.ArkAddress.decode(offchainAddress);
|
|
62
|
+
const offchainScript = base_1.hex.encode(offchainAddr.pkScript);
|
|
63
|
+
const outputFee = estimator.evalOffchainOutput({
|
|
64
|
+
amount,
|
|
65
|
+
script: offchainScript,
|
|
66
|
+
});
|
|
67
|
+
if (BigInt(outputFee.satoshis) > amount) {
|
|
68
|
+
throw new Error(`can't deduct fees from onboard amount (${outputFee.satoshis} > ${amount})`);
|
|
69
|
+
}
|
|
70
|
+
amount -= BigInt(outputFee.satoshis);
|
|
37
71
|
const outputs = [
|
|
38
72
|
{
|
|
39
73
|
address: offchainAddress,
|
|
@@ -48,7 +82,7 @@ class Ramps {
|
|
|
48
82
|
});
|
|
49
83
|
}
|
|
50
84
|
return this.wallet.settle({
|
|
51
|
-
inputs:
|
|
85
|
+
inputs: filteredBoardingUtxos,
|
|
52
86
|
outputs,
|
|
53
87
|
}, eventCallback);
|
|
54
88
|
}
|
|
@@ -65,20 +99,71 @@ class Ramps {
|
|
|
65
99
|
withRecoverable: true,
|
|
66
100
|
withUnrolled: false,
|
|
67
101
|
});
|
|
68
|
-
|
|
102
|
+
// Calculate input fees and filter out vtxos where fee >= value
|
|
103
|
+
const estimator = new arkfee_1.Estimator(feeInfo?.intentFee ?? {});
|
|
104
|
+
const filteredVtxos = [];
|
|
105
|
+
let totalAmount = 0n;
|
|
106
|
+
for (const vtxo of vtxos) {
|
|
107
|
+
const inputFee = estimator.evalOffchainInput({
|
|
108
|
+
amount: BigInt(vtxo.value),
|
|
109
|
+
type: vtxo.virtualStatus.state === "swept"
|
|
110
|
+
? "recoverable"
|
|
111
|
+
: "vtxo",
|
|
112
|
+
weight: 0,
|
|
113
|
+
birth: vtxo.createdAt,
|
|
114
|
+
expiry: vtxo.virtualStatus.batchExpiry
|
|
115
|
+
? new Date(vtxo.virtualStatus.batchExpiry * 1000)
|
|
116
|
+
: undefined,
|
|
117
|
+
});
|
|
118
|
+
if (inputFee.satoshis >= vtxo.value) {
|
|
119
|
+
// skip if fees are greater than or equal to the vtxo value
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
filteredVtxos.push(vtxo);
|
|
123
|
+
totalAmount += BigInt(vtxo.value) - BigInt(inputFee.satoshis);
|
|
124
|
+
}
|
|
125
|
+
if (filteredVtxos.length === 0) {
|
|
126
|
+
throw new Error("No vtxos available after deducting fees");
|
|
127
|
+
}
|
|
69
128
|
let change = 0n;
|
|
70
129
|
if (amount) {
|
|
71
130
|
if (amount > totalAmount) {
|
|
72
|
-
throw new Error("Amount is greater than total amount of vtxos");
|
|
131
|
+
throw new Error("Amount is greater than total amount of vtxos after fees");
|
|
73
132
|
}
|
|
74
133
|
change = totalAmount - amount;
|
|
75
134
|
}
|
|
76
135
|
amount = amount ?? totalAmount;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
136
|
+
const networkNames = [
|
|
137
|
+
"bitcoin",
|
|
138
|
+
"regtest",
|
|
139
|
+
"testnet",
|
|
140
|
+
"signet",
|
|
141
|
+
"mutinynet",
|
|
142
|
+
];
|
|
143
|
+
let destinationScript;
|
|
144
|
+
for (const networkName of networkNames) {
|
|
145
|
+
try {
|
|
146
|
+
const network = networks_1.networks[networkName];
|
|
147
|
+
const addr = (0, btc_signer_1.Address)(network).decode(destinationAddress);
|
|
148
|
+
destinationScript = btc_signer_1.OutScript.encode(addr);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Try next network
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!destinationScript) {
|
|
157
|
+
throw new Error(`Failed to decode destination address: ${destinationAddress}`);
|
|
158
|
+
}
|
|
159
|
+
const outputFee = estimator.evalOnchainOutput({
|
|
160
|
+
amount,
|
|
161
|
+
script: base_1.hex.encode(destinationScript),
|
|
162
|
+
});
|
|
163
|
+
if (BigInt(outputFee.satoshis) > amount) {
|
|
164
|
+
throw new Error(`can't deduct fees from offboard amount (${outputFee.satoshis} > ${amount})`);
|
|
80
165
|
}
|
|
81
|
-
amount -=
|
|
166
|
+
amount -= BigInt(outputFee.satoshis);
|
|
82
167
|
const outputs = [
|
|
83
168
|
{
|
|
84
169
|
address: destinationAddress,
|
|
@@ -93,7 +178,7 @@ class Ramps {
|
|
|
93
178
|
});
|
|
94
179
|
}
|
|
95
180
|
return this.wallet.settle({
|
|
96
|
-
inputs:
|
|
181
|
+
inputs: filteredVtxos,
|
|
97
182
|
outputs,
|
|
98
183
|
}, eventCallback);
|
|
99
184
|
}
|
|
@@ -8,7 +8,6 @@ const wallet_1 = require("../wallet");
|
|
|
8
8
|
const request_1 = require("./request");
|
|
9
9
|
const response_1 = require("./response");
|
|
10
10
|
const ark_1 = require("../../providers/ark");
|
|
11
|
-
const transactionHistory_1 = require("../../utils/transactionHistory");
|
|
12
11
|
const indexer_1 = require("../../providers/indexer");
|
|
13
12
|
const base_1 = require("@scure/base");
|
|
14
13
|
const indexedDB_1 = require("../../storage/indexedDB");
|
|
@@ -43,8 +42,8 @@ class ReadonlyHandler {
|
|
|
43
42
|
getBoardingAddress() {
|
|
44
43
|
return this.wallet.getBoardingAddress();
|
|
45
44
|
}
|
|
46
|
-
|
|
47
|
-
return this.wallet.
|
|
45
|
+
getTransactionHistory() {
|
|
46
|
+
return this.wallet.getTransactionHistory();
|
|
48
47
|
}
|
|
49
48
|
async handleReload(_) {
|
|
50
49
|
const pending = await this.wallet.fetchPendingTxs();
|
|
@@ -130,32 +129,6 @@ class Worker {
|
|
|
130
129
|
const address = await this.handler.getBoardingAddress();
|
|
131
130
|
return await this.walletRepository.getUtxos(address);
|
|
132
131
|
}
|
|
133
|
-
async getTransactionHistory() {
|
|
134
|
-
if (!this.handler)
|
|
135
|
-
return [];
|
|
136
|
-
let txs = [];
|
|
137
|
-
try {
|
|
138
|
-
const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.handler.getBoardingTxs();
|
|
139
|
-
const { spendable, spent } = await this.getAllVtxos();
|
|
140
|
-
// convert VTXOs to offchain transactions
|
|
141
|
-
const offchainTxs = (0, transactionHistory_1.vtxosToTxs)(spendable, spent, roundsToIgnore);
|
|
142
|
-
txs = [...boardingTxs, ...offchainTxs];
|
|
143
|
-
// sort transactions by creation time in descending order (newest first)
|
|
144
|
-
txs.sort(
|
|
145
|
-
// place createdAt = 0 (unconfirmed txs) first, then descending
|
|
146
|
-
(a, b) => {
|
|
147
|
-
if (a.createdAt === 0)
|
|
148
|
-
return -1;
|
|
149
|
-
if (b.createdAt === 0)
|
|
150
|
-
return 1;
|
|
151
|
-
return b.createdAt - a.createdAt;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
catch (error) {
|
|
155
|
-
console.error("Error getting transaction history:", error);
|
|
156
|
-
}
|
|
157
|
-
return txs;
|
|
158
|
-
}
|
|
159
132
|
async start(withServiceWorkerUpdate = true) {
|
|
160
133
|
self.addEventListener("message", async (event) => {
|
|
161
134
|
await this.handleMessage(event);
|
|
@@ -215,7 +188,7 @@ class Worker {
|
|
|
215
188
|
const coins = await this.handler.onchainProvider.getCoins(boardingAddress);
|
|
216
189
|
await this.walletRepository.saveUtxos(boardingAddress, coins.map((utxo) => (0, utils_1.extendCoin)(this.handler, utxo)));
|
|
217
190
|
// Get transaction history to cache boarding txs
|
|
218
|
-
const txs = await this.getTransactionHistory();
|
|
191
|
+
const txs = await this.handler.getTransactionHistory();
|
|
219
192
|
if (txs)
|
|
220
193
|
await this.walletRepository.saveTransactions(address, txs);
|
|
221
194
|
// unsubscribe previous subscription if any
|
|
@@ -564,7 +537,7 @@ class Worker {
|
|
|
564
537
|
return;
|
|
565
538
|
}
|
|
566
539
|
try {
|
|
567
|
-
const txs = await this.getTransactionHistory();
|
|
540
|
+
const txs = await this.handler.getTransactionHistory();
|
|
568
541
|
event.source?.postMessage(response_1.Response.transactionHistory(message.id, txs));
|
|
569
542
|
}
|
|
570
543
|
catch (error) {
|
|
@@ -41,7 +41,6 @@ const bip68 = __importStar(require("bip68"));
|
|
|
41
41
|
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
42
42
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
43
43
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
44
|
-
const transactionHistory_1 = require("../utils/transactionHistory");
|
|
45
44
|
const address_1 = require("../script/address");
|
|
46
45
|
const default_1 = require("../script/default");
|
|
47
46
|
const networks_1 = require("../networks");
|
|
@@ -64,6 +63,8 @@ const contractRepository_1 = require("../repositories/contractRepository");
|
|
|
64
63
|
const utils_1 = require("./utils");
|
|
65
64
|
const errors_1 = require("../providers/errors");
|
|
66
65
|
const batch_1 = require("./batch");
|
|
66
|
+
const arkfee_1 = require("../arkfee");
|
|
67
|
+
const transactionHistory_1 = require("../utils/transactionHistory");
|
|
67
68
|
/**
|
|
68
69
|
* Type guard function to check if an identity has a toReadonly method.
|
|
69
70
|
*/
|
|
@@ -267,30 +268,7 @@ class ReadonlyWallet {
|
|
|
267
268
|
scripts: [base_1.hex.encode(this.offchainTapscript.pkScript)],
|
|
268
269
|
});
|
|
269
270
|
const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
|
|
270
|
-
|
|
271
|
-
const spentVtxos = [];
|
|
272
|
-
for (const vtxo of response.vtxos) {
|
|
273
|
-
if ((0, _1.isSpendable)(vtxo)) {
|
|
274
|
-
spendableVtxos.push(vtxo);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
spentVtxos.push(vtxo);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
// convert VTXOs to offchain transactions
|
|
281
|
-
const offchainTxs = (0, transactionHistory_1.vtxosToTxs)(spendableVtxos, spentVtxos, commitmentsToIgnore);
|
|
282
|
-
const txs = [...boardingTxs, ...offchainTxs];
|
|
283
|
-
// sort transactions by creation time in descending order (newest first)
|
|
284
|
-
txs.sort(
|
|
285
|
-
// place createdAt = 0 (unconfirmed txs) first, then descending
|
|
286
|
-
(a, b) => {
|
|
287
|
-
if (a.createdAt === 0)
|
|
288
|
-
return -1;
|
|
289
|
-
if (b.createdAt === 0)
|
|
290
|
-
return 1;
|
|
291
|
-
return b.createdAt - a.createdAt;
|
|
292
|
-
});
|
|
293
|
-
return txs;
|
|
271
|
+
return (0, transactionHistory_1.buildTransactionHistory)(response.vtxos, boardingTxs, commitmentsToIgnore);
|
|
294
272
|
}
|
|
295
273
|
async getBoardingTxs() {
|
|
296
274
|
const utxos = [];
|
|
@@ -550,7 +528,23 @@ class Wallet extends ReadonlyWallet {
|
|
|
550
528
|
const virtualCoins = await this.getVirtualCoins({
|
|
551
529
|
withRecoverable: false,
|
|
552
530
|
});
|
|
553
|
-
|
|
531
|
+
let selected;
|
|
532
|
+
if (params.selectedVtxos) {
|
|
533
|
+
const selectedVtxoSum = params.selectedVtxos
|
|
534
|
+
.map((v) => v.value)
|
|
535
|
+
.reduce((a, b) => a + b, 0);
|
|
536
|
+
if (selectedVtxoSum < params.amount) {
|
|
537
|
+
throw new Error("Selected VTXOs do not cover specified amount");
|
|
538
|
+
}
|
|
539
|
+
const changeAmount = selectedVtxoSum - params.amount;
|
|
540
|
+
selected = {
|
|
541
|
+
inputs: params.selectedVtxos,
|
|
542
|
+
changeAmount: BigInt(changeAmount),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
selected = selectVirtualCoins(virtualCoins, params.amount);
|
|
547
|
+
}
|
|
554
548
|
const selectedLeaf = this.offchainTapscript.forfeit();
|
|
555
549
|
if (!selectedLeaf) {
|
|
556
550
|
throw new Error("Selected leaf not found");
|
|
@@ -680,25 +674,64 @@ class Wallet extends ReadonlyWallet {
|
|
|
680
674
|
// if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
|
|
681
675
|
// and send all to the offchain address
|
|
682
676
|
if (!params) {
|
|
677
|
+
const { fees } = await this.arkProvider.getInfo();
|
|
678
|
+
const estimator = new arkfee_1.Estimator(fees.intentFee);
|
|
683
679
|
let amount = 0;
|
|
684
680
|
const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
|
|
685
681
|
const boardingTimelock = exitScript.params.timelock;
|
|
686
682
|
const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock));
|
|
687
|
-
|
|
683
|
+
const filteredBoardingUtxos = [];
|
|
684
|
+
for (const utxo of boardingUtxos) {
|
|
685
|
+
const inputFee = estimator.evalOnchainInput({
|
|
686
|
+
amount: BigInt(utxo.value),
|
|
687
|
+
});
|
|
688
|
+
if (inputFee.value >= utxo.value) {
|
|
689
|
+
// skip if fees are greater than the utxo value
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
filteredBoardingUtxos.push(utxo);
|
|
693
|
+
amount += utxo.value - inputFee.satoshis;
|
|
694
|
+
}
|
|
688
695
|
const vtxos = await this.getVtxos({ withRecoverable: true });
|
|
689
|
-
|
|
690
|
-
const
|
|
696
|
+
const filteredVtxos = [];
|
|
697
|
+
for (const vtxo of vtxos) {
|
|
698
|
+
const inputFee = estimator.evalOffchainInput({
|
|
699
|
+
amount: BigInt(vtxo.value),
|
|
700
|
+
type: vtxo.virtualStatus.state === "swept"
|
|
701
|
+
? "recoverable"
|
|
702
|
+
: "vtxo",
|
|
703
|
+
weight: 0,
|
|
704
|
+
birth: vtxo.createdAt,
|
|
705
|
+
expiry: vtxo.virtualStatus.batchExpiry
|
|
706
|
+
? new Date(vtxo.virtualStatus.batchExpiry * 1000)
|
|
707
|
+
: new Date(),
|
|
708
|
+
});
|
|
709
|
+
if (inputFee.value >= vtxo.value) {
|
|
710
|
+
// skip if fees are greater than the vtxo value
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
filteredVtxos.push(vtxo);
|
|
714
|
+
amount += vtxo.value - inputFee.satoshis;
|
|
715
|
+
}
|
|
716
|
+
const inputs = [...filteredBoardingUtxos, ...filteredVtxos];
|
|
691
717
|
if (inputs.length === 0) {
|
|
692
718
|
throw new Error("No inputs found");
|
|
693
719
|
}
|
|
720
|
+
const output = {
|
|
721
|
+
address: await this.getAddress(),
|
|
722
|
+
amount: BigInt(amount),
|
|
723
|
+
};
|
|
724
|
+
const outputFee = estimator.evalOffchainOutput({
|
|
725
|
+
amount: output.amount,
|
|
726
|
+
script: base_1.hex.encode(address_1.ArkAddress.decode(output.address).pkScript),
|
|
727
|
+
});
|
|
728
|
+
output.amount -= BigInt(outputFee.satoshis);
|
|
729
|
+
if (output.amount <= this.dustAmount) {
|
|
730
|
+
throw new Error("Output amount is below dust limit");
|
|
731
|
+
}
|
|
694
732
|
params = {
|
|
695
733
|
inputs,
|
|
696
|
-
outputs: [
|
|
697
|
-
{
|
|
698
|
-
address: await this.getAddress(),
|
|
699
|
-
amount: BigInt(amount),
|
|
700
|
-
},
|
|
701
|
-
],
|
|
734
|
+
outputs: [output],
|
|
702
735
|
};
|
|
703
736
|
}
|
|
704
737
|
const onchainOutputIndexes = [];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Environment } from "@marcbachmann/cel-js";
|
|
2
|
+
/**
|
|
3
|
+
* Variable names used in CEL expressions
|
|
4
|
+
*/
|
|
5
|
+
export const AmountVariableName = "amount";
|
|
6
|
+
export const ExpiryVariableName = "expiry";
|
|
7
|
+
export const BirthVariableName = "birth";
|
|
8
|
+
export const WeightVariableName = "weight";
|
|
9
|
+
export const InputTypeVariableName = "inputType";
|
|
10
|
+
export const OutputScriptVariableName = "script";
|
|
11
|
+
const nowFunction = {
|
|
12
|
+
signature: "now(): double",
|
|
13
|
+
implementation: () => Math.floor(Date.now() / 1000),
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* IntentOutputEnv is the CEL environment for output fee calculation
|
|
17
|
+
* Variables: amount, script
|
|
18
|
+
*/
|
|
19
|
+
export const IntentOutputEnv = new Environment()
|
|
20
|
+
.registerVariable(AmountVariableName, "double")
|
|
21
|
+
.registerVariable(OutputScriptVariableName, "string")
|
|
22
|
+
.registerFunction(nowFunction.signature, nowFunction.implementation);
|
|
23
|
+
/**
|
|
24
|
+
* IntentOffchainInputEnv is the CEL environment for offchain input fee calculation
|
|
25
|
+
* Variables: amount, expiry, birth, weight, inputType
|
|
26
|
+
*/
|
|
27
|
+
export const IntentOffchainInputEnv = new Environment()
|
|
28
|
+
.registerVariable(AmountVariableName, "double")
|
|
29
|
+
.registerVariable(ExpiryVariableName, "double")
|
|
30
|
+
.registerVariable(BirthVariableName, "double")
|
|
31
|
+
.registerVariable(WeightVariableName, "double")
|
|
32
|
+
.registerVariable(InputTypeVariableName, "string")
|
|
33
|
+
.registerFunction(nowFunction.signature, nowFunction.implementation);
|
|
34
|
+
/**
|
|
35
|
+
* IntentOnchainInputEnv is the CEL environment for onchain input fee calculation
|
|
36
|
+
* Variables: amount
|
|
37
|
+
*/
|
|
38
|
+
export const IntentOnchainInputEnv = new Environment()
|
|
39
|
+
.registerVariable(AmountVariableName, "double")
|
|
40
|
+
.registerFunction(nowFunction.signature, nowFunction.implementation);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { IntentOffchainInputEnv, IntentOnchainInputEnv, IntentOutputEnv, } from "./celenv.js";
|
|
2
|
+
import { FeeAmount, } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Estimator evaluates CEL expressions to calculate fees for Ark intents
|
|
5
|
+
*/
|
|
6
|
+
export class Estimator {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new Estimator with the given config
|
|
9
|
+
* @param config - Configuration containing CEL programs for fee calculation
|
|
10
|
+
*/
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.intentOffchainInput = config.offchainInput
|
|
14
|
+
? parseProgram(config.offchainInput, IntentOffchainInputEnv)
|
|
15
|
+
: undefined;
|
|
16
|
+
this.intentOnchainInput = config.onchainInput
|
|
17
|
+
? parseProgram(config.onchainInput, IntentOnchainInputEnv)
|
|
18
|
+
: undefined;
|
|
19
|
+
this.intentOffchainOutput = config.offchainOutput
|
|
20
|
+
? parseProgram(config.offchainOutput, IntentOutputEnv)
|
|
21
|
+
: undefined;
|
|
22
|
+
this.intentOnchainOutput = config.onchainOutput
|
|
23
|
+
? parseProgram(config.onchainOutput, IntentOutputEnv)
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Evaluates the fee for a given vtxo input
|
|
28
|
+
* @param input - The offchain input to evaluate
|
|
29
|
+
* @returns The fee amount for this input
|
|
30
|
+
*/
|
|
31
|
+
evalOffchainInput(input) {
|
|
32
|
+
if (!this.intentOffchainInput) {
|
|
33
|
+
return FeeAmount.ZERO;
|
|
34
|
+
}
|
|
35
|
+
const args = inputToArgs(input);
|
|
36
|
+
return new FeeAmount(this.intentOffchainInput.program(args));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Evaluates the fee for a given boarding input
|
|
40
|
+
* @param input - The onchain input to evaluate
|
|
41
|
+
* @returns The fee amount for this input
|
|
42
|
+
*/
|
|
43
|
+
evalOnchainInput(input) {
|
|
44
|
+
if (!this.intentOnchainInput) {
|
|
45
|
+
return FeeAmount.ZERO;
|
|
46
|
+
}
|
|
47
|
+
const args = {
|
|
48
|
+
amount: Number(input.amount),
|
|
49
|
+
};
|
|
50
|
+
return new FeeAmount(this.intentOnchainInput.program(args));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Evaluates the fee for a given vtxo output
|
|
54
|
+
* @param output - The output to evaluate
|
|
55
|
+
* @returns The fee amount for this output
|
|
56
|
+
*/
|
|
57
|
+
evalOffchainOutput(output) {
|
|
58
|
+
if (!this.intentOffchainOutput) {
|
|
59
|
+
return FeeAmount.ZERO;
|
|
60
|
+
}
|
|
61
|
+
const args = outputToArgs(output);
|
|
62
|
+
return new FeeAmount(this.intentOffchainOutput.program(args));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Evaluates the fee for a given collaborative exit output
|
|
66
|
+
* @param output - The output to evaluate
|
|
67
|
+
* @returns The fee amount for this output
|
|
68
|
+
*/
|
|
69
|
+
evalOnchainOutput(output) {
|
|
70
|
+
if (!this.intentOnchainOutput) {
|
|
71
|
+
return FeeAmount.ZERO;
|
|
72
|
+
}
|
|
73
|
+
const args = outputToArgs(output);
|
|
74
|
+
return new FeeAmount(this.intentOnchainOutput.program(args));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Evaluates the fee for a given set of inputs and outputs
|
|
78
|
+
* @param offchainInputs - Array of offchain inputs to evaluate
|
|
79
|
+
* @param onchainInputs - Array of onchain inputs to evaluate
|
|
80
|
+
* @param offchainOutputs - Array of offchain outputs to evaluate
|
|
81
|
+
* @param onchainOutputs - Array of onchain outputs to evaluate
|
|
82
|
+
* @returns The total fee amount
|
|
83
|
+
*/
|
|
84
|
+
eval(offchainInputs, onchainInputs, offchainOutputs, onchainOutputs) {
|
|
85
|
+
let fee = FeeAmount.ZERO;
|
|
86
|
+
for (const input of offchainInputs) {
|
|
87
|
+
fee = fee.add(this.evalOffchainInput(input));
|
|
88
|
+
}
|
|
89
|
+
for (const input of onchainInputs) {
|
|
90
|
+
fee = fee.add(this.evalOnchainInput(input));
|
|
91
|
+
}
|
|
92
|
+
for (const output of offchainOutputs) {
|
|
93
|
+
fee = fee.add(this.evalOffchainOutput(output));
|
|
94
|
+
}
|
|
95
|
+
for (const output of onchainOutputs) {
|
|
96
|
+
fee = fee.add(this.evalOnchainOutput(output));
|
|
97
|
+
}
|
|
98
|
+
return fee;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function inputToArgs(input) {
|
|
102
|
+
const args = {
|
|
103
|
+
amount: Number(input.amount),
|
|
104
|
+
inputType: input.type,
|
|
105
|
+
weight: input.weight,
|
|
106
|
+
};
|
|
107
|
+
if (input.expiry) {
|
|
108
|
+
args.expiry = Math.floor(input.expiry.getTime() / 1000);
|
|
109
|
+
}
|
|
110
|
+
if (input.birth) {
|
|
111
|
+
args.birth = Math.floor(input.birth.getTime() / 1000);
|
|
112
|
+
}
|
|
113
|
+
return args;
|
|
114
|
+
}
|
|
115
|
+
function outputToArgs(output) {
|
|
116
|
+
return {
|
|
117
|
+
amount: Number(output.amount),
|
|
118
|
+
script: output.script,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Parses a CEL program and validates its return type
|
|
123
|
+
* @param text - The CEL program text to parse
|
|
124
|
+
* @param env - The CEL environment to use
|
|
125
|
+
* @returns parsed and validated program
|
|
126
|
+
*/
|
|
127
|
+
function parseProgram(text, env) {
|
|
128
|
+
const program = env.parse(text);
|
|
129
|
+
// Type check the program
|
|
130
|
+
const checkResult = program.check();
|
|
131
|
+
if (!checkResult.valid) {
|
|
132
|
+
throw new Error(`type check failed: ${checkResult.error?.message ?? "unknown error"}`);
|
|
133
|
+
}
|
|
134
|
+
// Verify return type is double
|
|
135
|
+
if (checkResult.type !== "double") {
|
|
136
|
+
throw new Error(`expected return type double, got ${checkResult.type}`);
|
|
137
|
+
}
|
|
138
|
+
return { program, text };
|
|
139
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Estimator } from './estimator.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FeeAmount is a wrapper around a number that represents a fee amount in satoshis floating point.
|
|
3
|
+
* @param value - The fee amount in floating point.
|
|
4
|
+
* @method satoshis - Returns the fee amount in satoshis as a integer.
|
|
5
|
+
* @example
|
|
6
|
+
* const fee = new FeeAmount(1.23456789);
|
|
7
|
+
* console.log(fee.value); // 1.23456789
|
|
8
|
+
* console.log(fee.satoshis); // 2
|
|
9
|
+
*/
|
|
10
|
+
export class FeeAmount {
|
|
11
|
+
constructor(value) {
|
|
12
|
+
this.value = value;
|
|
13
|
+
}
|
|
14
|
+
get satoshis() {
|
|
15
|
+
return this.value ? Math.ceil(this.value) : 0;
|
|
16
|
+
}
|
|
17
|
+
add(other) {
|
|
18
|
+
return new FeeAmount(this.value + other.value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
FeeAmount.ZERO = new FeeAmount(0);
|
package/dist/esm/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import { ContractRepositoryImpl } from './repositories/contractRepository.js';
|
|
|
32
32
|
import { ArkError, maybeArkError } from './providers/errors.js';
|
|
33
33
|
import { validateVtxoTxGraph, validateConnectorsTxGraph, } from './tree/validation.js';
|
|
34
34
|
import { buildForfeitTx } from './forfeit.js';
|
|
35
|
+
export * from './arkfee/index.js';
|
|
35
36
|
export {
|
|
36
37
|
// Wallets
|
|
37
38
|
Wallet, ReadonlyWallet, SingleKey, ReadonlySingleKey, OnchainWallet, Ramps, VtxoManager,
|
package/dist/esm/intent/index.js
CHANGED
|
@@ -54,6 +54,27 @@ export var Intent;
|
|
|
54
54
|
return craftToSignTx(toSpend, inputs, outputs);
|
|
55
55
|
}
|
|
56
56
|
Intent.create = create;
|
|
57
|
+
function fee(proof) {
|
|
58
|
+
let sumOfInputs = 0n;
|
|
59
|
+
for (let i = 0; i < proof.inputsLength; i++) {
|
|
60
|
+
const input = proof.getInput(i);
|
|
61
|
+
if (input.witnessUtxo === undefined)
|
|
62
|
+
throw new Error("intent proof input requires witness utxo");
|
|
63
|
+
sumOfInputs += input.witnessUtxo.amount;
|
|
64
|
+
}
|
|
65
|
+
let sumOfOutputs = 0n;
|
|
66
|
+
for (let i = 0; i < proof.outputsLength; i++) {
|
|
67
|
+
const output = proof.getOutput(i);
|
|
68
|
+
if (output.amount === undefined)
|
|
69
|
+
throw new Error("intent proof output requires amount");
|
|
70
|
+
sumOfOutputs += output.amount;
|
|
71
|
+
}
|
|
72
|
+
if (sumOfOutputs > sumOfInputs) {
|
|
73
|
+
throw new Error(`intent proof output amount is greater than input amount: ${sumOfOutputs} > ${sumOfInputs}`);
|
|
74
|
+
}
|
|
75
|
+
return Number(sumOfInputs - sumOfOutputs);
|
|
76
|
+
}
|
|
77
|
+
Intent.fee = fee;
|
|
57
78
|
function encodeMessage(message) {
|
|
58
79
|
switch (message.type) {
|
|
59
80
|
case "register":
|
|
@@ -44,11 +44,7 @@ export class RestArkProvider {
|
|
|
44
44
|
digest: fromServer.digest ?? "",
|
|
45
45
|
dust: BigInt(fromServer.dust ?? 0),
|
|
46
46
|
fees: {
|
|
47
|
-
intentFee: {
|
|
48
|
-
...fromServer.fees?.intentFee,
|
|
49
|
-
onchainInput: BigInt(fromServer.fees?.intentFee?.onchainInput ?? 0),
|
|
50
|
-
onchainOutput: BigInt(fromServer.fees?.intentFee?.onchainOutput ?? 0),
|
|
51
|
-
},
|
|
47
|
+
intentFee: fromServer.fees?.intentFee ?? {},
|
|
52
48
|
txFeeRate: fromServer?.fees?.txFeeRate ?? "",
|
|
53
49
|
},
|
|
54
50
|
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
@@ -61,6 +57,7 @@ export class RestArkProvider {
|
|
|
61
57
|
nextStartTime: BigInt(fromServer.scheduledSession.nextStartTime ?? 0),
|
|
62
58
|
nextEndTime: BigInt(fromServer.scheduledSession.nextEndTime ?? 0),
|
|
63
59
|
period: BigInt(fromServer.scheduledSession.period ?? 0),
|
|
60
|
+
fees: fromServer.scheduledSession.fees ?? {},
|
|
64
61
|
}
|
|
65
62
|
: undefined,
|
|
66
63
|
serviceStatus: fromServer.serviceStatus ?? {},
|