@arkade-os/sdk 0.4.20 → 0.4.22
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/contracts/contractWatcher.js +42 -20
- package/dist/cjs/providers/ark.js +65 -48
- package/dist/cjs/providers/indexer.js +60 -47
- package/dist/cjs/providers/utils.js +58 -12
- package/dist/cjs/wallet/delegator.js +27 -18
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +3 -1
- package/dist/cjs/wallet/vtxo-manager.js +7 -5
- package/dist/cjs/wallet/wallet.js +127 -153
- package/dist/esm/contracts/contractWatcher.js +40 -18
- package/dist/esm/providers/ark.js +65 -48
- package/dist/esm/providers/indexer.js +60 -47
- package/dist/esm/providers/utils.js +58 -12
- package/dist/esm/wallet/delegator.js +27 -18
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +3 -1
- package/dist/esm/wallet/vtxo-manager.js +7 -5
- package/dist/esm/wallet/wallet.js +127 -153
- package/dist/types/contracts/contractWatcher.d.ts +3 -0
- package/dist/types/contracts/types.d.ts +5 -5
- package/dist/types/providers/utils.d.ts +9 -5
- package/dist/types/wallet/delegator.d.ts +8 -3
- package/dist/types/wallet/wallet.d.ts +7 -6
- package/package.json +4 -4
|
@@ -153,62 +153,75 @@ export class RestIndexerProvider {
|
|
|
153
153
|
}
|
|
154
154
|
return data;
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
getSubscription(subscriptionId, abortSignal) {
|
|
157
157
|
const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
|
|
158
|
-
|
|
158
|
+
let iterator = null;
|
|
159
|
+
const closeIterator = () => iterator?.close();
|
|
160
|
+
const gen = (async function* () {
|
|
161
|
+
const abortHandler = closeIterator;
|
|
162
|
+
abortSignal?.addEventListener("abort", abortHandler);
|
|
159
163
|
try {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
164
|
+
while (!abortSignal?.aborted) {
|
|
165
|
+
try {
|
|
166
|
+
const currentIterator = eventSourceIterator(new EventSource(url));
|
|
167
|
+
iterator = currentIterator;
|
|
168
|
+
for await (const event of currentIterator) {
|
|
169
|
+
if (abortSignal?.aborted)
|
|
170
|
+
break;
|
|
171
|
+
try {
|
|
172
|
+
const data = JSON.parse(event.data);
|
|
173
|
+
if (data.event) {
|
|
174
|
+
yield {
|
|
175
|
+
txid: data.event.txid,
|
|
176
|
+
scripts: data.event.scripts || [],
|
|
177
|
+
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
178
|
+
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
179
|
+
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
180
|
+
tx: data.event.tx,
|
|
181
|
+
checkpointTxs: data.event.checkpointTxs,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error("Failed to parse subscription event:", err);
|
|
187
|
+
throw err;
|
|
182
188
|
}
|
|
183
189
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (abortSignal?.aborted ||
|
|
193
|
+
(error instanceof Error &&
|
|
194
|
+
error.name === "AbortError")) {
|
|
195
|
+
break;
|
|
187
196
|
}
|
|
197
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
198
|
+
if (isFetchTimeoutError(error)) {
|
|
199
|
+
console.debug("Timeout error ignored");
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (isEventSourceError(error)) {
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
console.error("Subscription error:", error);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
closeIterator();
|
|
210
|
+
iterator = null;
|
|
188
211
|
}
|
|
189
|
-
}
|
|
190
|
-
finally {
|
|
191
|
-
abortSignal?.removeEventListener("abort", abortHandler);
|
|
192
|
-
eventSource.close();
|
|
193
212
|
}
|
|
194
213
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
201
|
-
if (isFetchTimeoutError(error)) {
|
|
202
|
-
console.debug("Timeout error ignored");
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (isEventSourceError(error)) {
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
console.error("Subscription error:", error);
|
|
209
|
-
throw error;
|
|
214
|
+
finally {
|
|
215
|
+
abortSignal?.removeEventListener("abort", abortHandler);
|
|
216
|
+
closeIterator();
|
|
210
217
|
}
|
|
211
|
-
}
|
|
218
|
+
})();
|
|
219
|
+
const origReturn = gen.return.bind(gen);
|
|
220
|
+
gen.return = (value) => {
|
|
221
|
+
closeIterator();
|
|
222
|
+
return origReturn(value);
|
|
223
|
+
};
|
|
224
|
+
return gen;
|
|
212
225
|
}
|
|
213
226
|
async getVirtualTxs(txids, opts) {
|
|
214
227
|
let url = `${this.serverUrl}/v1/indexer/virtualTx/${txids.join(",")}`;
|
|
@@ -1,29 +1,67 @@
|
|
|
1
|
+
function createAbortError() {
|
|
2
|
+
const error = new Error("EventSource closed");
|
|
3
|
+
error.name = "AbortError";
|
|
4
|
+
return error;
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
|
-
* Creates
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
7
|
+
* Creates a close-aware EventSource async iterator.
|
|
8
|
+
*
|
|
9
|
+
* Listeners attach eagerly so events are buffered before the first next() call.
|
|
10
|
+
* close() closes the EventSource, removes listeners, and wakes any pending
|
|
11
|
+
* next() even when the browser does not emit an error from EventSource.close().
|
|
6
12
|
*/
|
|
7
13
|
export function eventSourceIterator(eventSource) {
|
|
8
14
|
const messageQueue = [];
|
|
9
15
|
const errorQueue = [];
|
|
10
16
|
let messageResolve = null;
|
|
11
17
|
let errorResolve = null;
|
|
18
|
+
let closed = false;
|
|
19
|
+
let cleanedUp = false;
|
|
20
|
+
const cleanup = () => {
|
|
21
|
+
if (cleanedUp)
|
|
22
|
+
return;
|
|
23
|
+
cleanedUp = true;
|
|
24
|
+
eventSource.removeEventListener("message", messageHandler);
|
|
25
|
+
eventSource.removeEventListener("error", errorHandler);
|
|
26
|
+
};
|
|
27
|
+
const close = () => {
|
|
28
|
+
if (closed)
|
|
29
|
+
return;
|
|
30
|
+
closed = true;
|
|
31
|
+
messageQueue.length = 0;
|
|
32
|
+
errorQueue.length = 0;
|
|
33
|
+
eventSource.close();
|
|
34
|
+
cleanup();
|
|
35
|
+
if (errorResolve) {
|
|
36
|
+
const reject = errorResolve;
|
|
37
|
+
messageResolve = null;
|
|
38
|
+
errorResolve = null;
|
|
39
|
+
reject(createAbortError());
|
|
40
|
+
}
|
|
41
|
+
};
|
|
12
42
|
const messageHandler = (event) => {
|
|
43
|
+
if (closed)
|
|
44
|
+
return;
|
|
13
45
|
if (messageResolve) {
|
|
14
|
-
messageResolve
|
|
46
|
+
const resolve = messageResolve;
|
|
15
47
|
messageResolve = null;
|
|
48
|
+
errorResolve = null;
|
|
49
|
+
resolve(event);
|
|
16
50
|
}
|
|
17
51
|
else {
|
|
18
52
|
messageQueue.push(event);
|
|
19
53
|
}
|
|
20
54
|
};
|
|
21
55
|
const errorHandler = () => {
|
|
56
|
+
if (closed)
|
|
57
|
+
return;
|
|
22
58
|
const error = new Error("EventSource error");
|
|
23
59
|
error.name = "EventSourceError";
|
|
24
60
|
if (errorResolve) {
|
|
25
|
-
errorResolve
|
|
61
|
+
const reject = errorResolve;
|
|
62
|
+
messageResolve = null;
|
|
26
63
|
errorResolve = null;
|
|
64
|
+
reject(error);
|
|
27
65
|
}
|
|
28
66
|
else {
|
|
29
67
|
errorQueue.push(error);
|
|
@@ -33,9 +71,9 @@ export function eventSourceIterator(eventSource) {
|
|
|
33
71
|
// even before the caller starts iterating
|
|
34
72
|
eventSource.addEventListener("message", messageHandler);
|
|
35
73
|
eventSource.addEventListener("error", errorHandler);
|
|
36
|
-
|
|
74
|
+
const gen = (async function* () {
|
|
37
75
|
try {
|
|
38
|
-
while (
|
|
76
|
+
while (!closed) {
|
|
39
77
|
// if we have queued messages, yield the first one, remove it from the queue
|
|
40
78
|
if (messageQueue.length > 0) {
|
|
41
79
|
yield messageQueue.shift();
|
|
@@ -54,17 +92,25 @@ export function eventSourceIterator(eventSource) {
|
|
|
54
92
|
messageResolve = null;
|
|
55
93
|
errorResolve = null;
|
|
56
94
|
});
|
|
57
|
-
if (result) {
|
|
95
|
+
if (!closed && result) {
|
|
58
96
|
yield result;
|
|
59
97
|
}
|
|
60
98
|
}
|
|
61
99
|
}
|
|
62
100
|
finally {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
eventSource.
|
|
101
|
+
closed = true;
|
|
102
|
+
cleanup();
|
|
103
|
+
eventSource.close();
|
|
66
104
|
}
|
|
67
105
|
})();
|
|
106
|
+
const origReturn = gen.return.bind(gen);
|
|
107
|
+
const managed = gen;
|
|
108
|
+
managed.close = close;
|
|
109
|
+
managed.return = (value) => {
|
|
110
|
+
close();
|
|
111
|
+
return origReturn(value);
|
|
112
|
+
};
|
|
113
|
+
return managed;
|
|
68
114
|
}
|
|
69
115
|
export function isEventSourceError(error) {
|
|
70
116
|
return error instanceof Error && error.name === "EventSourceError";
|
|
@@ -25,20 +25,30 @@ export class DelegatorManagerImpl {
|
|
|
25
25
|
// fetch server and delegator info once, shared across all groups
|
|
26
26
|
const arkInfo = await this.arkInfoProvider.getInfo();
|
|
27
27
|
const delegateInfo = await this.delegatorProvider.getDelegateInfo();
|
|
28
|
+
// keep only vtxos that can be signed by the delegate
|
|
29
|
+
const eligible = vtxos
|
|
30
|
+
.filter((v) => findDelegateTapLeaf(v, delegateInfo.pubkey) !== undefined)
|
|
31
|
+
.map((v) => v);
|
|
32
|
+
if (eligible.length === 0) {
|
|
33
|
+
return { delegated: [], failed: [] };
|
|
34
|
+
}
|
|
28
35
|
// if explicit delegateAt is provided, delegate all virtual outputs at once without sorting
|
|
29
36
|
if (delegateAt) {
|
|
30
37
|
try {
|
|
31
|
-
await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo,
|
|
38
|
+
await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, eligible, destinationScript, delegateAt);
|
|
32
39
|
}
|
|
33
40
|
catch (error) {
|
|
34
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
delegated: [],
|
|
43
|
+
failed: [{ outpoints: eligible, error }],
|
|
44
|
+
};
|
|
35
45
|
}
|
|
36
|
-
return { delegated:
|
|
46
|
+
return { delegated: eligible, failed: [] };
|
|
37
47
|
}
|
|
38
48
|
// if no explicit delegateAt is provided, sort virtual outputs by expiry and delegate in groups of the same expiry day
|
|
39
49
|
const groupByExpiry = new Map();
|
|
40
50
|
let recoverableVtxos = [];
|
|
41
|
-
for (const vtxo of
|
|
51
|
+
for (const vtxo of eligible) {
|
|
42
52
|
if (isRecoverable(vtxo)) {
|
|
43
53
|
recoverableVtxos.push(vtxo);
|
|
44
54
|
continue;
|
|
@@ -185,20 +195,7 @@ async function delegate(identity, delegatorProvider, arkInfo, delegateInfo, vtxo
|
|
|
185
195
|
await delegatorProvider.delegate(registerIntent, forfeits);
|
|
186
196
|
}
|
|
187
197
|
async function makeDelegateForfeitTx(input, connectorAmount, delegatePubkey, forfeitOutputScript, identity) {
|
|
188
|
-
|
|
189
|
-
delegatePubkey = delegatePubkey.slice(2);
|
|
190
|
-
}
|
|
191
|
-
const vtxoScript = VtxoScript.decode(input.tapTree);
|
|
192
|
-
const delegateTapLeaf = vtxoScript.leaves.find((tapLeaf) => {
|
|
193
|
-
const arkTapscript = decodeTapscript(scriptFromTapLeafScript(tapLeaf));
|
|
194
|
-
if (!MultisigTapscript.is(arkTapscript))
|
|
195
|
-
return false;
|
|
196
|
-
if (!arkTapscript.params.pubkeys
|
|
197
|
-
.map(hex.encode)
|
|
198
|
-
.includes(delegatePubkey))
|
|
199
|
-
return false;
|
|
200
|
-
return true;
|
|
201
|
-
});
|
|
198
|
+
const delegateTapLeaf = findDelegateTapLeaf(input, delegatePubkey);
|
|
202
199
|
if (!delegateTapLeaf) {
|
|
203
200
|
throw new Error(`delegate tap leaf not found for input: ${input.txid}:${input.vout}`);
|
|
204
201
|
}
|
|
@@ -286,3 +283,15 @@ function getDayTimestamp(timestamp) {
|
|
|
286
283
|
date.setUTCHours(0, 0, 0, 0);
|
|
287
284
|
return date.getTime();
|
|
288
285
|
}
|
|
286
|
+
function findDelegateTapLeaf(vtxo, delegatePubkey) {
|
|
287
|
+
if (!vtxo.tapTree)
|
|
288
|
+
return undefined;
|
|
289
|
+
const pk = delegatePubkey.length === 66 ? delegatePubkey.slice(2) : delegatePubkey;
|
|
290
|
+
const vtxoScript = VtxoScript.decode(vtxo.tapTree);
|
|
291
|
+
return vtxoScript.leaves.find((tapLeaf) => {
|
|
292
|
+
const arkTapscript = decodeTapscript(scriptFromTapLeafScript(tapLeaf));
|
|
293
|
+
if (!MultisigTapscript.is(arkTapscript))
|
|
294
|
+
return false;
|
|
295
|
+
return arkTapscript.params.pubkeys.map(hex.encode).includes(pk);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
@@ -700,7 +700,9 @@ export class WalletMessageHandler {
|
|
|
700
700
|
const { vtxoOutpoints, destination, delegateAt } = message.payload;
|
|
701
701
|
const allVtxos = await wallet.getVtxos();
|
|
702
702
|
const outpointSet = new Set(vtxoOutpoints.map((o) => `${o.txid}:${o.vout}`));
|
|
703
|
-
const filtered = allVtxos
|
|
703
|
+
const filtered = allVtxos
|
|
704
|
+
.filter((v) => outpointSet.has(`${v.txid}:${v.vout}`))
|
|
705
|
+
.map((v) => ({ ...v, contractScript: v.script }));
|
|
704
706
|
const result = await delegatorManager.delegate(filtered, destination, delegateAt !== undefined ? new Date(delegateAt) : undefined);
|
|
705
707
|
return {
|
|
706
708
|
tag: this.messageTag,
|
|
@@ -693,11 +693,13 @@ export class VtxoManager {
|
|
|
693
693
|
console.error("Error renewing VTXOs:", e);
|
|
694
694
|
});
|
|
695
695
|
}
|
|
696
|
-
delegatorManager
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
696
|
+
if (delegatorManager) {
|
|
697
|
+
delegatorManager
|
|
698
|
+
.delegate(event.vtxos, destination)
|
|
699
|
+
.catch((e) => {
|
|
700
|
+
console.error("Error delegating VTXOs:", e);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
701
703
|
});
|
|
702
704
|
return stopWatching;
|
|
703
705
|
}
|