@arkade-os/sdk 0.4.21 → 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.
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ContractWatcher = void 0;
4
- const utils_1 = require("../providers/utils");
4
+ const utils_1 = require("../wallet/utils");
5
+ const utils_2 = require("../providers/utils");
5
6
  /**
6
7
  * Watches multiple contracts for virtual output state changes with resilient connection handling.
7
8
  *
@@ -276,7 +277,7 @@ class ContractWatcher {
276
277
  // indefinitely and block the caller.
277
278
  // Error management must be implemented to ensure the connection
278
279
  // is restored and events are fired.
279
- if ((0, utils_1.isEventSourceError)(e)) {
280
+ if ((0, utils_2.isEventSourceError)(e)) {
280
281
  console.debug("ContractWatcher subscription disconnected; reconnecting");
281
282
  }
282
283
  else {
@@ -553,18 +554,22 @@ class ContractWatcher {
553
554
  const state = this.contracts.get(contractScript);
554
555
  if (!state)
555
556
  return;
557
+ const extended = [];
558
+ for (const v of vtxos) {
559
+ try {
560
+ const extendedVtxo = (0, utils_1.extendVirtualCoinForContract)(v, state.contract);
561
+ extended.push({ ...extendedVtxo, contractScript });
562
+ }
563
+ catch {
564
+ console.warn("failed to extend vtxo: ", v);
565
+ extended.push({ ...v, contractScript });
566
+ }
567
+ }
556
568
  switch (eventType) {
557
569
  case "vtxo_received":
558
570
  this.eventCallback({
559
571
  type: "vtxo_received",
560
- vtxos: vtxos.map((v) => ({
561
- ...v,
562
- contractScript,
563
- // These fields may not be available from basic VirtualCoin
564
- forfeitTapLeafScript: undefined,
565
- intentTapLeafScript: undefined,
566
- tapTree: undefined,
567
- })),
572
+ vtxos: extended,
568
573
  contractScript,
569
574
  contract: state.contract,
570
575
  timestamp,
@@ -573,14 +578,7 @@ class ContractWatcher {
573
578
  case "vtxo_spent":
574
579
  this.eventCallback({
575
580
  type: "vtxo_spent",
576
- vtxos: vtxos.map((v) => ({
577
- ...v,
578
- contractScript,
579
- // These fields may not be available from basic VirtualCoin
580
- forfeitTapLeafScript: undefined,
581
- intentTapLeafScript: undefined,
582
- tapTree: undefined,
583
- })),
581
+ vtxos: extended,
584
582
  contractScript,
585
583
  contract: state.contract,
586
584
  timestamp,
@@ -29,20 +29,30 @@ class DelegatorManagerImpl {
29
29
  // fetch server and delegator info once, shared across all groups
30
30
  const arkInfo = await this.arkInfoProvider.getInfo();
31
31
  const delegateInfo = await this.delegatorProvider.getDelegateInfo();
32
+ // keep only vtxos that can be signed by the delegate
33
+ const eligible = vtxos
34
+ .filter((v) => findDelegateTapLeaf(v, delegateInfo.pubkey) !== undefined)
35
+ .map((v) => v);
36
+ if (eligible.length === 0) {
37
+ return { delegated: [], failed: [] };
38
+ }
32
39
  // if explicit delegateAt is provided, delegate all virtual outputs at once without sorting
33
40
  if (delegateAt) {
34
41
  try {
35
- await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, vtxos, destinationScript, delegateAt);
42
+ await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, eligible, destinationScript, delegateAt);
36
43
  }
37
44
  catch (error) {
38
- return { delegated: [], failed: [{ outpoints: vtxos, error }] };
45
+ return {
46
+ delegated: [],
47
+ failed: [{ outpoints: eligible, error }],
48
+ };
39
49
  }
40
- return { delegated: vtxos, failed: [] };
50
+ return { delegated: eligible, failed: [] };
41
51
  }
42
52
  // if no explicit delegateAt is provided, sort virtual outputs by expiry and delegate in groups of the same expiry day
43
53
  const groupByExpiry = new Map();
44
54
  let recoverableVtxos = [];
45
- for (const vtxo of vtxos) {
55
+ for (const vtxo of eligible) {
46
56
  if ((0, __1.isRecoverable)(vtxo)) {
47
57
  recoverableVtxos.push(vtxo);
48
58
  continue;
@@ -190,20 +200,7 @@ async function delegate(identity, delegatorProvider, arkInfo, delegateInfo, vtxo
190
200
  await delegatorProvider.delegate(registerIntent, forfeits);
191
201
  }
192
202
  async function makeDelegateForfeitTx(input, connectorAmount, delegatePubkey, forfeitOutputScript, identity) {
193
- if (delegatePubkey.length === 66) {
194
- delegatePubkey = delegatePubkey.slice(2);
195
- }
196
- const vtxoScript = __1.VtxoScript.decode(input.tapTree);
197
- const delegateTapLeaf = vtxoScript.leaves.find((tapLeaf) => {
198
- const arkTapscript = (0, __1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(tapLeaf));
199
- if (!__1.MultisigTapscript.is(arkTapscript))
200
- return false;
201
- if (!arkTapscript.params.pubkeys
202
- .map(base_1.hex.encode)
203
- .includes(delegatePubkey))
204
- return false;
205
- return true;
206
- });
203
+ const delegateTapLeaf = findDelegateTapLeaf(input, delegatePubkey);
207
204
  if (!delegateTapLeaf) {
208
205
  throw new Error(`delegate tap leaf not found for input: ${input.txid}:${input.vout}`);
209
206
  }
@@ -291,3 +288,15 @@ function getDayTimestamp(timestamp) {
291
288
  date.setUTCHours(0, 0, 0, 0);
292
289
  return date.getTime();
293
290
  }
291
+ function findDelegateTapLeaf(vtxo, delegatePubkey) {
292
+ if (!vtxo.tapTree)
293
+ return undefined;
294
+ const pk = delegatePubkey.length === 66 ? delegatePubkey.slice(2) : delegatePubkey;
295
+ const vtxoScript = __1.VtxoScript.decode(vtxo.tapTree);
296
+ return vtxoScript.leaves.find((tapLeaf) => {
297
+ const arkTapscript = (0, __1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(tapLeaf));
298
+ if (!__1.MultisigTapscript.is(arkTapscript))
299
+ return false;
300
+ return arkTapscript.params.pubkeys.map(base_1.hex.encode).includes(pk);
301
+ });
302
+ }
@@ -706,7 +706,9 @@ class WalletMessageHandler {
706
706
  const { vtxoOutpoints, destination, delegateAt } = message.payload;
707
707
  const allVtxos = await wallet.getVtxos();
708
708
  const outpointSet = new Set(vtxoOutpoints.map((o) => `${o.txid}:${o.vout}`));
709
- const filtered = allVtxos.filter((v) => outpointSet.has(`${v.txid}:${v.vout}`));
709
+ const filtered = allVtxos
710
+ .filter((v) => outpointSet.has(`${v.txid}:${v.vout}`))
711
+ .map((v) => ({ ...v, contractScript: v.script }));
710
712
  const result = await delegatorManager.delegate(filtered, destination, delegateAt !== undefined ? new Date(delegateAt) : undefined);
711
713
  return {
712
714
  tag: this.messageTag,
@@ -698,11 +698,13 @@ class VtxoManager {
698
698
  console.error("Error renewing VTXOs:", e);
699
699
  });
700
700
  }
701
- delegatorManager
702
- ?.delegate(event.vtxos, destination)
703
- .catch((e) => {
704
- console.error("Error delegating VTXOs:", e);
705
- });
701
+ if (delegatorManager) {
702
+ delegatorManager
703
+ .delegate(event.vtxos, destination)
704
+ .catch((e) => {
705
+ console.error("Error delegating VTXOs:", e);
706
+ });
707
+ }
706
708
  });
707
709
  return stopWatching;
708
710
  }
@@ -1,3 +1,4 @@
1
+ import { extendVirtualCoinForContract } from '../wallet/utils.js';
1
2
  import { isEventSourceError } from '../providers/utils.js';
2
3
  /**
3
4
  * Watches multiple contracts for virtual output state changes with resilient connection handling.
@@ -550,18 +551,22 @@ export class ContractWatcher {
550
551
  const state = this.contracts.get(contractScript);
551
552
  if (!state)
552
553
  return;
554
+ const extended = [];
555
+ for (const v of vtxos) {
556
+ try {
557
+ const extendedVtxo = extendVirtualCoinForContract(v, state.contract);
558
+ extended.push({ ...extendedVtxo, contractScript });
559
+ }
560
+ catch {
561
+ console.warn("failed to extend vtxo: ", v);
562
+ extended.push({ ...v, contractScript });
563
+ }
564
+ }
553
565
  switch (eventType) {
554
566
  case "vtxo_received":
555
567
  this.eventCallback({
556
568
  type: "vtxo_received",
557
- vtxos: vtxos.map((v) => ({
558
- ...v,
559
- contractScript,
560
- // These fields may not be available from basic VirtualCoin
561
- forfeitTapLeafScript: undefined,
562
- intentTapLeafScript: undefined,
563
- tapTree: undefined,
564
- })),
569
+ vtxos: extended,
565
570
  contractScript,
566
571
  contract: state.contract,
567
572
  timestamp,
@@ -570,14 +575,7 @@ export class ContractWatcher {
570
575
  case "vtxo_spent":
571
576
  this.eventCallback({
572
577
  type: "vtxo_spent",
573
- vtxos: vtxos.map((v) => ({
574
- ...v,
575
- contractScript,
576
- // These fields may not be available from basic VirtualCoin
577
- forfeitTapLeafScript: undefined,
578
- intentTapLeafScript: undefined,
579
- tapTree: undefined,
580
- })),
578
+ vtxos: extended,
581
579
  contractScript,
582
580
  contract: state.contract,
583
581
  timestamp,
@@ -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, vtxos, destinationScript, delegateAt);
38
+ await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, eligible, destinationScript, delegateAt);
32
39
  }
33
40
  catch (error) {
34
- return { delegated: [], failed: [{ outpoints: vtxos, error }] };
41
+ return {
42
+ delegated: [],
43
+ failed: [{ outpoints: eligible, error }],
44
+ };
35
45
  }
36
- return { delegated: vtxos, failed: [] };
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 vtxos) {
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
- if (delegatePubkey.length === 66) {
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.filter((v) => outpointSet.has(`${v.txid}:${v.vout}`));
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
- ?.delegate(event.vtxos, destination)
698
- .catch((e) => {
699
- console.error("Error delegating VTXOs:", e);
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
  }
@@ -1,6 +1,6 @@
1
1
  import { Bytes } from "@scure/btc-signer/utils.js";
2
- import { TapLeafScript, VtxoScript } from "../script/base";
3
- import { VirtualCoin, ExtendedVirtualCoin } from "../wallet";
2
+ import { EncodedVtxoScript, TapLeafScript, VtxoScript } from "../script/base";
3
+ import { VirtualCoin, TapLeaves } from "../wallet";
4
4
  import { ContractFilter } from "../repositories";
5
5
  /**
6
6
  * Contract state indicating whether it should be actively monitored.
@@ -66,10 +66,10 @@ export interface Contract {
66
66
  /**
67
67
  * A virtual output that has been associated with a specific contract.
68
68
  */
