@arkade-os/sdk 0.3.8 → 0.3.10

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.
Files changed (41) hide show
  1. package/README.md +78 -1
  2. package/dist/cjs/identity/singleKey.js +33 -1
  3. package/dist/cjs/index.js +17 -2
  4. package/dist/cjs/intent/index.js +31 -2
  5. package/dist/cjs/providers/ark.js +15 -5
  6. package/dist/cjs/providers/indexer.js +2 -2
  7. package/dist/cjs/wallet/batch.js +183 -0
  8. package/dist/cjs/wallet/index.js +15 -0
  9. package/dist/cjs/wallet/serviceWorker/request.js +0 -2
  10. package/dist/cjs/wallet/serviceWorker/wallet.js +98 -34
  11. package/dist/cjs/wallet/serviceWorker/worker.js +163 -72
  12. package/dist/cjs/wallet/utils.js +2 -2
  13. package/dist/cjs/wallet/vtxo-manager.js +5 -0
  14. package/dist/cjs/wallet/wallet.js +358 -360
  15. package/dist/esm/identity/singleKey.js +31 -0
  16. package/dist/esm/index.js +12 -7
  17. package/dist/esm/intent/index.js +31 -2
  18. package/dist/esm/providers/ark.js +15 -5
  19. package/dist/esm/providers/indexer.js +2 -2
  20. package/dist/esm/wallet/batch.js +180 -0
  21. package/dist/esm/wallet/index.js +14 -0
  22. package/dist/esm/wallet/serviceWorker/request.js +0 -2
  23. package/dist/esm/wallet/serviceWorker/wallet.js +96 -33
  24. package/dist/esm/wallet/serviceWorker/worker.js +165 -74
  25. package/dist/esm/wallet/utils.js +2 -2
  26. package/dist/esm/wallet/vtxo-manager.js +6 -1
  27. package/dist/esm/wallet/wallet.js +359 -363
  28. package/dist/types/identity/index.d.ts +5 -3
  29. package/dist/types/identity/singleKey.d.ts +20 -1
  30. package/dist/types/index.d.ts +11 -8
  31. package/dist/types/intent/index.d.ts +19 -2
  32. package/dist/types/providers/ark.d.ts +9 -8
  33. package/dist/types/providers/indexer.d.ts +2 -2
  34. package/dist/types/wallet/batch.d.ts +87 -0
  35. package/dist/types/wallet/index.d.ts +76 -16
  36. package/dist/types/wallet/serviceWorker/request.d.ts +5 -1
  37. package/dist/types/wallet/serviceWorker/wallet.d.ts +46 -15
  38. package/dist/types/wallet/serviceWorker/worker.d.ts +6 -3
  39. package/dist/types/wallet/utils.d.ts +8 -3
  40. package/dist/types/wallet/wallet.d.ts +87 -36
  41. package/package.json +1 -1
@@ -83,4 +83,35 @@ export class SingleKey {
83
83
  return signAsync(message, this.key, { prehash: false });
84
84
  return schnorr.signAsync(message, this.key);
85
85
  }
86
+ async toReadonly() {
87
+ return new ReadonlySingleKey(await this.compressedPublicKey());
88
+ }
89
+ }
90
+ export class ReadonlySingleKey {
91
+ constructor(publicKey) {
92
+ this.publicKey = publicKey;
93
+ if (publicKey.length !== 33) {
94
+ throw new Error("Invalid public key length");
95
+ }
96
+ }
97
+ /**
98
+ * Create a ReadonlySingleKey from a compressed public key.
99
+ *
100
+ * @param publicKey - 33-byte compressed public key (02/03 prefix + 32-byte x coordinate)
101
+ * @returns A new ReadonlySingleKey instance
102
+ * @example
103
+ * ```typescript
104
+ * const pubkey = new Uint8Array(33); // your compressed public key
105
+ * const readonlyKey = ReadonlySingleKey.fromPublicKey(pubkey);
106
+ * ```
107
+ */
108
+ static fromPublicKey(publicKey) {
109
+ return new ReadonlySingleKey(publicKey);
110
+ }
111
+ xOnlyPublicKey() {
112
+ return Promise.resolve(this.publicKey.slice(1));
113
+ }
114
+ compressedPublicKey() {
115
+ return Promise.resolve(this.publicKey);
116
+ }
86
117
  }
