unlock flow

This commit is contained in:
boufni95 2024-07-05 16:19:43 +02:00
parent 52a27a8ff9
commit 2648f55df0
13 changed files with 1815 additions and 7 deletions

View file

@ -12,14 +12,17 @@ import { UserReceivingAddress } from "./build/src/services/storage/entity/UserRe
import { UserToUserPayment } from "./build/src/services/storage/entity/UserToUserPayment.js" import { UserToUserPayment } from "./build/src/services/storage/entity/UserToUserPayment.js"
import { UserTransactionPayment } from "./build/src/services/storage/entity/UserTransactionPayment.js" import { UserTransactionPayment } from "./build/src/services/storage/entity/UserTransactionPayment.js"
import { LspOrder } from "./build/src/services/storage/entity/LspOrder.js" import { LspOrder } from "./build/src/services/storage/entity/LspOrder.js"
import { LndNodeInfo } from "./build/src/services/storage/entity/LndNodeInfo.js"
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js' import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js' import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
import { LiquidityProvider1719335699480 } from './build/src/services/storage/migrations/1719335699480-liquidity_provider.js'
export default new DataSource({ export default new DataSource({
type: "sqlite", type: "sqlite",
database: "db.sqlite", database: "db.sqlite",
// logging: true, // logging: true,
migrations: [Initial1703170309875, LspOrder1718387847693], migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480],
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder], UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo],
// synchronize: true, // synchronize: true,
}) })
//npx typeorm migration:generate ./src/services/storage/migrations/lnd_node_info -d ./datasource.js

View file

@ -0,0 +1,180 @@
// @generated by protobuf-ts 2.8.1
// @generated from protobuf file "walletunlocker.proto" (package "lnrpc", syntax proto3)
// tslint:disable
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
import { WalletUnlocker } from "./walletunlocker.js";
import type { ChangePasswordResponse } from "./walletunlocker.js";
import type { ChangePasswordRequest } from "./walletunlocker.js";
import type { UnlockWalletResponse } from "./walletunlocker.js";
import type { UnlockWalletRequest } from "./walletunlocker.js";
import type { InitWalletResponse } from "./walletunlocker.js";
import type { InitWalletRequest } from "./walletunlocker.js";
import { stackIntercept } from "@protobuf-ts/runtime-rpc";
import type { GenSeedResponse } from "./walletunlocker.js";
import type { GenSeedRequest } from "./walletunlocker.js";
import type { UnaryCall } from "@protobuf-ts/runtime-rpc";
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
//
// Comments in this file will be directly parsed into the API
// Documentation as descriptions of the associated method, message, or field.
// These descriptions should go right above the definition of the object, and
// can be in either block or // comment format.
//
// An RPC method can be matched to an lncli command by placing a line in the
// beginning of the description in exactly the following format:
// lncli: `methodname`
//
// Failure to specify the exact name of the command will cause documentation
// generation to fail.
//
// More information on how exactly the gRPC documentation is generated from
// this proto file can be found here:
// https://github.com/lightninglabs/lightning-api
/**
* WalletUnlocker is a service that is used to set up a wallet password for
* lnd at first startup, and unlock a previously set up wallet.
*
* @generated from protobuf service lnrpc.WalletUnlocker
*/
export interface IWalletUnlockerClient {
/**
*
* GenSeed is the first method that should be used to instantiate a new lnd
* instance. This method allows a caller to generate a new aezeed cipher seed
* given an optional passphrase. If provided, the passphrase will be necessary
* to decrypt the cipherseed to expose the internal wallet seed.
*
* Once the cipherseed is obtained and verified by the user, the InitWallet
* method should be used to commit the newly generated seed, and create the
* wallet.
*
* @generated from protobuf rpc: GenSeed(lnrpc.GenSeedRequest) returns (lnrpc.GenSeedResponse);
*/
genSeed(input: GenSeedRequest, options?: RpcOptions): UnaryCall<GenSeedRequest, GenSeedResponse>;
/**
*
* InitWallet is used when lnd is starting up for the first time to fully
* initialize the daemon and its internal wallet. At the very least a wallet
* password must be provided. This will be used to encrypt sensitive material
* on disk.
*
* In the case of a recovery scenario, the user can also specify their aezeed
* mnemonic and passphrase. If set, then the daemon will use this prior state
* to initialize its internal wallet.
*
* Alternatively, this can be used along with the GenSeed RPC to obtain a
* seed, then present it to the user. Once it has been verified by the user,
* the seed can be fed into this RPC in order to commit the new wallet.
*
* @generated from protobuf rpc: InitWallet(lnrpc.InitWalletRequest) returns (lnrpc.InitWalletResponse);
*/
initWallet(input: InitWalletRequest, options?: RpcOptions): UnaryCall<InitWalletRequest, InitWalletResponse>;
/**
* lncli: `unlock`
* UnlockWallet is used at startup of lnd to provide a password to unlock
* the wallet database.
*
* @generated from protobuf rpc: UnlockWallet(lnrpc.UnlockWalletRequest) returns (lnrpc.UnlockWalletResponse);
*/
unlockWallet(input: UnlockWalletRequest, options?: RpcOptions): UnaryCall<UnlockWalletRequest, UnlockWalletResponse>;
/**
* lncli: `changepassword`
* ChangePassword changes the password of the encrypted wallet. This will
* automatically unlock the wallet database if successful.
*
* @generated from protobuf rpc: ChangePassword(lnrpc.ChangePasswordRequest) returns (lnrpc.ChangePasswordResponse);
*/
changePassword(input: ChangePasswordRequest, options?: RpcOptions): UnaryCall<ChangePasswordRequest, ChangePasswordResponse>;
}
//
// Comments in this file will be directly parsed into the API
// Documentation as descriptions of the associated method, message, or field.
// These descriptions should go right above the definition of the object, and
// can be in either block or // comment format.
//
// An RPC method can be matched to an lncli command by placing a line in the
// beginning of the description in exactly the following format:
// lncli: `methodname`
//
// Failure to specify the exact name of the command will cause documentation
// generation to fail.
//
// More information on how exactly the gRPC documentation is generated from
// this proto file can be found here:
// https://github.com/lightninglabs/lightning-api
/**
* WalletUnlocker is a service that is used to set up a wallet password for
* lnd at first startup, and unlock a previously set up wallet.
*
* @generated from protobuf service lnrpc.WalletUnlocker
*/
export class WalletUnlockerClient implements IWalletUnlockerClient, ServiceInfo {
typeName = WalletUnlocker.typeName;
methods = WalletUnlocker.methods;
options = WalletUnlocker.options;
constructor(private readonly _transport: RpcTransport) {
}
/**
*
* GenSeed is the first method that should be used to instantiate a new lnd
* instance. This method allows a caller to generate a new aezeed cipher seed
* given an optional passphrase. If provided, the passphrase will be necessary
* to decrypt the cipherseed to expose the internal wallet seed.
*
* Once the cipherseed is obtained and verified by the user, the InitWallet
* method should be used to commit the newly generated seed, and create the
* wallet.
*
* @generated from protobuf rpc: GenSeed(lnrpc.GenSeedRequest) returns (lnrpc.GenSeedResponse);
*/
genSeed(input: GenSeedRequest, options?: RpcOptions): UnaryCall<GenSeedRequest, GenSeedResponse> {
const method = this.methods[0], opt = this._transport.mergeOptions(options);
return stackIntercept<GenSeedRequest, GenSeedResponse>("unary", this._transport, method, opt, input);
}
/**
*
* InitWallet is used when lnd is starting up for the first time to fully
* initialize the daemon and its internal wallet. At the very least a wallet
* password must be provided. This will be used to encrypt sensitive material
* on disk.
*
* In the case of a recovery scenario, the user can also specify their aezeed
* mnemonic and passphrase. If set, then the daemon will use this prior state
* to initialize its internal wallet.
*
* Alternatively, this can be used along with the GenSeed RPC to obtain a
* seed, then present it to the user. Once it has been verified by the user,
* the seed can be fed into this RPC in order to commit the new wallet.
*
* @generated from protobuf rpc: InitWallet(lnrpc.InitWalletRequest) returns (lnrpc.InitWalletResponse);
*/
initWallet(input: InitWalletRequest, options?: RpcOptions): UnaryCall<InitWalletRequest, InitWalletResponse> {
const method = this.methods[1], opt = this._transport.mergeOptions(options);
return stackIntercept<InitWalletRequest, InitWalletResponse>("unary", this._transport, method, opt, input);
}
/**
* lncli: `unlock`
* UnlockWallet is used at startup of lnd to provide a password to unlock
* the wallet database.
*
* @generated from protobuf rpc: UnlockWallet(lnrpc.UnlockWalletRequest) returns (lnrpc.UnlockWalletResponse);
*/
unlockWallet(input: UnlockWalletRequest, options?: RpcOptions): UnaryCall<UnlockWalletRequest, UnlockWalletResponse> {
const method = this.methods[2], opt = this._transport.mergeOptions(options);
return stackIntercept<UnlockWalletRequest, UnlockWalletResponse>("unary", this._transport, method, opt, input);
}
/**
* lncli: `changepassword`
* ChangePassword changes the password of the encrypted wallet. This will
* automatically unlock the wallet database if successful.
*
* @generated from protobuf rpc: ChangePassword(lnrpc.ChangePasswordRequest) returns (lnrpc.ChangePasswordResponse);
*/
changePassword(input: ChangePasswordRequest, options?: RpcOptions): UnaryCall<ChangePasswordRequest, ChangePasswordResponse> {
const method = this.methods[3], opt = this._transport.mergeOptions(options);
return stackIntercept<ChangePasswordRequest, ChangePasswordResponse>("unary", this._transport, method, opt, input);
}
}