69
- export interface ContractVtxo extends ExtendedVirtualCoin {
70
- /** The contract script this virtual output belongs to. */
69
+ export type ContractVtxo = VirtualCoin & Partial<TapLeaves & EncodedVtxoScript> & {
70
+ extraWitness?: Bytes[];
71
71
  contractScript: string;
72
- }
72
+ };
73
73
  /**
74
74
  * Result of path selection, including the tapleaf to use and any extra witness data.
75
75
  */
@@ -1,17 +1,22 @@
1
1
  import { TransactionOutput } from "@scure/btc-signer/psbt";
2
- import { ArkProvider, DelegateInfo, ExtendedVirtualCoin, Identity, Outpoint } from "..";
2
+ import { ArkProvider, DelegateInfo, Identity, Outpoint } from "..";
3
+ import { ContractVtxo } from "../contracts/types";
3
4
  import { DelegatorProvider } from "../providers/delegator";
4
5
  import { Bytes } from "@scure/btc-signer/utils";
5
6
  export interface IDelegatorManager {
6
7
  /**
7
8
  * Delegate virtual outputs to the remote delegation service.
8
9
  *
10
+ * Vtxos that are not locked to a delegate-type contract (no tap leaf
11
+ * matches the delegator's pubkey) are filtered out silently, since they
12
+ * cannot be co-signed by the delegator.
13
+ *
9
14
  * @param vtxos - Virtual outputs to delegate
10
15
  * @param destination - Arkade address that should receive renewed funds
11
16
  * @param delegateAt - Optional timestamp to force a specific delegation time
12
17
  * @returns Successfully delegated and failed outpoint groups
13
18
  */
14
- delegate(vtxos: ExtendedVirtualCoin[], destination: string, delegateAt?: Date): Promise<{
19
+ delegate(vtxos: ContractVtxo[], destination: string, delegateAt?: Date): Promise<{
15
20
  delegated: Outpoint[];
16
21
  failed: {
17
22
  outpoints: Outpoint[];
@@ -28,7 +33,7 @@ export declare class DelegatorManagerImpl implements IDelegatorManager {
28
33
  /** Create a delegator manager from the configured provider, Arkade info source, and wallet identity. */
29
34
  constructor(delegatorProvider: DelegatorProvider, arkInfoProvider: Pick<ArkProvider, "getInfo">, identity: Identity);
30
35
  getDelegateInfo(): Promise<DelegateInfo>;
31
- delegate(vtxos: ExtendedVirtualCoin[], destination: string, delegateAt?: Date): Promise<{
36
+ delegate(vtxos: ContractVtxo[], destination: string, delegateAt?: Date): Promise<{
32
37
  delegated: Outpoint[];
33
38
  failed: {
34
39
  outpoints: Outpoint[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/sdk",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "description": "TypeScript SDK for building Bitcoin wallets using the Arkade protocol",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -109,9 +109,9 @@
109
109
  "peerDependencies": {
110
110
  "@react-native-async-storage/async-storage": ">=1.0.0",
111
111
  "expo": ">=54.0.0",
112
- "expo-background-task": "~1.0.10",
113
- "expo-sqlite": "~16.0.10",
114
- "expo-task-manager": "~14.0.9"
112
+ "expo-background-task": "~1.0.10 || >=55.0.0",
113
+ "expo-sqlite": "~16.0.10 || >=55.0.0",
114
+ "expo-task-manager": "~14.0.9 || >=55.0.0"
115
115
  },
116
116
  "peerDependenciesMeta": {
117
117
  "expo": {