package/dist/esm/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { Transaction } from './utils/transaction.js';
2
- import { SingleKey } from './identity/singleKey.js';
2
+ import { SingleKey, ReadonlySingleKey } from './identity/singleKey.js';
3
3
  import { ArkAddress } from './script/address.js';
4
4
  import { VHTLC } from './script/vhtlc.js';
5
5
  import { DefaultVtxo } from './script/default.js';
6
6
  import { VtxoScript, TapTreeCoder, } from './script/base.js';
7
- import { TxType, } from './wallet/index.js';
8
- import { Wallet, waitForIncomingFunds } from './wallet/wallet.js';
7
+ import { TxType, isSpendable, isSubdust, isRecoverable, isExpired, } from './wallet/index.js';
8
+ import { Batch } from './wallet/batch.js';
9
+ import { Wallet, ReadonlyWallet, waitForIncomingFunds, getSequence, } from './wallet/wallet.js';
9
10
  import { TxTree } from './tree/txTree.js';
10
11
  import { Ramps } from './wallet/ramps.js';
11
12
  import { isVtxoExpiringSoon, VtxoManager } from './wallet/vtxo-manager.js';
12
- import { ServiceWorkerWallet } from './wallet/serviceWorker/wallet.js';
13
+ import { ServiceWorkerWallet, ServiceWorkerReadonlyWallet, } from './wallet/serviceWorker/wallet.js';
13
14
  import { OnchainWallet } from './wallet/onchain.js';
14
15
  import { setupServiceWorker } from './wallet/serviceWorker/utils.js';
15
16
  import { Worker } from './wallet/serviceWorker/worker.js';
@@ -29,9 +30,11 @@ import { Unroll } from './wallet/unroll.js';
29
30
  import { WalletRepositoryImpl } from './repositories/walletRepository.js';
30
31
  import { ContractRepositoryImpl } from './repositories/contractRepository.js';
31
32
  import { ArkError, maybeArkError } from './providers/errors.js';
33
+ import { validateVtxoTxGraph, validateConnectorsTxGraph, } from './tree/validation.js';
34
+ import { buildForfeitTx } from './forfeit.js';
32
35
  export {
33
36
  // Wallets
34
- Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager,
37
+ Wallet, ReadonlyWallet, SingleKey, ReadonlySingleKey, OnchainWallet, Ramps, VtxoManager,
35
38
  // Providers
36
39
  ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider,
37
40
  // Script-related
@@ -39,7 +42,7 @@ ArkAddress, DefaultVtxo, VtxoScript, VHTLC,
39
42
  // Enums
40
43
  TxType, IndexerTxType, ChainTxType, SettlementEventType,
41
44
  // Service Worker
42
- setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response,
45
+ setupServiceWorker, Worker, ServiceWorkerWallet, ServiceWorkerReadonlyWallet, Request, Response,
43
46
  // Tapscript
44
47
  decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, TapTreeCoder,
45
48
  // Ark PSBT fields
@@ -59,4 +62,6 @@ TxTree,
59
62
  // Anchor
60
63
  P2A, Unroll, Transaction,
61
64
  // Errors
62
- ArkError, maybeArkError, };
65
+ ArkError, maybeArkError,
66
+ // Batch session
67
+ Batch, validateVtxoTxGraph, validateConnectorsTxGraph, buildForfeitTx, isRecoverable, isSpendable, isSubdust, isExpired, getSequence, };
@@ -33,12 +33,15 @@ export var Intent;
33
33
  * ownership of VTXOs and UTXOs. The proof includes the message to be
34
34
  * signed and the inputs/outputs that demonstrate ownership.
35
35
  *
36
- * @param message - The Intent message to be signed
36
+ * @param message - The Intent message to be signed, either raw string of Message object
37
37
  * @param inputs - Array of transaction inputs to prove ownership of
38
38
  * @param outputs - Optional array of transaction outputs
39
39
  * @returns An unsigned Intent proof transaction
40
40
  */
41
41
  function create(message, inputs, outputs = []) {
42
+ if (typeof message !== "string") {
43
+ message = encodeMessage(message);
44
+ }
42
45
  if (inputs.length == 0)
43
46
  throw new Error("intent proof requires at least one input");
44
47
  if (!validateInputs(inputs))
@@ -51,6 +54,29 @@ export var Intent;
51
54
  return craftToSignTx(toSpend, inputs, outputs);
52
55
  }