991
proto/lnd/walletunlocker.ts Normal file
View file

@ -0,0 +1,991 @@
// @generated by protobuf-ts 2.8.1
// @generated from protobuf file "walletunlocker.proto" (package "lnrpc", syntax proto3)
// tslint:disable
import { ServiceType } from "@protobuf-ts/runtime-rpc";
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
import type { IBinaryWriter } from "@protobuf-ts/runtime";
import { WireType } from "@protobuf-ts/runtime";
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
import type { IBinaryReader } from "@protobuf-ts/runtime";
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
import { ChanBackupSnapshot } from "./lightning.js";
/**
* @generated from protobuf message lnrpc.GenSeedRequest
*/
export interface GenSeedRequest {
/**
*
* aezeed_passphrase is an optional user provided passphrase that will be used
* to encrypt the generated aezeed cipher seed. When using REST, this field
* must be encoded as base64.
*
* @generated from protobuf field: bytes aezeed_passphrase = 1;
*/
aezeedPassphrase: Uint8Array;
/**
*
* seed_entropy is an optional 16-bytes generated via CSPRNG. If not
* specified, then a fresh set of randomness will be used to create the seed.
* When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes seed_entropy = 2;
*/
seedEntropy: Uint8Array;
}
/**
* @generated from protobuf message lnrpc.GenSeedResponse
*/
export interface GenSeedResponse {
/**
*
* cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
* cipher seed obtained by the user. This field is optional, as if not
* provided, then the daemon will generate a new cipher seed for the user.
* Otherwise, then the daemon will attempt to recover the wallet state linked
* to this cipher seed.
*
* @generated from protobuf field: repeated string cipher_seed_mnemonic = 1;
*/
cipherSeedMnemonic: string[];
/**
*
* enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
* cipher text before run through our mnemonic encoding scheme.
*
* @generated from protobuf field: bytes enciphered_seed = 2;
*/
encipheredSeed: Uint8Array;
}
/**
* @generated from protobuf message lnrpc.InitWalletRequest
*/
export interface InitWalletRequest {
/**
*
* wallet_password is the passphrase that should be used to encrypt the
* wallet. This MUST be at least 8 chars in length. After creation, this
* password is required to unlock the daemon. When using REST, this field
* must be encoded as base64.
*
* @generated from protobuf field: bytes wallet_password = 1;
*/
walletPassword: Uint8Array;
/**
*
* cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
* cipher seed obtained by the user. This may have been generated by the
* GenSeed method, or be an existing seed.
*
* @generated from protobuf field: repeated string cipher_seed_mnemonic = 2;
*/
cipherSeedMnemonic: string[];
/**
*
* aezeed_passphrase is an optional user provided passphrase that will be used
* to encrypt the generated aezeed cipher seed. When using REST, this field
* must be encoded as base64.
*
* @generated from protobuf field: bytes aezeed_passphrase = 3;
*/
aezeedPassphrase: Uint8Array;
/**
*
* recovery_window is an optional argument specifying the address lookahead
* when restoring a wallet seed. The recovery window applies to each
* individual branch of the BIP44 derivation paths. Supplying a recovery
* window of zero indicates that no addresses should be recovered, such after
* the first initialization of the wallet.
*
* @generated from protobuf field: int32 recovery_window = 4;
*/
recoveryWindow: number;
/**
*
* channel_backups is an optional argument that allows clients to recover the
* settled funds within a set of channels. This should be populated if the
* user was unable to close out all channels and sweep funds before partial or
* total data loss occurred. If specified, then after on-chain recovery of
* funds, lnd begin to carry out the data loss recovery protocol in order to
* recover the funds in each channel from a remote force closed transaction.
*
* @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 5;
*/
channelBackups?: ChanBackupSnapshot;
/**
*
* stateless_init is an optional argument instructing the daemon NOT to create
* any *.macaroon files in its filesystem. If this parameter is set, then the
* admin macaroon returned in the response MUST be stored by the caller of the
* RPC as otherwise all access to the daemon will be lost!
*
* @generated from protobuf field: bool stateless_init = 6;
*/
statelessInit: boolean;
/**
*
* extended_master_key is an alternative to specifying cipher_seed_mnemonic and
* aezeed_passphrase. Instead of deriving the master root key from the entropy
* of an aezeed cipher seed, the given extended master root key is used
* directly as the wallet's master key. This allows users to import/use a
* master key from another wallet. When doing so, lnd still uses its default
* SegWit only (BIP49/84) derivation paths and funds from custom/non-default
* derivation paths will not automatically appear in the on-chain wallet. Using
* an 'xprv' instead of an aezeed also has the disadvantage that the wallet's
* birthday is not known as that is an information that's only encoded in the
* aezeed, not the xprv. Therefore a birthday needs to be specified in
* extended_master_key_birthday_timestamp or a "safe" default value will be
* used.
*
* @generated from protobuf field: string extended_master_key = 7;
*/
extendedMasterKey: string;
/**
*
* extended_master_key_birthday_timestamp is the optional unix timestamp in
* seconds to use as the wallet's birthday when using an extended master key
* to restore the wallet. lnd will only start scanning for funds in blocks that
* are after the birthday which can speed up the process significantly. If the
* birthday is not known, this should be left at its default value of 0 in
* which case lnd will start scanning from the first SegWit block (481824 on
* mainnet).
*
* @generated from protobuf field: uint64 extended_master_key_birthday_timestamp = 8;
*/
extendedMasterKeyBirthdayTimestamp: bigint;
/**
*
* watch_only is the third option of initializing a wallet: by importing
* account xpubs only and therefore creating a watch-only wallet that does not
* contain any private keys. That means the wallet won't be able to sign for
* any of the keys and _needs_ to be run with a remote signer that has the
* corresponding private keys and can serve signing RPC requests.
*
* @generated from protobuf field: lnrpc.WatchOnly watch_only = 9;
*/
watchOnly?: WatchOnly;
/**
*
* macaroon_root_key is an optional 32 byte macaroon root key that can be
* provided when initializing the wallet rather than letting lnd generate one
* on its own.
*
* @generated from protobuf field: bytes macaroon_root_key = 10;
*/
macaroonRootKey: Uint8Array;
}
/**
* @generated from protobuf message lnrpc.InitWalletResponse
*/
export interface InitWalletResponse {
/**
*
* The binary serialized admin macaroon that can be used to access the daemon
* after creating the wallet. If the stateless_init parameter was set to true,
* this is the ONLY copy of the macaroon and MUST be stored safely by the
* caller. Otherwise a copy of this macaroon is also persisted on disk by the
* daemon, together with other macaroon files.
*
* @generated from protobuf field: bytes admin_macaroon = 1;
*/
adminMacaroon: Uint8Array;
}
/**
* @generated from protobuf message lnrpc.WatchOnly
*/
export interface WatchOnly {
/**
*
* The unix timestamp in seconds of when the master key was created. lnd will
* only start scanning for funds in blocks that are after the birthday which
* can speed up the process significantly. If the birthday is not known, this
* should be left at its default value of 0 in which case lnd will start
* scanning from the first SegWit block (481824 on mainnet).
*
* @generated from protobuf field: uint64 master_key_birthday_timestamp = 1;
*/
masterKeyBirthdayTimestamp: bigint;
/**
*
* The fingerprint of the root key (also known as the key with derivation path
* m/) from which the account public keys were derived from. This may be
* required by some hardware wallets for proper identification and signing. The
* bytes must be in big-endian order.
*
* @generated from protobuf field: bytes master_key_fingerprint = 2;
*/
masterKeyFingerprint: Uint8Array;
/**
*
* The list of accounts to import. There _must_ be an account for all of lnd's
* main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the
* coin type is always 0, even for testnet/regtest) and lnd's internal key
* scope (m/1017'/<coin_type>'/<account>'), where account is the key family as
* defined in `keychain/derivation.go` (currently indices 0 to 9).
*
* @generated from protobuf field: repeated lnrpc.WatchOnlyAccount accounts = 3;
*/
accounts: WatchOnlyAccount[];
}
/**
* @generated from protobuf message lnrpc.WatchOnlyAccount
*/
export interface WatchOnlyAccount {
/**
*
* Purpose is the first number in the derivation path, must be either 49, 84
* or 1017.
*
* @generated from protobuf field: uint32 purpose = 1;
*/
purpose: number;
/**
*
* Coin type is the second number in the derivation path, this is _always_ 0
* for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on
* testnet or regtest.
*
* @generated from protobuf field: uint32 coin_type = 2;
*/
coinType: number;
/**
*
* Account is the third number in the derivation path. For purposes 49 and 84
* at least the default account (index 0) needs to be created but optional
* additional accounts are allowed. For purpose 1017 there needs to be exactly
* one account for each of the key families defined in `keychain/derivation.go`
* (currently indices 0 to 9)
*
* @generated from protobuf field: uint32 account = 3;
*/
account: number;
/**
*
* The extended public key at depth 3 for the given account.
*
* @generated from protobuf field: string xpub = 4;
*/
xpub: string;
}
/**
* @generated from protobuf message lnrpc.UnlockWalletRequest
*/
export interface UnlockWalletRequest {
/**
*
* wallet_password should be the current valid passphrase for the daemon. This
* will be required to decrypt on-disk material that the daemon requires to
* function properly. When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes wallet_password = 1;
*/
walletPassword: Uint8Array;
/**
*
* recovery_window is an optional argument specifying the address lookahead
* when restoring a wallet seed. The recovery window applies to each
* individual branch of the BIP44 derivation paths. Supplying a recovery
* window of zero indicates that no addresses should be recovered, such after
* the first initialization of the wallet.
*
* @generated from protobuf field: int32 recovery_window = 2;
*/
recoveryWindow: number;
/**
*
* channel_backups is an optional argument that allows clients to recover the
* settled funds within a set of channels. This should be populated if the
* user was unable to close out all channels and sweep funds before partial or
* total data loss occurred. If specified, then after on-chain recovery of
* funds, lnd begin to carry out the data loss recovery protocol in order to
* recover the funds in each channel from a remote force closed transaction.
*
* @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 3;
*/
channelBackups?: ChanBackupSnapshot;
/**
*
* stateless_init is an optional argument instructing the daemon NOT to create
* any *.macaroon files in its file system.
*
* @generated from protobuf field: bool stateless_init = 4;
*/
statelessInit: boolean;
}
/**
* @generated from protobuf message lnrpc.UnlockWalletResponse
*/
export interface UnlockWalletResponse {
}
/**
* @generated from protobuf message lnrpc.ChangePasswordRequest
*/
export interface ChangePasswordRequest {
/**
*
* current_password should be the current valid passphrase used to unlock the
* daemon. When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes current_password = 1;
*/
currentPassword: Uint8Array;
/**
*
* new_password should be the new passphrase that will be needed to unlock the
* daemon. When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes new_password = 2;
*/
newPassword: Uint8Array;
/**
*
* stateless_init is an optional argument instructing the daemon NOT to create
* any *.macaroon files in its filesystem. If this parameter is set, then the
* admin macaroon returned in the response MUST be stored by the caller of the
* RPC as otherwise all access to the daemon will be lost!
*
* @generated from protobuf field: bool stateless_init = 3;
*/
statelessInit: boolean;
/**
*
* new_macaroon_root_key is an optional argument instructing the daemon to
* rotate the macaroon root key when set to true. This will invalidate all
* previously generated macaroons.
*
* @generated from protobuf field: bool new_macaroon_root_key = 4;
*/
newMacaroonRootKey: boolean;
}
/**
* @generated from protobuf message lnrpc.ChangePasswordResponse
*/
export interface ChangePasswordResponse {
/**
*
* The binary serialized admin macaroon that can be used to access the daemon
* after rotating the macaroon root key. If both the stateless_init and
* new_macaroon_root_key parameter were set to true, this is the ONLY copy of
* the macaroon that was created from the new root key and MUST be stored
* safely by the caller. Otherwise a copy of this macaroon is also persisted on
* disk by the daemon, together with other macaroon files.
*
* @generated from protobuf field: bytes admin_macaroon = 1;
*/
adminMacaroon: Uint8Array;
}
// @generated message type with reflection information, may provide speed optimized methods
class GenSeedRequest$Type extends MessageType<GenSeedRequest> {
constructor() {
super("lnrpc.GenSeedRequest", [
{ no: 1, name: "aezeed_passphrase", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 2, name: "seed_entropy", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<GenSeedRequest>): GenSeedRequest {
const message = { aezeedPassphrase: new Uint8Array(0), seedEntropy: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<GenSeedRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GenSeedRequest): GenSeedRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes aezeed_passphrase */ 1:
message.aezeedPassphrase = reader.bytes();
break;
case /* bytes seed_entropy */ 2:
message.seedEntropy = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GenSeedRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes aezeed_passphrase = 1; */
if (message.aezeedPassphrase.length)
writer.tag(1, WireType.LengthDelimited).bytes(message.aezeedPassphrase);
/* bytes seed_entropy = 2; */
if (message.seedEntropy.length)
writer.tag(2, WireType.LengthDelimited).bytes(message.seedEntropy);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.GenSeedRequest
*/
export const GenSeedRequest = new GenSeedRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GenSeedResponse$Type extends MessageType<GenSeedResponse> {
constructor() {
super("lnrpc.GenSeedResponse", [
{ no: 1, name: "cipher_seed_mnemonic", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "enciphered_seed", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<GenSeedResponse>): GenSeedResponse {
const message = { cipherSeedMnemonic: [], encipheredSeed: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<GenSeedResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GenSeedResponse): GenSeedResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* repeated string cipher_seed_mnemonic */ 1:
message.cipherSeedMnemonic.push(reader.string());
break;
case /* bytes enciphered_seed */ 2:
message.encipheredSeed = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GenSeedResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* repeated string cipher_seed_mnemonic = 1; */
for (let i = 0; i < message.cipherSeedMnemonic.length; i++)
writer.tag(1, WireType.LengthDelimited).string(message.cipherSeedMnemonic[i]);
/* bytes enciphered_seed = 2; */
if (message.encipheredSeed.length)
writer.tag(2, WireType.LengthDelimited).bytes(message.encipheredSeed);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.GenSeedResponse
*/
export const GenSeedResponse = new GenSeedResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class InitWalletRequest$Type extends MessageType<InitWalletRequest> {
constructor() {
super("lnrpc.InitWalletRequest", [
{ no: 1, name: "wallet_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 2, name: "cipher_seed_mnemonic", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
{ no: 3, name: "aezeed_passphrase", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 4, name: "recovery_window", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
{ no: 5, name: "channel_backups", kind: "message", T: () => ChanBackupSnapshot },
{ no: 6, name: "stateless_init", kind: "scalar", T: 8 /*ScalarType.BOOL*/ },
{ no: 7, name: "extended_master_key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 8, name: "extended_master_key_birthday_timestamp", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 9, name: "watch_only", kind: "message", T: () => WatchOnly },
{ no: 10, name: "macaroon_root_key", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<InitWalletRequest>): InitWalletRequest {
const message = { walletPassword: new Uint8Array(0), cipherSeedMnemonic: [], aezeedPassphrase: new Uint8Array(0), recoveryWindow: 0, statelessInit: false, extendedMasterKey: "", extendedMasterKeyBirthdayTimestamp: 0n, macaroonRootKey: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<InitWalletRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InitWalletRequest): InitWalletRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes wallet_password */ 1:
message.walletPassword = reader.bytes();
break;
case /* repeated string cipher_seed_mnemonic */ 2:
message.cipherSeedMnemonic.push(reader.string());
break;
case /* bytes aezeed_passphrase */ 3:
message.aezeedPassphrase = reader.bytes();
break;
case /* int32 recovery_window */ 4:
message.recoveryWindow = reader.int32();
break;
case /* lnrpc.ChanBackupSnapshot channel_backups */ 5:
message.channelBackups = ChanBackupSnapshot.internalBinaryRead(reader, reader.uint32(), options, message.channelBackups);
break;
case /* bool stateless_init */ 6:
message.statelessInit = reader.bool();
break;
case /* string extended_master_key */ 7:
message.extendedMasterKey = reader.string();
break;
case /* uint64 extended_master_key_birthday_timestamp */ 8:
message.extendedMasterKeyBirthdayTimestamp = reader.uint64().toBigInt();
break;
case /* lnrpc.WatchOnly watch_only */ 9:
message.watchOnly = WatchOnly.internalBinaryRead(reader, reader.uint32(), options, message.watchOnly);
break;
case /* bytes macaroon_root_key */ 10:
message.macaroonRootKey = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: InitWalletRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes wallet_password = 1; */
if (message.walletPassword.length)
writer.tag(1, WireType.LengthDelimited).bytes(message.walletPassword);
/* repeated string cipher_seed_mnemonic = 2; */
for (let i = 0; i < message.cipherSeedMnemonic.length; i++)
writer.tag(2, WireType.LengthDelimited).string(message.cipherSeedMnemonic[i]);
/* bytes aezeed_passphrase = 3; */
if (message.aezeedPassphrase.length)
writer.tag(3, WireType.LengthDelimited).bytes(message.aezeedPassphrase);
/* int32 recovery_window = 4; */
if (message.recoveryWindow !== 0)
writer.tag(4, WireType.Varint).int32(message.recoveryWindow);
/* lnrpc.ChanBackupSnapshot channel_backups = 5; */
if (message.channelBackups)
ChanBackupSnapshot.internalBinaryWrite(message.channelBackups, writer.tag(5, WireType.LengthDelimited).fork(), options).join();
/* bool stateless_init = 6; */
if (message.statelessInit !== false)
writer.tag(6, WireType.Varint).bool(message.statelessInit);
/* string extended_master_key = 7; */
if (message.extendedMasterKey !== "")
writer.tag(7, WireType.LengthDelimited).string(message.extendedMasterKey);
/* uint64 extended_master_key_birthday_timestamp = 8; */
if (message.extendedMasterKeyBirthdayTimestamp !== 0n)
writer.tag(8, WireType.Varint).uint64(message.extendedMasterKeyBirthdayTimestamp);
/* lnrpc.WatchOnly watch_only = 9; */
if (message.watchOnly)
WatchOnly.internalBinaryWrite(message.watchOnly, writer.tag(9, WireType.LengthDelimited).fork(), options).join();
/* bytes macaroon_root_key = 10; */
if (message.macaroonRootKey.length)
writer.tag(10, WireType.LengthDelimited).bytes(message.macaroonRootKey);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.InitWalletRequest
*/
export const InitWalletRequest = new InitWalletRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class InitWalletResponse$Type extends MessageType<InitWalletResponse> {
constructor() {
super("lnrpc.InitWalletResponse", [
{ no: 1, name: "admin_macaroon", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<InitWalletResponse>): InitWalletResponse {
const message = { adminMacaroon: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<InitWalletResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InitWalletResponse): InitWalletResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes admin_macaroon */ 1:
message.adminMacaroon = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: InitWalletResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes admin_macaroon = 1; */
if (message.adminMacaroon.length)
writer.tag(1, WireType.LengthDelimited).bytes(message.adminMacaroon);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.InitWalletResponse
*/
export const InitWalletResponse = new InitWalletResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class WatchOnly$Type extends MessageType<WatchOnly> {
constructor() {
super("lnrpc.WatchOnly", [
{ no: 1, name: "master_key_birthday_timestamp", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 2, name: "master_key_fingerprint", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 3, name: "accounts", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => WatchOnlyAccount }
]);
}
create(value?: PartialMessage<WatchOnly>): WatchOnly {
const message = { masterKeyBirthdayTimestamp: 0n, masterKeyFingerprint: new Uint8Array(0), accounts: [] };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<WatchOnly>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: WatchOnly): WatchOnly {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint64 master_key_birthday_timestamp */ 1:
message.masterKeyBirthdayTimestamp = reader.uint64().toBigInt();
break;
case /* bytes master_key_fingerprint */ 2:
message.masterKeyFingerprint = reader.bytes();
break;
case /* repeated lnrpc.WatchOnlyAccount accounts */ 3:
message.accounts.push(WatchOnlyAccount.internalBinaryRead(reader, reader.uint32(), options));
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: WatchOnly, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint64 master_key_birthday_timestamp = 1; */
if (message.masterKeyBirthdayTimestamp !== 0n)
writer.tag(1, WireType.Varint).uint64(message.masterKeyBirthdayTimestamp);
/* bytes master_key_fingerprint = 2; */
if (message.masterKeyFingerprint.length)
writer.tag(2, WireType.LengthDelimited).bytes(message.masterKeyFingerprint);
/* repeated lnrpc.WatchOnlyAccount accounts = 3; */
for (let i = 0; i < message.accounts.length; i++)
WatchOnlyAccount.internalBinaryWrite(message.accounts[i], writer.tag(3, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.WatchOnly
*/
export const WatchOnly = new WatchOnly$Type();
// @generated message type with reflection information, may provide speed optimized methods
class WatchOnlyAccount$Type extends MessageType<WatchOnlyAccount> {
constructor() {
super("lnrpc.WatchOnlyAccount", [
{ no: 1, name: "purpose", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 2, name: "coin_type", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 3, name: "account", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 4, name: "xpub", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<WatchOnlyAccount>): WatchOnlyAccount {
const message = { purpose: 0, coinType: 0, account: 0, xpub: "" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<WatchOnlyAccount>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: WatchOnlyAccount): WatchOnlyAccount {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 purpose */ 1:
message.purpose = reader.uint32();
break;
case /* uint32 coin_type */ 2:
message.coinType = reader.uint32();
break;
case /* uint32 account */ 3:
message.account = reader.uint32();
break;
case /* string xpub */ 4:
message.xpub = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: WatchOnlyAccount, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 purpose = 1; */
if (message.purpose !== 0)
writer.tag(1, WireType.Varint).uint32(message.purpose);
/* uint32 coin_type = 2; */
if (message.coinType !== 0)
writer.tag(2, WireType.Varint).uint32(message.coinType);
/* uint32 account = 3; */
if (message.account !== 0)
writer.tag(3, WireType.Varint).uint32(message.account);
/* string xpub = 4; */
if (message.xpub !== "")
writer.tag(4, WireType.LengthDelimited).string(message.xpub);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.WatchOnlyAccount
*/
export const WatchOnlyAccount = new WatchOnlyAccount$Type();
// @generated message type with reflection information, may provide speed optimized methods
class UnlockWalletRequest$Type extends MessageType<UnlockWalletRequest> {
constructor() {
super("lnrpc.UnlockWalletRequest", [
{ no: 1, name: "wallet_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 2, name: "recovery_window", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
{ no: 3, name: "channel_backups", kind: "message", T: () => ChanBackupSnapshot },
{ no: 4, name: "stateless_init", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
]);
}
create(value?: PartialMessage<UnlockWalletRequest>): UnlockWalletRequest {
const message = { walletPassword: new Uint8Array(0), recoveryWindow: 0, statelessInit: false };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<UnlockWalletRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UnlockWalletRequest): UnlockWalletRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes wallet_password */ 1:
message.walletPassword = reader.bytes();
break;
case /* int32 recovery_window */ 2:
message.recoveryWindow = reader.int32();
break;
case /* lnrpc.ChanBackupSnapshot channel_backups */ 3:
message.channelBackups = ChanBackupSnapshot.internalBinaryRead(reader, reader.uint32(), options, message.channelBackups);
break;
case /* bool stateless_init */ 4:
message.statelessInit = reader.bool();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: UnlockWalletRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes wallet_password = 1; */
if (message.walletPassword.length)
writer.tag(1, WireType.LengthDelimited).bytes(message.walletPassword);
/* int32 recovery_window = 2; */
if (message.recoveryWindow !== 0)
writer.tag(2, WireType.Varint).int32(message.recoveryWindow);
/* lnrpc.ChanBackupSnapshot channel_backups = 3; */
if (message.channelBackups)
ChanBackupSnapshot.internalBinaryWrite(message.channelBackups, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
/* bool stateless_init = 4; */
if (message.statelessInit !== false)
writer.tag(4, WireType.Varint).bool(message.statelessInit);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.UnlockWalletRequest
*/
export const UnlockWalletRequest = new UnlockWalletRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class UnlockWalletResponse$Type extends MessageType<UnlockWalletResponse> {
constructor() {
super("lnrpc.UnlockWalletResponse", []);
}
create(value?: PartialMessage<UnlockWalletResponse>): UnlockWalletResponse {
const message = {};
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<UnlockWalletResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UnlockWalletResponse): UnlockWalletResponse {
return target ?? this.create();
}
internalBinaryWrite(message: UnlockWalletResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.UnlockWalletResponse
*/
export const UnlockWalletResponse = new UnlockWalletResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ChangePasswordRequest$Type extends MessageType<ChangePasswordRequest> {
constructor() {
super("lnrpc.ChangePasswordRequest", [
{ no: 1, name: "current_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 2, name: "new_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 3, name: "stateless_init", kind: "scalar", T: 8 /*ScalarType.BOOL*/ },
{ no: 4, name: "new_macaroon_root_key", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
]);
}
create(value?: PartialMessage<ChangePasswordRequest>): ChangePasswordRequest {
const message = { currentPassword: new Uint8Array(0), newPassword: new Uint8Array(0), statelessInit: false, newMacaroonRootKey: false };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<ChangePasswordRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChangePasswordRequest): ChangePasswordRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes current_password */ 1:
message.currentPassword = reader.bytes();
break;
case /* bytes new_password */ 2:
message.newPassword = reader.bytes();
break;
case /* bool stateless_init */ 3:
message.statelessInit = reader.bool();
break;
case /* bool new_macaroon_root_key */ 4:
message.newMacaroonRootKey = reader.bool();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ChangePasswordRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes current_password = 1; */
if (message.currentPassword.length)
writer.tag(1, WireType.LengthDelimited).bytes(message.currentPassword);
/* bytes new_password = 2; */
if (message.newPassword.length)
writer.tag(2, WireType.LengthDelimited).bytes(message.newPassword);
/* bool stateless_init = 3; */
if (message.statelessInit !== false)
writer.tag(3, WireType.Varint).bool(message.statelessInit);
/* bool new_macaroon_root_key = 4; */
if (message.newMacaroonRootKey !== false)
writer.tag(4, WireType.Varint).bool(message.newMacaroonRootKey);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.ChangePasswordRequest
*/
export const ChangePasswordRequest = new ChangePasswordRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ChangePasswordResponse$Type extends MessageType<ChangePasswordResponse> {
constructor() {
super("lnrpc.ChangePasswordResponse", [
{ no: 1, name: "admin_macaroon", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<ChangePasswordResponse>): ChangePasswordResponse {
const message = { adminMacaroon: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<ChangePasswordResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChangePasswordResponse): ChangePasswordResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes admin_macaroon */ 1:
message.adminMacaroon = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ChangePasswordResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes admin_macaroon = 1; */
if (message.adminMacaroon.length)
writer.tag(1, WireType.LengthDelimited).bytes(message.adminMacaroon);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message lnrpc.ChangePasswordResponse
*/
export const ChangePasswordResponse = new ChangePasswordResponse$Type();
/**
* @generated ServiceType for protobuf service lnrpc.WalletUnlocker
*/
export const WalletUnlocker = new ServiceType("lnrpc.WalletUnlocker", [
{ name: "GenSeed", options: {}, I: GenSeedRequest, O: GenSeedResponse },
{ name: "InitWallet", options: {}, I: InitWalletRequest, O: InitWalletResponse },
{ name: "UnlockWallet", options: {}, I: UnlockWalletRequest, O: UnlockWalletResponse },
{ name: "ChangePassword", options: {}, I: ChangePasswordRequest, O: ChangePasswordResponse }
]);

View file

@ -0,0 +1,338 @@
syntax = "proto3";
package lnrpc;
import "lightning.proto";
option go_package = "github.com/lightningnetwork/lnd/lnrpc";
/*
* Comments in this file will be directly parsed into the API
* Documentation as descriptions of the associated method, message, or field.
* These descriptions should go right above the definition of the object, and
* can be in either block or // comment format.
*
* An RPC method can be matched to an lncli command by placing a line in the
* beginning of the description in exactly the following format:
* lncli: `methodname`
*
* Failure to specify the exact name of the command will cause documentation
* generation to fail.
*
* More information on how exactly the gRPC documentation is generated from
* this proto file can be found here:
* https://github.com/lightninglabs/lightning-api
*/
// WalletUnlocker is a service that is used to set up a wallet password for
// lnd at first startup, and unlock a previously set up wallet.
service WalletUnlocker {
/*
GenSeed is the first method that should be used to instantiate a new lnd
instance. This method allows a caller to generate a new aezeed cipher seed
given an optional passphrase. If provided, the passphrase will be necessary
to decrypt the cipherseed to expose the internal wallet seed.
Once the cipherseed is obtained and verified by the user, the InitWallet
method should be used to commit the newly generated seed, and create the
wallet.
*/
rpc GenSeed (GenSeedRequest) returns (GenSeedResponse);
/*
InitWallet is used when lnd is starting up for the first time to fully
initialize the daemon and its internal wallet. At the very least a wallet
password must be provided. This will be used to encrypt sensitive material
on disk.
In the case of a recovery scenario, the user can also specify their aezeed
mnemonic and passphrase. If set, then the daemon will use this prior state
to initialize its internal wallet.
Alternatively, this can be used along with the GenSeed RPC to obtain a
seed, then present it to the user. Once it has been verified by the user,
the seed can be fed into this RPC in order to commit the new wallet.
*/
rpc InitWallet (InitWalletRequest) returns (InitWalletResponse);
/* lncli: `unlock`
UnlockWallet is used at startup of lnd to provide a password to unlock
the wallet database.
*/
rpc UnlockWallet (UnlockWalletRequest) returns (UnlockWalletResponse);
/* lncli: `changepassword`
ChangePassword changes the password of the encrypted wallet. This will
automatically unlock the wallet database if successful.
*/
rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordResponse);
}
message GenSeedRequest {
/*
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed. When using REST, this field
must be encoded as base64.
*/
bytes aezeed_passphrase = 1;
/*
seed_entropy is an optional 16-bytes generated via CSPRNG. If not
specified, then a fresh set of randomness will be used to create the seed.
When using REST, this field must be encoded as base64.
*/
bytes seed_entropy = 2;
}
message GenSeedResponse {
/*
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This field is optional, as if not
provided, then the daemon will generate a new cipher seed for the user.
Otherwise, then the daemon will attempt to recover the wallet state linked
to this cipher seed.
*/
repeated string cipher_seed_mnemonic = 1;
/*
enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
cipher text before run through our mnemonic encoding scheme.
*/
bytes enciphered_seed = 2;
}
message InitWalletRequest {
/*
wallet_password is the passphrase that should be used to encrypt the
wallet. This MUST be at least 8 chars in length. After creation, this
password is required to unlock the daemon. When using REST, this field
must be encoded as base64.
*/
bytes wallet_password = 1;
/*
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This may have been generated by the
GenSeed method, or be an existing seed.
*/
repeated string cipher_seed_mnemonic = 2;
/*
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed. When using REST, this field
must be encoded as base64.
*/
bytes aezeed_passphrase = 3;
/*
recovery_window is an optional argument specifying the address lookahead
when restoring a wallet seed. The recovery window applies to each
individual branch of the BIP44 derivation paths. Supplying a recovery
window of zero indicates that no addresses should be recovered, such after
the first initialization of the wallet.
*/
int32 recovery_window = 4;
/*
channel_backups is an optional argument that allows clients to recover the
settled funds within a set of channels. This should be populated if the
user was unable to close out all channels and sweep funds before partial or
total data loss occurred. If specified, then after on-chain recovery of
funds, lnd begin to carry out the data loss recovery protocol in order to
recover the funds in each channel from a remote force closed transaction.
*/
ChanBackupSnapshot channel_backups = 5;
/*
stateless_init is an optional argument instructing the daemon NOT to create
any *.macaroon files in its filesystem. If this parameter is set, then the
admin macaroon returned in the response MUST be stored by the caller of the
RPC as otherwise all access to the daemon will be lost!
*/
bool stateless_init = 6;
/*
extended_master_key is an alternative to specifying cipher_seed_mnemonic and
aezeed_passphrase. Instead of deriving the master root key from the entropy
of an aezeed cipher seed, the given extended master root key is used
directly as the wallet's master key. This allows users to import/use a
master key from another wallet. When doing so, lnd still uses its default
SegWit only (BIP49/84) derivation paths and funds from custom/non-default
derivation paths will not automatically appear in the on-chain wallet. Using
an 'xprv' instead of an aezeed also has the disadvantage that the wallet's
birthday is not known as that is an information that's only encoded in the
aezeed, not the xprv. Therefore a birthday needs to be specified in
extended_master_key_birthday_timestamp or a "safe" default value will be
used.
*/
string extended_master_key = 7;
/*
extended_master_key_birthday_timestamp is the optional unix timestamp in
seconds to use as the wallet's birthday when using an extended master key
to restore the wallet. lnd will only start scanning for funds in blocks that
are after the birthday which can speed up the process significantly. If the
birthday is not known, this should be left at its default value of 0 in
which case lnd will start scanning from the first SegWit block (481824 on
mainnet).
*/
uint64 extended_master_key_birthday_timestamp = 8;
/*
watch_only is the third option of initializing a wallet: by importing
account xpubs only and therefore creating a watch-only wallet that does not
contain any private keys. That means the wallet won't be able to sign for
any of the keys and _needs_ to be run with a remote signer that has the
corresponding private keys and can serve signing RPC requests.
*/
WatchOnly watch_only = 9;
/*
macaroon_root_key is an optional 32 byte macaroon root key that can be
provided when initializing the wallet rather than letting lnd generate one
on its own.
*/
bytes macaroon_root_key = 10;
}
message InitWalletResponse {
/*
The binary serialized admin macaroon that can be used to access the daemon
after creating the wallet. If the stateless_init parameter was set to true,
this is the ONLY copy of the macaroon and MUST be stored safely by the
caller. Otherwise a copy of this macaroon is also persisted on disk by the
daemon, together with other macaroon files.
*/
bytes admin_macaroon = 1;
}
message WatchOnly {
/*
The unix timestamp in seconds of when the master key was created. lnd will
only start scanning for funds in blocks that are after the birthday which
can speed up the process significantly. If the birthday is not known, this
should be left at its default value of 0 in which case lnd will start
scanning from the first SegWit block (481824 on mainnet).
*/
uint64 master_key_birthday_timestamp = 1;
/*
The fingerprint of the root key (also known as the key with derivation path
m/) from which the account public keys were derived from. This may be
required by some hardware wallets for proper identification and signing. The
bytes must be in big-endian order.
*/
bytes master_key_fingerprint = 2;
/*
The list of accounts to import. There _must_ be an account for all of lnd's
main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the
coin type is always 0, even for testnet/regtest) and lnd's internal key
scope (m/1017'/<coin_type>'/<account>'), where account is the key family as
defined in `keychain/derivation.go` (currently indices 0 to 9).
*/
repeated WatchOnlyAccount accounts = 3;
}
message WatchOnlyAccount {
/*
Purpose is the first number in the derivation path, must be either 49, 84
or 1017.
*/
uint32 purpose = 1;
/*
Coin type is the second number in the derivation path, this is _always_ 0
for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on
testnet or regtest.
*/
uint32 coin_type = 2;
/*
Account is the third number in the derivation path. For purposes 49 and 84
at least the default account (index 0) needs to be created but optional
additional accounts are allowed. For purpose 1017 there needs to be exactly
one account for each of the key families defined in `keychain/derivation.go`
(currently indices 0 to 9)
*/
uint32 account = 3;
/*
The extended public key at depth 3 for the given account.
*/
string xpub = 4;
}
message UnlockWalletRequest {
/*
wallet_password should be the current valid passphrase for the daemon. This
will be required to decrypt on-disk material that the daemon requires to
function properly. When using REST, this field must be encoded as base64.
*/
bytes wallet_password = 1;
/*
recovery_window is an optional argument specifying the address lookahead
when restoring a wallet seed. The recovery window applies to each
individual branch of the BIP44 derivation paths. Supplying a recovery
window of zero indicates that no addresses should be recovered, such after
the first initialization of the wallet.
*/
int32 recovery_window = 2;
/*
channel_backups is an optional argument that allows clients to recover the
settled funds within a set of channels. This should be populated if the
user was unable to close out all channels and sweep funds before partial or
total data loss occurred. If specified, then after on-chain recovery of
funds, lnd begin to carry out the data loss recovery protocol in order to
recover the funds in each channel from a remote force closed transaction.
*/
ChanBackupSnapshot channel_backups = 3;
/*
stateless_init is an optional argument instructing the daemon NOT to create
any *.macaroon files in its file system.
*/
bool stateless_init = 4;
}
message UnlockWalletResponse {
}
message ChangePasswordRequest {
/*
current_password should be the current valid passphrase used to unlock the
daemon. When using REST, this field must be encoded as base64.
*/
bytes current_password = 1;
/*
new_password should be the new passphrase that will be needed to unlock the
daemon. When using REST, this field must be encoded as base64.
*/
bytes new_password = 2;
/*
stateless_init is an optional argument instructing the daemon NOT to create
any *.macaroon files in its filesystem. If this parameter is set, then the
admin macaroon returned in the response MUST be stored by the caller of the
RPC as otherwise all access to the daemon will be lost!
*/
bool stateless_init = 3;
/*
new_macaroon_root_key is an optional argument instructing the daemon to
rotate the macaroon root key when set to true. This will invalidate all
previously generated macaroons.
*/
bool new_macaroon_root_key = 4;
}
message ChangePasswordResponse {
/*
The binary serialized admin macaroon that can be used to access the daemon
after rotating the macaroon root key. If both the stateless_init and
new_macaroon_root_key parameter were set to true, this is the ONLY copy of
the macaroon that was created from the new root key and MUST be stored
safely by the caller. Otherwise a copy of this macaroon is also persisted on
disk by the daemon, together with other macaroon files.
*/
bytes admin_macaroon = 1;
}

View file

@ -0,0 +1,15 @@
import { InitWalletRequest } from "../../../proto/lnd/walletunlocker";
export const InitWalletReq = (secret: Buffer, cipherSeedMnemonic: string[]): InitWalletRequest => ({
aezeedPassphrase: Buffer.alloc(0),
walletPassword: secret,
cipherSeedMnemonic,
extendedMasterKey: "",
extendedMasterKeyBirthdayTimestamp: 0n,
macaroonRootKey: Buffer.alloc(0),
recoveryWindow: 0,
statelessInit: false,
channelBackups: undefined,
watchOnly: undefined,
})

View file

@ -1,5 +1,6 @@
import { PubLogger, getLogger } from "../helpers/logger.js" import { PubLogger, getLogger } from "../helpers/logger.js"
import { LiquidityProvider } from "../lnd/liquidityProvider.js" import { LiquidityProvider } from "../lnd/liquidityProvider.js"
import { Unlocker } from "./unlocker.js"
import Storage from "../storage/index.js" import Storage from "../storage/index.js"
import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js" import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js"
import Main from "./index.js" import Main from "./index.js"
@ -17,6 +18,8 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
if (manualMigration) { if (manualMigration) {
return return
} }
const unlocker = new Unlocker(mainSettings, storageManager)
await unlocker.Unlock()
const mainHandler = new Main(mainSettings, storageManager) const mainHandler = new Main(mainSettings, storageManager)
await mainHandler.lnd.Warmup() await mainHandler.lnd.Warmup()

View file

@ -13,6 +13,7 @@ export type MainSettings = {
watchDogSettings: WatchdogSettings, watchDogSettings: WatchdogSettings,
liquiditySettings: LiquiditySettings, liquiditySettings: LiquiditySettings,
jwtSecret: string jwtSecret: string
walletSecretPath: string
incomingTxFee: number incomingTxFee: number
outgoingTxFee: number outgoingTxFee: number
incomingAppInvoiceFee: number incomingAppInvoiceFee: number
@ -27,11 +28,13 @@ export type MainSettings = {
skipSanityCheck: boolean skipSanityCheck: boolean
disableExternalPayments: boolean disableExternalPayments: boolean
} }
export type BitcoinCoreSettings = { export type BitcoinCoreSettings = {
port: number port: number
user: string user: string
pass: string pass: string
} }
export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings } export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings }
export const LoadMainSettingsFromEnv = (): MainSettings => { export const LoadMainSettingsFromEnv = (): MainSettings => {
const storageSettings = LoadStorageSettingsFromEnv() const storageSettings = LoadStorageSettingsFromEnv()
@ -41,6 +44,7 @@ export const LoadMainSettingsFromEnv = (): MainSettings => {
storageSettings: storageSettings, storageSettings: storageSettings,
liquiditySettings: LoadLiquiditySettingsFromEnv(), liquiditySettings: LoadLiquiditySettingsFromEnv(),
jwtSecret: loadJwtSecret(storageSettings.dataDir), jwtSecret: loadJwtSecret(storageSettings.dataDir),
walletSecretPath: process.env.WALLET_SECRET_PATH || getDataPath(storageSettings.dataDir, ".wallet_secret"),
incomingTxFee: EnvCanBeInteger("INCOMING_CHAIN_FEE_ROOT_BPS", 0) / 10000, incomingTxFee: EnvCanBeInteger("INCOMING_CHAIN_FEE_ROOT_BPS", 0) / 10000,
outgoingTxFee: EnvCanBeInteger("OUTGOING_CHAIN_FEE_ROOT_BPS", 60) / 10000, outgoingTxFee: EnvCanBeInteger("OUTGOING_CHAIN_FEE_ROOT_BPS", 60) / 10000,
incomingAppInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS", 0) / 10000, incomingAppInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS", 0) / 10000,
@ -101,7 +105,7 @@ export const loadJwtSecret = (dataDir: string): string => {
return secret return secret
} }
log("JWT_SECRET not set in env, checking .jwt_secret file") log("JWT_SECRET not set in env, checking .jwt_secret file")
const secretPath = dataDir !== "" ? `${dataDir}/.jwt_secret` : ".jwt_secret" const secretPath = getDataPath(dataDir, ".jwt_secret")
try { try {
const fileContent = fs.readFileSync(secretPath, "utf-8") const fileContent = fs.readFileSync(secretPath, "utf-8")
return fileContent.trim() return fileContent.trim()
@ -112,3 +116,7 @@ export const loadJwtSecret = (dataDir: string): string => {
return secret return secret
} }
} }
export const getDataPath = (dataDir: string, dataPath: string) => {
return dataDir !== "" ? `${dataDir}/${dataPath}` : dataPath
}

View file

@ -0,0 +1,210 @@
import fs from 'fs'
import crypto from 'crypto'
import { GrpcTransport } from "@protobuf-ts/grpc-transport";
import { credentials, Metadata } from '@grpc/grpc-js'
import { getLogger } from '../helpers/logger.js';
import { WalletUnlockerClient } from '../../../proto/lnd/walletunlocker.client.js';
import { MainSettings } from '../main/settings.js';
import { InitWalletReq } from '../lnd/initWalletReq.js';
import Storage from '../storage/index.js'
import { LightningClient } from '../../../proto/lnd/lightning.client.js';
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
export class Unlocker {
settings: MainSettings
storage: Storage
abortController = new AbortController()
log = getLogger({ component: "unlocker" })
constructor(settings: MainSettings, storage: Storage) {
this.settings = settings
this.storage = storage
}
Stop = () => {
this.abortController.abort()
}
Unlock = async () => {
const macroonPath = this.settings.lndSettings.mainNode.lndMacaroonPath
const certPath = this.settings.lndSettings.mainNode.lndCertPath
let macaroon = ""
let lndCert: Buffer
try {
lndCert = fs.readFileSync(certPath)
} catch (err: any) {
throw new Error("failed to access lnd cert, make sure to set LND_CERT_PATH in .env, that the path is correct, and that lnd is running")
}
try {
macaroon = fs.readFileSync(macroonPath).toString('hex');
} catch (err: any) {
if (err.code !== 'ENOENT') {
throw err
}
}
const { ln, pub } = macaroon === "" ? await this.InitFlow(lndCert) : await this.UnlockFlow(lndCert, macaroon)
this.subscribeToBackups(ln, pub)
}
UnlockFlow = async (lndCert: Buffer, macaroon: string) => {
const ln = this.GetLightningClient(lndCert, macaroon)
const info = await this.GetLndInfo(ln)
if (info.ok) {
this.log("the wallet is already unlocked with pub:", info.pub)
return { ln, pub: info.pub }
}
if (info.failure !== 'locked') {
throw new Error("failed to get lnd info for reason: " + info.failure)
}
this.log("wallet is locked, unlocking...")
const secret = this.GetWalletSecret(false)
if (!secret) {
throw new Error("wallet secret not found to unlock wallet")
}
const unlocker = this.GetUnlockerClient(lndCert)
await unlocker.unlockWallet({ walletPassword: Buffer.from(secret, 'hex'), recoveryWindow: 0, statelessInit: false, channelBackups: undefined }, DeadLineMetadata())
const infoAfter = await this.GetLndInfo(ln)
if (!infoAfter.ok) {
throw new Error("failed to init lnd wallet " + infoAfter.failure)
}
this.log("unlocked wallet with pub:", infoAfter.pub)
return { ln, pub: infoAfter.pub }
}
InitFlow = async (lndCert: Buffer) => {
this.log("macaroon not found, creating wallet...")
const unlocker = this.GetUnlockerClient(lndCert)
const entropy = crypto.randomBytes(16)
const seedRes = await unlocker.genSeed({
aezeedPassphrase: Buffer.alloc(0),
seedEntropy: entropy
}, DeadLineMetadata())
console.log(seedRes.response.cipherSeedMnemonic)
console.log(seedRes.response.encipheredSeed)
this.log("seed created, encrypting and saving...")
const { encryptedData, secret } = this.EncryptWalletSeed(seedRes.response.cipherSeedMnemonic)
const req = InitWalletReq(secret, seedRes.response.cipherSeedMnemonic)
const initRes = await unlocker.initWallet(req, DeadLineMetadata(60 * 1000))
const adminMacaroon = Buffer.from(initRes.response.adminMacaroon).toString('hex')
const ln = this.GetLightningClient(lndCert, adminMacaroon)
const info = await this.GetLndInfo(ln)
if (!info.ok) {
throw new Error("failed to init lnd wallet " + info.failure)
}
await this.storage.liquidityStorage.SaveNodeSeed(info.pub, JSON.stringify(encryptedData))
this.log("created wallet with pub:", info.pub)
return { ln, pub: info.pub }
}
GetLndInfo = async (ln: LightningClient): Promise<{ ok: false, failure: 'locked' | 'unknown' } | { ok: true, pub: string }> => {
while (true) {
try {
const info = await ln.getInfo({}, DeadLineMetadata())
return { ok: true, pub: info.response.identityPubkey }
} catch (err: any) {
if (err.message === '2 UNKNOWN: wallet locked, unlock it to enable full RPC access') {
this.log("wallet is locked")
return { ok: false, failure: 'locked' }
} else if (err.message === '2 UNKNOWN: the RPC server is in the process of starting up, but not yet ready to accept calls') {
this.log("lnd is not ready yet, waiting...")
await new Promise((res) => setTimeout(res, 1000))
} else {
this.log("failed to get lnd info", err.message)
return { ok: false, failure: 'unknown' }
}
}
}
}
EncryptWalletSeed = (seed: string[]) => {
return this.encrypt(seed.join('+'))
}
DecryptWalletSeed = (data: { iv: string, encrypted: string }) => {
return this.decrypt(data).split('+')
}
EncryptBackup = (backup: Buffer) => {
return this.encrypt(backup.toString('hex'))
}
DecryptBackup = (data: { iv: string, encrypted: string }) => {
return Buffer.from(this.decrypt(data), 'hex')
}
encrypt = (text: string) => {
const sec = this.GetWalletSecret(true)
const secret = Buffer.from(sec, 'hex')
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes-256-cbc', secret, iv)
const rawData = Buffer.from(text, 'utf-8')
const cyData = cipher.update(rawData)
const encrypted = Buffer.concat([cyData, cipher.final()])
const encryptedData = { iv: iv.toString('hex'), encrypted: encrypted.toString('hex') }
return { encryptedData, secret }
}
decrypt = (data: { iv: string, encrypted: string }) => {
const sec = this.GetWalletSecret(false)
if (!sec) {
throw new Error("wallet secret not found to decrypt seed")
}
const secret = Buffer.from(sec, 'hex')
const iv = Buffer.from(data.iv, 'hex')
const encrypted = Buffer.from(data.encrypted, 'hex')
const decipher = crypto.createDecipheriv('aes-256-cbc', secret, iv)
const decrypted = decipher.update(encrypted)
const raw = Buffer.concat([decrypted, decipher.final()])
return raw.toString('utf-8')
}
GetWalletSecret = (create: boolean) => {
const path = this.settings.walletSecretPath
let secret = ""
try {
secret = fs.readFileSync(path, 'utf-8')
} catch {
this.log("the wallet secret file was not found")
}
if (secret === "" && create) {
secret = crypto.randomBytes(32).toString('hex')
fs.writeFileSync(path, secret)
}
return secret
}
subscribeToBackups = async (ln: LightningClient, pub: string) => {
this.log("subscribing to channel backups for: ", pub)
const stream = ln.subscribeChannelBackups({}, { abort: this.abortController.signal })
stream.responses.onMessage((msg) => {
if (msg.multiChanBackup) {
this.log("received backup, saving")
const { encryptedData } = this.EncryptBackup(Buffer.from(msg.multiChanBackup.multiChanBackup))
this.storage.liquidityStorage.SaveNodeBackup(pub, JSON.stringify(encryptedData))
}
})
}
GetUnlockerClient = (cert: Buffer) => {
const host = this.settings.lndSettings.mainNode.lndAddr
const channelCredentials = credentials.createSsl(cert)
const transport = new GrpcTransport({ host, channelCredentials })
const client = new WalletUnlockerClient(transport)
return client
}
GetLightningClient = (cert: Buffer, macaroon: string) => {
const host = this.settings.lndSettings.mainNode.lndAddr
const sslCreds = credentials.createSsl(cert)
const macaroonCreds = credentials.createFromMetadataGenerator(
function (args: any, callback: any) {
let metadata = new Metadata();
metadata.add('macaroon', macaroon);
callback(null, metadata);
},
);
const channelCredentials = credentials.combineChannelCredentials(
sslCreds,
macaroonCreds,
);
const transport = new GrpcTransport({ host, channelCredentials })
const client = new LightningClient(transport)
return client
}
}

View file

@ -9,7 +9,6 @@ import { EnvMustBeNonEmptyString } from "../helpers/envParser.js"
import { UserTransactionPayment } from "./entity/UserTransactionPayment.js" import { UserTransactionPayment } from "./entity/UserTransactionPayment.js"
import { UserBasicAuth } from "./entity/UserBasicAuth.js" import { UserBasicAuth } from "./entity/UserBasicAuth.js"
import { UserEphemeralKey } from "./entity/UserEphemeralKey.js" import { UserEphemeralKey } from "./entity/UserEphemeralKey.js"
import { Product } from "./entity/Product.js"
import { UserToUserPayment } from "./entity/UserToUserPayment.js" import { UserToUserPayment } from "./entity/UserToUserPayment.js"
import { Application } from "./entity/Application.js" import { Application } from "./entity/Application.js"
import { ApplicationUser } from "./entity/ApplicationUser.js" import { ApplicationUser } from "./entity/ApplicationUser.js"
@ -18,6 +17,8 @@ import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js"
import { getLogger } from "../helpers/logger.js" import { getLogger } from "../helpers/logger.js"
import { ChannelRouting } from "./entity/ChannelRouting.js" import { ChannelRouting } from "./entity/ChannelRouting.js"
import { LspOrder } from "./entity/LspOrder.js" import { LspOrder } from "./entity/LspOrder.js"
import { Product } from "./entity/Product.js"
import { LndNodeInfo } from "./entity/LndNodeInfo.js"
export type DbSettings = { export type DbSettings = {
@ -57,7 +58,7 @@ export default async (settings: DbSettings, migrations: Function[]): Promise<{ s
database: settings.databaseFile, database: settings.databaseFile,
// logging: true, // logging: true,
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder], UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo],
//synchronize: true, //synchronize: true,
migrations migrations
}).initialize() }).initialize()

View file

@ -0,0 +1,24 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
export class LndNodeInfo {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
pubkey: string
@Column({ nullable: true })
seed?: string
@Column({ nullable: true })
backup?: string
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,6 +1,7 @@
import { DataSource, EntityManager, MoreThan } from "typeorm" import { DataSource, EntityManager, MoreThan } from "typeorm"
import { LspOrder } from "./entity/LspOrder.js"; import { LspOrder } from "./entity/LspOrder.js";
import TransactionsQueue, { TX } from "./transactionsQueue.js"; import TransactionsQueue, { TX } from "./transactionsQueue.js";
import { LndNodeInfo } from "./entity/LndNodeInfo.js";
export class LiquidityStorage { export class LiquidityStorage {
DB: DataSource | EntityManager DB: DataSource | EntityManager
txQueue: TransactionsQueue txQueue: TransactionsQueue
@ -17,4 +18,23 @@ export class LiquidityStorage {
const entry = this.DB.getRepository(LspOrder).create(order) const entry = this.DB.getRepository(LspOrder).create(order)
return this.txQueue.PushToQueue<LspOrder>({ exec: async db => db.getRepository(LspOrder).save(entry), dbTx: false }) return this.txQueue.PushToQueue<LspOrder>({ exec: async db => db.getRepository(LspOrder).save(entry), dbTx: false })
} }
async SaveNodeSeed(pubkey: string, seed: string) {
const existing = await this.DB.getRepository(LndNodeInfo).findOne({ where: { pubkey } })
if (existing) {
throw new Error("A seed already exists for this pub key")
}
const entry = this.DB.getRepository(LndNodeInfo).create({ pubkey, seed })
return this.txQueue.PushToQueue<LndNodeInfo>({ exec: async db => db.getRepository(LndNodeInfo).save(entry), dbTx: false })
}
async SaveNodeBackup(pubkey: string, backup: string) {
const existing = await this.DB.getRepository(LndNodeInfo).findOne({ where: { pubkey } })
if (existing) {
await this.DB.getRepository(LndNodeInfo).update(existing.serial_id, { backup })
return
}
const entry = this.DB.getRepository(LndNodeInfo).create({ pubkey, backup })
await this.txQueue.PushToQueue<LndNodeInfo>({ exec: async db => db.getRepository(LndNodeInfo).save(entry), dbTx: false })
}
} }

View file

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class LndNodeInfo1720187506189 implements MigrationInterface {
name = 'LndNodeInfo1720187506189'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "lnd_node_info" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "pubkey" varchar NOT NULL, "seed" varchar, "backup" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "lnd_node_info"`);
}
}

View file

@ -6,7 +6,8 @@ import { LndMetrics1703170330183 } from './1703170330183-lnd_metrics.js'
import { ChannelRouting1709316653538 } from './1709316653538-channel_routing.js' import { ChannelRouting1709316653538 } from './1709316653538-channel_routing.js'
import { LspOrder1718387847693 } from './1718387847693-lsp_order.js' import { LspOrder1718387847693 } from './1718387847693-lsp_order.js'
import { LiquidityProvider1719335699480 } from './1719335699480-liquidity_provider.js' import { LiquidityProvider1719335699480 } from './1719335699480-liquidity_provider.js'
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480] import { LndNodeInfo1720187506189 } from './1720187506189-lnd_node_info.js'
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189]
const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538] const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538]
export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => { export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
if (arg === 'fake_initial_migration') { if (arg === 'fake_initial_migration') {