Files
app.zeusln.zeus/lndmobile/channel.ts
T
Evan Kaloudis 89447a25c2 feat: Developer Tools: add updateChannelPolicy with create_missing_edge
Adds an updateChannelPolicy command to the LND Developer Tools,
mirroring lncli updatechanpolicy: base_fee_msat, fee_rate_ppm,
time_lock_delta, and min/max_htlc_msat inputs, a create_missing_edge
toggle, and a global or per-channel target picker.

Backed by a raw PolicyUpdateRequest passthrough on lnd (REST),
embedded-lnd (new lndmobile binding + iOS dispatch table entry), and
lightning-node-connect, kept separate from the production setFees
flow so units and inbound-fee handling there are untouched.
2026-07-13 11:39:14 -04:00

535 lines
15 KiB
TypeScript

import {
sendCommand,
sendStreamCommand,
decodeStreamResult,
sendBidiStreamCommand,
writeToStream
} from './utils';
import { lnrpc } from './../proto/lightning';
import Long from 'long';
import Base64Utils from '../utils/Base64Utils';
/**
* @throws
*/
export const openChannelSync = async (
pubkey: string,
amount: number,
private_channel: boolean,
fee_rate_sat?: number,
scidAlias?: boolean,
min_confs?: number,
spend_unconfirmed?: boolean,
simpleTaprootChannel?: boolean,
fund_max?: boolean,
utxos?: Array<string>,
close_address?: string
): Promise<lnrpc.ChannelPoint> => {
const response = await sendCommand<
lnrpc.IOpenChannelRequest,
lnrpc.OpenChannelRequest,
lnrpc.ChannelPoint
>({
request: lnrpc.OpenChannelRequest,
response: lnrpc.ChannelPoint,
method: 'OpenChannelSync',
options: simpleTaprootChannel
? {
node_pubkey_string: pubkey,
local_funding_amount: fund_max
? undefined
: amount
? Long.fromValue(amount)
: undefined,
target_conf: fee_rate_sat ? undefined : 2,
private: private_channel,
sat_per_vbyte: fee_rate_sat
? Long.fromValue(fee_rate_sat)
: undefined,
scid_alias: scidAlias,
min_confs,
spend_unconfirmed,
fund_max,
close_address,
outpoints: utxos
? utxos.map((utxo: string) => {
const [txid_str, output_index] = utxo.split(':');
return {
txid_str,
output_index: Number(output_index)
};
})
: undefined,
commitment_type: lnrpc.CommitmentType.SIMPLE_TAPROOT
}
: {
node_pubkey_string: pubkey,
local_funding_amount: fund_max
? undefined
: amount
? Long.fromValue(amount)
: undefined,
target_conf: fee_rate_sat ? undefined : 2,
private: private_channel,
sat_per_vbyte: fee_rate_sat
? Long.fromValue(fee_rate_sat)
: undefined,
scid_alias: scidAlias,
min_confs,
spend_unconfirmed,
close_address,
outpoints: utxos
? utxos.map((utxo: string) => {
const [txid_str, output_index] = utxo.split(':');
return {
txid_str,
output_index: Number(output_index)
};
})
: undefined,
fund_max
}
});
return response;
};
/**
* @throws
*/
export const openChannelAll = async (
pubkey: string,
private_channel: boolean,
fee_rate_sat?: number
): Promise<lnrpc.ChannelPoint> => {
const response = await sendCommand<
lnrpc.IOpenChannelRequest,
lnrpc.OpenChannelRequest,
lnrpc.ChannelPoint
>({
request: lnrpc.OpenChannelRequest,
response: lnrpc.ChannelPoint,
method: 'OpenChannelSync',
options: {
node_pubkey_string: pubkey,
fund_max: true,
target_conf: fee_rate_sat ? undefined : 2,
private: private_channel,
sat_per_vbyte: fee_rate_sat
? Long.fromValue(fee_rate_sat)
: undefined,
scid_alias: true
}
});
return response;
};
/**
* @throws
*/
export const openChannel = async (
pubkey: string,
amount: number,
private_channel: boolean,
fee_rate_sat?: number,
scidAlias?: boolean,
min_confs?: number,
spend_unconfirmed?: boolean,
simpleTaprootChannel?: boolean,
fund_max?: boolean,
utxos?: Array<string>,
funding_shim?: any
): Promise<string> => {
let options: any = {
node_pubkey: Base64Utils.hexToBase64(pubkey),
local_funding_amount: amount ? Long.fromValue(amount) : undefined,
private: private_channel,
sat_per_vbyte: fee_rate_sat ? Long.fromValue(fee_rate_sat) : undefined,
scid_alias: scidAlias,
min_confs,
spend_unconfirmed,
fund_max,
outpoints: utxos
? utxos.map((utxo: string) => {
const [txid_str, output_index] = utxo.split(':');
return {
txid_str,
output_index: Number(output_index)
};
})
: undefined,
funding_shim
};
if (simpleTaprootChannel)
options.commitment_type = lnrpc.CommitmentType.SIMPLE_TAPROOT;
const response = await sendStreamCommand<
lnrpc.IOpenChannelRequest,
lnrpc.OpenChannelRequest
>({
request: lnrpc.OpenChannelRequest,
method: 'OpenChannel',
options
});
return response;
};
/**
* @throws
*/
export const closeChannel = async (
funding_txid: string,
output_index: number,
force?: boolean,
sat_per_vbyte?: number,
delivery_address?: string
): Promise<string> => {
const response = await sendStreamCommand<
lnrpc.ICloseChannelRequest,
lnrpc.CloseChannelRequest
>(
{
request: lnrpc.CloseChannelRequest,
method: 'CloseChannel',
options: {
channel_point: {
funding_txid_str: funding_txid,
output_index
},
sat_per_vbyte: sat_per_vbyte
? Long.fromValue(sat_per_vbyte)
: undefined,
delivery_address,
force
}
},
false
);
return response;
};
/**
* @throws
* TODO implement
*/
export const abandonChannel = async (
funding_txid: string,
output_index: number,
pending_funding_shim_only?: boolean,
i_know_what_i_am_doing?: boolean
): Promise<lnrpc.AbandonChannelResponse> => {
const options: any = {
channel_point: {
funding_txid_str: funding_txid,
output_index
}
};
// Only include boolean parameters if they are explicitly set (not undefined)
if (pending_funding_shim_only !== undefined) {
options.pending_funding_shim_only = pending_funding_shim_only;
}
if (i_know_what_i_am_doing !== undefined) {
options.i_know_what_i_am_doing = i_know_what_i_am_doing;
}
const response = await sendCommand<
lnrpc.IAbandonChannelRequest,
lnrpc.AbandonChannelRequest,
lnrpc.AbandonChannelResponse
>({
request: lnrpc.AbandonChannelRequest,
response: lnrpc.AbandonChannelResponse,
method: 'AbandonChannel',
options
});
return response;
};
/**
* @throws
*/
export const updateChannelPolicy = async (data: {
base_fee_msat?: string;
fee_rate_ppm?: number;
time_lock_delta?: number;
min_htlc_msat?: string;
min_htlc_msat_specified?: boolean;
max_htlc_msat?: string;
chan_point?: { funding_txid_str: string; output_index: number };
global?: boolean;
create_missing_edge?: boolean;
}): Promise<lnrpc.PolicyUpdateResponse> => {
const options: lnrpc.IPolicyUpdateRequest = {
base_fee_msat: data.base_fee_msat
? Long.fromValue(data.base_fee_msat)
: undefined,
fee_rate_ppm: data.fee_rate_ppm,
time_lock_delta: data.time_lock_delta,
min_htlc_msat: data.min_htlc_msat
? Long.fromValue(data.min_htlc_msat)
: undefined,
min_htlc_msat_specified: data.min_htlc_msat_specified,
max_htlc_msat: data.max_htlc_msat
? Long.fromValue(data.max_htlc_msat)
: undefined,
create_missing_edge: data.create_missing_edge
};
if (data.global) {
options.global = true;
} else if (data.chan_point) {
options.chan_point = {
funding_txid_str: data.chan_point.funding_txid_str,
output_index: data.chan_point.output_index
};
}
const response = await sendCommand<
lnrpc.IPolicyUpdateRequest,
lnrpc.PolicyUpdateRequest,
lnrpc.PolicyUpdateResponse
>({
request: lnrpc.PolicyUpdateRequest,
response: lnrpc.PolicyUpdateResponse,
method: 'UpdateChannelPolicy',
options
});
return response;
};
/**
* @throws
*/
export const pendingChannels =
async (): Promise<lnrpc.PendingChannelsResponse> => {
const response = await sendCommand<
lnrpc.IPendingChannelsRequest,
lnrpc.PendingChannelsRequest,
lnrpc.PendingChannelsResponse
>({
request: lnrpc.PendingChannelsRequest,
response: lnrpc.PendingChannelsResponse,
method: 'PendingChannels',
options: {}
});
return response;
};
/**
* @throws
*/
export const listChannels = async (): Promise<lnrpc.ListChannelsResponse> => {
const response = await sendCommand<
lnrpc.IListChannelsRequest,
lnrpc.ListChannelsRequest,
lnrpc.ListChannelsResponse
>({
request: lnrpc.ListChannelsRequest,
response: lnrpc.ListChannelsResponse,
method: 'ListChannels',
options: {}
});
return response;
};
/**
* @throws
*/
export const closedChannels =
async (): Promise<lnrpc.ClosedChannelsResponse> => {
const response = await sendCommand<
lnrpc.IClosedChannelsRequest,
lnrpc.ClosedChannelsRequest,
lnrpc.ClosedChannelsResponse
>({
request: lnrpc.ClosedChannelsRequest,
response: lnrpc.ClosedChannelsResponse,
method: 'ClosedChannels',
options: {}
});
return response;
};
/**
* @throws
*/
export const listPrivateChannels =
async (): Promise<lnrpc.ListChannelsResponse> => {
const response = await sendCommand<
lnrpc.IListChannelsRequest,
lnrpc.ListChannelsRequest,
lnrpc.ListChannelsResponse
>({
request: lnrpc.ListChannelsRequest,
response: lnrpc.ListChannelsResponse,
method: 'ListChannels',
options: {
private_only: true
}
});
return response;
};
/**
* @throws
*/
export const channelBalance =
async (): Promise<lnrpc.ChannelBalanceResponse> => {
const response = await sendCommand<
lnrpc.IChannelBalanceRequest,
lnrpc.ChannelBalanceRequest,
lnrpc.ChannelBalanceResponse
>({
request: lnrpc.ChannelBalanceRequest,
response: lnrpc.ChannelBalanceResponse,
method: 'ChannelBalance',
options: {}
});
return response;
};
/**
* @throws
*/
export const subscribeChannelEvents = async (): Promise<string> => {
const response = await sendStreamCommand<
lnrpc.IChannelEventSubscription,
lnrpc.ChannelEventSubscription
>(
{
request: lnrpc.ChannelEventSubscription,
method: 'SubscribeChannelEvents',
options: {}
},
false
);
return response;
};
/**
* @throws
*/
export const exportAllChannelBackups = (): Promise<lnrpc.ChanBackupSnapshot> =>
sendCommand<
lnrpc.IChanBackupExportRequest,
lnrpc.ChanBackupExportRequest,
lnrpc.ChanBackupSnapshot
>({
request: lnrpc.ChanBackupExportRequest,
response: lnrpc.ChanBackupSnapshot,
method: 'ExportAllChannelBackups',
options: {}
});
/**
* @throws
*/
export const restoreChannelBackups = async (
channels_backup_base64: string
): Promise<lnrpc.RestoreBackupResponse> => {
const response = await sendCommand<
lnrpc.IRestoreChanBackupRequest,
lnrpc.RestoreChanBackupRequest,
lnrpc.RestoreBackupResponse
>({
request: lnrpc.RestoreChanBackupRequest,
response: lnrpc.RestoreBackupResponse,
method: 'RestoreChannelBackups',
options: {
multi_chan_backup: Base64Utils.base64ToBytes(channels_backup_base64)
}
});
return response;
};
/**
* @throws
*/
export const verifyChanBackup = async (
channels_backup_base64: string
): Promise<lnrpc.VerifyChanBackupResponse> => {
const response = await sendCommand<
lnrpc.IChanBackupSnapshot,
lnrpc.ChanBackupSnapshot,
lnrpc.VerifyChanBackupResponse
>({
request: lnrpc.ChanBackupSnapshot,
response: lnrpc.VerifyChanBackupResponse,
method: 'VerifyChanBackup',
options: {
multi_chan_backup: {
multi_chan_backup: Base64Utils.base64ToBytes(
channels_backup_base64
)
}
}
});
return response;
};
/**
* @throws
*/
export const getChanInfo = async (
chan_id: Long
): Promise<lnrpc.ChannelEdge> => {
const response = await sendCommand<
lnrpc.IChanInfoRequest,
lnrpc.ChanInfoRequest,
lnrpc.ChannelEdge
>({
request: lnrpc.ChanInfoRequest,
response: lnrpc.ChannelEdge,
method: 'GetChanInfo',
options: {
chan_id
}
});
return response;
};
export const channelAcceptor = async () => {
return await sendBidiStreamCommand('ChannelAcceptor');
};
export const channelAcceptorResponse = async (
pending_chan_id: Uint8Array,
accept: boolean,
zero_conf: boolean = false
) => {
return await writeToStream({
method: 'ChannelAcceptor',
request: lnrpc.ChannelAcceptResponse,
options: {
accept,
pending_chan_id,
zero_conf
}
});
};
export const decodeChannelAcceptRequest = (
data: string
): lnrpc.ChannelAcceptRequest => {
return decodeStreamResult<lnrpc.ChannelAcceptRequest>({
response: lnrpc.ChannelAcceptRequest,
base64Result: data
});
};
export const decodeChannelEvent = (data: string): lnrpc.ChannelEventUpdate => {
return decodeStreamResult<lnrpc.ChannelEventUpdate>({
response: lnrpc.ChannelEventUpdate,
base64Result: data
});
};
export const decodeOpenStatusUpdate = (
data: string
): lnrpc.OpenStatusUpdate => {
return decodeStreamResult<lnrpc.OpenStatusUpdate>({
response: lnrpc.OpenStatusUpdate,
base64Result: data
});
};