53
56
  Intent.create = create;
57
+ function encodeMessage(message) {
58
+ switch (message.type) {
59
+ case "register":
60
+ return JSON.stringify({
61
+ type: "register",
62
+ onchain_output_indexes: message.onchain_output_indexes,
63
+ valid_at: message.valid_at,
64
+ expire_at: message.expire_at,
65
+ cosigners_public_keys: message.cosigners_public_keys,
66
+ });
67
+ case "delete":
68
+ return JSON.stringify({
69
+ type: "delete",
70
+ expire_at: message.expire_at,
71
+ });
72
+ case "get-pending-tx":
73
+ return JSON.stringify({
74
+ type: "get-pending-tx",
75
+ expire_at: message.expire_at,
76
+ });
77
+ }
78
+ }
79
+ Intent.encodeMessage = encodeMessage;
54
80
  })(Intent || (Intent = {}));
55
81
  const OP_RETURN_EMPTY_PKSCRIPT = new Uint8Array([OP.RETURN]);
56
82
  const ZERO_32 = new Uint8Array(32).fill(0);
@@ -105,9 +131,12 @@ function craftToSpendTx(message, pkScript) {
105
131
  // craftToSignTx creates the transaction that will be signed for the proof
106
132
  function craftToSignTx(toSpend, inputs, outputs) {
107
133
  const firstInput = inputs[0];
134
+ const lockTime = inputs
135
+ .map((input) => input.sequence || 0)
136
+ .reduce((a, b) => Math.max(a, b), 0);
108
137
  const tx = new Transaction({
109
138
  version: 2,
110
- lockTime: 0,
139
+ lockTime,
111
140
  });
112
141
  // add the first "toSpend" input
113
142
  tx.addInput({
@@ -1,6 +1,7 @@
1
1
  import { hex } from "@scure/base";
2
2
  import { eventSourceIterator } from './utils.js';
3
3
  import { maybeArkError } from './errors.js';
4
+ import { Intent } from '../intent/index.js';
4
5
  export var SettlementEventType;
5
6
  (function (SettlementEventType) {
6
7
  SettlementEventType["BatchStarted"] = "batch_started";
@@ -45,8 +46,12 @@ export class RestArkProvider {
45
46
  fees: {
46
47
  intentFee: {
47
48
  ...fromServer.fees?.intentFee,
48
- onchainInput: BigInt(fromServer.fees?.intentFee?.onchainInput ?? 0),
49
- onchainOutput: BigInt(fromServer.fees?.intentFee?.onchainOutput ?? 0),
49
+ onchainInput: BigInt(
50
+ // split(".")[0] to remove the decimal part
51
+ (fromServer.fees?.intentFee?.onchainInput ?? "0").split(".")[0] ?? 0),
52
+ onchainOutput: BigInt(
53
+ // split(".")[0] to remove the decimal part
54
+ (fromServer.fees?.intentFee?.onchainOutput ?? "0").split(".")[0] ?? 0),
50
55
  },
51
56
  txFeeRate: fromServer?.fees?.txFeeRate ?? "",
52
57
  },
@@ -123,7 +128,7 @@ export class RestArkProvider {
123
128
  body: JSON.stringify({
124
129
  intent: {
125
130
  proof: intent.proof,
126
- message: intent.message,
131
+ message: Intent.encodeMessage(intent.message),
127
132
  },
128
133
  }),
129
134
  });
@@ -144,7 +149,7 @@ export class RestArkProvider {
144
149
  body: JSON.stringify({
145
150
  intent: {
146
151
  proof: intent.proof,
147
- message: intent.message,
152
+ message: Intent.encodeMessage(intent.message),
148
153
  },
149
154
  }),
150
155
  });
@@ -324,7 +329,12 @@ export class RestArkProvider {
324
329
  headers: {
325
330
  "Content-Type": "application/json",
326
331
  },
327
- body: JSON.stringify({ intent }),
332
+ body: JSON.stringify({
333
+ intent: {
334
+ proof: intent.proof,
335
+ message: Intent.encodeMessage(intent.message),
336
+ },
337
+ }),
328
338
  });
329
339
  if (!response.ok) {
330
340
  const errorText = await response.text();
@@ -361,7 +361,7 @@ function convertVtxo(vtxo) {
361
361
  // Unexported namespace for type guards only
362
362
  var Response;
363
363
  (function (Response) {
364
- function isBatch(data) {
364
+ function isBatchInfo(data) {
365
365
  return (typeof data === "object" &&
366
366
  typeof data.totalOutputAmount === "string" &&
367
367
  typeof data.totalOutputVtxos === "number" &&
@@ -385,7 +385,7 @@ var Response;
385
385
  typeof data.totalOutputAmount === "string" &&
386
386
  typeof data.totalOutputVtxos === "number" &&
387
387
  typeof data.batches === "object" &&
388
- Object.values(data.batches).every(isBatch));
388
+ Object.values(data.batches).every(isBatchInfo));
389
389
  }
390
390
  Response.isCommitmentTx = isCommitmentTx;
391
391
  function isOutpoint(data) {
@@ -0,0 +1,180 @@
1
+ import { SettlementEventType } from '../providers/ark.js';
2
+ import { TxTree } from '../tree/txTree.js';
3
+ import { hex } from "@scure/base";
4
+ /**
5
+ * Batch namespace provides utilities for joining and processing batch session.
6
+ * The batch settlement process involves multiple events, this namespace provides abstractions and types to handle them.
7
+ * @see https://docs.arkadeos.com/learn/pillars/batch-swaps
8
+ * @example
9
+ * ```typescript
10
+ * // use wallet handler or create a custom one
11
+ * const handler = wallet.createBatchHandler(intentId, inputs, musig2session);
12
+ *
13
+ * const abortController = new AbortController();
14
+ * // Get event stream from Ark provider
15
+ * const eventStream = arkProvider.getEventStream(
16
+ * abortController.signal,
17
+ * ['your-topic-1', 'your-topic-2']
18
+ * );
19
+ *
20
+ * // Join the batch and process events
21
+ * try {
22
+ * const commitmentTxid = await Batch.join(eventStream, handler);
23
+ * console.log('Batch completed with commitment:', commitmentTxid);
24
+ * } catch (error) {
25
+ * console.error('Batch processing failed:', error);
26
+ * } finally {
27
+ * abortController.abort();
28
+ * }
29
+ * ```
30
+ */
31
+ export var Batch;
32
+ (function (Batch) {
33
+ // State machine steps for batch session
34
+ let Step;
35
+ (function (Step) {
36
+ Step["Start"] = "start";
37
+ Step["BatchStarted"] = "batch_started";
38
+ Step["TreeSigningStarted"] = "tree_signing_started";
39
+ Step["TreeNoncesAggregated"] = "tree_nonces_aggregated";
40
+ Step["BatchFinalization"] = "batch_finalization";
41
+ })(Step || (Step = {}));
42
+ /**
43
+ * Start the state machine that will process the batch events and join a batch.
44
+ * @param eventIterator - The events stream to process.
45
+ * @param handler - How to react to events.
46
+ * @param options - Options.
47
+ */
48
+ async function join(eventIterator, handler, options = {}) {
49
+ const { abortController, skipVtxoTreeSigning = false, eventCallback, } = options;
50
+ let step = Step.Start;
51
+ // keep track of tree transactions as they arrive
52
+ const flatVtxoTree = [];
53
+ const flatConnectorTree = [];
54
+ // once everything is collected, the TxTree objects are created
55
+ let vtxoTree = undefined;
56
+ let connectorTree = undefined;
57
+ for await (const event of eventIterator) {
58
+ if (abortController?.signal.aborted) {
59
+ throw new Error("canceled");
60
+ }
61
+ if (eventCallback) {
62
+ // don't wait for the callback to complete and ignore errors
63
+ eventCallback(event).catch(() => { });
64
+ }
65
+ switch (event.type) {
66
+ case SettlementEventType.BatchStarted: {
67
+ const e = event;
68
+ const { skip } = await handler.onBatchStarted(e);
69
+ if (!skip) {
70
+ step = Step.BatchStarted;
71
+ if (skipVtxoTreeSigning) {
72
+ // skip TxTree events and musig2 signatures and nonces
73
+ step = Step.TreeNoncesAggregated;
74
+ }
75
+ }
76
+ continue;
77
+ }
78
+ case SettlementEventType.BatchFinalized: {
79
+ if (step !== Step.BatchFinalization) {
80
+ continue;
81
+ }
82
+ if (handler.onBatchFinalized) {
83
+ await handler.onBatchFinalized(event);
84
+ }
85
+ return event.commitmentTxid;
86
+ }
87
+ case SettlementEventType.BatchFailed: {
88
+ if (handler.onBatchFailed) {
89
+ await handler.onBatchFailed(event);
90
+ continue;
91
+ }
92
+ throw new Error(event.reason);
93
+ }
94
+ case SettlementEventType.TreeTx: {
95
+ if (step !== Step.BatchStarted &&
96
+ step !== Step.TreeNoncesAggregated) {
97
+ continue;
98
+ }
99
+ // batchIndex 0 = vtxo tree, batchIndex 1 = connector tree
100
+ if (event.batchIndex === 0) {
101
+ flatVtxoTree.push(event.chunk);
102
+ }
103
+ else {
104
+ flatConnectorTree.push(event.chunk);
105
+ }
106
+ if (handler.onTreeTxEvent) {
107
+ await handler.onTreeTxEvent(event);
108
+ }
109
+ continue;
110
+ }
111
+ case SettlementEventType.TreeSignature: {
112
+ if (step !== Step.TreeNoncesAggregated) {
113
+ continue;
114
+ }
115
+ if (!vtxoTree) {
116
+ throw new Error("vtxo tree not initialized");
117
+ }
118
+ // push signature to the vtxo tree
119
+ const tapKeySig = hex.decode(event.signature);
120
+ vtxoTree.update(event.txid, (tx) => {
121
+ tx.updateInput(0, {
122
+ tapKeySig,
123
+ });
124
+ });
125
+ if (handler.onTreeSignatureEvent) {
126
+ await handler.onTreeSignatureEvent(event);
127
+ }
128
+ continue;
129
+ }
130
+ case SettlementEventType.TreeSigningStarted: {
131
+ if (step !== Step.BatchStarted) {
132
+ continue;
133
+ }
134
+ // create vtxo tree from collected chunks
135
+ vtxoTree = TxTree.create(flatVtxoTree);
136
+ const { skip } = await handler.onTreeSigningStarted(event, vtxoTree);
137
+ if (!skip) {
138
+ step = Step.TreeSigningStarted;
139
+ }
140
+ continue;
141
+ }
142
+ case SettlementEventType.TreeNonces: {
143
+ if (step !== Step.TreeSigningStarted) {
144
+ continue;
145
+ }
146
+ const { fullySigned } = await handler.onTreeNonces(event);
147
+ if (fullySigned) {
148
+ step = Step.TreeNoncesAggregated;
149
+ }
150
+ continue;
151
+ }
152
+ case SettlementEventType.BatchFinalization: {
153
+ if (step !== Step.TreeNoncesAggregated) {
154
+ continue;
155
+ }
156
+ // Build vtxo tree if it hasn't been built yet
157
+ if (!vtxoTree && flatVtxoTree.length > 0) {
158
+ vtxoTree = TxTree.create(flatVtxoTree);
159
+ }
160
+ if (!vtxoTree && !skipVtxoTreeSigning) {
161
+ throw new Error("vtxo tree not initialized");
162
+ }
163
+ // Build connector tree if we have chunks
164
+ if (flatConnectorTree.length > 0) {
165
+ connectorTree = TxTree.create(flatConnectorTree);
166
+ }
167
+ await handler.onBatchFinalization(event, vtxoTree, connectorTree);
168
+ step = Step.BatchFinalization;
169
+ continue;
170
+ }
171
+ default:
172
+ // unknown event type, continue
173
+ continue;
174
+ }
175
+ }
176
+ // iterator closed without finalization, something went wrong
177
+ throw new Error("event stream closed");
178
+ }
179
+ Batch.join = join;
180
+ })(Batch || (Batch = {}));
@@ -9,6 +9,20 @@ export function isSpendable(vtxo) {
9
9
  export function isRecoverable(vtxo) {
10
10
  return vtxo.virtualStatus.state === "swept" && isSpendable(vtxo);
11
11
  }
12
+ export function isExpired(vtxo) {
13
+ if (vtxo.virtualStatus.state === "swept")
14
+ return true; // swept by server = expired
15
+ const expiry = vtxo.virtualStatus.batchExpiry;
16
+ if (!expiry)
17
+ return false;
18
+ // we use this as a workaround to avoid issue on regtest where expiry date is expressed in blockheight instead of timestamp
19
+ // if expiry, as Date, is before 2025, then we admit it's too small to be a timestamp
20
+ // TODO: API should return the expiry unit
21
+ const expireAt = new Date(expiry);
22
+ if (expireAt.getFullYear() < 2025)
23
+ return false;
24
+ return expiry <= Date.now();
25
+ }
12
26
  export function isSubdust(vtxo, dust) {
13
27
  return vtxo.value < dust;
14
28
  }
@@ -11,8 +11,6 @@ export var Request;
11
11
  return (message.type === "INIT_WALLET" &&
12
12
  "arkServerUrl" in message &&
13
13
  typeof message.arkServerUrl === "string" &&
14
- "privateKey" in message &&
15
- typeof message.privateKey === "string" &&
16
14
  ("arkServerPublicKey" in message
17
15
  ? message.arkServerPublicKey === undefined ||
18
16
  typeof message.arkServerPublicKey === "string"
@@ -13,7 +13,16 @@ class UnexpectedResponseError extends Error {
13
13
  this.name = "UnexpectedResponseError";
14
14
  }
15
15
  }
16
- export class ServiceWorkerWallet {
16
+ const createCommon = (options) => {
17
+ // Default to IndexedDB for service worker context
18
+ const storage = new IndexedDBStorageAdapter(options.dbName || DEFAULT_DB_NAME, options.dbVersion);
19
+ // Create repositories
20
+ return {
21
+ walletRepo: new WalletRepositoryImpl(storage),
22
+ contractRepo: new ContractRepositoryImpl(storage),
23
+ };
24
+ };
25
+ export class ServiceWorkerReadonlyWallet {
17
26
  constructor(serviceWorker, identity, walletRepository, contractRepository) {
18
27
  this.serviceWorker = serviceWorker;
19
28
  this.identity = identity;
@@ -21,27 +30,17 @@ export class ServiceWorkerWallet {
21
30
  this.contractRepository = contractRepository;
22
31
  }
23
32
  static async create(options) {
24
- // Default to IndexedDB for service worker context
25
- const storage = new IndexedDBStorageAdapter(options.dbName || DEFAULT_DB_NAME, options.dbVersion);
26
- // Create repositories
27
- const walletRepo = new WalletRepositoryImpl(storage);
28
- const contractRepo = new ContractRepositoryImpl(storage);
29
- // Extract identity and check if it can expose private key
30
- const identity = isPrivateKeyIdentity(options.identity)
31
- ? options.identity
32
- : null;
33
- if (!identity) {
34
- throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose a single private key");
35
- }
36
- // Extract private key for service worker initialization
37
- const privateKey = identity.toHex();
33
+ const { walletRepo, contractRepo } = createCommon(options);
38
34
  // Create the wallet instance
39
- const wallet = new ServiceWorkerWallet(options.serviceWorker, identity, walletRepo, contractRepo);
35
+ const wallet = new ServiceWorkerReadonlyWallet(options.serviceWorker, options.identity, walletRepo, contractRepo);
36
+ const publicKey = await options.identity
37
+ .compressedPublicKey()
38
+ .then(hex.encode);
40
39
  // Initialize the service worker with the config
41
40
  const initMessage = {
42
41
  type: "INIT_WALLET",
43
42
  id: getRandomId(),
44
- privateKey,
43
+ key: { publicKey },
45
44
  arkServerUrl: options.arkServerUrl,
46
45
  arkServerPublicKey: options.arkServerPublicKey,
47
46
  };
@@ -56,14 +55,14 @@ export class ServiceWorkerWallet {
56
55
  * @example
57
56
  * ```typescript
58
57
  * // One-liner setup - handles everything automatically!
59
- * const wallet = await ServiceWorkerWallet.setup({
58
+ * const wallet = await ServiceWorkerReadonlyWallet.setup({
60
59
  * serviceWorkerPath: '/service-worker.js',
61
60
  * arkServerUrl: 'https://mutinynet.arkade.sh'
62
61
  * });
63
62
  *
64
- * // With custom identity
65
- * const identity = SingleKey.fromHex('your_private_key_hex');
66
- * const wallet = await ServiceWorkerWallet.setup({
63
+ * // With custom readonly identity
64
+ * const identity = ReadonlySingleKey.fromPublicKey('your_public_key_hex');
65
+ * const wallet = await ServiceWorkerReadonlyWallet.setup({
67
66
  * serviceWorkerPath: '/service-worker.js',
68
67
  * arkServerUrl: 'https://mutinynet.arkade.sh',
69
68
  * identity
@@ -74,7 +73,7 @@ export class ServiceWorkerWallet {
74
73
  // Register and setup the service worker
75
74
  const serviceWorker = await setupServiceWorker(options.serviceWorkerPath);
76
75
  // Use the existing create method
77
- return ServiceWorkerWallet.create({
76
+ return ServiceWorkerReadonlyWallet.create({
78
77
  ...options,
79
78
  serviceWorker,
80
79
  });
@@ -226,6 +225,81 @@ export class ServiceWorkerWallet {
226
225
  throw new Error(`Failed to get vtxos: ${error}`);
227
226
  }
228
227
  }
228
+ async reload() {
229
+ const message = {
230
+ type: "RELOAD_WALLET",
231
+ id: getRandomId(),
232
+ };
233
+ const response = await this.sendMessage(message);
234
+ if (Response.isWalletReloaded(response)) {
235
+ return response.success;
236
+ }
237
+ throw new UnexpectedResponseError(response);
238
+ }
239
+ }
240
+ export class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
241
+ constructor(serviceWorker, identity, walletRepository, contractRepository) {
242
+ super(serviceWorker, identity, walletRepository, contractRepository);
243
+ this.serviceWorker = serviceWorker;
244
+ this.identity = identity;
245
+ this.walletRepository = walletRepository;
246
+ this.contractRepository = contractRepository;
247
+ }
248
+ static async create(options) {
249
+ const { walletRepo, contractRepo } = createCommon(options);
250
+ // Extract identity and check if it can expose private key
251
+ const identity = isPrivateKeyIdentity(options.identity)
252
+ ? options.identity
253
+ : null;
254
+ if (!identity) {
255
+ throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose a single private key");
256
+ }
257
+ // Extract private key for service worker initialization
258
+ const privateKey = identity.toHex();
259
+ // Create the wallet instance
260
+ const wallet = new ServiceWorkerWallet(options.serviceWorker, identity, walletRepo, contractRepo);
261
+ // Initialize the service worker with the config
262
+ const initMessage = {
263
+ type: "INIT_WALLET",
264
+ id: getRandomId(),
265
+ key: { privateKey },
266
+ arkServerUrl: options.arkServerUrl,
267
+ arkServerPublicKey: options.arkServerPublicKey,
268
+ };
269
+ // Initialize the service worker
270
+ await wallet.sendMessage(initMessage);
271
+ return wallet;
272
+ }
273
+ /**
274
+ * Simplified setup method that handles service worker registration,
275
+ * identity creation, and wallet initialization automatically.
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * // One-liner setup - handles everything automatically!
280
+ * const wallet = await ServiceWorkerWallet.setup({
281
+ * serviceWorkerPath: '/service-worker.js',
282
+ * arkServerUrl: 'https://mutinynet.arkade.sh'
283
+ * });
284
+ *
285
+ * // With custom identity
286
+ * const identity = SingleKey.fromHex('your_private_key_hex');
287
+ * const wallet = await ServiceWorkerWallet.setup({
288
+ * serviceWorkerPath: '/service-worker.js',
289
+ * arkServerUrl: 'https://mutinynet.arkade.sh',
290
+ * identity
291
+ * });
292
+ * ```
293
+ */
294
+ static async setup(options) {
295
+ // Register and setup the service worker
296
+ const serviceWorker = await setupServiceWorker(options.serviceWorkerPath);
297
+ // Use the existing create method
298
+ return ServiceWorkerWallet.create({
299
+ ...options,
300
+ serviceWorker,
301
+ });
302
+ }
229
303
  async sendBitcoin(params) {
230
304
  const message = {
231
305
  type: "SEND_BITCOIN",
@@ -283,17 +357,6 @@ export class ServiceWorkerWallet {
283
357
  throw new Error(`Settlement failed: ${error}`);
284
358
  }
285
359
  }
286
- async reload() {
287
- const message = {
288
- type: "RELOAD_WALLET",
289
- id: getRandomId(),
290
- };
291
- const response = await this.sendMessage(message);
292
- if (Response.isWalletReloaded(response)) {
293
- return response.success;
294
- }
295
- throw new UnexpectedResponseError(response);
296
- }
297
360
  }
298
361
  function getRandomId() {
299
362
  const randomValue = crypto.getRandomValues(new Uint8Array(16));