@arkade-os/sdk 0.3.7 → 0.3.9
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 +78 -1
- package/dist/cjs/identity/singleKey.js +33 -1
- package/dist/cjs/index.js +17 -2
- package/dist/cjs/intent/index.js +31 -2
- package/dist/cjs/providers/ark.js +9 -3
- package/dist/cjs/providers/indexer.js +2 -2
- package/dist/cjs/wallet/batch.js +183 -0
- package/dist/cjs/wallet/index.js +15 -0
- package/dist/cjs/wallet/serviceWorker/request.js +0 -2
- package/dist/cjs/wallet/serviceWorker/wallet.js +98 -34
- package/dist/cjs/wallet/serviceWorker/worker.js +169 -69
- package/dist/cjs/wallet/utils.js +2 -2
- package/dist/cjs/wallet/vtxo-manager.js +5 -0
- package/dist/cjs/wallet/wallet.js +399 -356
- package/dist/esm/identity/singleKey.js +31 -0
- package/dist/esm/index.js +12 -7
- package/dist/esm/intent/index.js +31 -2
- package/dist/esm/providers/ark.js +9 -3
- package/dist/esm/providers/indexer.js +2 -2
- package/dist/esm/wallet/batch.js +180 -0
- package/dist/esm/wallet/index.js +14 -0
- package/dist/esm/wallet/serviceWorker/request.js +0 -2
- package/dist/esm/wallet/serviceWorker/wallet.js +96 -33
- package/dist/esm/wallet/serviceWorker/worker.js +171 -71
- package/dist/esm/wallet/utils.js +2 -2
- package/dist/esm/wallet/vtxo-manager.js +6 -1
- package/dist/esm/wallet/wallet.js +400 -359
- package/dist/types/identity/index.d.ts +5 -3
- package/dist/types/identity/singleKey.d.ts +20 -1
- package/dist/types/index.d.ts +11 -8
- package/dist/types/intent/index.d.ts +19 -2
- package/dist/types/providers/ark.d.ts +9 -8
- package/dist/types/providers/indexer.d.ts +2 -2
- package/dist/types/wallet/batch.d.ts +87 -0
- package/dist/types/wallet/index.d.ts +75 -16
- package/dist/types/wallet/serviceWorker/request.d.ts +5 -1
- package/dist/types/wallet/serviceWorker/wallet.d.ts +46 -15
- package/dist/types/wallet/serviceWorker/worker.d.ts +6 -3
- package/dist/types/wallet/utils.d.ts +8 -3
- package/dist/types/wallet/wallet.d.ts +96 -35
- package/package.json +123 -113
package/README.md
CHANGED
|
@@ -25,13 +25,90 @@ const identity = SingleKey.fromHex('your_private_key_hex')
|
|
|
25
25
|
const wallet = await Wallet.create({
|
|
26
26
|
identity,
|
|
27
27
|
// Esplora API, can be left empty - mempool.space API will be used
|
|
28
|
-
esploraUrl: 'https://mutinynet.com/api',
|
|
28
|
+
esploraUrl: 'https://mutinynet.com/api',
|
|
29
29
|
arkServerUrl: 'https://mutinynet.arkade.sh',
|
|
30
30
|
// Optional: specify storage adapter (defaults to InMemoryStorageAdapter)
|
|
31
31
|
// storage: new LocalStorageAdapter() // for browser persistence
|
|
32
32
|
})
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Readonly Wallets (Watch-Only)
|
|
36
|
+
|
|
37
|
+
The SDK supports readonly wallets that allow you to query wallet state without exposing private keys. This is useful for:
|
|
38
|
+
|
|
39
|
+
- **Watch-only wallets**: Monitor addresses and balances without transaction capabilities
|
|
40
|
+
- **Public interfaces**: Display wallet information safely in public-facing applications
|
|
41
|
+
- **Separate concerns**: Keep signing operations isolated from query operations
|
|
42
|
+
|
|
43
|
+
#### Creating a Readonly Wallet
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { ReadonlySingleKey, ReadonlyWallet } from '@arkade-os/sdk'
|
|
47
|
+
|
|
48
|
+
// Create a readonly identity from a public key
|
|
49
|
+
const identity = SingleKey.fromHex('your_public_key_hex')
|
|
50
|
+
const publicKey = await identity.compressedPublicKey()
|
|
51
|
+
const readonlyIdentity = ReadonlySingleKey.fromPublicKey(publicKey)
|
|
52
|
+
|
|
53
|
+
// Create a readonly wallet
|
|
54
|
+
const readonlyWallet = await ReadonlyWallet.create({
|
|
55
|
+
identity: readonlyIdentity,
|
|
56
|
+
arkServerUrl: 'https://mutinynet.arkade.sh'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Query operations work normally
|
|
60
|
+
const address = await readonlyWallet.getAddress()
|
|
61
|
+
const balance = await readonlyWallet.getBalance()
|
|
62
|
+
const vtxos = await readonlyWallet.getVtxos()
|
|
63
|
+
const history = await readonlyWallet.getTransactionHistory()
|
|
64
|
+
|
|
65
|
+
// Transaction methods are not available (TypeScript will prevent this)
|
|
66
|
+
// await readonlyWallet.sendBitcoin(...) // ❌ Type error!
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Converting Wallets to Readonly
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Wallet, SingleKey } from '@arkade-os/sdk'
|
|
73
|
+
|
|
74
|
+
// Create a full wallet
|
|
75
|
+
const identity = SingleKey.fromHex('your_private_key_hex')
|
|
76
|
+
const wallet = await Wallet.create({
|
|
77
|
+
identity,
|
|
78
|
+
arkServerUrl: 'https://mutinynet.arkade.sh'
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Convert to readonly wallet (safe to share)
|
|
82
|
+
const readonlyWallet = await wallet.toReadonly()
|
|
83
|
+
|
|
84
|
+
// The readonly wallet can query but not transact
|
|
85
|
+
const balance = await readonlyWallet.getBalance()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### Converting Identity to Readonly
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { SingleKey } from '@arkade-os/sdk'
|
|
92
|
+
|
|
93
|
+
// Full identity
|
|
94
|
+
const identity = SingleKey.fromHex('your_private_key_hex')
|
|
95
|
+
|
|
96
|
+
// Convert to readonly (no signing capability)
|
|
97
|
+
const readonlyIdentity = await identity.toReadonly()
|
|
98
|
+
|
|
99
|
+
// Use in readonly wallet
|
|
100
|
+
const readonlyWallet = await ReadonlyWallet.create({
|
|
101
|
+
identity: readonlyIdentity,
|
|
102
|
+
arkServerUrl: 'https://mutinynet.arkade.sh'
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Benefits:**
|
|
107
|
+
- ✅ Type-safe: Transaction methods don't exist on readonly types
|
|
108
|
+
- ✅ Secure: Private keys never leave the signing environment
|
|
109
|
+
- ✅ Flexible: Convert between full and readonly wallets as needed
|
|
110
|
+
- ✅ Same API: Query operations work identically on both wallet types
|
|
111
|
+
|
|
35
112
|
### Receiving Bitcoin
|
|
36
113
|
|
|
37
114
|
```typescript
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SingleKey = void 0;
|
|
3
|
+
exports.ReadonlySingleKey = exports.SingleKey = void 0;
|
|
4
4
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
5
5
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
6
|
const base_1 = require("@scure/base");
|
|
@@ -86,5 +86,37 @@ class SingleKey {
|
|
|
86
86
|
return (0, secp256k1_1.signAsync)(message, this.key, { prehash: false });
|
|
87
87
|
return secp256k1_1.schnorr.signAsync(message, this.key);
|
|
88
88
|
}
|
|
89
|
+
async toReadonly() {
|
|
90
|
+
return new ReadonlySingleKey(await this.compressedPublicKey());
|
|
91
|
+
}
|
|
89
92
|
}
|
|
90
93
|
exports.SingleKey = SingleKey;
|
|
94
|
+
class ReadonlySingleKey {
|
|
95
|
+
constructor(publicKey) {
|
|
96
|
+
this.publicKey = publicKey;
|
|
97
|
+
if (publicKey.length !== 33) {
|
|
98
|
+
throw new Error("Invalid public key length");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create a ReadonlySingleKey from a compressed public key.
|
|
103
|
+
*
|
|
104
|
+
* @param publicKey - 33-byte compressed public key (02/03 prefix + 32-byte x coordinate)
|
|
105
|
+
* @returns A new ReadonlySingleKey instance
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const pubkey = new Uint8Array(33); // your compressed public key
|
|
109
|
+
* const readonlyKey = ReadonlySingleKey.fromPublicKey(pubkey);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
static fromPublicKey(publicKey) {
|
|
113
|
+
return new ReadonlySingleKey(publicKey);
|
|
114
|
+
}
|
|
115
|
+
xOnlyPublicKey() {
|
|
116
|
+
return Promise.resolve(this.publicKey.slice(1));
|
|
117
|
+
}
|
|
118
|
+
compressedPublicKey() {
|
|
119
|
+
return Promise.resolve(this.publicKey);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.ReadonlySingleKey = ReadonlySingleKey;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.maybeArkError = exports.ArkError = exports.Transaction = exports.Unroll = void 0;
|
|
3
|
+
exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.isVtxoExpiringSoon = exports.combineTapscriptSigs = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.TapTreeCoder = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerReadonlyWallet = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.ReadonlySingleKey = exports.SingleKey = exports.ReadonlyWallet = exports.Wallet = void 0;
|
|
4
|
+
exports.getSequence = exports.isExpired = exports.isSubdust = exports.isSpendable = exports.isRecoverable = exports.buildForfeitTx = exports.validateConnectorsTxGraph = exports.validateVtxoTxGraph = exports.Batch = exports.maybeArkError = exports.ArkError = exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = void 0;
|
|
5
5
|
const transaction_1 = require("./utils/transaction");
|
|
6
6
|
Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_1.Transaction; } });
|
|
7
7
|
const singleKey_1 = require("./identity/singleKey");
|
|
8
8
|
Object.defineProperty(exports, "SingleKey", { enumerable: true, get: function () { return singleKey_1.SingleKey; } });
|
|
9
|
+
Object.defineProperty(exports, "ReadonlySingleKey", { enumerable: true, get: function () { return singleKey_1.ReadonlySingleKey; } });
|
|
9
10
|
const address_1 = require("./script/address");
|
|
10
11
|
Object.defineProperty(exports, "ArkAddress", { enumerable: true, get: function () { return address_1.ArkAddress; } });
|
|
11
12
|
const vhtlc_1 = require("./script/vhtlc");
|
|
@@ -17,9 +18,17 @@ Object.defineProperty(exports, "VtxoScript", { enumerable: true, get: function (
|
|
|
17
18
|
Object.defineProperty(exports, "TapTreeCoder", { enumerable: true, get: function () { return base_1.TapTreeCoder; } });
|
|
18
19
|
const wallet_1 = require("./wallet");
|
|
19
20
|
Object.defineProperty(exports, "TxType", { enumerable: true, get: function () { return wallet_1.TxType; } });
|
|
21
|
+
Object.defineProperty(exports, "isSpendable", { enumerable: true, get: function () { return wallet_1.isSpendable; } });
|
|
22
|
+
Object.defineProperty(exports, "isSubdust", { enumerable: true, get: function () { return wallet_1.isSubdust; } });
|
|
23
|
+
Object.defineProperty(exports, "isRecoverable", { enumerable: true, get: function () { return wallet_1.isRecoverable; } });
|
|
24
|
+
Object.defineProperty(exports, "isExpired", { enumerable: true, get: function () { return wallet_1.isExpired; } });
|
|
25
|
+
const batch_1 = require("./wallet/batch");
|
|
26
|
+
Object.defineProperty(exports, "Batch", { enumerable: true, get: function () { return batch_1.Batch; } });
|
|
20
27
|
const wallet_2 = require("./wallet/wallet");
|
|
21
28
|
Object.defineProperty(exports, "Wallet", { enumerable: true, get: function () { return wallet_2.Wallet; } });
|
|
29
|
+
Object.defineProperty(exports, "ReadonlyWallet", { enumerable: true, get: function () { return wallet_2.ReadonlyWallet; } });
|
|
22
30
|
Object.defineProperty(exports, "waitForIncomingFunds", { enumerable: true, get: function () { return wallet_2.waitForIncomingFunds; } });
|
|
31
|
+
Object.defineProperty(exports, "getSequence", { enumerable: true, get: function () { return wallet_2.getSequence; } });
|
|
23
32
|
const txTree_1 = require("./tree/txTree");
|
|
24
33
|
Object.defineProperty(exports, "TxTree", { enumerable: true, get: function () { return txTree_1.TxTree; } });
|
|
25
34
|
const ramps_1 = require("./wallet/ramps");
|
|
@@ -29,6 +38,7 @@ Object.defineProperty(exports, "isVtxoExpiringSoon", { enumerable: true, get: fu
|
|
|
29
38
|
Object.defineProperty(exports, "VtxoManager", { enumerable: true, get: function () { return vtxo_manager_1.VtxoManager; } });
|
|
30
39
|
const wallet_3 = require("./wallet/serviceWorker/wallet");
|
|
31
40
|
Object.defineProperty(exports, "ServiceWorkerWallet", { enumerable: true, get: function () { return wallet_3.ServiceWorkerWallet; } });
|
|
41
|
+
Object.defineProperty(exports, "ServiceWorkerReadonlyWallet", { enumerable: true, get: function () { return wallet_3.ServiceWorkerReadonlyWallet; } });
|
|
32
42
|
const onchain_1 = require("./wallet/onchain");
|
|
33
43
|
Object.defineProperty(exports, "OnchainWallet", { enumerable: true, get: function () { return onchain_1.OnchainWallet; } });
|
|
34
44
|
const utils_1 = require("./wallet/serviceWorker/utils");
|
|
@@ -87,3 +97,8 @@ Object.defineProperty(exports, "ContractRepositoryImpl", { enumerable: true, get
|
|
|
87
97
|
const errors_1 = require("./providers/errors");
|
|
88
98
|
Object.defineProperty(exports, "ArkError", { enumerable: true, get: function () { return errors_1.ArkError; } });
|
|
89
99
|
Object.defineProperty(exports, "maybeArkError", { enumerable: true, get: function () { return errors_1.maybeArkError; } });
|
|
100
|
+
const validation_1 = require("./tree/validation");
|
|
101
|
+
Object.defineProperty(exports, "validateVtxoTxGraph", { enumerable: true, get: function () { return validation_1.validateVtxoTxGraph; } });
|
|
102
|
+
Object.defineProperty(exports, "validateConnectorsTxGraph", { enumerable: true, get: function () { return validation_1.validateConnectorsTxGraph; } });
|
|
103
|
+
const forfeit_1 = require("./forfeit");
|
|
104
|
+
Object.defineProperty(exports, "buildForfeitTx", { enumerable: true, get: function () { return forfeit_1.buildForfeitTx; } });
|
package/dist/cjs/intent/index.js
CHANGED
|
@@ -36,12 +36,15 @@ var Intent;
|
|
|
36
36
|
* ownership of VTXOs and UTXOs. The proof includes the message to be
|
|
37
37
|
* signed and the inputs/outputs that demonstrate ownership.
|
|
38
38
|
*
|
|
39
|
-
* @param message - The Intent message to be signed
|
|
39
|
+
* @param message - The Intent message to be signed, either raw string of Message object
|
|
40
40
|
* @param inputs - Array of transaction inputs to prove ownership of
|
|
41
41
|
* @param outputs - Optional array of transaction outputs
|
|
42
42
|
* @returns An unsigned Intent proof transaction
|
|
43
43
|
*/
|
|
44
44
|
function create(message, inputs, outputs = []) {
|
|
45
|
+
if (typeof message !== "string") {
|
|
46
|
+
message = encodeMessage(message);
|
|
47
|
+
}
|
|
45
48
|
if (inputs.length == 0)
|
|
46
49
|
throw new Error("intent proof requires at least one input");
|
|
47
50
|
if (!validateInputs(inputs))
|
|
@@ -54,6 +57,29 @@ var Intent;
|
|
|
54
57
|
return craftToSignTx(toSpend, inputs, outputs);
|
|
55
58
|
}
|
|
56
59
|
Intent.create = create;
|
|
60
|
+
function encodeMessage(message) {
|
|
61
|
+
switch (message.type) {
|
|
62
|
+
case "register":
|
|
63
|
+
return JSON.stringify({
|
|
64
|
+
type: "register",
|
|
65
|
+
onchain_output_indexes: message.onchain_output_indexes,
|
|
66
|
+
valid_at: message.valid_at,
|
|
67
|
+
expire_at: message.expire_at,
|
|
68
|
+
cosigners_public_keys: message.cosigners_public_keys,
|
|
69
|
+
});
|
|
70
|
+
case "delete":
|
|
71
|
+
return JSON.stringify({
|
|
72
|
+
type: "delete",
|
|
73
|
+
expire_at: message.expire_at,
|
|
74
|
+
});
|
|
75
|
+
case "get-pending-tx":
|
|
76
|
+
return JSON.stringify({
|
|
77
|
+
type: "get-pending-tx",
|
|
78
|
+
expire_at: message.expire_at,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
Intent.encodeMessage = encodeMessage;
|
|
57
83
|
})(Intent || (exports.Intent = Intent = {}));
|
|
58
84
|
const OP_RETURN_EMPTY_PKSCRIPT = new Uint8Array([btc_signer_1.OP.RETURN]);
|
|
59
85
|
const ZERO_32 = new Uint8Array(32).fill(0);
|
|
@@ -108,9 +134,12 @@ function craftToSpendTx(message, pkScript) {
|
|
|
108
134
|
// craftToSignTx creates the transaction that will be signed for the proof
|
|
109
135
|
function craftToSignTx(toSpend, inputs, outputs) {
|
|
110
136
|
const firstInput = inputs[0];
|
|
137
|
+
const lockTime = inputs
|
|
138
|
+
.map((input) => input.sequence || 0)
|
|
139
|
+
.reduce((a, b) => Math.max(a, b), 0);
|
|
111
140
|
const tx = new transaction_1.Transaction({
|
|
112
141
|
version: 2,
|
|
113
|
-
lockTime
|
|
142
|
+
lockTime,
|
|
114
143
|
});
|
|
115
144
|
// add the first "toSpend" input
|
|
116
145
|
tx.addInput({
|
|
@@ -5,6 +5,7 @@ exports.isFetchTimeoutError = isFetchTimeoutError;
|
|
|
5
5
|
const base_1 = require("@scure/base");
|
|
6
6
|
const utils_1 = require("./utils");
|
|
7
7
|
const errors_1 = require("./errors");
|
|
8
|
+
const intent_1 = require("../intent");
|
|
8
9
|
var SettlementEventType;
|
|
9
10
|
(function (SettlementEventType) {
|
|
10
11
|
SettlementEventType["BatchStarted"] = "batch_started";
|
|
@@ -127,7 +128,7 @@ class RestArkProvider {
|
|
|
127
128
|
body: JSON.stringify({
|
|
128
129
|
intent: {
|
|
129
130
|
proof: intent.proof,
|
|
130
|
-
message: intent.message,
|
|
131
|
+
message: intent_1.Intent.encodeMessage(intent.message),
|
|
131
132
|
},
|
|
132
133
|
}),
|
|
133
134
|
});
|
|
@@ -148,7 +149,7 @@ class RestArkProvider {
|
|
|
148
149
|
body: JSON.stringify({
|
|
149
150
|
intent: {
|
|
150
151
|
proof: intent.proof,
|
|
151
|
-
message: intent.message,
|
|
152
|
+
message: intent_1.Intent.encodeMessage(intent.message),
|
|
152
153
|
},
|
|
153
154
|
}),
|
|
154
155
|
});
|
|
@@ -328,7 +329,12 @@ class RestArkProvider {
|
|
|
328
329
|
headers: {
|
|
329
330
|
"Content-Type": "application/json",
|
|
330
331
|
},
|
|
331
|
-
body: JSON.stringify({
|
|
332
|
+
body: JSON.stringify({
|
|
333
|
+
intent: {
|
|
334
|
+
proof: intent.proof,
|
|
335
|
+
message: intent_1.Intent.encodeMessage(intent.message),
|
|
336
|
+
},
|
|
337
|
+
}),
|
|
332
338
|
});
|
|
333
339
|
if (!response.ok) {
|
|
334
340
|
const errorText = await response.text();
|
|
@@ -365,7 +365,7 @@ function convertVtxo(vtxo) {
|
|
|
365
365
|
// Unexported namespace for type guards only
|
|
366
366
|
var Response;
|
|
367
367
|
(function (Response) {
|
|
368
|
-
function
|
|
368
|
+
function isBatchInfo(data) {
|
|
369
369
|
return (typeof data === "object" &&
|
|
370
370
|
typeof data.totalOutputAmount === "string" &&
|
|
371
371
|
typeof data.totalOutputVtxos === "number" &&
|
|
@@ -389,7 +389,7 @@ var Response;
|
|
|
389
389
|
typeof data.totalOutputAmount === "string" &&
|
|
390
390
|
typeof data.totalOutputVtxos === "number" &&
|
|
391
391
|
typeof data.batches === "object" &&
|
|
392
|
-
Object.values(data.batches).every(
|
|
392
|
+
Object.values(data.batches).every(isBatchInfo));
|
|
393
393
|
}
|
|
394
394
|
Response.isCommitmentTx = isCommitmentTx;
|
|
395
395
|
function isOutpoint(data) {
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Batch = void 0;
|
|
4
|
+
const ark_1 = require("../providers/ark");
|
|
5
|
+
const txTree_1 = require("../tree/txTree");
|
|
6
|
+
const base_1 = require("@scure/base");
|
|
7
|
+
/**
|
|
8
|
+
* Batch namespace provides utilities for joining and processing batch session.
|
|
9
|
+
* The batch settlement process involves multiple events, this namespace provides abstractions and types to handle them.
|
|
10
|
+
* @see https://docs.arkadeos.com/learn/pillars/batch-swaps
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // use wallet handler or create a custom one
|
|
14
|
+
* const handler = wallet.createBatchHandler(intentId, inputs, musig2session);
|
|
15
|
+
*
|
|
16
|
+
* const abortController = new AbortController();
|
|
17
|
+
* // Get event stream from Ark provider
|
|
18
|
+
* const eventStream = arkProvider.getEventStream(
|
|
19
|
+
* abortController.signal,
|
|
20
|
+
* ['your-topic-1', 'your-topic-2']
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* // Join the batch and process events
|
|
24
|
+
* try {
|
|
25
|
+
* const commitmentTxid = await Batch.join(eventStream, handler);
|
|
26
|
+
* console.log('Batch completed with commitment:', commitmentTxid);
|
|
27
|
+
* } catch (error) {
|
|
28
|
+
* console.error('Batch processing failed:', error);
|
|
29
|
+
* } finally {
|
|
30
|
+
* abortController.abort();
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
var Batch;
|
|
35
|
+
(function (Batch) {
|
|
36
|
+
// State machine steps for batch session
|
|
37
|
+
let Step;
|
|
38
|
+
(function (Step) {
|
|
39
|
+
Step["Start"] = "start";
|
|
40
|
+
Step["BatchStarted"] = "batch_started";
|
|
41
|
+
Step["TreeSigningStarted"] = "tree_signing_started";
|
|
42
|
+
Step["TreeNoncesAggregated"] = "tree_nonces_aggregated";
|
|
43
|
+
Step["BatchFinalization"] = "batch_finalization";
|
|
44
|
+
})(Step || (Step = {}));
|
|
45
|
+
/**
|
|
46
|
+
* Start the state machine that will process the batch events and join a batch.
|
|
47
|
+
* @param eventIterator - The events stream to process.
|
|
48
|
+
* @param handler - How to react to events.
|
|
49
|
+
* @param options - Options.
|
|
50
|
+
*/
|
|
51
|
+
async function join(eventIterator, handler, options = {}) {
|
|
52
|
+
const { abortController, skipVtxoTreeSigning = false, eventCallback, } = options;
|
|
53
|
+
let step = Step.Start;
|
|
54
|
+
// keep track of tree transactions as they arrive
|
|
55
|
+
const flatVtxoTree = [];
|
|
56
|
+
const flatConnectorTree = [];
|
|
57
|
+
// once everything is collected, the TxTree objects are created
|
|
58
|
+
let vtxoTree = undefined;
|
|
59
|
+
let connectorTree = undefined;
|
|
60
|
+
for await (const event of eventIterator) {
|
|
61
|
+
if (abortController?.signal.aborted) {
|
|
62
|
+
throw new Error("canceled");
|
|
63
|
+
}
|
|
64
|
+
if (eventCallback) {
|
|
65
|
+
// don't wait for the callback to complete and ignore errors
|
|
66
|
+
eventCallback(event).catch(() => { });
|
|
67
|
+
}
|
|
68
|
+
switch (event.type) {
|
|
69
|
+
case ark_1.SettlementEventType.BatchStarted: {
|
|
70
|
+
const e = event;
|
|
71
|
+
const { skip } = await handler.onBatchStarted(e);
|
|
72
|
+
if (!skip) {
|
|
73
|
+
step = Step.BatchStarted;
|
|
74
|
+
if (skipVtxoTreeSigning) {
|
|
75
|
+
// skip TxTree events and musig2 signatures and nonces
|
|
76
|
+
step = Step.TreeNoncesAggregated;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
case ark_1.SettlementEventType.BatchFinalized: {
|
|
82
|
+
if (step !== Step.BatchFinalization) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (handler.onBatchFinalized) {
|
|
86
|
+
await handler.onBatchFinalized(event);
|
|
87
|
+
}
|
|
88
|
+
return event.commitmentTxid;
|
|
89
|
+
}
|
|
90
|
+
case ark_1.SettlementEventType.BatchFailed: {
|
|
91
|
+
if (handler.onBatchFailed) {
|
|
92
|
+
await handler.onBatchFailed(event);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
throw new Error(event.reason);
|
|
96
|
+
}
|
|
97
|
+
case ark_1.SettlementEventType.TreeTx: {
|
|
98
|
+
if (step !== Step.BatchStarted &&
|
|
99
|
+
step !== Step.TreeNoncesAggregated) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// batchIndex 0 = vtxo tree, batchIndex 1 = connector tree
|
|
103
|
+
if (event.batchIndex === 0) {
|
|
104
|
+
flatVtxoTree.push(event.chunk);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
flatConnectorTree.push(event.chunk);
|
|
108
|
+
}
|
|
109
|
+
if (handler.onTreeTxEvent) {
|
|
110
|
+
await handler.onTreeTxEvent(event);
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
case ark_1.SettlementEventType.TreeSignature: {
|
|
115
|
+
if (step !== Step.TreeNoncesAggregated) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!vtxoTree) {
|
|
119
|
+
throw new Error("vtxo tree not initialized");
|
|
120
|
+
}
|
|
121
|
+
// push signature to the vtxo tree
|
|
122
|
+
const tapKeySig = base_1.hex.decode(event.signature);
|
|
123
|
+
vtxoTree.update(event.txid, (tx) => {
|
|
124
|
+
tx.updateInput(0, {
|
|
125
|
+
tapKeySig,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
if (handler.onTreeSignatureEvent) {
|
|
129
|
+
await handler.onTreeSignatureEvent(event);
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
case ark_1.SettlementEventType.TreeSigningStarted: {
|
|
134
|
+
if (step !== Step.BatchStarted) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// create vtxo tree from collected chunks
|
|
138
|
+
vtxoTree = txTree_1.TxTree.create(flatVtxoTree);
|
|
139
|
+
const { skip } = await handler.onTreeSigningStarted(event, vtxoTree);
|
|
140
|
+
if (!skip) {
|
|
141
|
+
step = Step.TreeSigningStarted;
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
case ark_1.SettlementEventType.TreeNonces: {
|
|
146
|
+
if (step !== Step.TreeSigningStarted) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const { fullySigned } = await handler.onTreeNonces(event);
|
|
150
|
+
if (fullySigned) {
|
|
151
|
+
step = Step.TreeNoncesAggregated;
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
case ark_1.SettlementEventType.BatchFinalization: {
|
|
156
|
+
if (step !== Step.TreeNoncesAggregated) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Build vtxo tree if it hasn't been built yet
|
|
160
|
+
if (!vtxoTree && flatVtxoTree.length > 0) {
|
|
161
|
+
vtxoTree = txTree_1.TxTree.create(flatVtxoTree);
|
|
162
|
+
}
|
|
163
|
+
if (!vtxoTree && !skipVtxoTreeSigning) {
|
|
164
|
+
throw new Error("vtxo tree not initialized");
|
|
165
|
+
}
|
|
166
|
+
// Build connector tree if we have chunks
|
|
167
|
+
if (flatConnectorTree.length > 0) {
|
|
168
|
+
connectorTree = txTree_1.TxTree.create(flatConnectorTree);
|
|
169
|
+
}
|
|
170
|
+
await handler.onBatchFinalization(event, vtxoTree, connectorTree);
|
|
171
|
+
step = Step.BatchFinalization;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
// unknown event type, continue
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// iterator closed without finalization, something went wrong
|
|
180
|
+
throw new Error("event stream closed");
|
|
181
|
+
}
|
|
182
|
+
Batch.join = join;
|
|
183
|
+
})(Batch || (exports.Batch = Batch = {}));
|
package/dist/cjs/wallet/index.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TxType = void 0;
|
|
4
4
|
exports.isSpendable = isSpendable;
|
|
5
5
|
exports.isRecoverable = isRecoverable;
|
|
6
|
+
exports.isExpired = isExpired;
|
|
6
7
|
exports.isSubdust = isSubdust;
|
|
7
8
|
var TxType;
|
|
8
9
|
(function (TxType) {
|
|
@@ -15,6 +16,20 @@ function isSpendable(vtxo) {
|
|
|
15
16
|
function isRecoverable(vtxo) {
|
|
16
17
|
return vtxo.virtualStatus.state === "swept" && isSpendable(vtxo);
|
|
17
18
|
}
|
|
19
|
+
function isExpired(vtxo) {
|
|
20
|
+
if (vtxo.virtualStatus.state === "swept")
|
|
21
|
+
return true; // swept by server = expired
|
|
22
|
+
const expiry = vtxo.virtualStatus.batchExpiry;
|
|
23
|
+
if (!expiry)
|
|
24
|
+
return false;
|
|
25
|
+
// we use this as a workaround to avoid issue on regtest where expiry date is expressed in blockheight instead of timestamp
|
|
26
|
+
// if expiry, as Date, is before 2025, then we admit it's too small to be a timestamp
|
|
27
|
+
// TODO: API should return the expiry unit
|
|
28
|
+
const expireAt = new Date(expiry);
|
|
29
|
+
if (expireAt.getFullYear() < 2025)
|
|
30
|
+
return false;
|
|
31
|
+
return expiry <= Date.now();
|
|
32
|
+
}
|
|
18
33
|
function isSubdust(vtxo, dust) {
|
|
19
34
|
return vtxo.value < dust;
|
|
20
35
|
}
|
|
@@ -14,8 +14,6 @@ var Request;
|
|
|
14
14
|
return (message.type === "INIT_WALLET" &&
|
|
15
15
|
"arkServerUrl" in message &&
|
|
16
16
|
typeof message.arkServerUrl === "string" &&
|
|
17
|
-
"privateKey" in message &&
|
|
18
|
-
typeof message.privateKey === "string" &&
|
|
19
17
|
("arkServerPublicKey" in message
|
|
20
18
|
? message.arkServerPublicKey === undefined ||
|
|
21
19
|
typeof message.arkServerPublicKey === "string"
|