@arkade-os/sdk 0.3.0-alpha.7 → 0.3.1-alpha.0
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 +115 -14
- package/dist/cjs/adapters/expo.js +8 -0
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +5 -2
- package/dist/cjs/identity/singleKey.js +5 -4
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/{bip322 → intent}/index.js +37 -55
- package/dist/cjs/providers/ark.js +62 -23
- package/dist/cjs/providers/expoArk.js +82 -0
- package/dist/cjs/providers/expoIndexer.js +105 -0
- package/dist/cjs/providers/indexer.js +3 -1
- package/dist/cjs/providers/utils.js +122 -0
- package/dist/cjs/script/base.js +1 -2
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +7 -8
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +117 -12
- package/dist/cjs/utils/unknownFields.js +5 -5
- package/dist/cjs/wallet/index.js +1 -1
- package/dist/cjs/wallet/onchain.js +4 -5
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
- package/dist/cjs/wallet/serviceWorker/wallet.js +4 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +25 -23
- package/dist/cjs/wallet/unroll.js +6 -7
- package/dist/cjs/wallet/utils.js +11 -0
- package/dist/cjs/wallet/vtxo-manager.js +381 -0
- package/dist/cjs/wallet/wallet.js +130 -143
- package/dist/esm/adapters/expo.js +3 -0
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +4 -1
- package/dist/esm/identity/singleKey.js +7 -6
- package/dist/esm/index.js +7 -6
- package/dist/esm/{bip322 → intent}/index.js +31 -48
- package/dist/esm/providers/ark.js +62 -23
- package/dist/esm/providers/expoArk.js +78 -0
- package/dist/esm/providers/expoIndexer.js +101 -0
- package/dist/esm/providers/indexer.js +3 -1
- package/dist/esm/providers/utils.js +87 -0
- package/dist/esm/script/base.js +1 -2
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +8 -9
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +108 -5
- package/dist/esm/utils/unknownFields.js +1 -1
- package/dist/esm/wallet/index.js +1 -1
- package/dist/esm/wallet/onchain.js +1 -2
- package/dist/esm/wallet/serviceWorker/utils.js +1 -8
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -9
- package/dist/esm/wallet/serviceWorker/worker.js +26 -24
- package/dist/esm/wallet/unroll.js +2 -3
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/vtxo-manager.js +372 -0
- package/dist/esm/wallet/wallet.js +124 -137
- package/dist/types/adapters/expo.d.ts +4 -0
- package/dist/types/arknote/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +2 -2
- package/dist/types/identity/index.d.ts +1 -1
- package/dist/types/identity/singleKey.d.ts +1 -1
- package/dist/types/index.d.ts +8 -7
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/providers/ark.d.ts +190 -22
- package/dist/types/providers/expoArk.d.ts +22 -0
- package/dist/types/providers/expoIndexer.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +8 -8
- package/dist/types/providers/utils.d.ts +18 -0
- package/dist/types/script/base.d.ts +3 -2
- package/dist/types/tree/signingSession.d.ts +10 -10
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +16 -4
- package/dist/types/utils/unknownFields.d.ts +2 -2
- package/dist/types/wallet/index.d.ts +47 -7
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
- package/dist/types/wallet/serviceWorker/worker.d.ts +3 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +2 -0
- package/dist/types/wallet/vtxo-manager.d.ts +207 -0
- package/dist/types/wallet/wallet.d.ts +16 -4
- package/package.json +11 -3
- package/dist/cjs/bip322/errors.js +0 -13
- package/dist/esm/bip322/errors.js +0 -9
- package/dist/types/bip322/errors.d.ts +0 -6
- package/dist/types/bip322/index.d.ts +0 -57
package/README.md
CHANGED
|
@@ -108,26 +108,76 @@ const settleTxid = await wallet.settle({
|
|
|
108
108
|
})
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
### VTXO Management (Renewal & Recovery)
|
|
112
|
+
|
|
113
|
+
VTXOs have an expiration time (batch expiry). The SDK provides the `VtxoManager` class to handle both:
|
|
114
|
+
|
|
115
|
+
- **Renewal**: Renew VTXOs before they expire to maintain unilateral control of the funds.
|
|
116
|
+
- **Recovery**: Reclaim swept or expired VTXOs back to the wallet in case renewal window was missed.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { VtxoManager } from '@arkade-os/sdk'
|
|
120
|
+
|
|
121
|
+
// Create manager with optional renewal configuration
|
|
122
|
+
const manager = new VtxoManager(wallet, {
|
|
123
|
+
enabled: true, // Enable expiration monitoring
|
|
124
|
+
thresholdPercentage: 10 // Alert when 10% of lifetime remains (default)
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Renewal: Prevent Expiration
|
|
129
|
+
|
|
130
|
+
Renew VTXOs before they expire to keep your liquidity accessible. This settles all VTXOs (including recoverable ones) back to your wallet with a fresh expiration time.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Check which VTXOs are expiring soon
|
|
134
|
+
const expiringVtxos = await manager.getExpiringVtxos()
|
|
135
|
+
if (expiringVtxos.length > 0) {
|
|
136
|
+
console.log(`${expiringVtxos.length} VTXOs expiring soon`)
|
|
137
|
+
|
|
138
|
+
expiringVtxos.forEach(vtxo => {
|
|
139
|
+
const timeLeft = vtxo.virtualStatus.batchExpiry! - Date.now()
|
|
140
|
+
const hoursLeft = Math.floor(timeLeft / (1000 * 60 * 60))
|
|
141
|
+
console.log(`VTXO ${vtxo.txid} expires in ${hoursLeft} hours`)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// Renew all VTXOs to prevent expiration
|
|
145
|
+
const txid = await manager.renewVtxos()
|
|
146
|
+
console.log('Renewed:', txid)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Override threshold percentage (e.g., renew when 5% of time remains)
|
|
150
|
+
const urgentlyExpiring = await manager.getExpiringVtxos(5)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
#### Recovery: Reclaim Swept VTXOs
|
|
155
|
+
|
|
156
|
+
Recover VTXOs that have been swept by the server or consolidate small amounts (subdust).
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Check what's recoverable
|
|
160
|
+
const balance = await manager.getRecoverableBalance()
|
|
161
|
+
console.log(`Recoverable: ${balance.recoverable} sats`)
|
|
162
|
+
console.log(`Subdust: ${balance.subdust} sats`)
|
|
163
|
+
console.log(`Subdust included: ${balance.includesSubdust}`)
|
|
164
|
+
console.log(`VTXO count: ${balance.vtxoCount}`)
|
|
165
|
+
|
|
166
|
+
if (balance.recoverable > 0n) {
|
|
167
|
+
// Recover swept VTXOs and preconfirmed subdust
|
|
168
|
+
const txid = await manager.recoverVtxos((event) => {
|
|
169
|
+
console.log('Settlement event:', event.type)
|
|
170
|
+
})
|
|
171
|
+
console.log('Recovered:', txid)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
111
175
|
|
|
112
176
|
### Transaction History
|
|
113
177
|
|
|
114
178
|
```typescript
|
|
115
179
|
// Get transaction history
|
|
116
180
|
const history = await wallet.getTransactionHistory()
|
|
117
|
-
console.log('History:', history)
|
|
118
|
-
|
|
119
|
-
// Example history entry:
|
|
120
|
-
{
|
|
121
|
-
key: {
|
|
122
|
-
boardingTxid: '...', // for boarding transactions
|
|
123
|
-
commitmentTxid: '...', // for commitment transactions
|
|
124
|
-
redeemTxid: '...' // for regular transactions
|
|
125
|
-
},
|
|
126
|
-
type: TxType.TxReceived, // or TxType.TxSent
|
|
127
|
-
amount: 50000,
|
|
128
|
-
settled: true,
|
|
129
|
-
createdAt: 1234567890
|
|
130
|
-
}
|
|
131
181
|
```
|
|
132
182
|
|
|
133
183
|
### Offboarding
|
|
@@ -288,6 +338,55 @@ const wallet = await Wallet.create({
|
|
|
288
338
|
})
|
|
289
339
|
```
|
|
290
340
|
|
|
341
|
+
### Using with Expo/React Native
|
|
342
|
+
|
|
343
|
+
For React Native and Expo applications where standard EventSource and fetch streaming may not work properly, use the Expo-compatible providers:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { Wallet, SingleKey } from '@arkade-os/sdk'
|
|
347
|
+
import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'
|
|
348
|
+
|
|
349
|
+
const identity = SingleKey.fromHex('your_private_key_hex')
|
|
350
|
+
|
|
351
|
+
const wallet = await Wallet.create({
|
|
352
|
+
identity: identity,
|
|
353
|
+
esploraUrl: 'https://mutinynet.com/api',
|
|
354
|
+
arkProvider: new ExpoArkProvider('https://mutinynet.arkade.sh'), // For settlement events and transactions streaming
|
|
355
|
+
indexerProvider: new ExpoIndexerProvider('https://mutinynet.arkade.sh'), // For address subscriptions and VTXO updates
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// use expo/fetch for streaming support (SSE)
|
|
359
|
+
// All other wallet functionality remains the same
|
|
360
|
+
const balance = await wallet.getBalance()
|
|
361
|
+
const address = await wallet.getAddress()
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Both ExpoArkProvider and ExpoIndexerProvider are available as adapters following the SDK's modular architecture pattern. This keeps the main SDK bundle clean while providing opt-in functionality for specific environments:
|
|
365
|
+
|
|
366
|
+
- **ExpoArkProvider**: Handles settlement events and transaction streaming using expo/fetch for Server-Sent Events
|
|
367
|
+
- **ExpoIndexerProvider**: Handles address subscriptions and VTXO updates using expo/fetch for JSON streaming
|
|
368
|
+
|
|
369
|
+
#### Crypto Polyfill Requirement
|
|
370
|
+
|
|
371
|
+
Install `expo-crypto` and polyfill `crypto.getRandomValues()` at the top of your app entry point:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
npx expo install expo-crypto
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// App.tsx or index.js - MUST be first import
|
|
379
|
+
import * as Crypto from 'expo-crypto';
|
|
380
|
+
if (!global.crypto) global.crypto = {} as any;
|
|
381
|
+
global.crypto.getRandomValues = Crypto.getRandomValues;
|
|
382
|
+
|
|
383
|
+
// Now import the SDK
|
|
384
|
+
import { Wallet, SingleKey } from '@arkade-os/sdk';
|
|
385
|
+
import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo';
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
This is required for MuSig2 settlements and cryptographic operations.
|
|
389
|
+
|
|
291
390
|
### Repository Pattern
|
|
292
391
|
|
|
293
392
|
Access low-level data management through repositories:
|
|
@@ -311,6 +410,8 @@ await wallet.contractRepository.saveToContractCollection(
|
|
|
311
410
|
const swaps = await wallet.contractRepository.getContractCollection('swaps')
|
|
312
411
|
```
|
|
313
412
|
|
|
413
|
+
_For complete API documentation, visit our [TypeScript documentation](https://arkade-os.github.io/ts-sdk/)._
|
|
414
|
+
|
|
314
415
|
## Development
|
|
315
416
|
|
|
316
417
|
### Requirements
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExpoIndexerProvider = exports.ExpoArkProvider = void 0;
|
|
4
|
+
// Expo adapter for React Native/Expo environments
|
|
5
|
+
var expoArk_1 = require("../providers/expoArk");
|
|
6
|
+
Object.defineProperty(exports, "ExpoArkProvider", { enumerable: true, get: function () { return expoArk_1.ExpoArkProvider; } });
|
|
7
|
+
var expoIndexer_1 = require("../providers/expoIndexer");
|
|
8
|
+
Object.defineProperty(exports, "ExpoIndexerProvider", { enumerable: true, get: function () { return expoIndexer_1.ExpoIndexerProvider; } });
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ArkNote = void 0;
|
|
4
4
|
const base_1 = require("@scure/base");
|
|
5
|
-
const base_2 = require("../script/base");
|
|
6
5
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
7
|
-
const
|
|
6
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
7
|
+
const base_2 = require("../script/base");
|
|
8
8
|
/**
|
|
9
9
|
* ArkNotes are special virtual coins in the Ark protocol that can be created
|
|
10
10
|
* and spent without requiring any transactions. The server mints them, and they
|
|
@@ -85,5 +85,5 @@ function readUInt32BE(array, offset) {
|
|
|
85
85
|
return view.getUint32(0, false);
|
|
86
86
|
}
|
|
87
87
|
function noteTapscript(preimageHash) {
|
|
88
|
-
return
|
|
88
|
+
return btc_signer_1.Script.encode(["SHA256", preimageHash, "EQUAL"]);
|
|
89
89
|
}
|
package/dist/cjs/forfeit.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildForfeitTx = buildForfeitTx;
|
|
4
|
-
const
|
|
4
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
5
5
|
const anchor_1 = require("./utils/anchor");
|
|
6
6
|
function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
|
|
7
|
-
const tx = new
|
|
7
|
+
const tx = new btc_signer_1.Transaction({
|
|
8
8
|
version: 3,
|
|
9
9
|
lockTime: txLocktime,
|
|
10
|
+
allowUnknownOutputs: true,
|
|
11
|
+
allowUnknown: true,
|
|
12
|
+
allowUnknownInputs: true,
|
|
10
13
|
});
|
|
11
14
|
let amount = 0n;
|
|
12
15
|
for (const input of inputs) {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SingleKey = void 0;
|
|
4
4
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
5
|
-
const base_1 = require("@scure/base");
|
|
6
5
|
const transaction_js_1 = require("@scure/btc-signer/transaction.js");
|
|
6
|
+
const base_1 = require("@scure/base");
|
|
7
7
|
const signingSession_1 = require("../tree/signingSession");
|
|
8
8
|
const secp256k1_1 = require("@noble/secp256k1");
|
|
9
9
|
const ZERO_32 = new Uint8Array(32).fill(0);
|
|
@@ -82,9 +82,10 @@ class SingleKey {
|
|
|
82
82
|
signerSession() {
|
|
83
83
|
return signingSession_1.TreeSignerSession.random();
|
|
84
84
|
}
|
|
85
|
-
async signMessage(message) {
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
async signMessage(message, signatureType = "schnorr") {
|
|
86
|
+
if (signatureType === "ecdsa")
|
|
87
|
+
return (0, secp256k1_1.sign)(message, this.key, { prehash: false });
|
|
88
|
+
return secp256k1_1.schnorr.sign(message, this.key);
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
exports.SingleKey = SingleKey;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.
|
|
3
|
+
exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = 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.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = 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.SingleKey = exports.Wallet = void 0;
|
|
4
4
|
const transaction_js_1 = require("@scure/btc-signer/transaction.js");
|
|
5
5
|
Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_js_1.Transaction; } });
|
|
6
6
|
const singleKey_1 = require("./identity/singleKey");
|
|
@@ -22,6 +22,8 @@ const txTree_1 = require("./tree/txTree");
|
|
|
22
22
|
Object.defineProperty(exports, "TxTree", { enumerable: true, get: function () { return txTree_1.TxTree; } });
|
|
23
23
|
const ramps_1 = require("./wallet/ramps");
|
|
24
24
|
Object.defineProperty(exports, "Ramps", { enumerable: true, get: function () { return ramps_1.Ramps; } });
|
|
25
|
+
const vtxo_manager_1 = require("./wallet/vtxo-manager");
|
|
26
|
+
Object.defineProperty(exports, "VtxoManager", { enumerable: true, get: function () { return vtxo_manager_1.VtxoManager; } });
|
|
25
27
|
const wallet_3 = require("./wallet/serviceWorker/wallet");
|
|
26
28
|
Object.defineProperty(exports, "ServiceWorkerWallet", { enumerable: true, get: function () { return wallet_3.ServiceWorkerWallet; } });
|
|
27
29
|
const onchain_1 = require("./wallet/onchain");
|
|
@@ -48,7 +50,9 @@ Object.defineProperty(exports, "CSVMultisigTapscript", { enumerable: true, get:
|
|
|
48
50
|
Object.defineProperty(exports, "decodeTapscript", { enumerable: true, get: function () { return tapscript_1.decodeTapscript; } });
|
|
49
51
|
Object.defineProperty(exports, "MultisigTapscript", { enumerable: true, get: function () { return tapscript_1.MultisigTapscript; } });
|
|
50
52
|
const arkTransaction_1 = require("./utils/arkTransaction");
|
|
53
|
+
Object.defineProperty(exports, "hasBoardingTxExpired", { enumerable: true, get: function () { return arkTransaction_1.hasBoardingTxExpired; } });
|
|
51
54
|
Object.defineProperty(exports, "buildOffchainTx", { enumerable: true, get: function () { return arkTransaction_1.buildOffchainTx; } });
|
|
55
|
+
Object.defineProperty(exports, "verifyTapscriptSignatures", { enumerable: true, get: function () { return arkTransaction_1.verifyTapscriptSignatures; } });
|
|
52
56
|
const unknownFields_1 = require("./utils/unknownFields");
|
|
53
57
|
Object.defineProperty(exports, "VtxoTaprootTree", { enumerable: true, get: function () { return unknownFields_1.VtxoTaprootTree; } });
|
|
54
58
|
Object.defineProperty(exports, "ConditionWitness", { enumerable: true, get: function () { return unknownFields_1.ConditionWitness; } });
|
|
@@ -58,8 +62,8 @@ Object.defineProperty(exports, "ArkPsbtFieldKey", { enumerable: true, get: funct
|
|
|
58
62
|
Object.defineProperty(exports, "ArkPsbtFieldKeyType", { enumerable: true, get: function () { return unknownFields_1.ArkPsbtFieldKeyType; } });
|
|
59
63
|
Object.defineProperty(exports, "CosignerPublicKey", { enumerable: true, get: function () { return unknownFields_1.CosignerPublicKey; } });
|
|
60
64
|
Object.defineProperty(exports, "VtxoTreeExpiry", { enumerable: true, get: function () { return unknownFields_1.VtxoTreeExpiry; } });
|
|
61
|
-
const
|
|
62
|
-
Object.defineProperty(exports, "
|
|
65
|
+
const intent_1 = require("./intent");
|
|
66
|
+
Object.defineProperty(exports, "Intent", { enumerable: true, get: function () { return intent_1.Intent; } });
|
|
63
67
|
const arknote_1 = require("./arknote");
|
|
64
68
|
Object.defineProperty(exports, "ArkNote", { enumerable: true, get: function () { return arknote_1.ArkNote; } });
|
|
65
69
|
const networks_1 = require("./networks");
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
const script_js_1 = require("@scure/btc-signer/script.js");
|
|
6
|
-
const transaction_js_1 = require("@scure/btc-signer/transaction.js");
|
|
7
|
-
const script_js_2 = require("@scure/btc-signer/script.js");
|
|
8
|
-
const errors_1 = require("./errors");
|
|
3
|
+
exports.Intent = void 0;
|
|
4
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
9
5
|
const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
|
|
10
|
-
const base_1 = require("@scure/base");
|
|
11
6
|
/**
|
|
12
|
-
*
|
|
7
|
+
* Intent proof implementation for Bitcoin message signing.
|
|
13
8
|
*
|
|
14
|
-
*
|
|
9
|
+
* Intent proof defines a standard for signing Bitcoin messages as well as proving
|
|
15
10
|
* ownership of coins. This namespace provides utilities for creating and
|
|
16
|
-
* validating
|
|
11
|
+
* validating Intent proof.
|
|
17
12
|
*
|
|
13
|
+
* it is greatly inspired by BIP322.
|
|
18
14
|
* @see https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki
|
|
19
15
|
*
|
|
20
16
|
* @example
|
|
21
17
|
* ```typescript
|
|
22
|
-
* // Create a
|
|
23
|
-
* const proof =
|
|
18
|
+
* // Create a Intent proof
|
|
19
|
+
* const proof = Intent.create(
|
|
24
20
|
* "Hello Bitcoin!",
|
|
25
21
|
* [input],
|
|
26
22
|
* [output]
|
|
@@ -29,65 +25,46 @@ const base_1 = require("@scure/base");
|
|
|
29
25
|
* // Sign the proof
|
|
30
26
|
* const signedProof = await identity.sign(proof);
|
|
31
27
|
*
|
|
32
|
-
* // Extract the signature
|
|
33
|
-
* const signature = BIP322.signature(signedProof);
|
|
34
|
-
* ```
|
|
35
28
|
*/
|
|
36
|
-
var
|
|
37
|
-
(function (
|
|
29
|
+
var Intent;
|
|
30
|
+
(function (Intent) {
|
|
38
31
|
/**
|
|
39
|
-
* Creates a new
|
|
32
|
+
* Creates a new Intent proof unsigned transaction.
|
|
40
33
|
*
|
|
41
34
|
* This function constructs a special transaction that can be signed to prove
|
|
42
35
|
* ownership of VTXOs and UTXOs. The proof includes the message to be
|
|
43
36
|
* signed and the inputs/outputs that demonstrate ownership.
|
|
44
37
|
*
|
|
45
|
-
* @param message - The
|
|
38
|
+
* @param message - The Intent message to be signed
|
|
46
39
|
* @param inputs - Array of transaction inputs to prove ownership of
|
|
47
40
|
* @param outputs - Optional array of transaction outputs
|
|
48
|
-
* @returns An unsigned
|
|
41
|
+
* @returns An unsigned Intent proof transaction
|
|
49
42
|
*/
|
|
50
43
|
function create(message, inputs, outputs = []) {
|
|
51
44
|
if (inputs.length == 0)
|
|
52
|
-
throw
|
|
45
|
+
throw new Error("intent proof requires at least one input");
|
|
53
46
|
if (!validateInputs(inputs))
|
|
54
|
-
throw
|
|
47
|
+
throw new Error("invalid inputs");
|
|
55
48
|
if (!validateOutputs(outputs))
|
|
56
|
-
throw
|
|
49
|
+
throw new Error("invalid outputs");
|
|
57
50
|
// create the initial transaction to spend
|
|
58
51
|
const toSpend = craftToSpendTx(message, inputs[0].witnessUtxo.script);
|
|
59
52
|
// create the transaction to sign
|
|
60
53
|
return craftToSignTx(toSpend, inputs, outputs);
|
|
61
54
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*
|
|
66
|
-
* This function takes a signed proof transaction and converts it into a
|
|
67
|
-
* base64-encoded signature string. If the proof's inputs have special
|
|
68
|
-
* spending conditions, a custom finalizer can be provided.
|
|
69
|
-
*
|
|
70
|
-
* @param signedProof - The signed BIP-322 proof transaction
|
|
71
|
-
* @param finalizer - Optional custom finalizer function
|
|
72
|
-
* @returns Base64-encoded BIP-322 signature
|
|
73
|
-
*/
|
|
74
|
-
function signature(signedProof, finalizer = (tx) => tx.finalize()) {
|
|
75
|
-
finalizer(signedProof);
|
|
76
|
-
return base_1.base64.encode(signedProof.extract());
|
|
77
|
-
}
|
|
78
|
-
BIP322.signature = signature;
|
|
79
|
-
})(BIP322 || (exports.BIP322 = BIP322 = {}));
|
|
80
|
-
const OP_RETURN_EMPTY_PKSCRIPT = new Uint8Array([script_js_1.OP.RETURN]);
|
|
55
|
+
Intent.create = create;
|
|
56
|
+
})(Intent || (exports.Intent = Intent = {}));
|
|
57
|
+
const OP_RETURN_EMPTY_PKSCRIPT = new Uint8Array([btc_signer_1.OP.RETURN]);
|
|
81
58
|
const ZERO_32 = new Uint8Array(32).fill(0);
|
|
82
59
|
const MAX_INDEX = 0xffffffff;
|
|
83
|
-
const
|
|
60
|
+
const TAG_INTENT_PROOF = "ark-intent-proof-message";
|
|
84
61
|
function validateInput(input) {
|
|
85
62
|
if (input.index === undefined)
|
|
86
|
-
throw
|
|
63
|
+
throw new Error("intent proof input requires index");
|
|
87
64
|
if (input.txid === undefined)
|
|
88
|
-
throw
|
|
65
|
+
throw new Error("intent proof input requires txid");
|
|
89
66
|
if (input.witnessUtxo === undefined)
|
|
90
|
-
throw
|
|
67
|
+
throw new Error("intent proof input requires witness utxo");
|
|
91
68
|
return true;
|
|
92
69
|
}
|
|
93
70
|
function validateInputs(inputs) {
|
|
@@ -96,9 +73,9 @@ function validateInputs(inputs) {
|
|
|
96
73
|
}
|
|
97
74
|
function validateOutput(output) {
|
|
98
75
|
if (output.amount === undefined)
|
|
99
|
-
throw
|
|
76
|
+
throw new Error("intent proof output requires amount");
|
|
100
77
|
if (output.script === undefined)
|
|
101
|
-
throw
|
|
78
|
+
throw new Error("intent proof output requires script");
|
|
102
79
|
return true;
|
|
103
80
|
}
|
|
104
81
|
function validateOutputs(outputs) {
|
|
@@ -108,7 +85,7 @@ function validateOutputs(outputs) {
|
|
|
108
85
|
// craftToSpendTx creates the initial transaction that will be spent in the proof
|
|
109
86
|
function craftToSpendTx(message, pkScript) {
|
|
110
87
|
const messageHash = hashMessage(message);
|
|
111
|
-
const tx = new
|
|
88
|
+
const tx = new btc_signer_1.Transaction({
|
|
112
89
|
version: 0,
|
|
113
90
|
allowUnknownOutputs: true,
|
|
114
91
|
allowUnknown: true,
|
|
@@ -126,14 +103,14 @@ function craftToSpendTx(message, pkScript) {
|
|
|
126
103
|
script: pkScript,
|
|
127
104
|
});
|
|
128
105
|
tx.updateInput(0, {
|
|
129
|
-
finalScriptSig:
|
|
106
|
+
finalScriptSig: btc_signer_1.Script.encode(["OP_0", messageHash]),
|
|
130
107
|
});
|
|
131
108
|
return tx;
|
|
132
109
|
}
|
|
133
110
|
// craftToSignTx creates the transaction that will be signed for the proof
|
|
134
111
|
function craftToSignTx(toSpend, inputs, outputs) {
|
|
135
112
|
const firstInput = inputs[0];
|
|
136
|
-
const tx = new
|
|
113
|
+
const tx = new btc_signer_1.Transaction({
|
|
137
114
|
version: 2,
|
|
138
115
|
allowUnknownOutputs: outputs.length === 0,
|
|
139
116
|
allowUnknown: true,
|
|
@@ -149,14 +126,19 @@ function craftToSignTx(toSpend, inputs, outputs) {
|
|
|
149
126
|
script: firstInput.witnessUtxo.script,
|
|
150
127
|
amount: 0n,
|
|
151
128
|
},
|
|
152
|
-
sighashType:
|
|
129
|
+
sighashType: btc_signer_1.SigHash.ALL,
|
|
153
130
|
});
|
|
154
131
|
// add other inputs
|
|
155
|
-
for (const input of inputs) {
|
|
132
|
+
for (const [i, input] of inputs.entries()) {
|
|
156
133
|
tx.addInput({
|
|
157
134
|
...input,
|
|
158
|
-
sighashType:
|
|
135
|
+
sighashType: btc_signer_1.SigHash.ALL,
|
|
159
136
|
});
|
|
137
|
+
if (input.unknown?.length) {
|
|
138
|
+
tx.updateInput(i + 1, {
|
|
139
|
+
unknown: input.unknown,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
160
142
|
}
|
|
161
143
|
// add the special OP_RETURN output if no outputs are provided
|
|
162
144
|
if (outputs.length === 0) {
|
|
@@ -176,5 +158,5 @@ function craftToSignTx(toSpend, inputs, outputs) {
|
|
|
176
158
|
return tx;
|
|
177
159
|
}
|
|
178
160
|
function hashMessage(message) {
|
|
179
|
-
return secp256k1_js_1.schnorr.utils.taggedHash(
|
|
161
|
+
return secp256k1_js_1.schnorr.utils.taggedHash(TAG_INTENT_PROOF, new TextEncoder().encode(message));
|
|
180
162
|
}
|
|
@@ -36,25 +36,36 @@ class RestArkProvider {
|
|
|
36
36
|
}
|
|
37
37
|
const fromServer = await response.json();
|
|
38
38
|
return {
|
|
39
|
-
...fromServer,
|
|
40
|
-
vtxoTreeExpiry: BigInt(fromServer.vtxoTreeExpiry ?? 0),
|
|
41
|
-
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
42
|
-
roundInterval: BigInt(fromServer.roundInterval ?? 0),
|
|
43
|
-
dust: BigInt(fromServer.dust ?? 0),
|
|
44
|
-
utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
|
|
45
|
-
utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
|
|
46
|
-
vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
|
|
47
|
-
vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
|
|
48
39
|
boardingExitDelay: BigInt(fromServer.boardingExitDelay ?? 0),
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
checkpointTapscript: fromServer.checkpointTapscript ?? "",
|
|
41
|
+
deprecatedSigners: fromServer.deprecatedSigners?.map((signer) => ({
|
|
42
|
+
cutoffDate: BigInt(signer.cutoffDate ?? 0),
|
|
43
|
+
pubkey: signer.pubkey ?? "",
|
|
44
|
+
})) ?? [],
|
|
45
|
+
digest: fromServer.digest ?? "",
|
|
46
|
+
dust: BigInt(fromServer.dust ?? 0),
|
|
47
|
+
fees: fromServer.fees,
|
|
48
|
+
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
49
|
+
forfeitPubkey: fromServer.forfeitPubkey ?? "",
|
|
50
|
+
network: fromServer.network ?? "",
|
|
51
|
+
scheduledSession: "scheduledSession" in fromServer &&
|
|
52
|
+
fromServer.scheduledSession != null
|
|
51
53
|
? {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
duration: BigInt(fromServer.scheduledSession.duration ?? 0),
|
|
55
|
+
nextStartTime: BigInt(fromServer.scheduledSession.nextStartTime ?? 0),
|
|
56
|
+
nextEndTime: BigInt(fromServer.scheduledSession.nextEndTime ?? 0),
|
|
57
|
+
period: BigInt(fromServer.scheduledSession.period ?? 0),
|
|
56
58
|
}
|
|
57
59
|
: undefined,
|
|
60
|
+
serviceStatus: fromServer.serviceStatus ?? {},
|
|
61
|
+
sessionDuration: BigInt(fromServer.sessionDuration ?? 0),
|
|
62
|
+
signerPubkey: fromServer.signerPubkey ?? "",
|
|
63
|
+
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
64
|
+
utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
|
|
65
|
+
utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
|
|
66
|
+
version: fromServer.version ?? "",
|
|
67
|
+
vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
|
|
68
|
+
vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0),
|
|
58
69
|
};
|
|
59
70
|
}
|
|
60
71
|
async submitTx(signedArkTx, checkpointTxs) {
|
|
@@ -65,8 +76,8 @@ class RestArkProvider {
|
|
|
65
76
|
"Content-Type": "application/json",
|
|
66
77
|
},
|
|
67
78
|
body: JSON.stringify({
|
|
68
|
-
signedArkTx
|
|
69
|
-
checkpointTxs
|
|
79
|
+
signedArkTx,
|
|
80
|
+
checkpointTxs,
|
|
70
81
|
}),
|
|
71
82
|
});
|
|
72
83
|
if (!response.ok) {
|
|
@@ -115,7 +126,7 @@ class RestArkProvider {
|
|
|
115
126
|
},
|
|
116
127
|
body: JSON.stringify({
|
|
117
128
|
intent: {
|
|
118
|
-
|
|
129
|
+
proof: intent.proof,
|
|
119
130
|
message: intent.message,
|
|
120
131
|
},
|
|
121
132
|
}),
|
|
@@ -136,7 +147,7 @@ class RestArkProvider {
|
|
|
136
147
|
},
|
|
137
148
|
body: JSON.stringify({
|
|
138
149
|
proof: {
|
|
139
|
-
|
|
150
|
+
proof: intent.proof,
|
|
140
151
|
message: intent.message,
|
|
141
152
|
},
|
|
142
153
|
}),
|
|
@@ -309,6 +320,31 @@ class RestArkProvider {
|
|
|
309
320
|
}
|
|
310
321
|
}
|
|
311
322
|
}
|
|
323
|
+
async getPendingTxs(intent) {
|
|
324
|
+
const url = `${this.serverUrl}/v1/tx/pending`;
|
|
325
|
+
const response = await fetch(url, {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: {
|
|
328
|
+
"Content-Type": "application/json",
|
|
329
|
+
},
|
|
330
|
+
body: JSON.stringify({ intent }),
|
|
331
|
+
});
|
|
332
|
+
if (!response.ok) {
|
|
333
|
+
const errorText = await response.text();
|
|
334
|
+
try {
|
|
335
|
+
const grpcError = JSON.parse(errorText);
|
|
336
|
+
// gRPC errors usually have a message and code field
|
|
337
|
+
throw new Error(`Failed to get pending transactions: ${grpcError.message || grpcError.error || errorText}`);
|
|
338
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
339
|
+
}
|
|
340
|
+
catch (_) {
|
|
341
|
+
// If JSON parse fails, use the raw error text
|
|
342
|
+
throw new Error(`Failed to get pending transactions: ${errorText}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const data = await response.json();
|
|
346
|
+
return data.pendingTxs;
|
|
347
|
+
}
|
|
312
348
|
parseSettlementEvent(data) {
|
|
313
349
|
// Check for BatchStarted event
|
|
314
350
|
if (data.batchStarted) {
|
|
@@ -387,6 +423,10 @@ class RestArkProvider {
|
|
|
387
423
|
signature: data.treeSignature.signature,
|
|
388
424
|
};
|
|
389
425
|
}
|
|
426
|
+
// TODO: Handle TreeNoncesEvent when implemented server-side
|
|
427
|
+
if (data.treeNonces) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
390
430
|
// Skip heartbeat events
|
|
391
431
|
if (data.heartbeat) {
|
|
392
432
|
return null;
|
|
@@ -431,17 +471,16 @@ function encodeMusig2Nonces(nonces) {
|
|
|
431
471
|
for (const [txid, nonce] of nonces) {
|
|
432
472
|
noncesObject[txid] = base_1.hex.encode(nonce.pubNonce);
|
|
433
473
|
}
|
|
434
|
-
return
|
|
474
|
+
return noncesObject;
|
|
435
475
|
}
|
|
436
476
|
function encodeMusig2Signatures(signatures) {
|
|
437
477
|
const sigObject = {};
|
|
438
478
|
for (const [txid, sig] of signatures) {
|
|
439
479
|
sigObject[txid] = base_1.hex.encode(sig.encode());
|
|
440
480
|
}
|
|
441
|
-
return
|
|
481
|
+
return sigObject;
|
|
442
482
|
}
|
|
443
|
-
function decodeMusig2Nonces(
|
|
444
|
-
const noncesObject = JSON.parse(str);
|
|
483
|
+
function decodeMusig2Nonces(noncesObject) {
|
|
445
484
|
return new Map(Object.entries(noncesObject).map(([txid, nonce]) => {
|
|
446
485
|
if (typeof nonce !== "string") {
|
|
447
486
|
throw new Error("invalid nonce");
|