@arkade-os/sdk 0.3.12 → 0.4.0-next.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 +483 -54
- package/dist/cjs/adapters/expo-db.js +35 -0
- package/dist/cjs/asset/assetGroup.js +141 -0
- package/dist/cjs/asset/assetId.js +88 -0
- package/dist/cjs/asset/assetInput.js +204 -0
- package/dist/cjs/asset/assetOutput.js +159 -0
- package/dist/cjs/asset/assetRef.js +82 -0
- package/dist/cjs/asset/index.js +24 -0
- package/dist/cjs/asset/metadata.js +172 -0
- package/dist/cjs/asset/packet.js +164 -0
- package/dist/cjs/asset/types.js +25 -0
- package/dist/cjs/asset/utils.js +105 -0
- package/dist/cjs/contracts/arkcontract.js +148 -0
- package/dist/cjs/contracts/contractManager.js +436 -0
- package/dist/cjs/contracts/contractWatcher.js +567 -0
- package/dist/cjs/contracts/handlers/default.js +85 -0
- package/dist/cjs/contracts/handlers/delegate.js +89 -0
- package/dist/cjs/contracts/handlers/helpers.js +105 -0
- package/dist/cjs/contracts/handlers/index.js +19 -0
- package/dist/cjs/contracts/handlers/registry.js +89 -0
- package/dist/cjs/contracts/handlers/vhtlc.js +193 -0
- package/dist/cjs/contracts/index.js +41 -0
- package/dist/cjs/contracts/types.js +2 -0
- package/dist/cjs/db/manager.js +97 -0
- package/dist/cjs/forfeit.js +12 -8
- package/dist/cjs/identity/index.js +1 -0
- package/dist/cjs/identity/seedIdentity.js +255 -0
- package/dist/cjs/index.js +70 -14
- package/dist/cjs/intent/index.js +28 -2
- package/dist/cjs/providers/ark.js +7 -0
- package/dist/cjs/providers/delegator.js +66 -0
- package/dist/cjs/providers/expoIndexer.js +5 -0
- package/dist/cjs/providers/indexer.js +68 -1
- package/dist/cjs/providers/onchain.js +2 -2
- package/dist/cjs/providers/utils.js +1 -0
- package/dist/cjs/repositories/contractRepository.js +0 -103
- package/dist/cjs/repositories/inMemory/contractRepository.js +55 -0
- package/dist/cjs/repositories/inMemory/walletRepository.js +80 -0
- package/dist/cjs/repositories/index.js +16 -0
- package/dist/cjs/repositories/indexedDB/contractRepository.js +187 -0
- package/dist/cjs/repositories/indexedDB/db.js +57 -0
- package/dist/cjs/repositories/indexedDB/schema.js +159 -0
- package/dist/cjs/repositories/indexedDB/walletRepository.js +338 -0
- package/dist/cjs/repositories/indexedDB/websqlAdapter.js +144 -0
- package/dist/cjs/repositories/migrations/contractRepositoryImpl.js +127 -0
- package/dist/cjs/repositories/migrations/fromStorageAdapter.js +66 -0
- package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +180 -0
- package/dist/cjs/repositories/walletRepository.js +0 -169
- package/dist/cjs/script/base.js +54 -0
- package/dist/cjs/script/delegate.js +49 -0
- package/dist/cjs/storage/asyncStorage.js +4 -1
- package/dist/cjs/storage/fileSystem.js +3 -0
- package/dist/cjs/storage/inMemory.js +3 -0
- package/dist/cjs/storage/indexedDB.js +5 -1
- package/dist/cjs/storage/localStorage.js +3 -0
- package/dist/cjs/utils/arkTransaction.js +16 -0
- package/dist/cjs/utils/transactionHistory.js +50 -0
- package/dist/cjs/utils/txSizeEstimator.js +39 -14
- package/dist/cjs/wallet/asset-manager.js +338 -0
- package/dist/cjs/wallet/asset.js +117 -0
- package/dist/cjs/wallet/batch.js +1 -1
- package/dist/cjs/wallet/delegator.js +235 -0
- package/dist/cjs/wallet/expo/background.js +133 -0
- package/dist/cjs/wallet/expo/index.js +9 -0
- package/dist/cjs/wallet/expo/wallet.js +231 -0
- package/dist/cjs/wallet/onchain.js +57 -12
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +568 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +383 -102
- package/dist/cjs/wallet/unroll.js +7 -2
- package/dist/cjs/wallet/utils.js +60 -0
- package/dist/cjs/wallet/validation.js +151 -0
- package/dist/cjs/wallet/vtxo-manager.js +1 -1
- package/dist/cjs/wallet/wallet.js +702 -260
- package/dist/cjs/worker/browser/service-worker-manager.js +82 -0
- package/dist/cjs/{wallet/serviceWorker → worker/browser}/utils.js +2 -1
- package/dist/cjs/worker/expo/asyncStorageTaskQueue.js +78 -0
- package/dist/cjs/worker/expo/index.js +12 -0
- package/dist/cjs/worker/expo/processors/contractPollProcessor.js +61 -0
- package/dist/cjs/worker/expo/processors/index.js +6 -0
- package/dist/cjs/worker/expo/taskQueue.js +41 -0
- package/dist/cjs/worker/expo/taskRunner.js +57 -0
- package/dist/cjs/worker/messageBus.js +252 -0
- package/dist/esm/adapters/expo-db.js +27 -0
- package/dist/esm/asset/assetGroup.js +137 -0
- package/dist/esm/asset/assetId.js +84 -0
- package/dist/esm/asset/assetInput.js +199 -0
- package/dist/esm/asset/assetOutput.js +154 -0
- package/dist/esm/asset/assetRef.js +78 -0
- package/dist/esm/asset/index.js +8 -0
- package/dist/esm/asset/metadata.js +167 -0
- package/dist/esm/asset/packet.js +159 -0
- package/dist/esm/asset/types.js +22 -0
- package/dist/esm/asset/utils.js +99 -0
- package/dist/esm/contracts/arkcontract.js +141 -0
- package/dist/esm/contracts/contractManager.js +432 -0
- package/dist/esm/contracts/contractWatcher.js +563 -0
- package/dist/esm/contracts/handlers/default.js +82 -0
- package/dist/esm/contracts/handlers/delegate.js +86 -0
- package/dist/esm/contracts/handlers/helpers.js +66 -0
- package/dist/esm/contracts/handlers/index.js +12 -0
- package/dist/esm/contracts/handlers/registry.js +86 -0
- package/dist/esm/contracts/handlers/vhtlc.js +190 -0
- package/dist/esm/contracts/index.js +13 -0
- package/dist/esm/contracts/types.js +1 -0
- package/dist/esm/db/manager.js +92 -0
- package/dist/esm/forfeit.js +11 -8
- package/dist/esm/identity/index.js +1 -0
- package/dist/esm/identity/seedIdentity.js +249 -0
- package/dist/esm/index.js +25 -15
- package/dist/esm/intent/index.js +28 -2
- package/dist/esm/providers/ark.js +7 -0
- package/dist/esm/providers/delegator.js +62 -0
- package/dist/esm/providers/expoIndexer.js +5 -0
- package/dist/esm/providers/indexer.js +68 -1
- package/dist/esm/providers/onchain.js +2 -2
- package/dist/esm/providers/utils.js +1 -0
- package/dist/esm/repositories/contractRepository.js +1 -101
- package/dist/esm/repositories/inMemory/contractRepository.js +51 -0
- package/dist/esm/repositories/inMemory/walletRepository.js +76 -0
- package/dist/esm/repositories/index.js +8 -0
- package/dist/esm/repositories/indexedDB/contractRepository.js +183 -0
- package/dist/esm/repositories/indexedDB/db.js +42 -0
- package/dist/esm/repositories/indexedDB/schema.js +155 -0
- package/dist/esm/repositories/indexedDB/walletRepository.js +334 -0
- package/dist/esm/repositories/indexedDB/websqlAdapter.js +138 -0
- package/dist/esm/repositories/migrations/contractRepositoryImpl.js +121 -0
- package/dist/esm/repositories/migrations/fromStorageAdapter.js +58 -0
- package/dist/esm/repositories/migrations/walletRepositoryImpl.js +176 -0
- package/dist/esm/repositories/walletRepository.js +1 -167
- package/dist/esm/script/base.js +21 -1
- package/dist/esm/script/delegate.js +46 -0
- package/dist/esm/storage/asyncStorage.js +4 -1
- package/dist/esm/storage/fileSystem.js +3 -0
- package/dist/esm/storage/inMemory.js +3 -0
- package/dist/esm/storage/indexedDB.js +5 -1
- package/dist/esm/storage/localStorage.js +3 -0
- package/dist/esm/utils/arkTransaction.js +15 -0
- package/dist/esm/utils/transactionHistory.js +50 -0
- package/dist/esm/utils/txSizeEstimator.js +39 -14
- package/dist/esm/wallet/asset-manager.js +333 -0
- package/dist/esm/wallet/asset.js +111 -0
- package/dist/esm/wallet/batch.js +1 -1
- package/dist/esm/wallet/delegator.js +231 -0
- package/dist/esm/wallet/expo/background.js +128 -0
- package/dist/esm/wallet/expo/index.js +2 -0
- package/dist/esm/wallet/expo/wallet.js +194 -0
- package/dist/esm/wallet/onchain.js +57 -12
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +564 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +382 -101
- package/dist/esm/wallet/unroll.js +7 -2
- package/dist/esm/wallet/utils.js +55 -0
- package/dist/esm/wallet/validation.js +139 -0
- package/dist/esm/wallet/vtxo-manager.js +1 -1
- package/dist/esm/wallet/wallet.js +704 -229
- package/dist/esm/worker/browser/service-worker-manager.js +76 -0
- package/dist/esm/{wallet/serviceWorker → worker/browser}/utils.js +2 -1
- package/dist/esm/worker/expo/asyncStorageTaskQueue.js +74 -0
- package/dist/esm/worker/expo/index.js +4 -0
- package/dist/esm/worker/expo/processors/contractPollProcessor.js +58 -0
- package/dist/esm/worker/expo/processors/index.js +1 -0
- package/dist/esm/worker/expo/taskQueue.js +37 -0
- package/dist/esm/worker/expo/taskRunner.js +54 -0
- package/dist/esm/worker/messageBus.js +248 -0
- package/dist/types/adapters/expo-db.d.ts +7 -0
- package/dist/types/asset/assetGroup.d.ts +28 -0
- package/dist/types/asset/assetId.d.ts +19 -0
- package/dist/types/asset/assetInput.d.ts +46 -0
- package/dist/types/asset/assetOutput.d.ts +39 -0
- package/dist/types/asset/assetRef.d.ts +25 -0
- package/dist/types/asset/index.d.ts +8 -0
- package/dist/types/asset/metadata.d.ts +37 -0
- package/dist/types/asset/packet.d.ts +27 -0
- package/dist/types/asset/types.d.ts +18 -0
- package/dist/types/asset/utils.d.ts +21 -0
- package/dist/types/contracts/arkcontract.d.ts +101 -0
- package/dist/types/contracts/contractManager.d.ts +331 -0
- package/dist/types/contracts/contractWatcher.d.ts +192 -0
- package/dist/types/contracts/handlers/default.d.ts +19 -0
- package/dist/types/contracts/handlers/delegate.d.ts +21 -0
- package/dist/types/contracts/handlers/helpers.d.ts +18 -0
- package/dist/types/contracts/handlers/index.d.ts +7 -0
- package/dist/types/contracts/handlers/registry.d.ts +65 -0
- package/dist/types/contracts/handlers/vhtlc.d.ts +32 -0
- package/dist/types/contracts/index.d.ts +14 -0
- package/dist/types/contracts/types.d.ts +222 -0
- package/dist/types/db/manager.d.ts +22 -0
- package/dist/types/forfeit.d.ts +2 -1
- package/dist/types/identity/index.d.ts +1 -0
- package/dist/types/identity/seedIdentity.d.ts +128 -0
- package/dist/types/index.d.ts +21 -12
- package/dist/types/intent/index.d.ts +2 -1
- package/dist/types/providers/ark.d.ts +11 -2
- package/dist/types/providers/delegator.d.ts +29 -0
- package/dist/types/providers/indexer.d.ts +11 -1
- package/dist/types/repositories/contractRepository.d.ts +30 -19
- package/dist/types/repositories/inMemory/contractRepository.d.ts +17 -0
- package/dist/types/repositories/inMemory/walletRepository.d.ts +26 -0
- package/dist/types/repositories/index.d.ts +7 -0
- package/dist/types/repositories/indexedDB/contractRepository.d.ts +21 -0
- package/dist/types/repositories/indexedDB/db.d.ts +56 -0
- package/dist/types/repositories/indexedDB/schema.d.ts +8 -0
- package/dist/types/repositories/indexedDB/walletRepository.d.ts +25 -0
- package/dist/types/repositories/indexedDB/websqlAdapter.d.ts +49 -0
- package/dist/types/repositories/migrations/contractRepositoryImpl.d.ts +24 -0
- package/dist/types/repositories/migrations/fromStorageAdapter.d.ts +19 -0
- package/dist/types/repositories/migrations/walletRepositoryImpl.d.ts +27 -0
- package/dist/types/repositories/walletRepository.d.ts +13 -24
- package/dist/types/script/base.d.ts +1 -0
- package/dist/types/script/delegate.d.ts +36 -0
- package/dist/types/storage/asyncStorage.d.ts +4 -0
- package/dist/types/storage/fileSystem.d.ts +3 -0
- package/dist/types/storage/inMemory.d.ts +3 -0
- package/dist/types/storage/index.d.ts +3 -0
- package/dist/types/storage/indexedDB.d.ts +3 -0
- package/dist/types/storage/localStorage.d.ts +3 -0
- package/dist/types/utils/arkTransaction.d.ts +6 -0
- package/dist/types/utils/txSizeEstimator.d.ts +12 -2
- package/dist/types/wallet/asset-manager.d.ts +78 -0
- package/dist/types/wallet/asset.d.ts +21 -0
- package/dist/types/wallet/batch.d.ts +1 -1
- package/dist/types/wallet/delegator.d.ts +24 -0
- package/dist/types/wallet/expo/background.d.ts +66 -0
- package/dist/types/wallet/expo/index.d.ts +4 -0
- package/dist/types/wallet/expo/wallet.d.ts +97 -0
- package/dist/types/wallet/index.d.ts +75 -2
- package/dist/types/wallet/onchain.d.ts +22 -1
- package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +366 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +20 -11
- package/dist/types/wallet/utils.d.ts +13 -1
- package/dist/types/wallet/validation.d.ts +24 -0
- package/dist/types/wallet/wallet.d.ts +111 -17
- package/dist/types/worker/browser/service-worker-manager.d.ts +21 -0
- package/dist/types/{wallet/serviceWorker → worker/browser}/utils.d.ts +2 -1
- package/dist/types/worker/expo/asyncStorageTaskQueue.d.ts +46 -0
- package/dist/types/worker/expo/index.d.ts +7 -0
- package/dist/types/worker/expo/processors/contractPollProcessor.d.ts +14 -0
- package/dist/types/worker/expo/processors/index.d.ts +1 -0
- package/dist/types/worker/expo/taskQueue.d.ts +50 -0
- package/dist/types/worker/expo/taskRunner.d.ts +42 -0
- package/dist/types/worker/messageBus.d.ts +109 -0
- package/package.json +71 -17
- package/dist/cjs/wallet/serviceWorker/request.js +0 -78
- package/dist/cjs/wallet/serviceWorker/response.js +0 -222
- package/dist/cjs/wallet/serviceWorker/worker.js +0 -655
- package/dist/esm/wallet/serviceWorker/request.js +0 -75
- package/dist/esm/wallet/serviceWorker/response.js +0 -219
- package/dist/esm/wallet/serviceWorker/worker.js +0 -651
- package/dist/types/wallet/serviceWorker/request.d.ts +0 -74
- package/dist/types/wallet/serviceWorker/response.d.ts +0 -123
- package/dist/types/wallet/serviceWorker/worker.d.ts +0 -53
|
@@ -1,43 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.Wallet = exports.ReadonlyWallet = void 0;
|
|
37
|
-
exports.
|
|
4
|
+
exports.selectVirtualCoins = selectVirtualCoins;
|
|
38
5
|
exports.waitForIncomingFunds = waitForIncomingFunds;
|
|
39
6
|
const base_1 = require("@scure/base");
|
|
40
|
-
const bip68 = __importStar(require("bip68"));
|
|
41
7
|
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
42
8
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
43
9
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
@@ -48,7 +14,9 @@ const onchain_1 = require("../providers/onchain");
|
|
|
48
14
|
const ark_1 = require("../providers/ark");
|
|
49
15
|
const forfeit_1 = require("../forfeit");
|
|
50
16
|
const validation_1 = require("../tree/validation");
|
|
17
|
+
const validation_2 = require("./validation");
|
|
51
18
|
const _1 = require(".");
|
|
19
|
+
const asset_1 = require("./asset");
|
|
52
20
|
const base_2 = require("../script/base");
|
|
53
21
|
const tapscript_1 = require("../script/tapscript");
|
|
54
22
|
const arkTransaction_1 = require("../utils/arkTransaction");
|
|
@@ -56,15 +24,18 @@ const vtxo_manager_1 = require("./vtxo-manager");
|
|
|
56
24
|
const arknote_1 = require("../arknote");
|
|
57
25
|
const intent_1 = require("../intent");
|
|
58
26
|
const indexer_1 = require("../providers/indexer");
|
|
59
|
-
const unknownFields_1 = require("../utils/unknownFields");
|
|
60
|
-
const inMemory_1 = require("../storage/inMemory");
|
|
61
|
-
const walletRepository_1 = require("../repositories/walletRepository");
|
|
62
|
-
const contractRepository_1 = require("../repositories/contractRepository");
|
|
63
27
|
const utils_1 = require("./utils");
|
|
64
28
|
const errors_1 = require("../providers/errors");
|
|
65
29
|
const batch_1 = require("./batch");
|
|
66
30
|
const arkfee_1 = require("../arkfee");
|
|
67
31
|
const transactionHistory_1 = require("../utils/transactionHistory");
|
|
32
|
+
const asset_manager_1 = require("./asset-manager");
|
|
33
|
+
const delegate_1 = require("../script/delegate");
|
|
34
|
+
const delegator_1 = require("./delegator");
|
|
35
|
+
const repositories_1 = require("../repositories");
|
|
36
|
+
const contractManager_1 = require("../contracts/contractManager");
|
|
37
|
+
const handlers_1 = require("../contracts/handlers");
|
|
38
|
+
const helpers_1 = require("../contracts/handlers/helpers");
|
|
68
39
|
/**
|
|
69
40
|
* Type guard function to check if an identity has a toReadonly method.
|
|
70
41
|
*/
|
|
@@ -75,7 +46,10 @@ function hasToReadonly(identity) {
|
|
|
75
46
|
typeof identity.toReadonly === "function");
|
|
76
47
|
}
|
|
77
48
|
class ReadonlyWallet {
|
|
78
|
-
|
|
49
|
+
get assetManager() {
|
|
50
|
+
return this._assetManager;
|
|
51
|
+
}
|
|
52
|
+
constructor(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegatorProvider, watcherConfig) {
|
|
79
53
|
this.identity = identity;
|
|
80
54
|
this.network = network;
|
|
81
55
|
this.onchainProvider = onchainProvider;
|
|
@@ -86,12 +60,15 @@ class ReadonlyWallet {
|
|
|
86
60
|
this.dustAmount = dustAmount;
|
|
87
61
|
this.walletRepository = walletRepository;
|
|
88
62
|
this.contractRepository = contractRepository;
|
|
63
|
+
this.delegatorProvider = delegatorProvider;
|
|
64
|
+
this.watcherConfig = watcherConfig;
|
|
65
|
+
this._assetManager = new asset_manager_1.ReadonlyAssetManager(this.indexerProvider);
|
|
89
66
|
}
|
|
90
67
|
/**
|
|
91
68
|
* Protected helper to set up shared wallet configuration.
|
|
92
69
|
* Extracts common logic used by both ReadonlyWallet.create() and Wallet.create().
|
|
93
70
|
*/
|
|
94
|
-
static async setupWalletConfig(config,
|
|
71
|
+
static async setupWalletConfig(config, pubKey) {
|
|
95
72
|
// Use provided arkProvider instance or create a new one from arkServerUrl
|
|
96
73
|
const arkProvider = config.arkProvider ||
|
|
97
74
|
(() => {
|
|
@@ -143,22 +120,26 @@ class ReadonlyWallet {
|
|
|
143
120
|
};
|
|
144
121
|
// Generate tapscripts for offchain and boarding address
|
|
145
122
|
const serverPubKey = base_1.hex.decode(info.signerPubkey).slice(1);
|
|
146
|
-
const
|
|
147
|
-
|
|
123
|
+
const delegatePubKey = config.delegatorProvider
|
|
124
|
+
? await config.delegatorProvider
|
|
125
|
+
.getDelegateInfo()
|
|
126
|
+
.then((info) => base_1.hex.decode(info.pubkey).slice(1))
|
|
127
|
+
: undefined;
|
|
128
|
+
const offchainOptions = {
|
|
129
|
+
pubKey,
|
|
148
130
|
serverPubKey,
|
|
149
131
|
csvTimelock: exitTimelock,
|
|
150
|
-
}
|
|
132
|
+
};
|
|
133
|
+
const offchainTapscript = !delegatePubKey
|
|
134
|
+
? new default_1.DefaultVtxo.Script(offchainOptions)
|
|
135
|
+
: new delegate_1.DelegateVtxo.Script({ ...offchainOptions, delegatePubKey });
|
|
151
136
|
const boardingTapscript = new default_1.DefaultVtxo.Script({
|
|
152
|
-
|
|
153
|
-
serverPubKey,
|
|
137
|
+
...offchainOptions,
|
|
154
138
|
csvTimelock: boardingTimelock,
|
|
155
139
|
});
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const storage = config.storage || new inMemory_1.InMemoryStorageAdapter();
|
|
160
|
-
const walletRepository = new walletRepository_1.WalletRepositoryImpl(storage);
|
|
161
|
-
const contractRepository = new contractRepository_1.ContractRepositoryImpl(storage);
|
|
140
|
+
const walletRepository = config.storage?.walletRepository ?? new repositories_1.IndexedDBWalletRepository();
|
|
141
|
+
const contractRepository = config.storage?.contractRepository ??
|
|
142
|
+
new repositories_1.IndexedDBContractRepository();
|
|
162
143
|
return {
|
|
163
144
|
arkProvider,
|
|
164
145
|
indexerProvider,
|
|
@@ -172,6 +153,7 @@ class ReadonlyWallet {
|
|
|
172
153
|
walletRepository,
|
|
173
154
|
contractRepository,
|
|
174
155
|
info,
|
|
156
|
+
delegatorProvider: config.delegatorProvider,
|
|
175
157
|
};
|
|
176
158
|
}
|
|
177
159
|
static async create(config) {
|
|
@@ -180,11 +162,18 @@ class ReadonlyWallet {
|
|
|
180
162
|
throw new Error("Invalid configured public key");
|
|
181
163
|
}
|
|
182
164
|
const setup = await ReadonlyWallet.setupWalletConfig(config, pubkey);
|
|
183
|
-
return new ReadonlyWallet(config.identity, setup.network, setup.onchainProvider, setup.indexerProvider, setup.serverPubKey, setup.offchainTapscript, setup.boardingTapscript, setup.dustAmount, setup.walletRepository, setup.contractRepository);
|
|
165
|
+
return new ReadonlyWallet(config.identity, setup.network, setup.onchainProvider, setup.indexerProvider, setup.serverPubKey, setup.offchainTapscript, setup.boardingTapscript, setup.dustAmount, setup.walletRepository, setup.contractRepository, setup.delegatorProvider, config.watcherConfig);
|
|
184
166
|
}
|
|
185
167
|
get arkAddress() {
|
|
186
168
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
187
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Get the contract script for the wallet's default address.
|
|
172
|
+
* This is the pkScript hex, used to identify the wallet in ContractManager.
|
|
173
|
+
*/
|
|
174
|
+
get defaultContractScript() {
|
|
175
|
+
return base_1.hex.encode(this.offchainTapscript.pkScript);
|
|
176
|
+
}
|
|
188
177
|
async getAddress() {
|
|
189
178
|
return this.arkAddress.encode();
|
|
190
179
|
}
|
|
@@ -222,6 +211,22 @@ class ReadonlyWallet {
|
|
|
222
211
|
.reduce((sum, coin) => sum + coin.value, 0);
|
|
223
212
|
const totalBoarding = confirmed + unconfirmed;
|
|
224
213
|
const totalOffchain = settled + preconfirmed + recoverable;
|
|
214
|
+
// aggregate asset balances from spendable vtxos
|
|
215
|
+
const assetBalances = new Map();
|
|
216
|
+
for (const vtxo of vtxos) {
|
|
217
|
+
if (!(0, _1.isSpendable)(vtxo))
|
|
218
|
+
continue;
|
|
219
|
+
if (vtxo.assets) {
|
|
220
|
+
for (const a of vtxo.assets) {
|
|
221
|
+
const current = assetBalances.get(a.assetId) ?? 0;
|
|
222
|
+
assetBalances.set(a.assetId, current + a.amount);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const assets = Array.from(assetBalances.entries()).map(([assetId, amount]) => ({
|
|
227
|
+
assetId,
|
|
228
|
+
amount,
|
|
229
|
+
}));
|
|
225
230
|
return {
|
|
226
231
|
boarding: {
|
|
227
232
|
confirmed,
|
|
@@ -233,20 +238,39 @@ class ReadonlyWallet {
|
|
|
233
238
|
available: settled + preconfirmed,
|
|
234
239
|
recoverable,
|
|
235
240
|
total: totalBoarding + totalOffchain,
|
|
241
|
+
assets,
|
|
236
242
|
};
|
|
237
243
|
}
|
|
238
244
|
async getVtxos(filter) {
|
|
239
245
|
const address = await this.getAddress();
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
const scriptMap = await this.getScriptMap();
|
|
247
|
+
const f = filter ?? { withRecoverable: true, withUnrolled: false };
|
|
248
|
+
const allExtended = [];
|
|
249
|
+
// Query each script separately so we can extend VTXOs with the correct tapscript
|
|
250
|
+
for (const [scriptHex, vtxoScript] of scriptMap) {
|
|
251
|
+
const response = await this.indexerProvider.getVtxos({
|
|
252
|
+
scripts: [scriptHex],
|
|
253
|
+
});
|
|
254
|
+
let vtxos = response.vtxos.filter(_1.isSpendable);
|
|
255
|
+
if (!f.withRecoverable) {
|
|
256
|
+
vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo) && !(0, _1.isExpired)(vtxo));
|
|
257
|
+
}
|
|
258
|
+
if (f.withUnrolled) {
|
|
259
|
+
const spentVtxos = response.vtxos.filter((vtxo) => !(0, _1.isSpendable)(vtxo));
|
|
260
|
+
vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
|
|
261
|
+
}
|
|
262
|
+
for (const vtxo of vtxos) {
|
|
263
|
+
allExtended.push({
|
|
264
|
+
...vtxo,
|
|
265
|
+
forfeitTapLeafScript: vtxoScript.forfeit(),
|
|
266
|
+
intentTapLeafScript: vtxoScript.forfeit(),
|
|
267
|
+
tapTree: vtxoScript.encode(),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
247
271
|
// Update cache with fresh data
|
|
248
|
-
await this.walletRepository.saveVtxos(address,
|
|
249
|
-
return
|
|
272
|
+
await this.walletRepository.saveVtxos(address, allExtended);
|
|
273
|
+
return allExtended;
|
|
250
274
|
}
|
|
251
275
|
async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
|
|
252
276
|
const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
|
|
@@ -264,9 +288,8 @@ class ReadonlyWallet {
|
|
|
264
288
|
return vtxos;
|
|
265
289
|
}
|
|
266
290
|
async getTransactionHistory() {
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
});
|
|
291
|
+
const scripts = await this.getWalletScripts();
|
|
292
|
+
const response = await this.indexerProvider.getVtxos({ scripts });
|
|
270
293
|
const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
|
|
271
294
|
const getTxCreatedAt = (txid) => this.indexerProvider
|
|
272
295
|
.getVtxos({ outpoints: [{ txid, vout: 0 }] })
|
|
@@ -376,17 +399,20 @@ class ReadonlyWallet {
|
|
|
376
399
|
});
|
|
377
400
|
}
|
|
378
401
|
if (this.indexerProvider && arkAddress) {
|
|
379
|
-
const
|
|
380
|
-
const subscriptionId = await this.indexerProvider.subscribeForScripts(
|
|
381
|
-
base_1.hex.encode(offchainScript.pkScript),
|
|
382
|
-
]);
|
|
402
|
+
const walletScripts = await this.getWalletScripts();
|
|
403
|
+
const subscriptionId = await this.indexerProvider.subscribeForScripts(walletScripts);
|
|
383
404
|
const abortController = new AbortController();
|
|
384
405
|
const subscription = this.indexerProvider.getSubscription(subscriptionId, abortController.signal);
|
|
385
406
|
indexerStopFunc = async () => {
|
|
386
407
|
abortController.abort();
|
|
387
408
|
await this.indexerProvider?.unsubscribeForScripts(subscriptionId);
|
|
388
409
|
};
|
|
389
|
-
// Handle subscription updates asynchronously without blocking
|
|
410
|
+
// Handle subscription updates asynchronously without blocking.
|
|
411
|
+
// Note: subscription covers all wallet scripts (default + delegate),
|
|
412
|
+
// but we can't determine which script each VTXO belongs to from the
|
|
413
|
+
// subscription event. VTXOs are extended with the current offchainTapscript;
|
|
414
|
+
// this is for notification/display only — not for spending.
|
|
415
|
+
// For correct extension metadata, use getVtxos() which queries per-script.
|
|
390
416
|
(async () => {
|
|
391
417
|
try {
|
|
392
418
|
for await (const update of subscription) {
|
|
@@ -413,7 +439,7 @@ class ReadonlyWallet {
|
|
|
413
439
|
}
|
|
414
440
|
async fetchPendingTxs() {
|
|
415
441
|
// get non-swept VTXOs, rely on the indexer only in case DB doesn't have the right state
|
|
416
|
-
const scripts =
|
|
442
|
+
const scripts = await this.getWalletScripts();
|
|
417
443
|
let { vtxos } = await this.indexerProvider.getVtxos({
|
|
418
444
|
scripts,
|
|
419
445
|
});
|
|
@@ -423,6 +449,183 @@ class ReadonlyWallet {
|
|
|
423
449
|
vtxo.arkTxId !== undefined)
|
|
424
450
|
.map((_) => _.arkTxId);
|
|
425
451
|
}
|
|
452
|
+
// ========================================================================
|
|
453
|
+
// Multi-script support (default + delegate addresses)
|
|
454
|
+
// ========================================================================
|
|
455
|
+
/**
|
|
456
|
+
* Get all pkScript hex strings for the wallet's own addresses
|
|
457
|
+
* (both delegate and non-delegate, current and historical).
|
|
458
|
+
* Falls back to only the current script if ContractManager is not yet initialized.
|
|
459
|
+
*/
|
|
460
|
+
async getWalletScripts() {
|
|
461
|
+
if (this._contractManager) {
|
|
462
|
+
try {
|
|
463
|
+
const contracts = await this._contractManager.getContracts({
|
|
464
|
+
type: ["default", "delegate"],
|
|
465
|
+
});
|
|
466
|
+
if (contracts.length > 0) {
|
|
467
|
+
return contracts.map((c) => c.script);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
// fall through to current script only
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return [base_1.hex.encode(this.offchainTapscript.pkScript)];
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Build a map of scriptHex → VtxoScript for all wallet contracts,
|
|
478
|
+
* so VTXOs can be extended with the correct tapscript per contract.
|
|
479
|
+
*/
|
|
480
|
+
async getScriptMap() {
|
|
481
|
+
const map = new Map();
|
|
482
|
+
// Always include the current script
|
|
483
|
+
const currentScriptHex = base_1.hex.encode(this.offchainTapscript.pkScript);
|
|
484
|
+
map.set(currentScriptHex, this.offchainTapscript);
|
|
485
|
+
if (this._contractManager) {
|
|
486
|
+
try {
|
|
487
|
+
const contracts = await this._contractManager.getContracts({
|
|
488
|
+
type: ["default", "delegate"],
|
|
489
|
+
});
|
|
490
|
+
for (const contract of contracts) {
|
|
491
|
+
if (map.has(contract.script))
|
|
492
|
+
continue;
|
|
493
|
+
const handler = handlers_1.contractHandlers.get(contract.type);
|
|
494
|
+
if (handler) {
|
|
495
|
+
const script = handler.createScript(contract.params);
|
|
496
|
+
map.set(contract.script, script);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
// ContractManager error — only current script in map
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return map;
|
|
505
|
+
}
|
|
506
|
+
// ========================================================================
|
|
507
|
+
// Contract Management
|
|
508
|
+
// ========================================================================
|
|
509
|
+
/**
|
|
510
|
+
* Get the ContractManager for managing contracts including the wallet's default address.
|
|
511
|
+
*
|
|
512
|
+
* The ContractManager handles:
|
|
513
|
+
* - The wallet's default receiving address (as a "default" contract)
|
|
514
|
+
* - External contracts (Boltz swaps, HTLCs, etc.)
|
|
515
|
+
* - Multi-contract watching with resilient connections
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```typescript
|
|
519
|
+
* const manager = await wallet.getContractManager();
|
|
520
|
+
*
|
|
521
|
+
* // Create a contract for a Boltz swap
|
|
522
|
+
* const contract = await manager.createContract({
|
|
523
|
+
* label: "Boltz Swap",
|
|
524
|
+
* type: "vhtlc",
|
|
525
|
+
* params: { ... },
|
|
526
|
+
* script: swapScript,
|
|
527
|
+
* address: swapAddress,
|
|
528
|
+
* });
|
|
529
|
+
*
|
|
530
|
+
* // Start watching for events (includes wallet's default address)
|
|
531
|
+
* const stop = await manager.onContractEvent((event) => {
|
|
532
|
+
* console.log(`${event.type} on ${event.contractScript}`);
|
|
533
|
+
* });
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
536
|
+
async getContractManager() {
|
|
537
|
+
// Return existing manager if already initialized
|
|
538
|
+
if (this._contractManager) {
|
|
539
|
+
return this._contractManager;
|
|
540
|
+
}
|
|
541
|
+
// If initialization is in progress, wait for it
|
|
542
|
+
if (this._contractManagerInitializing) {
|
|
543
|
+
return this._contractManagerInitializing;
|
|
544
|
+
}
|
|
545
|
+
// Start initialization and store the promise
|
|
546
|
+
this._contractManagerInitializing = this.initializeContractManager();
|
|
547
|
+
try {
|
|
548
|
+
const manager = await this._contractManagerInitializing;
|
|
549
|
+
this._contractManager = manager;
|
|
550
|
+
return manager;
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
// Clear the initializing promise so subsequent calls can retry
|
|
554
|
+
this._contractManagerInitializing = undefined;
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
// Clear the initializing promise after completion
|
|
559
|
+
this._contractManagerInitializing = undefined;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async initializeContractManager() {
|
|
563
|
+
const manager = await contractManager_1.ContractManager.create({
|
|
564
|
+
indexerProvider: this.indexerProvider,
|
|
565
|
+
contractRepository: this.contractRepository,
|
|
566
|
+
walletRepository: this.walletRepository,
|
|
567
|
+
getDefaultAddress: () => this.getAddress(),
|
|
568
|
+
watcherConfig: this.watcherConfig,
|
|
569
|
+
});
|
|
570
|
+
// Register the wallet's current address as a contract
|
|
571
|
+
const csvTimelock = this.offchainTapscript.options.csvTimelock ??
|
|
572
|
+
default_1.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
573
|
+
const csvTimelockStr = (0, helpers_1.timelockToSequence)(csvTimelock).toString();
|
|
574
|
+
const isDelegateScript = this.offchainTapscript instanceof delegate_1.DelegateVtxo.Script;
|
|
575
|
+
if (isDelegateScript) {
|
|
576
|
+
const delegateScript = this
|
|
577
|
+
.offchainTapscript;
|
|
578
|
+
// Register the delegate contract (current address)
|
|
579
|
+
await manager.createContract({
|
|
580
|
+
type: "delegate",
|
|
581
|
+
params: {
|
|
582
|
+
pubKey: base_1.hex.encode(delegateScript.options.pubKey),
|
|
583
|
+
serverPubKey: base_1.hex.encode(delegateScript.options.serverPubKey),
|
|
584
|
+
delegatePubKey: base_1.hex.encode(delegateScript.options.delegatePubKey),
|
|
585
|
+
csvTimelock: csvTimelockStr,
|
|
586
|
+
},
|
|
587
|
+
script: this.defaultContractScript,
|
|
588
|
+
address: await this.getAddress(),
|
|
589
|
+
state: "active",
|
|
590
|
+
});
|
|
591
|
+
// Also register the non-delegate version so old VTXOs remain visible
|
|
592
|
+
const nonDelegateScript = new default_1.DefaultVtxo.Script({
|
|
593
|
+
pubKey: delegateScript.options.pubKey,
|
|
594
|
+
serverPubKey: delegateScript.options.serverPubKey,
|
|
595
|
+
csvTimelock,
|
|
596
|
+
});
|
|
597
|
+
await manager.createContract({
|
|
598
|
+
type: "default",
|
|
599
|
+
params: {
|
|
600
|
+
pubKey: base_1.hex.encode(delegateScript.options.pubKey),
|
|
601
|
+
serverPubKey: base_1.hex.encode(delegateScript.options.serverPubKey),
|
|
602
|
+
csvTimelock: csvTimelockStr,
|
|
603
|
+
},
|
|
604
|
+
script: base_1.hex.encode(nonDelegateScript.pkScript),
|
|
605
|
+
address: nonDelegateScript
|
|
606
|
+
.address(this.network.hrp, this.arkServerPublicKey)
|
|
607
|
+
.encode(),
|
|
608
|
+
state: "active",
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
// Register the default contract (current address)
|
|
613
|
+
await manager.createContract({
|
|
614
|
+
type: "default",
|
|
615
|
+
params: {
|
|
616
|
+
pubKey: base_1.hex.encode(this.offchainTapscript.options.pubKey),
|
|
617
|
+
serverPubKey: base_1.hex.encode(this.offchainTapscript.options.serverPubKey),
|
|
618
|
+
csvTimelock: csvTimelockStr,
|
|
619
|
+
},
|
|
620
|
+
script: this.defaultContractScript,
|
|
621
|
+
address: await this.getAddress(),
|
|
622
|
+
state: "active",
|
|
623
|
+
});
|
|
624
|
+
// Any old "delegate" contract from a prior wallet incarnation
|
|
625
|
+
// is already loaded by ContractManager.initialize() from ContractRepository
|
|
626
|
+
}
|
|
627
|
+
return manager;
|
|
628
|
+
}
|
|
426
629
|
}
|
|
427
630
|
exports.ReadonlyWallet = ReadonlyWallet;
|
|
428
631
|
/**
|
|
@@ -459,8 +662,8 @@ exports.ReadonlyWallet = ReadonlyWallet;
|
|
|
459
662
|
* ```
|
|
460
663
|
*/
|
|
461
664
|
class Wallet extends ReadonlyWallet {
|
|
462
|
-
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository, renewalConfig) {
|
|
463
|
-
super(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository);
|
|
665
|
+
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository, renewalConfig, delegatorProvider, watcherConfig) {
|
|
666
|
+
super(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegatorProvider, watcherConfig);
|
|
464
667
|
this.networkName = networkName;
|
|
465
668
|
this.arkProvider = arkProvider;
|
|
466
669
|
this.serverUnrollScript = serverUnrollScript;
|
|
@@ -472,6 +675,13 @@ class Wallet extends ReadonlyWallet {
|
|
|
472
675
|
...vtxo_manager_1.DEFAULT_RENEWAL_CONFIG,
|
|
473
676
|
...renewalConfig,
|
|
474
677
|
};
|
|
678
|
+
this.delegatorManager = delegatorProvider
|
|
679
|
+
? new delegator_1.DelegatorManagerImpl(delegatorProvider, arkProvider, identity)
|
|
680
|
+
: undefined;
|
|
681
|
+
}
|
|
682
|
+
get assetManager() {
|
|
683
|
+
this._walletAssetManager ?? (this._walletAssetManager = new asset_manager_1.AssetManager(this));
|
|
684
|
+
return this._walletAssetManager;
|
|
475
685
|
}
|
|
476
686
|
static async create(config) {
|
|
477
687
|
const pubkey = await config.identity.xOnlyPublicKey();
|
|
@@ -494,7 +704,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
494
704
|
const forfeitPubkey = base_1.hex.decode(setup.info.forfeitPubkey).slice(1);
|
|
495
705
|
const forfeitAddress = (0, btc_signer_1.Address)(setup.network).decode(setup.info.forfeitAddress);
|
|
496
706
|
const forfeitOutputScript = btc_signer_1.OutScript.encode(forfeitAddress);
|
|
497
|
-
return new Wallet(config.identity, setup.network, setup.networkName, setup.onchainProvider, setup.arkProvider, setup.indexerProvider, setup.serverPubKey, setup.offchainTapscript, setup.boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, setup.dustAmount, setup.walletRepository, setup.contractRepository, config.renewalConfig);
|
|
707
|
+
return new Wallet(config.identity, setup.network, setup.networkName, setup.onchainProvider, setup.arkProvider, setup.indexerProvider, setup.serverPubKey, setup.offchainTapscript, setup.boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, setup.dustAmount, setup.walletRepository, setup.contractRepository, config.renewalConfig, config.delegatorProvider, config.watcherConfig);
|
|
498
708
|
}
|
|
499
709
|
/**
|
|
500
710
|
* Convert this wallet to a readonly wallet.
|
|
@@ -518,21 +728,16 @@ class Wallet extends ReadonlyWallet {
|
|
|
518
728
|
const readonlyIdentity = hasToReadonly(this.identity)
|
|
519
729
|
? await this.identity.toReadonly()
|
|
520
730
|
: this.identity; // Identity extends ReadonlyIdentity, so this is safe
|
|
521
|
-
return new ReadonlyWallet(readonlyIdentity, this.network, this.onchainProvider, this.indexerProvider, this.arkServerPublicKey, this.offchainTapscript, this.boardingTapscript, this.dustAmount, this.walletRepository, this.contractRepository);
|
|
731
|
+
return new ReadonlyWallet(readonlyIdentity, this.network, this.onchainProvider, this.indexerProvider, this.arkServerPublicKey, this.offchainTapscript, this.boardingTapscript, this.dustAmount, this.walletRepository, this.contractRepository, this.delegatorProvider, this.watcherConfig);
|
|
522
732
|
}
|
|
523
733
|
async sendBitcoin(params) {
|
|
524
734
|
if (params.amount <= 0) {
|
|
525
735
|
throw new Error("Amount must be positive");
|
|
526
736
|
}
|
|
527
|
-
if (!isValidArkAddress(params.address)) {
|
|
737
|
+
if (!(0, arkTransaction_1.isValidArkAddress)(params.address)) {
|
|
528
738
|
throw new Error("Invalid Ark address " + params.address);
|
|
529
739
|
}
|
|
530
|
-
|
|
531
|
-
const virtualCoins = await this.getVirtualCoins({
|
|
532
|
-
withRecoverable: false,
|
|
533
|
-
});
|
|
534
|
-
let selected;
|
|
535
|
-
if (params.selectedVtxos) {
|
|
740
|
+
if (params.selectedVtxos && params.selectedVtxos.length > 0) {
|
|
536
741
|
const selectedVtxoSum = params.selectedVtxos
|
|
537
742
|
.map((v) => v.value)
|
|
538
743
|
.reduce((a, b) => a + b, 0);
|
|
@@ -540,125 +745,38 @@ class Wallet extends ReadonlyWallet {
|
|
|
540
745
|
throw new Error("Selected VTXOs do not cover specified amount");
|
|
541
746
|
}
|
|
542
747
|
const changeAmount = selectedVtxoSum - params.amount;
|
|
543
|
-
selected = {
|
|
748
|
+
const selected = {
|
|
544
749
|
inputs: params.selectedVtxos,
|
|
545
750
|
changeAmount: BigInt(changeAmount),
|
|
546
751
|
};
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (!selectedLeaf) {
|
|
553
|
-
throw new Error("Selected leaf not found");
|
|
554
|
-
}
|
|
555
|
-
const outputAddress = address_1.ArkAddress.decode(params.address);
|
|
556
|
-
const outputScript = BigInt(params.amount) < this.dustAmount
|
|
557
|
-
? outputAddress.subdustPkScript
|
|
558
|
-
: outputAddress.pkScript;
|
|
559
|
-
const outputs = [
|
|
560
|
-
{
|
|
561
|
-
script: outputScript,
|
|
562
|
-
amount: BigInt(params.amount),
|
|
563
|
-
},
|
|
564
|
-
];
|
|
565
|
-
// add change output if needed
|
|
566
|
-
if (selected.changeAmount > 0n) {
|
|
567
|
-
const changeOutputScript = selected.changeAmount < this.dustAmount
|
|
568
|
-
? this.arkAddress.subdustPkScript
|
|
569
|
-
: this.arkAddress.pkScript;
|
|
570
|
-
outputs.push({
|
|
571
|
-
script: changeOutputScript,
|
|
572
|
-
amount: BigInt(selected.changeAmount),
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
const tapTree = this.offchainTapscript.encode();
|
|
576
|
-
const offchainTx = (0, arkTransaction_1.buildOffchainTx)(selected.inputs.map((input) => ({
|
|
577
|
-
...input,
|
|
578
|
-
tapLeafScript: selectedLeaf,
|
|
579
|
-
tapTree,
|
|
580
|
-
})), outputs, this.serverUnrollScript);
|
|
581
|
-
const signedVirtualTx = await this.identity.sign(offchainTx.arkTx);
|
|
582
|
-
const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base_1.base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base_1.base64.encode(c.toPSBT())));
|
|
583
|
-
// sign the checkpoints
|
|
584
|
-
const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
|
|
585
|
-
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
|
|
586
|
-
const signedCheckpoint = await this.identity.sign(tx);
|
|
587
|
-
return base_1.base64.encode(signedCheckpoint.toPSBT());
|
|
588
|
-
}));
|
|
589
|
-
await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
|
|
590
|
-
try {
|
|
591
|
-
// mark VTXOs as spent and optionally add the change VTXO
|
|
592
|
-
const spentVtxos = [];
|
|
593
|
-
const commitmentTxIds = new Set();
|
|
594
|
-
let batchExpiry = Number.MAX_SAFE_INTEGER;
|
|
595
|
-
for (const [inputIndex, input] of selected.inputs.entries()) {
|
|
596
|
-
const vtxo = (0, utils_1.extendVirtualCoin)(this, input);
|
|
597
|
-
const checkpointB64 = signedCheckpointTxs[inputIndex];
|
|
598
|
-
const checkpoint = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(checkpointB64));
|
|
599
|
-
spentVtxos.push({
|
|
600
|
-
...vtxo,
|
|
601
|
-
virtualStatus: { ...vtxo.virtualStatus, state: "spent" },
|
|
602
|
-
spentBy: checkpoint.id,
|
|
603
|
-
arkTxId: arkTxid,
|
|
604
|
-
isSpent: true,
|
|
605
|
-
});
|
|
606
|
-
if (vtxo.virtualStatus.commitmentTxIds) {
|
|
607
|
-
for (const commitmentTxId of vtxo.virtualStatus
|
|
608
|
-
.commitmentTxIds) {
|
|
609
|
-
commitmentTxIds.add(commitmentTxId);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
if (vtxo.virtualStatus.batchExpiry) {
|
|
613
|
-
batchExpiry = Math.min(batchExpiry, vtxo.virtualStatus.batchExpiry);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
const createdAt = Date.now();
|
|
617
|
-
const addr = this.arkAddress.encode();
|
|
618
|
-
if (selected.changeAmount > 0n &&
|
|
619
|
-
batchExpiry !== Number.MAX_SAFE_INTEGER) {
|
|
620
|
-
const changeVtxo = {
|
|
621
|
-
txid: arkTxid,
|
|
622
|
-
vout: outputs.length - 1,
|
|
623
|
-
createdAt: new Date(createdAt),
|
|
624
|
-
forfeitTapLeafScript: this.offchainTapscript.forfeit(),
|
|
625
|
-
intentTapLeafScript: this.offchainTapscript.forfeit(),
|
|
626
|
-
isUnrolled: false,
|
|
627
|
-
isSpent: false,
|
|
628
|
-
tapTree: this.offchainTapscript.encode(),
|
|
629
|
-
value: Number(selected.changeAmount),
|
|
630
|
-
virtualStatus: {
|
|
631
|
-
state: "preconfirmed",
|
|
632
|
-
commitmentTxIds: Array.from(commitmentTxIds),
|
|
633
|
-
batchExpiry,
|
|
634
|
-
},
|
|
635
|
-
status: {
|
|
636
|
-
confirmed: false,
|
|
637
|
-
},
|
|
638
|
-
};
|
|
639
|
-
await this.walletRepository.saveVtxos(addr, [changeVtxo]);
|
|
640
|
-
}
|
|
641
|
-
await this.walletRepository.saveVtxos(addr, spentVtxos);
|
|
642
|
-
await this.walletRepository.saveTransactions(addr, [
|
|
752
|
+
const outputAddress = address_1.ArkAddress.decode(params.address);
|
|
753
|
+
const outputScript = BigInt(params.amount) < this.dustAmount
|
|
754
|
+
? outputAddress.subdustPkScript
|
|
755
|
+
: outputAddress.pkScript;
|
|
756
|
+
const outputs = [
|
|
643
757
|
{
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
commitmentTxid: "",
|
|
647
|
-
arkTxid: arkTxid,
|
|
648
|
-
},
|
|
649
|
-
amount: params.amount,
|
|
650
|
-
type: _1.TxType.TxSent,
|
|
651
|
-
settled: false,
|
|
652
|
-
createdAt: Date.now(),
|
|
758
|
+
script: outputScript,
|
|
759
|
+
amount: BigInt(params.amount),
|
|
653
760
|
},
|
|
654
|
-
]
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
761
|
+
];
|
|
762
|
+
// add change output if needed
|
|
763
|
+
if (selected.changeAmount > 0n) {
|
|
764
|
+
const changeOutputScript = selected.changeAmount < this.dustAmount
|
|
765
|
+
? this.arkAddress.subdustPkScript
|
|
766
|
+
: this.arkAddress.pkScript;
|
|
767
|
+
outputs.push({
|
|
768
|
+
script: changeOutputScript,
|
|
769
|
+
amount: BigInt(selected.changeAmount),
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(selected.inputs, outputs);
|
|
773
|
+
await this.updateDbAfterOffchainTx(selected.inputs, arkTxid, signedCheckpointTxs, params.amount, selected.changeAmount, selected.changeAmount > 0n ? outputs.length - 1 : 0);
|
|
660
774
|
return arkTxid;
|
|
661
775
|
}
|
|
776
|
+
return this.send({
|
|
777
|
+
address: params.address,
|
|
778
|
+
amount: params.amount,
|
|
779
|
+
});
|
|
662
780
|
}
|
|
663
781
|
async settle(params, eventCallback) {
|
|
664
782
|
if (params?.inputs) {
|
|
@@ -759,6 +877,49 @@ class Wallet extends ReadonlyWallet {
|
|
|
759
877
|
script,
|
|
760
878
|
});
|
|
761
879
|
}
|
|
880
|
+
// if some of the inputs hold assets, build the asset packet and append as output
|
|
881
|
+
// in the intent proof tx, there is a "fake" input at index 0
|
|
882
|
+
// so the real coin indices are offset by +1
|
|
883
|
+
const assetInputs = new Map();
|
|
884
|
+
for (let i = 0; i < params.inputs.length; i++) {
|
|
885
|
+
if ("assets" in params.inputs[i]) {
|
|
886
|
+
const assets = params.inputs[i]
|
|
887
|
+
.assets;
|
|
888
|
+
if (assets && assets.length > 0) {
|
|
889
|
+
assetInputs.set(i + 1, assets);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
let outputAssets;
|
|
894
|
+
let assetOutputIndex; // where to send the assets
|
|
895
|
+
if (assetInputs.size > 0) {
|
|
896
|
+
// collect all input assets and assign them to the first offchain output
|
|
897
|
+
const allAssets = new Map();
|
|
898
|
+
for (const [, assets] of assetInputs) {
|
|
899
|
+
for (const asset of assets) {
|
|
900
|
+
const existing = allAssets.get(asset.assetId) ?? 0n;
|
|
901
|
+
allAssets.set(asset.assetId, existing + BigInt(asset.amount));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
outputAssets = [];
|
|
905
|
+
for (const [assetId, amount] of allAssets) {
|
|
906
|
+
outputAssets.push({ assetId, amount: Number(amount) });
|
|
907
|
+
}
|
|
908
|
+
const firstOffchainIndex = params.outputs.findIndex((_, i) => !onchainOutputIndexes.includes(i));
|
|
909
|
+
if (firstOffchainIndex === -1) {
|
|
910
|
+
throw new Error("Cannot settle assets without an offchain output");
|
|
911
|
+
}
|
|
912
|
+
assetOutputIndex = firstOffchainIndex;
|
|
913
|
+
}
|
|
914
|
+
const recipients = params.outputs.map((output, i) => ({
|
|
915
|
+
address: output.address,
|
|
916
|
+
amount: Number(output.amount),
|
|
917
|
+
assets: i === assetOutputIndex ? outputAssets : undefined,
|
|
918
|
+
}));
|
|
919
|
+
if (outputAssets && outputAssets.length > 0) {
|
|
920
|
+
const assetPacket = (0, asset_1.createAssetPacket)(assetInputs, recipients);
|
|
921
|
+
outputs.push(assetPacket.txOut());
|
|
922
|
+
}
|
|
762
923
|
// session holds the state of the musig2 signing process of the vtxo tree
|
|
763
924
|
let session;
|
|
764
925
|
const signingPublicKeys = [];
|
|
@@ -775,17 +936,19 @@ class Wallet extends ReadonlyWallet {
|
|
|
775
936
|
...signingPublicKeys,
|
|
776
937
|
...params.inputs.map((input) => `${input.txid}:${input.vout}`),
|
|
777
938
|
];
|
|
778
|
-
const handler = this.createBatchHandler(intentId, params.inputs, session);
|
|
939
|
+
const handler = this.createBatchHandler(intentId, params.inputs, recipients, session);
|
|
779
940
|
const abortController = new AbortController();
|
|
780
941
|
try {
|
|
781
942
|
const stream = this.arkProvider.getEventStream(abortController.signal, topics);
|
|
782
|
-
|
|
943
|
+
const commitmentTxid = await batch_1.Batch.join(stream, handler, {
|
|
783
944
|
abortController,
|
|
784
945
|
skipVtxoTreeSigning: !hasOffchainOutputs,
|
|
785
946
|
eventCallback: eventCallback
|
|
786
947
|
? (event) => Promise.resolve(eventCallback(event))
|
|
787
948
|
: undefined,
|
|
788
949
|
});
|
|
950
|
+
await this.updateDbAfterSettle(params.inputs, commitmentTxid);
|
|
951
|
+
return commitmentTxid;
|
|
789
952
|
}
|
|
790
953
|
catch (error) {
|
|
791
954
|
// delete the intent to not be stuck in the queue
|
|
@@ -890,8 +1053,9 @@ class Wallet extends ReadonlyWallet {
|
|
|
890
1053
|
* @param intentId - The intent ID.
|
|
891
1054
|
* @param inputs - The inputs of the intent.
|
|
892
1055
|
* @param session - The musig2 signing session, if not provided, the signing will be skipped.
|
|
1056
|
+
* @param expectedRecipients - Expected recipients to validate in the vtxo tree.
|
|
893
1057
|
*/
|
|
894
|
-
createBatchHandler(intentId, inputs, session) {
|
|
1058
|
+
createBatchHandler(intentId, inputs, expectedRecipients, session) {
|
|
895
1059
|
let sweepTapTreeRoot;
|
|
896
1060
|
return {
|
|
897
1061
|
onBatchStarted: async (event) => {
|
|
@@ -939,7 +1103,10 @@ class Wallet extends ReadonlyWallet {
|
|
|
939
1103
|
// validate the unsigned vtxo tree
|
|
940
1104
|
const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
|
|
941
1105
|
(0, validation_1.validateVtxoTxGraph)(vtxoTree, commitmentTx, sweepTapTreeRoot);
|
|
942
|
-
//
|
|
1106
|
+
// validate that all expected receivers are in the vtxo tree with correct amounts and assets
|
|
1107
|
+
if (expectedRecipients && expectedRecipients.length > 0) {
|
|
1108
|
+
(0, validation_2.validateBatchRecipients)(commitmentTx, vtxoTree.leaves(), expectedRecipients, this.network);
|
|
1109
|
+
}
|
|
943
1110
|
const sharedOutput = commitmentTx.getOutput(0);
|
|
944
1111
|
if (!sharedOutput?.amount) {
|
|
945
1112
|
throw new Error("Shared output not found");
|
|
@@ -995,16 +1162,15 @@ class Wallet extends ReadonlyWallet {
|
|
|
995
1162
|
throw error;
|
|
996
1163
|
}
|
|
997
1164
|
}
|
|
998
|
-
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
|
|
999
|
-
const inputs = this.prepareIntentProofInputs(coins);
|
|
1165
|
+
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys, validAt) {
|
|
1000
1166
|
const message = {
|
|
1001
1167
|
type: "register",
|
|
1002
1168
|
onchain_output_indexes: onchainOutputsIndexes,
|
|
1003
|
-
valid_at: 0,
|
|
1169
|
+
valid_at: validAt ? Math.floor(validAt) : 0,
|
|
1004
1170
|
expire_at: 0,
|
|
1005
1171
|
cosigners_public_keys: cosignerPubKeys,
|
|
1006
1172
|
};
|
|
1007
|
-
const proof = intent_1.Intent.create(message,
|
|
1173
|
+
const proof = intent_1.Intent.create(message, coins, outputs);
|
|
1008
1174
|
const signedProof = await this.identity.sign(proof);
|
|
1009
1175
|
return {
|
|
1010
1176
|
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
@@ -1012,25 +1178,23 @@ class Wallet extends ReadonlyWallet {
|
|
|
1012
1178
|
};
|
|
1013
1179
|
}
|
|
1014
1180
|
async makeDeleteIntentSignature(coins) {
|
|
1015
|
-
const inputs = this.prepareIntentProofInputs(coins);
|
|
1016
1181
|
const message = {
|
|
1017
1182
|
type: "delete",
|
|
1018
1183
|
expire_at: 0,
|
|
1019
1184
|
};
|
|
1020
|
-
const proof = intent_1.Intent.create(message,
|
|
1185
|
+
const proof = intent_1.Intent.create(message, coins, []);
|
|
1021
1186
|
const signedProof = await this.identity.sign(proof);
|
|
1022
1187
|
return {
|
|
1023
1188
|
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
1024
1189
|
message,
|
|
1025
1190
|
};
|
|
1026
1191
|
}
|
|
1027
|
-
async makeGetPendingTxIntentSignature(
|
|
1028
|
-
const inputs = this.prepareIntentProofInputs(vtxos);
|
|
1192
|
+
async makeGetPendingTxIntentSignature(coins) {
|
|
1029
1193
|
const message = {
|
|
1030
1194
|
type: "get-pending-tx",
|
|
1031
1195
|
expire_at: 0,
|
|
1032
1196
|
};
|
|
1033
|
-
const proof = intent_1.Intent.create(message,
|
|
1197
|
+
const proof = intent_1.Intent.create(message, coins, []);
|
|
1034
1198
|
const signedProof = await this.identity.sign(proof);
|
|
1035
1199
|
return {
|
|
1036
1200
|
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
@@ -1045,17 +1209,28 @@ class Wallet extends ReadonlyWallet {
|
|
|
1045
1209
|
async finalizePendingTxs(vtxos) {
|
|
1046
1210
|
const MAX_INPUTS_PER_INTENT = 20;
|
|
1047
1211
|
if (!vtxos || vtxos.length === 0) {
|
|
1048
|
-
//
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1212
|
+
// Query per-script so each VTXO is extended with the correct tapscript
|
|
1213
|
+
const scriptMap = await this.getScriptMap();
|
|
1214
|
+
const allExtended = [];
|
|
1215
|
+
for (const [scriptHex, vtxoScript] of scriptMap) {
|
|
1216
|
+
const { vtxos: fetchedVtxos } = await this.indexerProvider.getVtxos({
|
|
1217
|
+
scripts: [scriptHex],
|
|
1218
|
+
});
|
|
1219
|
+
const pending = fetchedVtxos.filter((vtxo) => vtxo.virtualStatus.state !== "swept" &&
|
|
1220
|
+
vtxo.virtualStatus.state !== "settled");
|
|
1221
|
+
for (const vtxo of pending) {
|
|
1222
|
+
allExtended.push({
|
|
1223
|
+
...vtxo,
|
|
1224
|
+
forfeitTapLeafScript: vtxoScript.forfeit(),
|
|
1225
|
+
intentTapLeafScript: vtxoScript.forfeit(),
|
|
1226
|
+
tapTree: vtxoScript.encode(),
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (allExtended.length === 0) {
|
|
1056
1231
|
return { finalized: [], pending: [] };
|
|
1057
1232
|
}
|
|
1058
|
-
vtxos =
|
|
1233
|
+
vtxos = allExtended;
|
|
1059
1234
|
}
|
|
1060
1235
|
const finalized = [];
|
|
1061
1236
|
const pending = [];
|
|
@@ -1084,60 +1259,327 @@ class Wallet extends ReadonlyWallet {
|
|
|
1084
1259
|
}
|
|
1085
1260
|
return { finalized, pending };
|
|
1086
1261
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1262
|
+
/**
|
|
1263
|
+
* Send BTC and/or assets to one or more recipients.
|
|
1264
|
+
*
|
|
1265
|
+
* @param recipients - Array of recipients with their addresses, BTC amounts, and assets
|
|
1266
|
+
* @returns Promise resolving to the ark transaction ID
|
|
1267
|
+
*
|
|
1268
|
+
* @example
|
|
1269
|
+
* ```typescript
|
|
1270
|
+
* const txid = await wallet.send({
|
|
1271
|
+
* address: 'ark1...',
|
|
1272
|
+
* amount: 1000, // (optional, default to dust) btc amount to send to the output
|
|
1273
|
+
* assets: [{ assetId: 'abc123...', amount: 50 }] // (optional) list of assets to send
|
|
1274
|
+
* });
|
|
1275
|
+
* ```
|
|
1276
|
+
*/
|
|
1277
|
+
async send(...args) {
|
|
1278
|
+
if (args.length === 0) {
|
|
1279
|
+
throw new Error("At least one receiver is required");
|
|
1280
|
+
}
|
|
1281
|
+
// validate recipients and populate undefined amount with dust amount
|
|
1282
|
+
const recipients = (0, utils_1.validateRecipients)(args, Number(this.dustAmount));
|
|
1283
|
+
const address = await this.getAddress();
|
|
1284
|
+
const outputAddress = address_1.ArkAddress.decode(address);
|
|
1285
|
+
const virtualCoins = await this.getVirtualCoins({
|
|
1286
|
+
withRecoverable: false,
|
|
1287
|
+
});
|
|
1288
|
+
// keep track of asset changes
|
|
1289
|
+
const assetChanges = new Map();
|
|
1290
|
+
let selectedCoins = [];
|
|
1291
|
+
let btcAmountToSelect = 0;
|
|
1292
|
+
for (const recipient of recipients) {
|
|
1293
|
+
btcAmountToSelect += Math.max(recipient.amount, Number(this.dustAmount));
|
|
1294
|
+
}
|
|
1295
|
+
// select assets
|
|
1296
|
+
for (const recipient of recipients) {
|
|
1297
|
+
if (!recipient.assets) {
|
|
1298
|
+
continue;
|
|
1095
1299
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1300
|
+
for (const receiverAsset of recipient.assets) {
|
|
1301
|
+
let amountToSelect = BigInt(receiverAsset.amount);
|
|
1302
|
+
// check if existing change covers the needed amount
|
|
1303
|
+
const existingChange = assetChanges.get(receiverAsset.assetId) ?? 0n;
|
|
1304
|
+
if (existingChange >= amountToSelect) {
|
|
1305
|
+
assetChanges.set(receiverAsset.assetId, existingChange - amountToSelect);
|
|
1306
|
+
if (assetChanges.get(receiverAsset.assetId) === 0n) {
|
|
1307
|
+
assetChanges.delete(receiverAsset.assetId);
|
|
1308
|
+
}
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
if (existingChange > 0n) {
|
|
1312
|
+
amountToSelect -= existingChange;
|
|
1313
|
+
assetChanges.delete(receiverAsset.assetId);
|
|
1314
|
+
}
|
|
1315
|
+
const availableCoins = virtualCoins.filter((c) => !selectedCoins.find((sc) => sc.txid === c.txid && sc.vout === c.vout));
|
|
1316
|
+
const { selected, totalAssetAmount } = (0, asset_1.selectCoinsWithAsset)(availableCoins, receiverAsset.assetId, amountToSelect);
|
|
1317
|
+
for (const coin of selected) {
|
|
1318
|
+
selectedCoins.push(coin);
|
|
1319
|
+
// asset coins contain btc, subtract from total amount to select
|
|
1320
|
+
btcAmountToSelect -= coin.value;
|
|
1321
|
+
// coin may contain other assets, add them to asset changes
|
|
1322
|
+
if (coin.assets) {
|
|
1323
|
+
for (const a of coin.assets) {
|
|
1324
|
+
if (a.assetId === receiverAsset.assetId) {
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
const existing = assetChanges.get(a.assetId) ?? 0n;
|
|
1328
|
+
assetChanges.set(a.assetId, existing + BigInt(a.amount));
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const assetChangeAmount = totalAssetAmount - amountToSelect;
|
|
1333
|
+
if (assetChangeAmount > 0n) {
|
|
1334
|
+
const existing = assetChanges.get(receiverAsset.assetId) ?? 0n;
|
|
1335
|
+
assetChanges.set(receiverAsset.assetId, existing + assetChangeAmount);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
// select remaining btc
|
|
1340
|
+
if (btcAmountToSelect > 0) {
|
|
1341
|
+
const availableCoins = virtualCoins.filter((c) => !selectedCoins.find((sc) => sc.txid === c.txid && sc.vout === c.vout));
|
|
1342
|
+
const { inputs: btcCoins } = selectVirtualCoins(availableCoins, btcAmountToSelect);
|
|
1343
|
+
// some coins may contain assets, add them to asset changes
|
|
1344
|
+
for (const coin of btcCoins) {
|
|
1345
|
+
if (coin.assets) {
|
|
1346
|
+
for (const asset of coin.assets) {
|
|
1347
|
+
const existing = assetChanges.get(asset.assetId) ?? 0n;
|
|
1348
|
+
assetChanges.set(asset.assetId, existing + BigInt(asset.amount));
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
selectedCoins = [...selectedCoins, ...btcCoins];
|
|
1353
|
+
}
|
|
1354
|
+
let totalBtcSelected = selectedCoins.reduce((sum, c) => sum + c.value, 0);
|
|
1355
|
+
// build tx outputs
|
|
1356
|
+
const outputs = recipients.map((recipient) => ({
|
|
1357
|
+
script: recipient.script,
|
|
1358
|
+
amount: BigInt(recipient.amount),
|
|
1359
|
+
}));
|
|
1360
|
+
const totalBtcOutput = outputs.reduce((sum, o) => sum + Number(o.amount), 0);
|
|
1361
|
+
let changeAmount = totalBtcSelected - totalBtcOutput;
|
|
1362
|
+
// enforce minimum change amount when there are asset changes
|
|
1363
|
+
if (assetChanges.size > 0 && changeAmount < Number(this.dustAmount)) {
|
|
1364
|
+
const availableCoins = virtualCoins.filter((c) => !selectedCoins.find((sc) => sc.txid === c.txid && sc.vout === c.vout));
|
|
1365
|
+
const { inputs: extraCoins } = selectVirtualCoins(availableCoins, Number(this.dustAmount) - changeAmount);
|
|
1366
|
+
for (const coin of extraCoins) {
|
|
1367
|
+
if (coin.assets) {
|
|
1368
|
+
for (const asset of coin.assets) {
|
|
1369
|
+
const existing = assetChanges.get(asset.assetId) ?? 0n;
|
|
1370
|
+
assetChanges.set(asset.assetId, existing + BigInt(asset.amount));
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
selectedCoins = [...selectedCoins, ...extraCoins];
|
|
1375
|
+
totalBtcSelected += extraCoins.reduce((sum, c) => sum + c.value, 0);
|
|
1376
|
+
changeAmount = totalBtcSelected - totalBtcOutput;
|
|
1377
|
+
}
|
|
1378
|
+
// build change receiver with BTC change and all asset changes
|
|
1379
|
+
let changeReceiver;
|
|
1380
|
+
let changeIndex = 0;
|
|
1381
|
+
if (changeAmount > 0) {
|
|
1382
|
+
const changeAssets = [];
|
|
1383
|
+
for (const [assetId, amount] of assetChanges) {
|
|
1384
|
+
if (amount > 0n) {
|
|
1385
|
+
changeAssets.push({ assetId, amount: Number(amount) });
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
changeIndex = outputs.length;
|
|
1389
|
+
outputs.push({
|
|
1390
|
+
script: BigInt(changeAmount) < this.dustAmount
|
|
1391
|
+
? outputAddress.subdustPkScript
|
|
1392
|
+
: outputAddress.pkScript,
|
|
1393
|
+
amount: BigInt(changeAmount),
|
|
1106
1394
|
});
|
|
1395
|
+
changeReceiver = {
|
|
1396
|
+
address: address,
|
|
1397
|
+
amount: changeAmount,
|
|
1398
|
+
assets: changeAssets.length > 0 ? changeAssets : undefined,
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
// create asset packet only if there are assets involved
|
|
1402
|
+
const assetInputs = (0, asset_1.selectedCoinsToAssetInputs)(selectedCoins);
|
|
1403
|
+
const hasAssets = assetInputs.size > 0 ||
|
|
1404
|
+
recipients.some((r) => r.assets && r.assets.length > 0);
|
|
1405
|
+
if (hasAssets) {
|
|
1406
|
+
const assetPacket = (0, asset_1.createAssetPacket)(assetInputs, recipients, changeReceiver);
|
|
1407
|
+
outputs.push(assetPacket.txOut());
|
|
1107
1408
|
}
|
|
1108
|
-
|
|
1409
|
+
const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
|
|
1410
|
+
const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
1411
|
+
await this.updateDbAfterOffchainTx(selectedCoins, arkTxid, signedCheckpointTxs, sentAmount, BigInt(changeAmount), changeReceiver ? changeIndex : 0, changeReceiver?.assets);
|
|
1412
|
+
return arkTxid;
|
|
1109
1413
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1414
|
+
/**
|
|
1415
|
+
* Build an offchain transaction from the given inputs and outputs,
|
|
1416
|
+
* sign it, submit to the ark provider, and finalize.
|
|
1417
|
+
* @returns The ark transaction id and server-signed checkpoint PSBTs (for bookkeeping)
|
|
1418
|
+
*/
|
|
1419
|
+
async buildAndSubmitOffchainTx(inputs, outputs) {
|
|
1420
|
+
const tapLeafScript = this.offchainTapscript.forfeit();
|
|
1421
|
+
if (!tapLeafScript) {
|
|
1422
|
+
throw new Error("Selected leaf not found");
|
|
1423
|
+
}
|
|
1424
|
+
const tapTree = this.offchainTapscript.encode();
|
|
1425
|
+
const offchainTx = (0, arkTransaction_1.buildOffchainTx)(inputs.map((input) => ({
|
|
1426
|
+
...input,
|
|
1427
|
+
tapLeafScript,
|
|
1428
|
+
tapTree,
|
|
1429
|
+
})), outputs, this.serverUnrollScript);
|
|
1430
|
+
const signedVirtualTx = await this.identity.sign(offchainTx.arkTx);
|
|
1431
|
+
const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base_1.base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base_1.base64.encode(c.toPSBT())));
|
|
1432
|
+
const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
|
|
1433
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
|
|
1434
|
+
const signedCheckpoint = await this.identity.sign(tx);
|
|
1435
|
+
return base_1.base64.encode(signedCheckpoint.toPSBT());
|
|
1436
|
+
}));
|
|
1437
|
+
await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
|
|
1438
|
+
return { arkTxid, signedCheckpointTxs };
|
|
1439
|
+
}
|
|
1440
|
+
// mark vtxo spent and save change vtxo if any
|
|
1441
|
+
async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, changeAssets) {
|
|
1118
1442
|
try {
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1443
|
+
const spentVtxos = [];
|
|
1444
|
+
const commitmentTxIds = new Set();
|
|
1445
|
+
let batchExpiry = Number.MAX_SAFE_INTEGER;
|
|
1446
|
+
if (inputs.length !== signedCheckpointTxs.length) {
|
|
1447
|
+
console.warn(`updateDbAfterOffchainTx: inputs length (${inputs.length}) differs from signedCheckpointTxs length (${signedCheckpointTxs.length})`);
|
|
1448
|
+
}
|
|
1449
|
+
const safeLength = Math.min(inputs.length, signedCheckpointTxs.length);
|
|
1450
|
+
for (const [inputIndex, input] of inputs.entries()) {
|
|
1451
|
+
const vtxo = (0, utils_1.extendVirtualCoin)(this, input);
|
|
1452
|
+
if (inputIndex < safeLength &&
|
|
1453
|
+
signedCheckpointTxs[inputIndex]) {
|
|
1454
|
+
const checkpoint = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(signedCheckpointTxs[inputIndex]));
|
|
1455
|
+
spentVtxos.push({
|
|
1456
|
+
...vtxo,
|
|
1457
|
+
virtualStatus: {
|
|
1458
|
+
...vtxo.virtualStatus,
|
|
1459
|
+
state: "spent",
|
|
1460
|
+
},
|
|
1461
|
+
spentBy: checkpoint.id,
|
|
1462
|
+
arkTxId: arkTxid,
|
|
1463
|
+
isSpent: true,
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
else {
|
|
1467
|
+
spentVtxos.push({
|
|
1468
|
+
...vtxo,
|
|
1469
|
+
virtualStatus: {
|
|
1470
|
+
...vtxo.virtualStatus,
|
|
1471
|
+
state: "spent",
|
|
1472
|
+
},
|
|
1473
|
+
arkTxId: arkTxid,
|
|
1474
|
+
isSpent: true,
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
if (vtxo.virtualStatus.commitmentTxIds) {
|
|
1478
|
+
for (const id of vtxo.virtualStatus.commitmentTxIds) {
|
|
1479
|
+
commitmentTxIds.add(id);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
if (vtxo.virtualStatus.batchExpiry) {
|
|
1483
|
+
batchExpiry = Math.min(batchExpiry, vtxo.virtualStatus.batchExpiry);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
const createdAt = Date.now();
|
|
1487
|
+
const addr = this.arkAddress.encode();
|
|
1488
|
+
// Only save a change VTXO for preconfirmed coins (those with a batchExpiry).
|
|
1489
|
+
// Inputs without a batchExpiry are already settled/unrolled and don't need tracking.
|
|
1490
|
+
let changeVtxo;
|
|
1491
|
+
if (changeAmount > 0n && batchExpiry !== Number.MAX_SAFE_INTEGER) {
|
|
1492
|
+
changeVtxo = {
|
|
1493
|
+
txid: arkTxid,
|
|
1494
|
+
vout: changeVout,
|
|
1495
|
+
createdAt: new Date(createdAt),
|
|
1496
|
+
forfeitTapLeafScript: this.offchainTapscript.forfeit(),
|
|
1497
|
+
intentTapLeafScript: this.offchainTapscript.forfeit(),
|
|
1498
|
+
isUnrolled: false,
|
|
1499
|
+
isSpent: false,
|
|
1500
|
+
tapTree: this.offchainTapscript.encode(),
|
|
1501
|
+
value: Number(changeAmount),
|
|
1502
|
+
virtualStatus: {
|
|
1503
|
+
state: "preconfirmed",
|
|
1504
|
+
commitmentTxIds: Array.from(commitmentTxIds),
|
|
1505
|
+
batchExpiry,
|
|
1506
|
+
},
|
|
1507
|
+
status: {
|
|
1508
|
+
confirmed: false,
|
|
1509
|
+
},
|
|
1510
|
+
assets: changeAssets,
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
await this.walletRepository.saveVtxos(addr, changeVtxo ? [...spentVtxos, changeVtxo] : spentVtxos);
|
|
1514
|
+
await this.walletRepository.saveTransactions(addr, [
|
|
1515
|
+
{
|
|
1516
|
+
key: {
|
|
1517
|
+
boardingTxid: "",
|
|
1518
|
+
commitmentTxid: "",
|
|
1519
|
+
arkTxid: arkTxid,
|
|
1520
|
+
},
|
|
1521
|
+
amount: sentAmount,
|
|
1522
|
+
type: _1.TxType.TxSent,
|
|
1523
|
+
settled: false,
|
|
1524
|
+
createdAt,
|
|
1525
|
+
},
|
|
1526
|
+
]);
|
|
1123
1527
|
}
|
|
1124
|
-
catch {
|
|
1125
|
-
|
|
1126
|
-
sequence = Number(params.absoluteTimelock);
|
|
1528
|
+
catch (e) {
|
|
1529
|
+
console.warn("error saving offchain tx to repository", e);
|
|
1127
1530
|
}
|
|
1128
1531
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1532
|
+
// mark vtxo spent & settled, remove boarding utxo
|
|
1533
|
+
async updateDbAfterSettle(inputs, commitmentTxid) {
|
|
1534
|
+
try {
|
|
1535
|
+
const addr = this.arkAddress.encode();
|
|
1536
|
+
const boardingAddress = await this.getBoardingAddress();
|
|
1537
|
+
const spentVtxos = [];
|
|
1538
|
+
const inputArkTxIds = new Set();
|
|
1539
|
+
const boardingUtxoToRemove = new Set();
|
|
1540
|
+
const isVtxo = (input) => "virtualStatus" in input;
|
|
1541
|
+
for (const input of inputs) {
|
|
1542
|
+
if (isVtxo(input)) {
|
|
1543
|
+
// vtxo = mark it settled
|
|
1544
|
+
const vtxo = (0, utils_1.extendVirtualCoin)(this, input);
|
|
1545
|
+
if (vtxo.arkTxId) {
|
|
1546
|
+
inputArkTxIds.add(vtxo.arkTxId);
|
|
1547
|
+
}
|
|
1548
|
+
spentVtxos.push({
|
|
1549
|
+
...vtxo,
|
|
1550
|
+
virtualStatus: {
|
|
1551
|
+
...vtxo.virtualStatus,
|
|
1552
|
+
state: "settled",
|
|
1553
|
+
},
|
|
1554
|
+
settledBy: commitmentTxid,
|
|
1555
|
+
isSpent: true,
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
else {
|
|
1559
|
+
// boarding utxo = remove it
|
|
1560
|
+
boardingUtxoToRemove.add(`${input.txid}:${input.vout}`);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (spentVtxos.length > 0) {
|
|
1564
|
+
await this.walletRepository.saveVtxos(addr, spentVtxos);
|
|
1565
|
+
}
|
|
1566
|
+
if (boardingUtxoToRemove.size > 0) {
|
|
1567
|
+
const currentUtxos = await this.walletRepository.getUtxos(boardingAddress);
|
|
1568
|
+
const filtered = currentUtxos.filter((u) => !boardingUtxoToRemove.has(`${u.txid}:${u.vout}`));
|
|
1569
|
+
// Clear and re-save the filtered list
|
|
1570
|
+
await this.walletRepository.deleteUtxos(boardingAddress);
|
|
1571
|
+
if (filtered.length > 0) {
|
|
1572
|
+
await this.walletRepository.saveUtxos(boardingAddress, filtered);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
catch (e) {
|
|
1577
|
+
console.warn("error updating repository after settle", e);
|
|
1578
|
+
}
|
|
1139
1579
|
}
|
|
1140
1580
|
}
|
|
1581
|
+
exports.Wallet = Wallet;
|
|
1582
|
+
Wallet.MIN_FEE_RATE = 1; // sats/vbyte
|
|
1141
1583
|
/**
|
|
1142
1584
|
* Select virtual coins to reach a target amount, prioritizing those closer to expiry
|
|
1143
1585
|
* @param coins List of virtual coins to select from
|