@arkade-os/sdk 0.4.22 → 0.4.24
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 +116 -13
- package/dist/cjs/contracts/arkcontract.js +2 -1
- package/dist/cjs/contracts/contractManager.js +29 -4
- package/dist/cjs/contracts/contractWatcher.js +9 -3
- package/dist/cjs/contracts/handlers/default.js +3 -2
- package/dist/cjs/contracts/handlers/delegate.js +3 -2
- package/dist/cjs/contracts/handlers/helpers.js +2 -58
- package/dist/cjs/contracts/handlers/vhtlc.js +7 -6
- package/dist/cjs/contracts/vtxoOwnership.js +60 -0
- package/dist/cjs/identity/descriptor.js +75 -4
- package/dist/cjs/identity/hdCapableIdentity.js +2 -0
- package/dist/cjs/identity/seedIdentity.js +225 -103
- package/dist/cjs/identity/serialize.js +5 -0
- package/dist/cjs/identity/staticDescriptorProvider.js +1 -1
- package/dist/cjs/index.js +12 -3
- package/dist/cjs/providers/electrum.js +285 -79
- package/dist/cjs/providers/expoIndexer.js +1 -1
- package/dist/cjs/providers/indexer.js +2 -2
- package/dist/cjs/providers/onchain.js +9 -3
- package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +6 -2
- package/dist/cjs/repositories/realm/walletRepository.js +2 -2
- package/dist/cjs/repositories/serialization.js +34 -1
- package/dist/cjs/repositories/sqlite/walletRepository.js +4 -2
- package/dist/cjs/script/address.js +2 -1
- package/dist/cjs/script/base.js +12 -47
- package/dist/cjs/script/tapscript.js +97 -73
- package/dist/cjs/utils/timelock.js +59 -0
- package/dist/cjs/utils/transactionHistory.js +4 -4
- package/dist/cjs/utils/unknownFields.js +2 -39
- package/dist/cjs/wallet/asset-manager.js +18 -18
- package/dist/cjs/wallet/asset.js +10 -8
- package/dist/cjs/wallet/delegator.js +2 -2
- package/dist/cjs/wallet/hdDescriptorProvider.js +159 -0
- package/dist/cjs/wallet/index.js +5 -1
- package/dist/cjs/wallet/onchain.js +2 -1
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +60 -10
- package/dist/cjs/wallet/serviceWorker/wallet.js +5 -4
- package/dist/cjs/wallet/unroll.js +79 -67
- package/dist/cjs/wallet/validation.js +2 -3
- package/dist/cjs/wallet/wallet.js +91 -22
- package/dist/cjs/worker/expo/processors/contractPollProcessor.js +7 -2
- package/dist/esm/contracts/arkcontract.js +2 -1
- package/dist/esm/contracts/contractManager.js +29 -4
- package/dist/esm/contracts/contractWatcher.js +9 -3
- package/dist/esm/contracts/handlers/default.js +2 -1
- package/dist/esm/contracts/handlers/delegate.js +2 -1
- package/dist/esm/contracts/handlers/helpers.js +1 -22
- package/dist/esm/contracts/handlers/vhtlc.js +2 -1
- package/dist/esm/contracts/vtxoOwnership.js +53 -0
- package/dist/esm/identity/descriptor.js +74 -5
- package/dist/esm/identity/hdCapableIdentity.js +1 -0
- package/dist/esm/identity/seedIdentity.js +225 -103
- package/dist/esm/identity/serialize.js +5 -0
- package/dist/esm/identity/staticDescriptorProvider.js +1 -1
- package/dist/esm/index.js +7 -4
- package/dist/esm/providers/electrum.js +284 -78
- package/dist/esm/providers/expoIndexer.js +1 -1
- package/dist/esm/providers/indexer.js +2 -2
- package/dist/esm/providers/onchain.js +9 -3
- package/dist/esm/repositories/migrations/walletRepositoryImpl.js +6 -2
- package/dist/esm/repositories/realm/walletRepository.js +3 -3
- package/dist/esm/repositories/serialization.js +27 -0
- package/dist/esm/repositories/sqlite/walletRepository.js +5 -3
- package/dist/esm/script/address.js +2 -1
- package/dist/esm/script/base.js +12 -14
- package/dist/esm/script/tapscript.js +97 -40
- package/dist/esm/utils/timelock.js +22 -0
- package/dist/esm/utils/transactionHistory.js +4 -4
- package/dist/esm/utils/unknownFields.js +2 -6
- package/dist/esm/wallet/asset-manager.js +18 -18
- package/dist/esm/wallet/asset.js +10 -8
- package/dist/esm/wallet/delegator.js +2 -2
- package/dist/esm/wallet/hdDescriptorProvider.js +155 -0
- package/dist/esm/wallet/index.js +4 -0
- package/dist/esm/wallet/onchain.js +2 -1
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +60 -10
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -4
- package/dist/esm/wallet/unroll.js +78 -67
- package/dist/esm/wallet/validation.js +2 -3
- package/dist/esm/wallet/wallet.js +88 -20
- package/dist/esm/worker/expo/processors/contractPollProcessor.js +7 -2
- package/dist/types/contracts/arkcontract.d.ts +1 -1
- package/dist/types/contracts/handlers/helpers.d.ts +0 -9
- package/dist/types/contracts/vtxoOwnership.d.ts +25 -0
- package/dist/types/identity/descriptor.d.ts +26 -0
- package/dist/types/identity/descriptorProvider.d.ts +11 -4
- package/dist/types/identity/hdCapableIdentity.d.ts +44 -0
- package/dist/types/identity/index.d.ts +1 -0
- package/dist/types/identity/seedIdentity.d.ts +113 -29
- package/dist/types/identity/serialize.d.ts +12 -0
- package/dist/types/identity/staticDescriptorProvider.d.ts +1 -1
- package/dist/types/index.d.ts +6 -3
- package/dist/types/providers/electrum.d.ts +115 -15
- package/dist/types/providers/onchain.d.ts +6 -0
- package/dist/types/repositories/serialization.d.ts +26 -2
- package/dist/types/script/address.d.ts +1 -1
- package/dist/types/script/tapscript.d.ts +4 -0
- package/dist/types/utils/timelock.d.ts +9 -0
- package/dist/types/wallet/hdDescriptorProvider.d.ts +93 -0
- package/dist/types/wallet/index.d.ts +19 -10
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/wallet.d.ts +1 -1
- package/dist/types/wallet/unroll.d.ts +10 -0
- package/dist/types/wallet/wallet.d.ts +4 -1
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { buildForfeitTx } from '../forfeit.js';
|
|
|
11
11
|
import { validateConnectorsTxGraph, validateVtxoTxGraph, } from '../tree/validation.js';
|
|
12
12
|
import { validateBatchRecipients } from './validation.js';
|
|
13
13
|
import { isBatchSignable } from '../identity/index.js';
|
|
14
|
-
import { isExpired, isRecoverable, isSpendable, isSubdust, TxType, } from './index.js';
|
|
14
|
+
import { DEFAULT_ARKADE_SERVER_URL, isExpired, isRecoverable, isSpendable, isSubdust, TxType, } from './index.js';
|
|
15
15
|
import { createAssetPacket, selectCoinsWithAsset, selectedCoinsToAssetInputs, } from './asset.js';
|
|
16
16
|
import { VtxoScript } from '../script/base.js';
|
|
17
17
|
import { CSVMultisigTapscript } from '../script/tapscript.js';
|
|
@@ -32,8 +32,10 @@ import { DelegatorManagerImpl, findDestinationOutputIndex, } from './delegator.j
|
|
|
32
32
|
import { IndexedDBContractRepository, IndexedDBWalletRepository, } from '../repositories/index.js';
|
|
33
33
|
import { ContractManager } from '../contracts/contractManager.js';
|
|
34
34
|
import { contractHandlers } from '../contracts/handlers/index.js';
|
|
35
|
-
import { timelockToSequence } from '../
|
|
35
|
+
import { timelockToSequence } from '../utils/timelock.js';
|
|
36
36
|
import { clearSyncCursor, updateWalletState } from '../utils/syncCursors.js';
|
|
37
|
+
import { validateVtxosForScript } from '../contracts/vtxoOwnership.js';
|
|
38
|
+
export const getArkadeServerUrl = ({ arkServerUrl, }) => arkServerUrl || DEFAULT_ARKADE_SERVER_URL;
|
|
37
39
|
// Historical unilateral exit delay for mainnet (~7 days in seconds).
|
|
38
40
|
// Kept so existing wallets can still discover and spend VTXOs sent to the
|
|
39
41
|
// legacy address after arkd starts advertising a different delay.
|
|
@@ -120,10 +122,7 @@ export class ReadonlyWallet {
|
|
|
120
122
|
// Use provided arkProvider instance or create a new one from arkServerUrl
|
|
121
123
|
const arkProvider = config.arkProvider ||
|
|
122
124
|
(() => {
|
|
123
|
-
|
|
124
|
-
throw new Error("Either arkProvider or arkServerUrl must be provided");
|
|
125
|
-
}
|
|
126
|
-
return new RestArkProvider(config.arkServerUrl);
|
|
125
|
+
return new RestArkProvider(getArkadeServerUrl(config));
|
|
127
126
|
})();
|
|
128
127
|
// Extract arkServerUrl from provider if not explicitly provided
|
|
129
128
|
const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
|
|
@@ -302,7 +301,7 @@ export class ReadonlyWallet {
|
|
|
302
301
|
continue;
|
|
303
302
|
if (vtxo.assets) {
|
|
304
303
|
for (const a of vtxo.assets) {
|
|
305
|
-
const current = assetBalances.get(a.assetId) ??
|
|
304
|
+
const current = assetBalances.get(a.assetId) ?? 0n;
|
|
306
305
|
assetBalances.set(a.assetId, current + a.amount);
|
|
307
306
|
}
|
|
308
307
|
}
|
|
@@ -1123,12 +1122,12 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1123
1122
|
for (const [, assets] of assetInputs) {
|
|
1124
1123
|
for (const asset of assets) {
|
|
1125
1124
|
const existing = allAssets.get(asset.assetId) ?? 0n;
|
|
1126
|
-
allAssets.set(asset.assetId, existing +
|
|
1125
|
+
allAssets.set(asset.assetId, existing + asset.amount);
|
|
1127
1126
|
}
|
|
1128
1127
|
}
|
|
1129
1128
|
outputAssets = [];
|
|
1130
1129
|
for (const [assetId, amount] of allAssets) {
|
|
1131
|
-
outputAssets.push({ assetId, amount
|
|
1130
|
+
outputAssets.push({ assetId, amount });
|
|
1132
1131
|
}
|
|
1133
1132
|
}
|
|
1134
1133
|
const recipients = params.outputs.map((output, i) => ({
|
|
@@ -1563,7 +1562,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1563
1562
|
* const txid = await wallet.send({
|
|
1564
1563
|
* address: 'ark1q...',
|
|
1565
1564
|
* amount: 1000, // (optional, default to dust) btc amount to send to the output
|
|
1566
|
-
* assets: [{ assetId: 'abc123...', amount:
|
|
1565
|
+
* assets: [{ assetId: 'abc123...', amount: 50n }] // (optional) list of assets to send
|
|
1567
1566
|
* });
|
|
1568
1567
|
* ```
|
|
1569
1568
|
*/
|
|
@@ -1594,7 +1593,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1594
1593
|
continue;
|
|
1595
1594
|
}
|
|
1596
1595
|
for (const receiverAsset of recipient.assets) {
|
|
1597
|
-
let amountToSelect =
|
|
1596
|
+
let amountToSelect = receiverAsset.amount;
|
|
1598
1597
|
// check if existing change covers the needed amount
|
|
1599
1598
|
const existingChange = assetChanges.get(receiverAsset.assetId) ?? 0n;
|
|
1600
1599
|
if (existingChange >= amountToSelect) {
|
|
@@ -1621,7 +1620,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1621
1620
|
continue;
|
|
1622
1621
|
}
|
|
1623
1622
|
const existing = assetChanges.get(a.assetId) ?? 0n;
|
|
1624
|
-
assetChanges.set(a.assetId, existing +
|
|
1623
|
+
assetChanges.set(a.assetId, existing + a.amount);
|
|
1625
1624
|
}
|
|
1626
1625
|
}
|
|
1627
1626
|
}
|
|
@@ -1641,7 +1640,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1641
1640
|
if (coin.assets) {
|
|
1642
1641
|
for (const asset of coin.assets) {
|
|
1643
1642
|
const existing = assetChanges.get(asset.assetId) ?? 0n;
|
|
1644
|
-
assetChanges.set(asset.assetId, existing +
|
|
1643
|
+
assetChanges.set(asset.assetId, existing + asset.amount);
|
|
1645
1644
|
}
|
|
1646
1645
|
}
|
|
1647
1646
|
}
|
|
@@ -1663,7 +1662,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1663
1662
|
if (coin.assets) {
|
|
1664
1663
|
for (const asset of coin.assets) {
|
|
1665
1664
|
const existing = assetChanges.get(asset.assetId) ?? 0n;
|
|
1666
|
-
assetChanges.set(asset.assetId, existing +
|
|
1665
|
+
assetChanges.set(asset.assetId, existing + asset.amount);
|
|
1667
1666
|
}
|
|
1668
1667
|
}
|
|
1669
1668
|
}
|
|
@@ -1678,7 +1677,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1678
1677
|
const changeAssets = [];
|
|
1679
1678
|
for (const [assetId, amount] of assetChanges) {
|
|
1680
1679
|
if (amount > 0n) {
|
|
1681
|
-
changeAssets.push({ assetId, amount
|
|
1680
|
+
changeAssets.push({ assetId, amount });
|
|
1682
1681
|
}
|
|
1683
1682
|
}
|
|
1684
1683
|
changeIndex = outputs.length;
|
|
@@ -1825,7 +1824,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1825
1824
|
}
|
|
1826
1825
|
}
|
|
1827
1826
|
const createdAt = Date.now();
|
|
1828
|
-
const
|
|
1827
|
+
const primaryAddr = this.arkAddress.encode();
|
|
1829
1828
|
// Only save a change virtual output for preconfirmed coins (those with a batchExpiry).
|
|
1830
1829
|
// Inputs without a batchExpiry are already settled/unrolled and don't need tracking.
|
|
1831
1830
|
let changeVtxo;
|
|
@@ -1852,8 +1851,45 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1852
1851
|
script: hex.encode(this.offchainTapscript.pkScript),
|
|
1853
1852
|
};
|
|
1854
1853
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1854
|
+
// Route spent rows to their owning contract bucket. The wallet's
|
|
1855
|
+
// primary contract is registered with the manager at boot, so
|
|
1856
|
+
// `addrByScript` already includes it; in a multi-contract spend
|
|
1857
|
+
// each input may belong to a different contract.
|
|
1858
|
+
const contracts = await cm.getContracts();
|
|
1859
|
+
const addrByScript = new Map(contracts.map((c) => [c.script, c.address]));
|
|
1860
|
+
const spentByScript = new Map();
|
|
1861
|
+
for (const v of spentVtxos) {
|
|
1862
|
+
if (!v.script) {
|
|
1863
|
+
throw new Error(`Wallet.updateDbAfterOffchainTx: spent VTXO ${v.txid}:${v.vout} has no script`);
|
|
1864
|
+
}
|
|
1865
|
+
const arr = spentByScript.get(v.script) ?? [];
|
|
1866
|
+
arr.push(v);
|
|
1867
|
+
spentByScript.set(v.script, arr);
|
|
1868
|
+
}
|
|
1869
|
+
const byAddress = new Map();
|
|
1870
|
+
for (const [script, vtxos] of spentByScript) {
|
|
1871
|
+
// User-initiated send path: a wrong-script row here means the
|
|
1872
|
+
// wallet is about to record ownership against the wrong
|
|
1873
|
+
// contract — fail loudly rather than persist inconsistent state.
|
|
1874
|
+
validateVtxosForScript(vtxos, script, "Wallet.updateDbAfterOffchainTx");
|
|
1875
|
+
const targetAddr = addrByScript.get(script);
|
|
1876
|
+
if (!targetAddr) {
|
|
1877
|
+
throw new Error(`Wallet.updateDbAfterOffchainTx: no contract owns script ${script}`);
|
|
1878
|
+
}
|
|
1879
|
+
const bucket = byAddress.get(targetAddr) ?? [];
|
|
1880
|
+
bucket.push(...vtxos);
|
|
1881
|
+
byAddress.set(targetAddr, bucket);
|
|
1882
|
+
}
|
|
1883
|
+
// Change is always primary-script by construction.
|
|
1884
|
+
if (changeVtxo) {
|
|
1885
|
+
const bucket = byAddress.get(primaryAddr) ?? [];
|
|
1886
|
+
bucket.push(changeVtxo);
|
|
1887
|
+
byAddress.set(primaryAddr, bucket);
|
|
1888
|
+
}
|
|
1889
|
+
for (const [addr, vtxos] of byAddress) {
|
|
1890
|
+
await this.walletRepository.saveVtxos(addr, vtxos);
|
|
1891
|
+
}
|
|
1892
|
+
await this.walletRepository.saveTransactions(primaryAddr, [
|
|
1857
1893
|
{
|
|
1858
1894
|
key: {
|
|
1859
1895
|
boardingTxid: "",
|
|
@@ -1869,12 +1905,12 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1869
1905
|
}
|
|
1870
1906
|
catch (e) {
|
|
1871
1907
|
console.warn("error saving offchain tx to repository", e);
|
|
1908
|
+
throw e;
|
|
1872
1909
|
}
|
|
1873
1910
|
}
|
|
1874
1911
|
// mark virtual outputs as spent/settled, remove boarding inputs
|
|
1875
1912
|
async updateDbAfterSettle(inputs, commitmentTxid) {
|
|
1876
1913
|
try {
|
|
1877
|
-
const addr = this.arkAddress.encode();
|
|
1878
1914
|
const boardingAddress = await this.getBoardingAddress();
|
|
1879
1915
|
const spentVtxos = [];
|
|
1880
1916
|
const inputArkTxIds = new Set();
|
|
@@ -1907,7 +1943,38 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1907
1943
|
}
|
|
1908
1944
|
}
|
|
1909
1945
|
if (spentVtxos.length > 0) {
|
|
1910
|
-
|
|
1946
|
+
// Route settled rows to their owning contract bucket. In a
|
|
1947
|
+
// multi-contract settle the inputs may belong to several
|
|
1948
|
+
// contracts; the wallet's primary contract is registered with
|
|
1949
|
+
// the manager at boot, so its address is in `addrByScript`
|
|
1950
|
+
// alongside the rest.
|
|
1951
|
+
const contracts = await cm.getContracts();
|
|
1952
|
+
const addrByScript = new Map(contracts.map((c) => [c.script, c.address]));
|
|
1953
|
+
const byAddress = new Map();
|
|
1954
|
+
const byScript = new Map();
|
|
1955
|
+
for (const v of spentVtxos) {
|
|
1956
|
+
if (!v.script) {
|
|
1957
|
+
throw new Error(`Wallet.updateDbAfterSettle: spent VTXO ${v.txid}:${v.vout} has no script`);
|
|
1958
|
+
}
|
|
1959
|
+
const arr = byScript.get(v.script) ?? [];
|
|
1960
|
+
arr.push(v);
|
|
1961
|
+
byScript.set(v.script, arr);
|
|
1962
|
+
}
|
|
1963
|
+
for (const [script, vtxos] of byScript) {
|
|
1964
|
+
// User-initiated settle path: refuse to record a settle
|
|
1965
|
+
// against the wrong script.
|
|
1966
|
+
validateVtxosForScript(vtxos, script, "Wallet.updateDbAfterSettle");
|
|
1967
|
+
const targetAddr = addrByScript.get(script);
|
|
1968
|
+
if (!targetAddr) {
|
|
1969
|
+
throw new Error(`Wallet.updateDbAfterSettle: no contract owns script ${script}`);
|
|
1970
|
+
}
|
|
1971
|
+
const bucket = byAddress.get(targetAddr) ?? [];
|
|
1972
|
+
bucket.push(...vtxos);
|
|
1973
|
+
byAddress.set(targetAddr, bucket);
|
|
1974
|
+
}
|
|
1975
|
+
for (const [bucketAddr, vtxos] of byAddress) {
|
|
1976
|
+
await this.walletRepository.saveVtxos(bucketAddr, vtxos);
|
|
1977
|
+
}
|
|
1911
1978
|
}
|
|
1912
1979
|
if (boardingUtxoToRemove.size > 0) {
|
|
1913
1980
|
const currentUtxos = await this.walletRepository.getUtxos(boardingAddress);
|
|
@@ -1921,6 +1988,7 @@ export class Wallet extends ReadonlyWallet {
|
|
|
1921
1988
|
}
|
|
1922
1989
|
catch (e) {
|
|
1923
1990
|
console.warn("error updating repository after settle", e);
|
|
1991
|
+
throw e;
|
|
1924
1992
|
}
|
|
1925
1993
|
}
|
|
1926
1994
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { warnAndFilterVtxosForScript } from '../../../contracts/vtxoOwnership.js';
|
|
1
2
|
export const CONTRACT_POLL_TASK_TYPE = "contract-poll";
|
|
2
3
|
/**
|
|
3
4
|
* Polls the indexer for the latest VTXO state of every contract and
|
|
@@ -40,8 +41,12 @@ export const contractPollProcessor = {
|
|
|
40
41
|
hasMore = page ? vtxos.length === pageSize : false;
|
|
41
42
|
pageIndex++;
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// Skip wrong-script rows (legacy duplicates or indexer drift)
|
|
45
|
+
// before persisting; the loop must keep going for the remaining
|
|
46
|
+
// contracts even when one row is rejected.
|
|
47
|
+
const filtered = warnAndFilterVtxosForScript(allVtxos, contract.script, "contractPollProcessor");
|
|
48
|
+
await walletRepository.saveVtxos(contract.address, filtered);
|
|
49
|
+
vtxosSaved += filtered.length;
|
|
45
50
|
contractsProcessed++;
|
|
46
51
|
}
|
|
47
52
|
return {
|
|
@@ -88,7 +88,7 @@ export declare function contractFromArkContract(encoded: string, options?: {
|
|
|
88
88
|
* @param options - Additional options
|
|
89
89
|
* @returns A complete Contract object
|
|
90
90
|
*/
|
|
91
|
-
export declare function contractFromArkContractWithAddress(encoded: string, serverPubKey: Uint8Array, addressPrefix
|
|
91
|
+
export declare function contractFromArkContractWithAddress(encoded: string, serverPubKey: Uint8Array, addressPrefix?: string, options?: {
|
|
92
92
|
label?: string;
|
|
93
93
|
state?: "active" | "inactive";
|
|
94
94
|
metadata?: Record<string, unknown>;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import { RelativeTimelock } from "../../script/tapscript";
|
|
2
1
|
import { Contract, PathContext } from "../types";
|
|
3
|
-
/**
|
|
4
|
-
* Convert RelativeTimelock to BIP68 sequence number.
|
|
5
|
-
*/
|
|
6
|
-
export declare function timelockToSequence(timelock: RelativeTimelock): number;
|
|
7
|
-
/**
|
|
8
|
-
* Convert BIP68 sequence number back to RelativeTimelock.
|
|
9
|
-
*/
|
|
10
|
-
export declare function sequenceToTimelock(sequence: number): RelativeTimelock;
|
|
11
2
|
/**
|
|
12
3
|
* Resolve wallet's role from explicit role or by matching descriptor/pubkey.
|
|
13
4
|
*/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { VirtualCoin } from "../wallet";
|
|
2
|
+
/**
|
|
3
|
+
* Tier 1 helpers that enforce VTXO ownership at call sites that already know
|
|
4
|
+
* the intended contract script. Address-keyed repositories may still hand back
|
|
5
|
+
* legacy duplicate rows under the wrong bucket; these helpers gate reads and
|
|
6
|
+
* writes so a wrong-script row never wins.
|
|
7
|
+
*
|
|
8
|
+
* `script` is the authoritative ownership key. Equality is strict: a missing
|
|
9
|
+
* or empty `vtxo.script` never matches.
|
|
10
|
+
*/
|
|
11
|
+
export declare function vtxoOutpoint(vtxo: Pick<VirtualCoin, "txid" | "vout">): string;
|
|
12
|
+
export declare function isVtxoForScript(vtxo: Pick<VirtualCoin, "script">, script: string): boolean;
|
|
13
|
+
export declare function filterVtxosForScript<T extends Pick<VirtualCoin, "script">>(vtxos: T[], script: string): T[];
|
|
14
|
+
/**
|
|
15
|
+
* Background/indexer sync flavour: drop wrong-script rows and log enough
|
|
16
|
+
* context to identify each rejection. Returns only matching rows so the
|
|
17
|
+
* caller can keep going.
|
|
18
|
+
*/
|
|
19
|
+
export declare function warnAndFilterVtxosForScript<T extends Pick<VirtualCoin, "txid" | "vout" | "script">>(vtxos: T[], script: string, context: string): T[];
|
|
20
|
+
/**
|
|
21
|
+
* User-initiated transaction/signing flavour: throw before persisting or
|
|
22
|
+
* signing inconsistent ownership state. Silently skipping here would hide a
|
|
23
|
+
* serious bug in the wallet's spend path.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateVtxosForScript(vtxos: Array<Pick<VirtualCoin, "txid" | "vout" | "script">>, script: string, context: string): void;
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True iff `descriptor` is a Bitcoin mainnet descriptor (xpub-prefixed
|
|
3
|
+
* BIP32 key).
|
|
4
|
+
*
|
|
5
|
+
* Note: testnet, signet, regtest, mutinynet, and other non-mainnet
|
|
6
|
+
* networks all share the same `tpub` BIP32 version bytes — they cannot
|
|
7
|
+
* be distinguished from one another at the descriptor level. Callers
|
|
8
|
+
* that need a `Network` constants object for `expand()` pick
|
|
9
|
+
* `networks.bitcoin` vs `networks.testnet` themselves; callers that
|
|
10
|
+
* need the *actual* network the wallet is on must track that
|
|
11
|
+
* out-of-band.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isMainnetDescriptor(descriptor: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Shared "does `candidate` belong to the identity backed by
|
|
16
|
+
* `ourDescriptor`?" predicate.
|
|
17
|
+
*
|
|
18
|
+
* - HD descriptors (expanding to a `bip32` key) match by account xpub
|
|
19
|
+
* on both sides — index-agnostic, so a wildcard template and any
|
|
20
|
+
* concrete index under it all collapse to the same xpub.
|
|
21
|
+
* - Bare `tr(pubkey)` candidates fall back to comparing the candidate
|
|
22
|
+
* pubkey against `ourXOnlyPubkey` (the cached pubkey on the identity
|
|
23
|
+
* side, since pulling it from `ourDescriptor` would require an index
|
|
24
|
+
* substitution the caller already performed).
|
|
25
|
+
*/
|
|
26
|
+
export declare function descriptorIsOurs(candidate: string, ourDescriptor: string, ourXOnlyPubkey: Uint8Array): boolean;
|
|
1
27
|
/**
|
|
2
28
|
* Check if a string is a descriptor of the shape `tr(...)`.
|
|
3
29
|
*
|
|
@@ -13,12 +13,19 @@ export interface DescriptorSigningRequest {
|
|
|
13
13
|
*
|
|
14
14
|
* Implementations include:
|
|
15
15
|
* - {@link StaticDescriptorProvider}: wraps a legacy {@link Identity} with a single key.
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
16
|
+
* - {@link HDDescriptorProvider}: rotates through HD-derived descriptors.
|
|
17
|
+
*
|
|
18
|
+
* The provider has no read accessor for "current" — it is a pure descriptor
|
|
19
|
+
* allocator. "What addresses am I currently bound to?" is a question the
|
|
20
|
+
* contract repository answers, not the provider.
|
|
18
21
|
*/
|
|
19
22
|
export interface DescriptorProvider {
|
|
20
|
-
/**
|
|
21
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Allocate a new signing descriptor. For HD providers each call advances
|
|
25
|
+
* the internal index and returns a fresh descriptor; for single-key
|
|
26
|
+
* providers each call returns the same descriptor.
|
|
27
|
+
*/
|
|
28
|
+
getNextSigningDescriptor(): Promise<string>;
|
|
22
29
|
/** Checks if a descriptor belongs to this provider. */
|
|
23
30
|
isOurs(descriptor: string): boolean;
|
|
24
31
|
/** Signs transactions, each with its own descriptor-derived key. */
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Identity, ReadonlyIdentity } from ".";
|
|
2
|
+
import { DescriptorSigningRequest } from "./descriptorProvider";
|
|
3
|
+
import { Transaction } from "../utils/transaction";
|
|
4
|
+
/**
|
|
5
|
+
* Read-side HD capability marker. Exposes the wildcard-suffixed account
|
|
6
|
+
* descriptor *template* and the descriptor-membership predicate, but no
|
|
7
|
+
* signing primitives — suitable for watch-only identities backed by an
|
|
8
|
+
* xpub.
|
|
9
|
+
*
|
|
10
|
+
* Extracted from {@link HDCapableIdentity} so that
|
|
11
|
+
* `ReadonlyDescriptorIdentity` can stand in for an HD wallet's read-only
|
|
12
|
+
* surface (template-aware, derives pubkeys at any index) without having
|
|
13
|
+
* to claim signing capability it cannot honour.
|
|
14
|
+
*/
|
|
15
|
+
export interface ReadonlyHDCapableIdentity extends ReadonlyIdentity {
|
|
16
|
+
/**
|
|
17
|
+
* The wildcard-suffixed account descriptor template
|
|
18
|
+
* (e.g. `tr([fp/86'/0'/0']xpub/0/*)`). Consumers materialize a
|
|
19
|
+
* concrete descriptor by replacing the `*` with a derivation index.
|
|
20
|
+
*/
|
|
21
|
+
readonly descriptor: string;
|
|
22
|
+
/** True iff `descriptor` derives from this identity's xpub/seed. */
|
|
23
|
+
isOurs(descriptor: string): boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Capability marker for identities that can be rotated through an HD
|
|
27
|
+
* derivation tree AND can sign at each rotated index.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately does NOT extend `DescriptorProvider`: if an HD-capable
|
|
30
|
+
* identity were silently usable as a concrete descriptor source, callers
|
|
31
|
+
* could bypass receive rotation and unknowingly reuse a single address
|
|
32
|
+
* forever. To use this identity as a wallet's descriptor source, wrap
|
|
33
|
+
* it explicitly:
|
|
34
|
+
*
|
|
35
|
+
* - `HDDescriptorProvider` — rotating, recommended for new wallets.
|
|
36
|
+
* - `StaticDescriptorProvider` — pinned to a single key, for legacy or
|
|
37
|
+
* explicitly-non-rotating use cases.
|
|
38
|
+
*/
|
|
39
|
+
export interface HDCapableIdentity extends ReadonlyHDCapableIdentity, Identity {
|
|
40
|
+
/** Signs each request with the key derived from its descriptor. */
|
|
41
|
+
signWithDescriptor(requests: DescriptorSigningRequest[]): Promise<Transaction[]>;
|
|
42
|
+
/** Signs a message using the key derived from `descriptor`. */
|
|
43
|
+
signMessageWithDescriptor(descriptor: string, message: Uint8Array, signatureType?: "schnorr" | "ecdsa"): Promise<Uint8Array>;
|
|
44
|
+
}
|
|
@@ -52,4 +52,5 @@ export * from "./serialize";
|
|
|
52
52
|
export { isDescriptor, normalizeToDescriptor, extractPubKey, parseHDDescriptor, } from "./descriptor";
|
|
53
53
|
export type { ParsedHDDescriptor } from "./descriptor";
|
|
54
54
|
export type { DescriptorProvider, DescriptorSigningRequest, } from "./descriptorProvider";
|
|
55
|
+
export type { HDCapableIdentity, ReadonlyHDCapableIdentity, } from "./hdCapableIdentity";
|
|
55
56
|
export { StaticDescriptorProvider } from "./staticDescriptorProvider";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Identity, ReadonlyIdentity } from ".";
|
|
2
1
|
import { Transaction } from "../utils/transaction";
|
|
3
2
|
import { SignerSession } from "../tree/signingSession";
|
|
4
3
|
import type { SerializedSigningIdentity, SerializedReadonlyIdentity } from "./serialize";
|
|
4
|
+
import { DescriptorSigningRequest } from "./descriptorProvider";
|
|
5
|
+
import { HDCapableIdentity, ReadonlyHDCapableIdentity } from "./hdCapableIdentity";
|
|
5
6
|
/** Used for default BIP86 derivation with network selection. */
|
|
6
7
|
export interface NetworkOptions {
|
|
7
8
|
/**
|
|
@@ -11,12 +12,16 @@ export interface NetworkOptions {
|
|
|
11
12
|
*/
|
|
12
13
|
isMainnet?: boolean;
|
|
13
14
|
}
|
|
14
|
-
/** Used for
|
|
15
|
+
/** Used for a caller-supplied account-descriptor template. */
|
|
15
16
|
export interface DescriptorOptions {
|
|
16
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Account-descriptor *template* — must end with the BIP-32 wildcard
|
|
19
|
+
* suffix `/*)`. Stored as-is on {@link SeedIdentity.descriptor} and
|
|
20
|
+
* read by HD providers to rotate through derivation indices.
|
|
21
|
+
*/
|
|
17
22
|
descriptor: string;
|
|
18
23
|
}
|
|
19
|
-
/** Either default BIP86 derivation (with optional network selection) or a
|
|
24
|
+
/** Either default BIP86 derivation (with optional network selection) or a caller-supplied template. */
|
|
20
25
|
export type SeedIdentityOptions = NetworkOptions | DescriptorOptions;
|
|
21
26
|
/** Used for deriving an identity from a BIP39 mnemonic. */
|
|
22
27
|
export type MnemonicOptions = SeedIdentityOptions & {
|
|
@@ -24,44 +29,77 @@ export type MnemonicOptions = SeedIdentityOptions & {
|
|
|
24
29
|
passphrase?: string;
|
|
25
30
|
};
|
|
26
31
|
/**
|
|
27
|
-
* Seed-based identity derived from a raw seed and an
|
|
32
|
+
* Seed-based identity derived from a raw seed and an account descriptor
|
|
33
|
+
* *template*.
|
|
28
34
|
*
|
|
29
35
|
* This is the recommended identity type for most applications. It uses
|
|
30
|
-
* standard BIP86 (Taproot) derivation by default
|
|
31
|
-
*
|
|
36
|
+
* standard BIP86 (Taproot) derivation by default; callers that need a
|
|
37
|
+
* different path supply the wildcard template directly.
|
|
32
38
|
*
|
|
33
39
|
* Prefer this (or @see MnemonicIdentity) over `SingleKey` for new
|
|
34
40
|
* integrations — `SingleKey` exists for backward compatibility with
|
|
35
41
|
* raw nsec-style keys.
|
|
36
42
|
*
|
|
37
|
-
*
|
|
43
|
+
* The identity holds the wildcard *template* (e.g.
|
|
44
|
+
* `tr([fp/86'/0'/0']xpub/0/*)`) on its public {@link descriptor}
|
|
45
|
+
* field. HD rotation reads it directly; consumers that need a
|
|
46
|
+
* concrete descriptor at a specific index materialize it themselves
|
|
47
|
+
* (see `HDDescriptorProvider` in the wallet layer).
|
|
48
|
+
*
|
|
49
|
+
* Exposes seed-level primitives (signing, derivation, the template)
|
|
50
|
+
* but is deliberately NOT a `DescriptorProvider`. Wrap it explicitly
|
|
51
|
+
* to get one:
|
|
52
|
+
* - `HDDescriptorProvider` for rotating receive addresses.
|
|
53
|
+
* - {@link StaticDescriptorProvider} for legacy, single-key behaviour.
|
|
54
|
+
*
|
|
55
|
+
* The split prevents a SeedIdentity from being silently used as a
|
|
56
|
+
* concrete descriptor source, which would defeat HD rotation without
|
|
57
|
+
* any compile-time signal that something was wrong.
|
|
38
58
|
*
|
|
39
59
|
* @example
|
|
40
60
|
* ```typescript
|
|
41
61
|
* const seed = mnemonicToSeedSync(mnemonic);
|
|
42
62
|
*
|
|
43
|
-
* // Testnet (BIP86
|
|
63
|
+
* // Testnet (BIP86 wildcard descriptor m/86'/1'/0'/0/*)
|
|
44
64
|
* const identity = SeedIdentity.fromSeed(seed, { isMainnet: false });
|
|
45
65
|
*
|
|
46
|
-
* // Mainnet (BIP86
|
|
66
|
+
* // Mainnet (BIP86 wildcard descriptor m/86'/0'/0'/0/*)
|
|
47
67
|
* const identity = SeedIdentity.fromSeed(seed, { isMainnet: true });
|
|
48
68
|
*
|
|
49
|
-
* //
|
|
69
|
+
* // Caller-supplied wildcard descriptor (must end in `/*)`).
|
|
50
70
|
* const identity = SeedIdentity.fromSeed(seed, { descriptor });
|
|
51
71
|
* ```
|
|
52
72
|
*/
|
|
53
|
-
export declare class SeedIdentity implements
|
|
73
|
+
export declare class SeedIdentity implements HDCapableIdentity {
|
|
54
74
|
private readonly derivedKey;
|
|
75
|
+
/**
|
|
76
|
+
* Wildcard account-descriptor template (e.g.
|
|
77
|
+
* `tr([fp/86'/0'/0']xpub/0/*)`). The canonical thing to pass
|
|
78
|
+
* through the system; consumers materialize a concrete descriptor
|
|
79
|
+
* at a specific index themselves (see `HDDescriptorProvider` in
|
|
80
|
+
* the wallet layer for the rotating-counter use case).
|
|
81
|
+
*/
|
|
55
82
|
readonly descriptor: string;
|
|
56
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Constructs a SeedIdentity from a 64-byte seed and either a
|
|
85
|
+
* caller-supplied wildcard descriptor (`{ descriptor }`) or the
|
|
86
|
+
* default BIP86 path at the requested network (`{ isMainnet }`).
|
|
87
|
+
* Prefer the {@link fromSeed} factory for symmetry with
|
|
88
|
+
* {@link MnemonicIdentity.fromMnemonic}.
|
|
89
|
+
*
|
|
90
|
+
* Throws on a non-wildcard descriptor, an xpub mismatch with the
|
|
91
|
+
* seed, or a missing derivation path.
|
|
92
|
+
*/
|
|
93
|
+
constructor(seed: Uint8Array, opts?: SeedIdentityOptions);
|
|
57
94
|
/**
|
|
58
95
|
* Creates a SeedIdentity from a raw 64-byte seed.
|
|
59
96
|
*
|
|
60
97
|
* Pass `{ isMainnet }` for default BIP86 derivation, or
|
|
61
|
-
* `{ descriptor }` for a
|
|
98
|
+
* `{ descriptor }` for a caller-supplied account-descriptor
|
|
99
|
+
* template (the option's value must end with `/*)`).
|
|
62
100
|
*
|
|
63
101
|
* @param seed - 64-byte seed (typically from mnemonicToSeedSync)
|
|
64
|
-
* @param opts - Network selection or
|
|
102
|
+
* @param opts - Network selection or descriptor template.
|
|
65
103
|
*/
|
|
66
104
|
static fromSeed(seed: Uint8Array, opts?: SeedIdentityOptions): SeedIdentity;
|
|
67
105
|
xOnlyPublicKey(): Promise<Uint8Array>;
|
|
@@ -70,9 +108,29 @@ export declare class SeedIdentity implements Identity {
|
|
|
70
108
|
signMessage(message: Uint8Array, signatureType?: "schnorr" | "ecdsa"): Promise<Uint8Array>;
|
|
71
109
|
signerSession(): SignerSession;
|
|
72
110
|
/**
|
|
73
|
-
* Converts to a watch-only identity that cannot sign.
|
|
111
|
+
* Converts to a watch-only identity that cannot sign. Carries the
|
|
112
|
+
* template forward, so the readonly side stays HD-capable (can
|
|
113
|
+
* derive descriptors at any index without seed access).
|
|
74
114
|
*/
|
|
75
115
|
toReadonly(): Promise<ReadonlyDescriptorIdentity>;
|
|
116
|
+
/**
|
|
117
|
+
* Returns true when `descriptor` is derived from this identity's seed.
|
|
118
|
+
* HD descriptors match by account xpub; bare `tr(pubkey)` descriptors
|
|
119
|
+
* match by raw pubkey. See {@link descriptorIsOurs}.
|
|
120
|
+
*/
|
|
121
|
+
isOurs(descriptor: string): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Signs each request with the key derived from its descriptor.
|
|
124
|
+
* Each descriptor must share this identity's seed ({@link isOurs}).
|
|
125
|
+
*/
|
|
126
|
+
signWithDescriptor(requests: DescriptorSigningRequest[]): Promise<Transaction[]>;
|
|
127
|
+
/**
|
|
128
|
+
* Signs a message with the key derived from `descriptor`.
|
|
129
|
+
*/
|
|
130
|
+
signMessageWithDescriptor(descriptor: string, message: Uint8Array, signatureType?: "schnorr" | "ecdsa"): Promise<Uint8Array>;
|
|
131
|
+
private derivePrivateKeyForDescriptor;
|
|
132
|
+
private signTxWithKey;
|
|
133
|
+
private signMessageWithKey;
|
|
76
134
|
}
|
|
77
135
|
/**
|
|
78
136
|
* Mnemonic-based identity derived from a BIP39 phrase.
|
|
@@ -96,40 +154,66 @@ export declare class MnemonicIdentity extends SeedIdentity {
|
|
|
96
154
|
* Creates a MnemonicIdentity from a BIP39 mnemonic phrase.
|
|
97
155
|
*
|
|
98
156
|
* Pass `{ isMainnet }` for default BIP86 derivation, or
|
|
99
|
-
* `{ descriptor }` for a
|
|
157
|
+
* `{ descriptor }` for a caller-supplied account-descriptor
|
|
158
|
+
* template (the option's value must end with `/*)`).
|
|
100
159
|
*
|
|
101
160
|
* @param phrase - BIP39 mnemonic phrase (12 or 24 words)
|
|
102
|
-
* @param opts - Network selection or
|
|
161
|
+
* @param opts - Network selection or descriptor template, plus optional passphrase
|
|
103
162
|
*/
|
|
104
163
|
static fromMnemonic(phrase: string, opts?: MnemonicOptions): MnemonicIdentity;
|
|
105
164
|
}
|
|
106
165
|
/**
|
|
107
|
-
* Watch-only identity from
|
|
166
|
+
* Watch-only HD identity from a descriptor *template*.
|
|
108
167
|
*
|
|
109
168
|
* Can derive public keys but cannot sign transactions. Use this for
|
|
110
|
-
* watch-only wallets
|
|
111
|
-
*
|
|
169
|
+
* watch-only wallets — given just an xpub-based template, the readonly
|
|
170
|
+
* side still rotates through HD indices.
|
|
171
|
+
*
|
|
172
|
+
* Constructed from a wildcard template (e.g.
|
|
173
|
+
* `tr([fp/86'/0'/0']xpub.../0/*)`); the {@link descriptor} field
|
|
174
|
+
* holds it for HD providers to consume.
|
|
112
175
|
*
|
|
113
176
|
* @example
|
|
114
177
|
* ```typescript
|
|
115
|
-
* const
|
|
116
|
-
*
|
|
117
|
-
*
|
|
178
|
+
* const ro = ReadonlyDescriptorIdentity.fromDescriptor(
|
|
179
|
+
* "tr([fp/86'/0'/0']xpub.../0/*)"
|
|
180
|
+
* );
|
|
181
|
+
* ro.descriptor;
|
|
182
|
+
* // => "tr([fp/86'/0'/0']xpub.../0/*)" — the template
|
|
118
183
|
* ```
|
|
119
184
|
*/
|
|
120
|
-
export declare class ReadonlyDescriptorIdentity implements
|
|
185
|
+
export declare class ReadonlyDescriptorIdentity implements ReadonlyHDCapableIdentity {
|
|
186
|
+
/**
|
|
187
|
+
* Index-0 expansion of {@link descriptor}. Both the x-only pubkey
|
|
188
|
+
* (taproot, returned by the library as 32 bytes) and the compressed
|
|
189
|
+
* pubkey (derived through the bip32 node when needed) are read off
|
|
190
|
+
* this on demand — no separate caches.
|
|
191
|
+
*/
|
|
192
|
+
private readonly indexZero;
|
|
193
|
+
/**
|
|
194
|
+
* Wildcard account-descriptor template (e.g.
|
|
195
|
+
* `tr([fp/86'/0'/0']xpub/0/*)`). HD rotation consumers materialize
|
|
196
|
+
* a concrete descriptor at a specific index themselves.
|
|
197
|
+
*/
|
|
121
198
|
readonly descriptor: string;
|
|
122
|
-
private readonly xOnlyPubKey;
|
|
123
|
-
private readonly compressedPubKey;
|
|
124
199
|
private constructor();
|
|
125
200
|
/**
|
|
126
|
-
* Creates a ReadonlyDescriptorIdentity from an
|
|
201
|
+
* Creates a ReadonlyDescriptorIdentity from an account-descriptor
|
|
202
|
+
* *template* (must end with the BIP-32 wildcard suffix `/*)`).
|
|
127
203
|
*
|
|
128
|
-
* @param descriptor - Taproot
|
|
204
|
+
* @param descriptor - Wildcard-suffixed Taproot template
|
|
205
|
+
* (`tr([fp/path']xpub.../child/*)`).
|
|
129
206
|
*/
|
|
130
207
|
static fromDescriptor(descriptor: string): ReadonlyDescriptorIdentity;
|
|
131
208
|
xOnlyPublicKey(): Promise<Uint8Array>;
|
|
132
209
|
compressedPublicKey(): Promise<Uint8Array>;
|
|
210
|
+
/**
|
|
211
|
+
* Returns true when `descriptor` derives from this identity's xpub.
|
|
212
|
+
* HD descriptors match by account xpub; bare `tr(pubkey)` descriptors
|
|
213
|
+
* fall back to comparing against the index-0 x-only pubkey. See
|
|
214
|
+
* {@link descriptorIsOurs}.
|
|
215
|
+
*/
|
|
216
|
+
isOurs(descriptor: string): boolean;
|
|
133
217
|
}
|
|
134
218
|
/**
|
|
135
219
|
* Serialize a seed-backed signing identity into a
|