beignet 0.26.0 → 0.28.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 +11 -0
- package/dist/cli/auth.js +7 -0
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/beignet-node.js +301 -38
- package/dist/cli/beignet-node.js.map +1 -1
- package/dist/cli/cli.js +65 -2
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/config.js +7 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/daemon-options.js +1 -0
- package/dist/cli/daemon-options.js.map +1 -1
- package/dist/cli/daemon.js +49 -4
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/errors.js +7 -1
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/onchain-sweep.js +391 -0
- package/dist/cli/onchain-sweep.js.map +1 -0
- package/dist/cli/openapi.js +274 -6
- package/dist/cli/openapi.js.map +1 -1
- package/dist/lightning/chain/chain-monitor.js +3 -0
- package/dist/lightning/chain/chain-monitor.js.map +1 -1
- package/dist/lightning/chain/closing.js.map +1 -1
- package/dist/lightning/chain/types.js.map +1 -1
- package/dist/lightning/channel/channel-manager.js +111 -11
- package/dist/lightning/channel/channel-manager.js.map +1 -1
- package/dist/lightning/channel/channel-state.js.map +1 -1
- package/dist/lightning/channel/channel.js +386 -37
- package/dist/lightning/channel/channel.js.map +1 -1
- package/dist/lightning/channel/commitment-builder.js +2 -0
- package/dist/lightning/channel/commitment-builder.js.map +1 -1
- package/dist/lightning/channel/validation.js +10 -7
- package/dist/lightning/channel/validation.js.map +1 -1
- package/dist/lightning/channel/zero-reserve.js +3 -0
- package/dist/lightning/channel/zero-reserve.js.map +1 -0
- package/dist/lightning/features/flags.js +2 -0
- package/dist/lightning/features/flags.js.map +1 -1
- package/dist/lightning/gossip/gossip-sync.js +118 -34
- package/dist/lightning/gossip/gossip-sync.js.map +1 -1
- package/dist/lightning/gossip/network-graph.js +49 -20
- package/dist/lightning/gossip/network-graph.js.map +1 -1
- package/dist/lightning/gossip/pathfinding.js +3 -1
- package/dist/lightning/gossip/pathfinding.js.map +1 -1
- package/dist/lightning/gossip/pay-all.js +197 -0
- package/dist/lightning/gossip/pay-all.js.map +1 -0
- package/dist/lightning/gossip/rapid-sync.js +261 -86
- package/dist/lightning/gossip/rapid-sync.js.map +1 -1
- package/dist/lightning/keys/wallet-keys.js +10 -4
- package/dist/lightning/keys/wallet-keys.js.map +1 -1
- package/dist/lightning/message/codec.js +52 -1
- package/dist/lightning/message/codec.js.map +1 -1
- package/dist/lightning/message/dual-funding.js +30 -5
- package/dist/lightning/message/dual-funding.js.map +1 -1
- package/dist/lightning/node/lightning-node.js +385 -54
- package/dist/lightning/node/lightning-node.js.map +1 -1
- package/dist/lightning/node/types.js +3 -0
- package/dist/lightning/node/types.js.map +1 -1
- package/dist/lightning/storage/encryption.js +5 -5
- package/dist/lightning/storage/encryption.js.map +1 -1
- package/dist/lightning/storage/serialization.js +28 -0
- package/dist/lightning/storage/serialization.js.map +1 -1
- package/dist/lightning/storage/sqlite-storage.js +4 -2
- package/dist/lightning/storage/sqlite-storage.js.map +1 -1
- package/dist/lightning/watchtower/watchtower-client.js +8 -2
- package/dist/lightning/watchtower/watchtower-client.js.map +1 -1
- package/dist/types/cli/beignet-node.d.ts +20 -3
- package/dist/types/cli/errors.d.ts +3 -0
- package/dist/types/cli/index.d.ts +1 -0
- package/dist/types/cli/onchain-sweep.d.ts +57 -0
- package/dist/types/cli/types.d.ts +35 -0
- package/dist/types/lightning/chain/closing.d.ts +8 -0
- package/dist/types/lightning/chain/types.d.ts +1 -0
- package/dist/types/lightning/channel/channel-manager.d.ts +5 -1
- package/dist/types/lightning/channel/channel-state.d.ts +10 -0
- package/dist/types/lightning/channel/channel.d.ts +16 -2
- package/dist/types/lightning/channel/commitment-builder.d.ts +1 -0
- package/dist/types/lightning/channel/validation.d.ts +6 -3
- package/dist/types/lightning/channel/zero-reserve.d.ts +10 -0
- package/dist/types/lightning/features/flags.d.ts +1 -0
- package/dist/types/lightning/gossip/gossip-sync.d.ts +14 -0
- package/dist/types/lightning/gossip/network-graph.d.ts +8 -0
- package/dist/types/lightning/gossip/pathfinding.d.ts +8 -1
- package/dist/types/lightning/gossip/pay-all.d.ts +26 -0
- package/dist/types/lightning/gossip/rapid-sync.d.ts +10 -0
- package/dist/types/lightning/keys/wallet-keys.d.ts +2 -0
- package/dist/types/lightning/message/codec.d.ts +4 -0
- package/dist/types/lightning/message/dual-funding.d.ts +3 -0
- package/dist/types/lightning/node/lightning-node.d.ts +39 -3
- package/dist/types/lightning/node/types.d.ts +25 -0
- package/dist/types/lightning/storage/encryption.d.ts +1 -0
- package/dist/types/lightning/storage/serialization.d.ts +17 -0
- package/dist/types/types/wallet.d.ts +3 -1
- package/dist/types/wallet.js.map +1 -1
- package/dist/wallet/index.js +4 -2
- package/dist/wallet/index.js.map +1 -1
- package/docs/IROH-QUALIFICATION-2026-10-03.json +239 -0
- package/docs/IROH-QUALIFICATION-2026-10-03.md +115 -0
- package/docs/PAY-ALL.md +61 -0
- package/docs/ZERO-RESERVE.md +47 -0
- package/package.json +4 -4
package/dist/cli/beignet-node.js
CHANGED
|
@@ -43,6 +43,7 @@ const helpers_1 = require("../utils/helpers");
|
|
|
43
43
|
const conversion_1 = require("../utils/conversion");
|
|
44
44
|
const wallet_3 = require("../types/wallet");
|
|
45
45
|
const wallet_storage_1 = require("./wallet-storage");
|
|
46
|
+
const onchain_sweep_1 = require("./onchain-sweep");
|
|
46
47
|
const fs_utils_1 = require("./fs-utils");
|
|
47
48
|
const backup_mac_1 = require("./backup-mac");
|
|
48
49
|
const node_storage_view_1 = require("./node-storage-view");
|
|
@@ -174,6 +175,9 @@ function parseScid(scid) {
|
|
|
174
175
|
}
|
|
175
176
|
exports.parseScid = parseScid;
|
|
176
177
|
exports.ENGINE_PAYMENT_ERROR_CODES = {
|
|
178
|
+
PAY_ALL_REVIEW_EXPIRED: 'PAY_ALL_REVIEW_EXPIRED',
|
|
179
|
+
PAY_ALL_REMAINDER: 'PAY_ALL_REMAINDER',
|
|
180
|
+
PAY_ALL_BUDGET_MISMATCH: 'PAY_ALL_BUDGET_MISMATCH',
|
|
177
181
|
NO_ROUTE: 'NO_ROUTE',
|
|
178
182
|
DUPLICATE_PAYMENT: 'DUPLICATE_PAYMENT',
|
|
179
183
|
NO_CHANNEL_TO_HOP: 'PEER_NOT_CONNECTED',
|
|
@@ -718,6 +722,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
718
722
|
this.rapidGossipSync = true;
|
|
719
723
|
this._initialGossipPrime = null;
|
|
720
724
|
this._releaseBootRgs = null;
|
|
725
|
+
this._rapidGossipSync = null;
|
|
721
726
|
this._deferredGossipSyncs = new Set();
|
|
722
727
|
this.electrumServerCount = 1;
|
|
723
728
|
this._failoverInProgress = false;
|
|
@@ -861,6 +866,10 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
861
866
|
throw new errors_1.BeignetError('INVALID_PARAMS', `leaseRates: ${refusal}`);
|
|
862
867
|
}
|
|
863
868
|
}
|
|
869
|
+
if (opts.waiveClientReserve !== undefined &&
|
|
870
|
+
typeof opts.waiveClientReserve !== 'boolean') {
|
|
871
|
+
throw new errors_1.BeignetError('INVALID_PARAMS', 'waiveClientReserve must be a boolean');
|
|
872
|
+
}
|
|
864
873
|
if (opts.jitReceive !== undefined) {
|
|
865
874
|
const refusal = jitReceiveRefusal(opts.jitReceive);
|
|
866
875
|
if (refusal !== null) {
|
|
@@ -908,7 +917,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
908
917
|
}
|
|
909
918
|
let encryptionKey;
|
|
910
919
|
if (opts.storageEncryption ?? true) {
|
|
911
|
-
encryptionKey = (0, encryption_1.deriveStorageKey)(
|
|
920
|
+
encryptionKey = (0, encryption_1.deriveStorageKey)(this.walletSeed());
|
|
912
921
|
}
|
|
913
922
|
this._storageEncryptionKey = encryptionKey;
|
|
914
923
|
this.finishStagedCapsuleRestore(dbPath);
|
|
@@ -969,6 +978,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
969
978
|
};
|
|
970
979
|
const walletResult = await wallet_1.Wallet.create({
|
|
971
980
|
mnemonic: this.mnemonic,
|
|
981
|
+
seed: this.walletSeed(),
|
|
972
982
|
network: beignetNetwork,
|
|
973
983
|
disableRefreshOnCreate: true,
|
|
974
984
|
...(opts.addressType
|
|
@@ -1086,6 +1096,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
1086
1096
|
this._nodeStorageView = (0, node_storage_view_1.nodeStorageView)(this.storage);
|
|
1087
1097
|
this.node = lightning_node_1.LightningNode.fromMnemonic(this.mnemonic, {
|
|
1088
1098
|
coinType,
|
|
1099
|
+
seed: this.walletSeed(),
|
|
1089
1100
|
network: lnNetwork,
|
|
1090
1101
|
storage: this._nodeStorageView,
|
|
1091
1102
|
newChannelsRefused: () => this.newChannelRefusal(),
|
|
@@ -1135,6 +1146,11 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
1135
1146
|
}
|
|
1136
1147
|
: {}),
|
|
1137
1148
|
...(opts.fforIssuer === true ? { fforIssuer: { enabled: true } } : {}),
|
|
1149
|
+
zeroReserve: {
|
|
1150
|
+
role: 'primary',
|
|
1151
|
+
advertise: opts.waiveClientReserve === true,
|
|
1152
|
+
waiveClientReserve: opts.waiveClientReserve === true
|
|
1153
|
+
},
|
|
1138
1154
|
jitReceive: opts.jitReceive?.enabled === true
|
|
1139
1155
|
? {
|
|
1140
1156
|
enabled: true,
|
|
@@ -1770,6 +1786,10 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
1770
1786
|
}
|
|
1771
1787
|
if (this.rapidGossipSync && this.networkName === 'mainnet') {
|
|
1772
1788
|
const rgs = this.syncRapidGossip().catch((err) => {
|
|
1789
|
+
if (err instanceof rapid_sync_1.RapidGossipCancelledError && this.destroyed) {
|
|
1790
|
+
this.log('debug', 'Rapid gossip sync cancelled by shutdown', {});
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1773
1793
|
this.log('warn', 'Rapid gossip sync failed', {
|
|
1774
1794
|
error: err instanceof Error ? err.message : String(err)
|
|
1775
1795
|
});
|
|
@@ -2700,6 +2720,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
2700
2720
|
return path.join(this.dataDir, `${this.networkName}.capsule-restore.json`);
|
|
2701
2721
|
}
|
|
2702
2722
|
carryDaemonState(from, to) {
|
|
2723
|
+
this.onchainSweeps().carryTo(to);
|
|
2703
2724
|
const overrides = from.loadWalletData(auth_1.AUTH_KEY_OVERRIDES_STORAGE_KEY);
|
|
2704
2725
|
if (overrides !== null) {
|
|
2705
2726
|
to.saveWalletData(auth_1.AUTH_KEY_OVERRIDES_STORAGE_KEY, overrides);
|
|
@@ -3078,10 +3099,19 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
3078
3099
|
}
|
|
3079
3100
|
nodeSecret() {
|
|
3080
3101
|
if (!this._nodeSecret) {
|
|
3081
|
-
|
|
3102
|
+
if (!bip39.validateMnemonic(this.mnemonic)) {
|
|
3103
|
+
throw new Error('Invalid BIP39 mnemonic');
|
|
3104
|
+
}
|
|
3105
|
+
this._nodeSecret = (0, wallet_keys_1.deriveLightningKeysFromSeed)(this.walletSeed(), this.toCoinType(this.networkName)).nodePrivateKey;
|
|
3082
3106
|
}
|
|
3083
3107
|
return this._nodeSecret;
|
|
3084
3108
|
}
|
|
3109
|
+
walletSeed() {
|
|
3110
|
+
if (!this._walletSeed) {
|
|
3111
|
+
this._walletSeed = bip39.mnemonicToSeedSync(this.mnemonic);
|
|
3112
|
+
}
|
|
3113
|
+
return Buffer.from(this._walletSeed);
|
|
3114
|
+
}
|
|
3085
3115
|
async restoreFromGuardians() {
|
|
3086
3116
|
if (this._restoreInFlight) {
|
|
3087
3117
|
throw new errors_1.BeignetError('RESTORE_IN_PROGRESS', 'A guardian restore is already running; watch ' +
|
|
@@ -3286,9 +3316,15 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
3286
3316
|
]);
|
|
3287
3317
|
let totalMsat = 0n;
|
|
3288
3318
|
for (const ch of this.node.listChannels()) {
|
|
3289
|
-
|
|
3319
|
+
const effectiveState = ch.state === types_7.ChannelState.AWAITING_REESTABLISH
|
|
3320
|
+
? this.node
|
|
3321
|
+
.getChannelManager()
|
|
3322
|
+
.getChannel(ch.channelId)
|
|
3323
|
+
?.getFullState().preReestablishState ?? ch.state
|
|
3324
|
+
: ch.state;
|
|
3325
|
+
if (!recovering.has(effectiveState))
|
|
3290
3326
|
continue;
|
|
3291
|
-
if (
|
|
3327
|
+
if (effectiveState === types_7.ChannelState.FORCE_CLOSED &&
|
|
3292
3328
|
this.isForceCloseBalanceInWallet(ch.channelId)) {
|
|
3293
3329
|
continue;
|
|
3294
3330
|
}
|
|
@@ -3730,6 +3766,50 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
3730
3766
|
}
|
|
3731
3767
|
});
|
|
3732
3768
|
}
|
|
3769
|
+
onchainSweeps() {
|
|
3770
|
+
return new onchain_sweep_1.OnchainSweeps({
|
|
3771
|
+
storage: this.storage,
|
|
3772
|
+
wallet: this.wallet,
|
|
3773
|
+
network: this.getBitcoinNetwork(),
|
|
3774
|
+
broadcast: (hex) => this._broadcastRawTx(hex),
|
|
3775
|
+
admitAndSave: (debitSats, save) => {
|
|
3776
|
+
this._checkMaxPayment(debitSats);
|
|
3777
|
+
this._checkSpendLimit(debitSats);
|
|
3778
|
+
const before = {
|
|
3779
|
+
total: this._dailySpentSats,
|
|
3780
|
+
onchain: this._dailySpentOnchainSats,
|
|
3781
|
+
lightning: this._dailySpentLightningSats,
|
|
3782
|
+
reset: this._dailySpendResetTime
|
|
3783
|
+
};
|
|
3784
|
+
try {
|
|
3785
|
+
this.storage.transaction(() => {
|
|
3786
|
+
this._recordSpend(debitSats, 'onchain');
|
|
3787
|
+
this._persistSpendState(true);
|
|
3788
|
+
save();
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
catch (error) {
|
|
3792
|
+
this._dailySpentSats = before.total;
|
|
3793
|
+
this._dailySpentOnchainSats = before.onchain;
|
|
3794
|
+
this._dailySpentLightningSats = before.lightning;
|
|
3795
|
+
this._dailySpendResetTime = before.reset;
|
|
3796
|
+
throw error;
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
prepareOnchainSweep(request) {
|
|
3802
|
+
return this._runOnchainSend(() => this.onchainSweeps().prepare(request));
|
|
3803
|
+
}
|
|
3804
|
+
submitOnchainSweep(requestId) {
|
|
3805
|
+
return this._runOnchainSend(() => this.onchainSweeps().submit(requestId));
|
|
3806
|
+
}
|
|
3807
|
+
getOnchainSweep(requestId) {
|
|
3808
|
+
return this.onchainSweeps().get(requestId);
|
|
3809
|
+
}
|
|
3810
|
+
cancelOnchainSweep(requestId) {
|
|
3811
|
+
return this._runOnchainSend(() => this.onchainSweeps().cancel(requestId));
|
|
3812
|
+
}
|
|
3733
3813
|
async bumpFeeOnchain(txid, satsPerVbyte) {
|
|
3734
3814
|
this._validateTxid(txid);
|
|
3735
3815
|
this._validateFeeRate(satsPerVbyte, true);
|
|
@@ -3942,9 +4022,48 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
3942
4022
|
};
|
|
3943
4023
|
}
|
|
3944
4024
|
listOnchainTransactions() {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
.
|
|
4025
|
+
const transactions = new Map(Object.values(this.wallet.transactions).map((tx) => {
|
|
4026
|
+
const info = this.toOnchainTxInfo(tx);
|
|
4027
|
+
return [info.txid, info];
|
|
4028
|
+
}));
|
|
4029
|
+
for (const close of this.node.listExternalClosePayments()) {
|
|
4030
|
+
const bitcoin = require('bitcoinjs-lib');
|
|
4031
|
+
transactions.set(close.txid, {
|
|
4032
|
+
txid: close.txid,
|
|
4033
|
+
type: 'sent',
|
|
4034
|
+
valueSats: close.amountSats,
|
|
4035
|
+
feeSats: close.feeSats,
|
|
4036
|
+
satsPerVbyte: close.satsPerVbyte,
|
|
4037
|
+
address: bitcoin.address.fromOutputScript(Buffer.from(close.scriptHex, 'hex'), this.getBitcoinNetwork()),
|
|
4038
|
+
confirmed: close.confirmationHeight > 0,
|
|
4039
|
+
...(close.confirmationHeight > 0
|
|
4040
|
+
? { height: close.confirmationHeight }
|
|
4041
|
+
: {}),
|
|
4042
|
+
timestamp: close.timestamp,
|
|
4043
|
+
source: 'cooperative-close',
|
|
4044
|
+
channelId: close.channelId
|
|
4045
|
+
});
|
|
4046
|
+
}
|
|
4047
|
+
if (typeof this.storage?.loadWalletData === 'function') {
|
|
4048
|
+
for (const sweep of this.onchainSweeps().list()) {
|
|
4049
|
+
if (!sweep.txid ||
|
|
4050
|
+
!['submitted', 'confirmed'].includes(sweep.status) ||
|
|
4051
|
+
transactions.has(sweep.txid))
|
|
4052
|
+
continue;
|
|
4053
|
+
transactions.set(sweep.txid, {
|
|
4054
|
+
txid: sweep.txid,
|
|
4055
|
+
type: 'sent',
|
|
4056
|
+
valueSats: sweep.amountSats,
|
|
4057
|
+
feeSats: sweep.feeSats,
|
|
4058
|
+
satsPerVbyte: sweep.satsPerVbyte,
|
|
4059
|
+
address: sweep.address,
|
|
4060
|
+
confirmed: sweep.status === 'confirmed',
|
|
4061
|
+
timestamp: sweep.createdAt,
|
|
4062
|
+
source: 'onchain-sweep'
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
return [...transactions.values()].sort((a, b) => b.timestamp - a.timestamp);
|
|
3948
4067
|
}
|
|
3949
4068
|
listUtxos() {
|
|
3950
4069
|
return this.wallet.listUtxos().map((utxo) => ({
|
|
@@ -3958,19 +4077,23 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
3958
4077
|
}
|
|
3959
4078
|
async freezeUtxo(txid, index) {
|
|
3960
4079
|
this._validateTxid(txid);
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
4080
|
+
return this._runOnchainSend(async () => {
|
|
4081
|
+
this.onchainSweeps().assertInputMutable(txid, index);
|
|
4082
|
+
const res = await this.wallet.freezeUtxo({ txid, index });
|
|
4083
|
+
if (res.isErr())
|
|
4084
|
+
throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, res.error.message);
|
|
4085
|
+
return { frozen: `${txid}:${index}` };
|
|
4086
|
+
});
|
|
3966
4087
|
}
|
|
3967
4088
|
async unfreezeUtxo(txid, index) {
|
|
3968
4089
|
this._validateTxid(txid);
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
4090
|
+
return this._runOnchainSend(async () => {
|
|
4091
|
+
this.onchainSweeps().assertInputMutable(txid, index);
|
|
4092
|
+
const res = await this.wallet.unfreezeUtxo({ txid, index });
|
|
4093
|
+
if (res.isErr())
|
|
4094
|
+
throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, res.error.message);
|
|
4095
|
+
return { unfrozen: `${txid}:${index}` };
|
|
4096
|
+
});
|
|
3974
4097
|
}
|
|
3975
4098
|
async setAddressLabel(address, label) {
|
|
3976
4099
|
const res = await this.wallet.setAddressLabel(address, label);
|
|
@@ -4085,24 +4208,58 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
4085
4208
|
this._lastGraphSyncAt = Date.now();
|
|
4086
4209
|
return synced;
|
|
4087
4210
|
}
|
|
4088
|
-
|
|
4211
|
+
syncRapidGossip() {
|
|
4212
|
+
if (this._rapidGossipSync)
|
|
4213
|
+
return this._rapidGossipSync;
|
|
4214
|
+
const run = this.runRapidGossipSync();
|
|
4215
|
+
this._rapidGossipSync = run;
|
|
4216
|
+
const clear = () => {
|
|
4217
|
+
if (this._rapidGossipSync === run)
|
|
4218
|
+
this._rapidGossipSync = null;
|
|
4219
|
+
};
|
|
4220
|
+
void run.then(clear, clear);
|
|
4221
|
+
return run;
|
|
4222
|
+
}
|
|
4223
|
+
async runRapidGossipSync() {
|
|
4089
4224
|
if (this.networkName !== 'mainnet') {
|
|
4090
4225
|
this.log('warn', 'Rapid gossip sync is only available on mainnet', {});
|
|
4091
4226
|
return null;
|
|
4092
4227
|
}
|
|
4093
4228
|
const url = this.rapidGossipSyncUrl ?? rapid_sync_1.DEFAULT_RGS_URL;
|
|
4094
4229
|
this.log('info', 'Rapid gossip sync: downloading snapshot', { url });
|
|
4230
|
+
const downloadStart = Date.now();
|
|
4095
4231
|
const data = await (0, rapid_sync_1.fetchRapidGossipSnapshot)(url);
|
|
4096
|
-
const
|
|
4232
|
+
const downloadMs = Date.now() - downloadStart;
|
|
4233
|
+
if (this.destroyed)
|
|
4234
|
+
throw new rapid_sync_1.RapidGossipCancelledError();
|
|
4235
|
+
let slices = 0;
|
|
4236
|
+
let busyMs = 0;
|
|
4237
|
+
const applyStart = Date.now();
|
|
4238
|
+
const result = await this.node.loadRapidGossipSnapshotAsync(data, {
|
|
4239
|
+
cancelled: () => this.destroyed,
|
|
4240
|
+
onSlice: (ms) => {
|
|
4241
|
+
slices++;
|
|
4242
|
+
busyMs += ms;
|
|
4243
|
+
}
|
|
4244
|
+
});
|
|
4245
|
+
const applyMs = Date.now() - applyStart;
|
|
4097
4246
|
this._lastGraphSyncAt = Date.now();
|
|
4098
4247
|
this.log('info', 'Rapid gossip sync complete', {
|
|
4099
4248
|
channelsAdded: result.channelsAdded,
|
|
4100
4249
|
updatesApplied: result.updatesApplied,
|
|
4101
|
-
nodes: result.nodeCount
|
|
4250
|
+
nodes: result.nodeCount,
|
|
4251
|
+
downloadMs,
|
|
4252
|
+
applyMs,
|
|
4253
|
+
busyMs,
|
|
4254
|
+
slices
|
|
4102
4255
|
});
|
|
4103
4256
|
this.emit('gossip:synced', {
|
|
4104
4257
|
channelsAdded: result.channelsAdded,
|
|
4105
|
-
updatesApplied: result.updatesApplied
|
|
4258
|
+
updatesApplied: result.updatesApplied,
|
|
4259
|
+
downloadMs,
|
|
4260
|
+
applyMs,
|
|
4261
|
+
busyMs,
|
|
4262
|
+
slices
|
|
4106
4263
|
});
|
|
4107
4264
|
return {
|
|
4108
4265
|
channelsAdded: result.channelsAdded,
|
|
@@ -4182,11 +4339,45 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
4182
4339
|
await Promise.all(openPromises);
|
|
4183
4340
|
return opened;
|
|
4184
4341
|
}
|
|
4185
|
-
async
|
|
4342
|
+
async closeQuote(channelId, address, acceptStaleStateRisk = false) {
|
|
4186
4343
|
const idBuf = requireChannelIdHex(channelId);
|
|
4344
|
+
const script = this._decodeSpliceDestination(address);
|
|
4345
|
+
if (!this.node.getChannel(idBuf))
|
|
4346
|
+
throw new errors_1.BeignetError(errors_1.BeignetErrorCode.CHANNEL_NOT_FOUND, `Channel not found: ${channelId}`);
|
|
4347
|
+
const destination = script ?? (await this.cooperativeCloseDestination());
|
|
4348
|
+
try {
|
|
4349
|
+
return this.node.closeQuote(idBuf, destination, acceptStaleStateRisk);
|
|
4350
|
+
}
|
|
4351
|
+
catch (error) {
|
|
4352
|
+
throw new errors_1.BeignetError('CLOSE_UNAVAILABLE', error.message);
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
async closeChannel(channelId, acceptStaleStateRisk = false, address) {
|
|
4356
|
+
const idBuf = requireChannelIdHex(channelId);
|
|
4357
|
+
const destination = this._decodeSpliceDestination(address);
|
|
4187
4358
|
if (!this.node.getChannel(idBuf)) {
|
|
4188
4359
|
throw new errors_1.BeignetError(errors_1.BeignetErrorCode.CHANNEL_NOT_FOUND, `Channel not found: ${idBuf.toString('hex')}`);
|
|
4189
4360
|
}
|
|
4361
|
+
const scriptPubkey = destination ?? (await this.cooperativeCloseDestination());
|
|
4362
|
+
let externalSpendSats;
|
|
4363
|
+
if (destination) {
|
|
4364
|
+
let quote;
|
|
4365
|
+
try {
|
|
4366
|
+
quote = this.node.closeQuote(idBuf, destination, acceptStaleStateRisk);
|
|
4367
|
+
}
|
|
4368
|
+
catch (error) {
|
|
4369
|
+
throw new errors_1.BeignetError('CLOSE_UNAVAILABLE', error.message);
|
|
4370
|
+
}
|
|
4371
|
+
externalSpendSats = quote.amountSats + quote.feeSats;
|
|
4372
|
+
this._checkMaxPayment(externalSpendSats);
|
|
4373
|
+
this._checkSpendLimit(externalSpendSats);
|
|
4374
|
+
}
|
|
4375
|
+
const result = this.node.closeChannel(idBuf, scriptPubkey, acceptStaleStateRisk, destination !== undefined);
|
|
4376
|
+
if (result.ok && externalSpendSats !== undefined)
|
|
4377
|
+
this._recordSpend(externalSpendSats, 'onchain');
|
|
4378
|
+
return result;
|
|
4379
|
+
}
|
|
4380
|
+
async cooperativeCloseDestination() {
|
|
4190
4381
|
let scriptPubkey = await this.boundedCurrentWalletAddressScript();
|
|
4191
4382
|
if (!scriptPubkey)
|
|
4192
4383
|
scriptPubkey = this.sweepDestinationScript;
|
|
@@ -4194,7 +4385,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
4194
4385
|
const bitcoin = require('bitcoinjs-lib');
|
|
4195
4386
|
scriptPubkey = bitcoin.address.toOutputScript(this.node.getFundingAddress(), this.getBitcoinNetwork());
|
|
4196
4387
|
}
|
|
4197
|
-
return
|
|
4388
|
+
return scriptPubkey;
|
|
4198
4389
|
}
|
|
4199
4390
|
async boundedCurrentWalletAddressScript() {
|
|
4200
4391
|
return this.raceWithTimeout(this.resolveCurrentWalletAddressScript().catch(() => undefined), this._closeAddressLookupTimeoutMs);
|
|
@@ -4814,6 +5005,12 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
4814
5005
|
peerPubkey,
|
|
4815
5006
|
state: ch.state,
|
|
4816
5007
|
localBalanceSats: Number(ch.localBalanceMsat / 1000n),
|
|
5008
|
+
maxSendableSats: Number((ch.spendableOutboundMsat ?? 0n) / 1000n),
|
|
5009
|
+
localReserveWaived: ch.localReserveWaived === true,
|
|
5010
|
+
remoteReserveWaived: ch.remoteReserveWaived === true,
|
|
5011
|
+
isOpener: ch.isOpener,
|
|
5012
|
+
localReserveSats: Number((ch.localReserveMsat ?? 0n) / 1000n),
|
|
5013
|
+
remoteReserveSats: Number((ch.remoteReserveMsat ?? 0n) / 1000n),
|
|
4817
5014
|
remoteBalanceSats: Number(ch.remoteBalanceMsat / 1000n),
|
|
4818
5015
|
capacitySats: Number(ch.fundingSatoshis),
|
|
4819
5016
|
isAnchor: (0, types_7.isAnchorChannel)(ch.channelType ?? null)
|
|
@@ -5680,7 +5877,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
5680
5877
|
this._persistSpendState();
|
|
5681
5878
|
return count;
|
|
5682
5879
|
}
|
|
5683
|
-
_persistSpendState() {
|
|
5880
|
+
_persistSpendState(strict = false) {
|
|
5684
5881
|
if (this._dailySpendLimitSats === undefined)
|
|
5685
5882
|
return;
|
|
5686
5883
|
const storage = this.storage;
|
|
@@ -5702,6 +5899,8 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
5702
5899
|
storage.saveMetadata(exports.DAILY_SPEND_STATE_KEY, JSON.stringify(state));
|
|
5703
5900
|
}
|
|
5704
5901
|
catch (err) {
|
|
5902
|
+
if (strict)
|
|
5903
|
+
throw err;
|
|
5705
5904
|
this.log('warn', 'Could not persist the daily spend ledger', {
|
|
5706
5905
|
error: err instanceof Error ? err.message : String(err)
|
|
5707
5906
|
});
|
|
@@ -6161,6 +6360,39 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6161
6360
|
}
|
|
6162
6361
|
return new errors_1.BeignetError(code, msg);
|
|
6163
6362
|
}
|
|
6363
|
+
quotePayAll(bolt11, maxFeeMsat) {
|
|
6364
|
+
try {
|
|
6365
|
+
const quote = this.node.quotePayAll(bolt11, requireMsatValue(maxFeeMsat, 'maxFeeMsat'));
|
|
6366
|
+
return {
|
|
6367
|
+
...quote,
|
|
6368
|
+
debitMsat: quote.debitMsat.toString(),
|
|
6369
|
+
maxFeeMsat: quote.maxFeeMsat.toString(),
|
|
6370
|
+
minRecipientMsat: quote.minRecipientMsat.toString(),
|
|
6371
|
+
remainderMsat: quote.remainderMsat.toString()
|
|
6372
|
+
};
|
|
6373
|
+
}
|
|
6374
|
+
catch (err) {
|
|
6375
|
+
throw this._toBeignetPaymentError(err);
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
async payInvoiceAll(bolt11, debitMsat, maxFeeMsat, timeoutMs = 60000) {
|
|
6379
|
+
this._checkDraining();
|
|
6380
|
+
const decoded = decodeInvoiceInput(bolt11);
|
|
6381
|
+
const debit = requireMsatValue(debitMsat, 'debitMsat');
|
|
6382
|
+
const feeCap = requireMsatValue(maxFeeMsat, 'maxFeeMsat');
|
|
6383
|
+
if (debit <= 0n || debit > 0xffffffffffffffffn || feeCap >= debit) {
|
|
6384
|
+
throw new errors_1.BeignetError('INVALID_PARAMS', 'Pay-all requires a positive u64 debitMsat and maxFeeMsat below it');
|
|
6385
|
+
}
|
|
6386
|
+
if (!Number.isSafeInteger(timeoutMs) ||
|
|
6387
|
+
timeoutMs < 1 ||
|
|
6388
|
+
timeoutMs > 2147483647) {
|
|
6389
|
+
throw new errors_1.BeignetError('INVALID_PARAMS', 'timeoutMs must be a positive timer duration');
|
|
6390
|
+
}
|
|
6391
|
+
const claim = this._admitLightningSpend(decoded.paymentHash.toString('hex'), debit, 0n);
|
|
6392
|
+
return this.waitForInvoicePayment(decoded.paymentHash, timeoutMs, claim, () => {
|
|
6393
|
+
this.node.sendPayAll(bolt11, debit, feeCap);
|
|
6394
|
+
});
|
|
6395
|
+
}
|
|
6164
6396
|
async payInvoice(bolt11, timeoutMs = 60000, maxFeeSats, amountSats, metadata, cltvLimit, maxFeeMsatCap) {
|
|
6165
6397
|
this._checkDraining();
|
|
6166
6398
|
const decoded = decodeInvoiceInput(bolt11);
|
|
@@ -6173,10 +6405,16 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6173
6405
|
const maxFeeMsat = resolveMaxFeeMsat(maxFeeSats, maxFeeMsatCap, payAmountMsat);
|
|
6174
6406
|
const maxCltvExpiryHeight = this._cltvCeiling(cltvLimit);
|
|
6175
6407
|
const claim = this._admitLightningSpend(paymentHashHex, payAmountMsat, maxFeeMsat);
|
|
6408
|
+
return this.waitForInvoicePayment(decoded.paymentHash, timeoutMs, claim, () => {
|
|
6409
|
+
this.node.sendPayment(bolt11, undefined, maxFeeMsat, amountMsat, maxCltvExpiryHeight, undefined, metadata);
|
|
6410
|
+
});
|
|
6411
|
+
}
|
|
6412
|
+
waitForInvoicePayment(paymentHash, timeoutMs, claim, dispatch) {
|
|
6413
|
+
const paymentHashHex = paymentHash.toString('hex');
|
|
6176
6414
|
return new Promise((resolve, reject) => {
|
|
6177
6415
|
const timer = setTimeout(() => {
|
|
6178
6416
|
cleanup();
|
|
6179
|
-
reject(this._paymentTimeout(
|
|
6417
|
+
reject(this._paymentTimeout(paymentHash, 'Payment', timeoutMs, claim));
|
|
6180
6418
|
}, timeoutMs);
|
|
6181
6419
|
const cleanup = () => {
|
|
6182
6420
|
clearTimeout(timer);
|
|
@@ -6200,13 +6438,19 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6200
6438
|
};
|
|
6201
6439
|
this.node.on('payment:sent', onSent);
|
|
6202
6440
|
this.node.on('payment:failed', onFailed);
|
|
6441
|
+
const beforeDispatch = this.node.getPayment(paymentHash);
|
|
6203
6442
|
try {
|
|
6204
|
-
|
|
6443
|
+
dispatch();
|
|
6205
6444
|
}
|
|
6206
6445
|
catch (err) {
|
|
6207
6446
|
cleanup();
|
|
6208
|
-
|
|
6447
|
+
const dispatched = this.node.getPayment(paymentHash) !== beforeDispatch;
|
|
6448
|
+
if (dispatched && this.node.hasHtlcInFlight(paymentHash)) {
|
|
6449
|
+
this.node.failPaymentUnlessInFlight(paymentHash);
|
|
6450
|
+
}
|
|
6451
|
+
else if (claim) {
|
|
6209
6452
|
this._closeAsyncSpendClaim(paymentHashHex, claim);
|
|
6453
|
+
}
|
|
6210
6454
|
reject(this._toBeignetPaymentError(err));
|
|
6211
6455
|
}
|
|
6212
6456
|
});
|
|
@@ -6621,6 +6865,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6621
6865
|
return null;
|
|
6622
6866
|
const feeMsat = paymentFeeMsat(record);
|
|
6623
6867
|
return {
|
|
6868
|
+
...(record.payAll ? { payAll: this.toPaymentInfo(record).payAll } : {}),
|
|
6624
6869
|
paymentHash: proof.paymentHash.toString('hex'),
|
|
6625
6870
|
preimage: proof.preimage.toString('hex'),
|
|
6626
6871
|
amountSats: paymentAmountSats(record),
|
|
@@ -6711,6 +6956,17 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6711
6956
|
}
|
|
6712
6957
|
toPaymentInfo(p) {
|
|
6713
6958
|
const info = {
|
|
6959
|
+
...(p.payAll
|
|
6960
|
+
? {
|
|
6961
|
+
payAll: {
|
|
6962
|
+
debitMsat: p.payAll.debitMsat.toString(),
|
|
6963
|
+
maxFeeMsat: p.payAll.maxFeeMsat.toString(),
|
|
6964
|
+
deliveredMsat: p.payAll.deliveredMsat.toString(),
|
|
6965
|
+
feeMsat: p.payAll.feeMsat.toString(),
|
|
6966
|
+
remainderMsat: p.payAll.remainderMsat.toString()
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
: {}),
|
|
6714
6970
|
paymentHash: p.paymentHash.toString('hex'),
|
|
6715
6971
|
amountSats: paymentAmountSats(p),
|
|
6716
6972
|
status: p.status,
|
|
@@ -6853,10 +7109,10 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6853
7109
|
isAnchor: (0, types_7.isAnchorChannel)(state.channelType)
|
|
6854
7110
|
};
|
|
6855
7111
|
}
|
|
6856
|
-
spliceQuote(channelId, direction, feeratePerkw) {
|
|
7112
|
+
spliceQuote(channelId, direction, feeratePerkw, destinationAddress) {
|
|
6857
7113
|
const idBuf = requireChannelIdHex(channelId);
|
|
6858
7114
|
requireU32(feeratePerkw, 'feeratePerkw', 1, splice_weight_1.MAX_SPLICE_FEERATE_PERKW);
|
|
6859
|
-
return fundingOrRefuse(() => this.node.spliceQuote(idBuf, direction, feeratePerkw));
|
|
7115
|
+
return fundingOrRefuse(() => this.node.spliceQuote(idBuf, direction, feeratePerkw, this._decodeSpliceDestination(destinationAddress)));
|
|
6860
7116
|
}
|
|
6861
7117
|
spliceIn(channelId, amountSats, feeratePerkw, fundingUtxos) {
|
|
6862
7118
|
const idBuf = requireChannelIdHex(channelId);
|
|
@@ -6865,10 +7121,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6865
7121
|
const result = fundingOrRefuse(() => this.node.spliceIn(idBuf, BigInt(amountSats), feeratePerkw, fundingUtxos));
|
|
6866
7122
|
return result;
|
|
6867
7123
|
}
|
|
6868
|
-
|
|
6869
|
-
const idBuf = requireChannelIdHex(channelId);
|
|
6870
|
-
requirePositiveSafeInteger(amountSats, 'amountSats');
|
|
6871
|
-
requireU32(feeratePerkw, 'feeratePerkw', 1, splice_weight_1.MAX_SPLICE_FEERATE_PERKW);
|
|
7124
|
+
_decodeSpliceDestination(destinationAddress) {
|
|
6872
7125
|
let destinationScript;
|
|
6873
7126
|
if (destinationAddress !== undefined) {
|
|
6874
7127
|
if (typeof destinationAddress !== 'string' ||
|
|
@@ -6883,6 +7136,13 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
6883
7136
|
throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `destinationAddress is not a valid address for this network: ${err instanceof Error ? err.message : 'failed to decode'}`);
|
|
6884
7137
|
}
|
|
6885
7138
|
}
|
|
7139
|
+
return destinationScript;
|
|
7140
|
+
}
|
|
7141
|
+
spliceOut(channelId, amountSats, feeratePerkw, destinationAddress) {
|
|
7142
|
+
const idBuf = requireChannelIdHex(channelId);
|
|
7143
|
+
requirePositiveSafeInteger(amountSats, 'amountSats');
|
|
7144
|
+
requireU32(feeratePerkw, 'feeratePerkw', 1, splice_weight_1.MAX_SPLICE_FEERATE_PERKW);
|
|
7145
|
+
const destinationScript = this._decodeSpliceDestination(destinationAddress);
|
|
6886
7146
|
let externalSpendSats;
|
|
6887
7147
|
if (destinationScript !== undefined) {
|
|
6888
7148
|
const feeSats = Number((0, splice_weight_1.spliceFeeSats)((0, splice_weight_1.estimateSpliceTxWeight)({
|
|
@@ -7327,7 +7587,9 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
7327
7587
|
const snapshot = this.node.getLiquiditySnapshot();
|
|
7328
7588
|
let reserveMsat = 0n;
|
|
7329
7589
|
let sendableMsat = 0n;
|
|
7590
|
+
let maxSendableMsat = 0n;
|
|
7330
7591
|
for (const ch of this.node.listChannels()) {
|
|
7592
|
+
maxSendableMsat += ch.spendableOutboundMsat ?? 0n;
|
|
7331
7593
|
if (isHeldRestore(ch))
|
|
7332
7594
|
continue;
|
|
7333
7595
|
if (ch.state !== types_7.ChannelState.NORMAL && !ch.htlcUsable)
|
|
@@ -7351,6 +7613,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
7351
7613
|
inboundLiquidityPct: snapshot.inboundLiquidityPct,
|
|
7352
7614
|
reserveSats: Number(reserveMsat / 1000n),
|
|
7353
7615
|
sendableSats: Number(sendableMsat / 1000n),
|
|
7616
|
+
maxSendableSats: Number(maxSendableMsat / 1000n),
|
|
7354
7617
|
recommendations: snapshot.recommendations
|
|
7355
7618
|
};
|
|
7356
7619
|
}
|
|
@@ -7986,7 +8249,7 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
7986
8249
|
}
|
|
7987
8250
|
async backup(destPath) {
|
|
7988
8251
|
await this.storage.backup(destPath);
|
|
7989
|
-
await (0, backup_mac_1.writeBackupMac)((0, backup_mac_1.deriveBackupMacKey)(
|
|
8252
|
+
await (0, backup_mac_1.writeBackupMac)((0, backup_mac_1.deriveBackupMacKey)(this.walletSeed()), destPath);
|
|
7990
8253
|
}
|
|
7991
8254
|
storageFiles() {
|
|
7992
8255
|
const dbPath = fs.realpathSync.native(path.join(this.dataDir, `${this.networkName}.db`));
|
|
@@ -8037,14 +8300,14 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
8037
8300
|
createdAt: Date.now(),
|
|
8038
8301
|
channels: data.channels
|
|
8039
8302
|
};
|
|
8040
|
-
const seed =
|
|
8303
|
+
const seed = this.walletSeed();
|
|
8041
8304
|
const encoded = (0, scb_1.encodeScb)(backup, seed);
|
|
8042
8305
|
const scbPath = path.join(this.dataDir, 'channels.scb');
|
|
8043
8306
|
(0, fs_utils_1.writeFileAtomic)(scbPath, encoded);
|
|
8044
8307
|
return { encoded, channelCount: data.channels.length, path: scbPath };
|
|
8045
8308
|
}
|
|
8046
8309
|
async restoreFromScb(encoded) {
|
|
8047
|
-
const seed =
|
|
8310
|
+
const seed = this.walletSeed();
|
|
8048
8311
|
const backup = (0, scb_1.decodeScb)(encoded.trim(), seed);
|
|
8049
8312
|
const expectedNetwork = this.toLnNetwork(this.networkName);
|
|
8050
8313
|
if (backup.network !== expectedNetwork) {
|
|
@@ -8165,14 +8428,14 @@ class BeignetNode extends events_1.EventEmitter {
|
|
|
8165
8428
|
});
|
|
8166
8429
|
}
|
|
8167
8430
|
}
|
|
8168
|
-
const seed =
|
|
8431
|
+
const seed = this.walletSeed();
|
|
8169
8432
|
this.offerRetrievedScb((0, scb_1.encodeScb)(embedded, seed), embedded, peerPubkey, 'capsule');
|
|
8170
8433
|
return;
|
|
8171
8434
|
}
|
|
8172
8435
|
let backup;
|
|
8173
8436
|
const encoded = blob.toString('utf8');
|
|
8174
8437
|
try {
|
|
8175
|
-
const seed =
|
|
8438
|
+
const seed = this.walletSeed();
|
|
8176
8439
|
backup = (0, scb_1.decodeScb)(encoded, seed);
|
|
8177
8440
|
}
|
|
8178
8441
|
catch {
|