Lease API
The Lease API exposes Hydra App's LiquidityService — a service-backed surface for provisioning channel liquidity, releasing channels (withdraw or cooperative close), and extending existing asset leases. Hydra App completes fee settlement and any required local confirmations internally before returning the final operation result.
JSON-RPC namespace: liquidity
Endpoints
- Request Channel Liquidity
- Estimate Request Channel Liquidity Fee
- Request Channel Release
- Estimate Request Channel Release Fee
- Request Channel Lease Extension
- Estimate Request Channel Lease Extension Fee
Read-only discovery
Common types
ChannelLiquidityRequestOperation (one of)
Used by RequestChannelLiquidity and its estimator. Exactly one variant must be set.
| Variant | Fields | Purpose |
|---|---|---|
open | target_node_pubkey?, asset_liquidity | Open a new channel and provision liquidity |
deposit | channel_id, asset_liquidity | Deposit into an existing channel |
deposit_any | target_node_pubkey?, asset_liquidity | Deposit into any suitable channel; otherwise open a new one |
open_or_deposit | target_node_pubkey?, asset_liquidity | Same as deposit_any but biases toward opening |
asset_liquidity is map<string, AssetLiquidity> keyed by asset_id.
AssetLiquidity:
| Field | Type | Description |
|---|---|---|
server_amount | DecimalString | Provider-side contribution |
client_amount | DecimalString | Local/client-side contribution (used for service-broadcasted outbound funding or dual-funded requests) |
ChannelReleaseOperation (one of)
Used by RequestChannelRelease and its estimator. Exactly one variant must be set.
| Variant | Fields | Purpose |
|---|---|---|
withdraw | channel_id, asset_amounts{} | Cooperatively withdraw funds from the channel. The service broadcasts one withdrawal paying each asset's requested side(s) out, and pays the gas. |
cooperative_close | channel_id, asset_ids[] | Cooperatively close asset channels. Empty asset_ids closes every asset channel; otherwise only the listed ones. |
withdraw.asset_amounts — per-asset, per-side split
Changed 2026-07-23 — breaking: this replaced the old asset_ids[] list.
asset_amounts is a map of asset_id → AssetWithdrawAmounts:
| Field | Type | Description |
|---|---|---|
server_amount | Amount (optional) | Releases the service's own balance to the service wallet |
client_amount | Amount (optional) | Pays the client's balance out to the client's wallet |
An absent side withdraws nothing; at least one side must be present. channel_id may be empty on fee estimates only (EstimateRequestChannelReleaseFee), and every requested side must then be an exact amount.
Migrating from
asset_ids[]: a list entry"0xTOKEN"becomes a map entry"0xTOKEN": { client_amount: { exact: { amount: "..." } } }— you must now say how much and whose balance moves, not just which asset.
Fee payment (one of)
All RPCs that take a fee require exactly one fee_payment variant.
| Variant | Fields | When to use |
|---|---|---|
onchain_fee_payment | OnchainFeePayment (UTXO or Account flow) | Pay the service fee on chain |
offchain_fee_payment | OffchainFeePayment (empty) | Pay via invoice / offchain payment |
dual_fund_fee_payment | DualFundFeePayment (empty) | Settle the fee inside the dual-fund flow (provisioning RPCs only) |
sponsored_deposit_fee_payment | SponsoredDepositFeePayment (empty) | (2026-09-11) The wallet holds the token but no gas. The service funds the channel from your tokens on its own transaction and takes its fee out of the deposit (provisioning RPCs only) |
sponsored_withdrawal_fee_payment | SponsoredWithdrawalFeePayment (empty) | (2026-09-11) The wallet holds a channel balance but no gas. The service broadcasts the withdrawal and takes its fee out of what is released (release RPCs only) |
OnchainFeePayment (one of):
| Variant | Fields |
|---|---|
utxo | refund_address |
account | sender_address, refund_address? |
Fee methods are not interchangeable across the three operations:
Operation Accepts RequestChannelLiquidityonchain·offchain·dual_fund·sponsored_depositRequestChannelReleaseonchain·offchain·sponsored_withdrawalRequestChannelLeaseExtensiononchain·offchain
SponsoredDepositFeePayment — a deposit for a wallet with no gas
Added 2026-09-11.
The service funds the channel from this wallet's tokens, on its own transaction, and takes its fee out of the deposit. Hydra App signs the token permit that authorises the pull internally — you do not build or sign one yourself.
This exists for the wallet that holds a token and nothing else: no native asset, so no way to pay for a funding transaction of its own. Every other fee method assumes you can broadcast.
The fee is the quote the request references, or the service's price at execution when it references none — see Quotes. Because the fee comes out of the deposit, the channel ends up holding less than client_amount; estimate first so the user sees which figure is which.
The same rail inside a simple swap is
DEPOSIT_RAIL_LIQUIDITY_SERVICE, and its post-swap counterpart isEXIT_RAIL_LIQUIDITY_SERVICE.
SponsoredWithdrawalFeePayment — an exit for a wallet with no gas
Added 2026-09-11. RequestChannelRelease only.
The service broadcasts this wallet's withdrawal on its own transaction and takes its fee out of what is released — credited to the service in the state co-signed alongside it. Hydra App arms the funding allowance that authorises the credit internally; you do not call SetFundingAllowance yourself.
The gap this closesRelease used to offer two ways to pay, and a wallet with a channel balance and no gas could use neither:
- On-chain needs a funded wallet — which is exactly what is missing.
- Off-chain needs a channel balance to pay the invoice from — but
withdraw allis about to take that balance away.So the channel was stranded: funds in it, no way to get them out. This is the only fee method that works from that state, and it is why it exists.
What your node actually does: arm the allowance authorising the credit, then confirm. It never pays a fee of its own, so it takes the funding-allowance confirmation path a dual-funded open takes rather than the settlement path. A request you never confirm takes its allowance back rather than leaving standing consent for the service to draw on.
The fee is the quote the request references, or the service's price at execution when it references none — the same rule as every other method; see Quotes.
Because the fee comes out of the released amount, what lands in your wallet is less than the balance you withdrew. Estimate first so the user sees both figures.
The mechanism underneath is the generic
UpdateChannelBalanceCredit— a withdrawal that hands part of itself to the peer. The swap-flow equivalent isEXIT_RAIL_LIQUIDITY_SERVICE; the entry-side counterpart isSponsoredDepositFeePayment.
Quotes: pinning an estimated price
Added 2026-09-11.
Every Estimate*Fee call returns a quote alongside the fee:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | The estimated fee, in the asset named by payment_asset_id |
quote_id | string (optional) | Names the quote this fee was read from |
valid_until_timestamp_seconds | uint64 (optional) | When the quote stops being honoured (Unix seconds) |
Pass quote_id back on the matching request and the service bills at that quote's price for as long as the quote is valid. Omit it and the service prices the request when it executes — which is a different number if the market moved in between.
A quote_id that has expired, or no longer fits the request, causes the request to be refused rather than silently re-priced. That is the point: the user agreed to a figure, and a request that can no longer honour it should come back for a fresh estimate, not quietly cost more.
Estimate*Fee ──▶ { fee, quote_id, valid_until_timestamp_seconds }
│
show `fee` to the user, they accept
│
▼
Request* ──▶ same request + quote_id ──▶ billed at `fee`
└─▶ refused if expired / changed
GetLiquidityServiceInfo.quote_validity_secsis how long every quote the service issues stays valid, so you can size the confirmation window before you ask for one.
A quote does not reserve liquidity. It fixes the price, not the capacity — a lease that no longer fits the provider's available liquidity is refused even inside its validity window.
Request Channel Liquidity
Ask the liquidity service to provision channel liquidity on the client's behalf. Hydra App completes fee settlement and the required local confirmations before returning.
Method: RequestChannelLiquidity
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Channel network |
operation | ChannelLiquidityRequestOperation | YES | Provisioning action (open / deposit / deposit_any / open_or_deposit) |
lease_duration_seconds | uint64 | Conditional | Required when any requested asset has server_amount > 0; otherwise omit |
payment_network | Network | YES | Network used to pay the service fee |
payment_asset_id | string | YES | Asset used to pay the service fee |
fee_payment | one of OnchainFeePayment / OffchainFeePayment / DualFundFeePayment / SponsoredDepositFeePayment | YES | Exactly one |
quote_id | string | no | (2026-09-11) The quote an earlier estimate of this same request returned — see Quotes. Absent prices the request at execution |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Funding transaction ID |
channel_id | string | Channel ID of the (new or updated) channel |
Example Request (open channel, offchain fee):
import { LiquidityServiceClient } from './proto/LiquidityServiceClientPb'
import {
RequestChannelLiquidityRequest,
ChannelLiquidityRequestOperation,
AssetLiquidity,
OffchainFeePayment
} from './proto/liquidity_pb'
const client = new LiquidityServiceClient('http://localhost:5003')
const request = new RequestChannelLiquidityRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' }) // Bitcoin Signet
// Open new channel with 0.1 BTC on the server side
const open = new ChannelLiquidityRequestOperation.Open()
const liq = new AssetLiquidity()
liq.setServerAmount({ value: '0.1' })
liq.setClientAmount({ value: '0' })
open.getAssetLiquidityMap().set('0x0000000000000000000000000000000000000000000000000000000000000000', liq)
const op = new ChannelLiquidityRequestOperation()
op.setOpen(open)
request.setOperation(op)
request.setLeaseDurationSeconds(2592000) // 30 days
request.setPaymentNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentAssetId('0x0000000000000000000000000000000000000000000000000000000000000000')
request.setOffchainFeePayment(new OffchainFeePayment())
const response = await client.requestChannelLiquidity(request, {})
console.log('Channel:', response.getChannelId(), 'TX:', response.getTxid())
Example Response:
{
"txid": "abc123...",
"channel_id": "ch_def456"
}
Estimate Request Channel Liquidity Fee
Estimate the service fee for a channel liquidity request before executing it. Takes the same RequestChannelLiquidityRequest and returns just the fee.
Method: EstimateRequestChannelLiquidityFee
Parameters: Same as Request Channel Liquidity.
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated service fee, denominated in the asset identified by payment_asset_id |
quote_id | string (optional) | (2026-09-11) Pass this back on the matching request to be billed at fee — see Quotes |
valid_until_timestamp_seconds | uint64 (optional) | (2026-09-11) When the quote stops being honoured (Unix seconds) |
Example Request:
// Build the same RequestChannelLiquidityRequest as you would for the actual call
const response = await client.estimateRequestChannelLiquidityFee(request, {})
console.log('Estimated fee:', response.getFee()?.getValue())
Example Response:
{
"fee": { "value": "0.0001" },
"quoteId": "q_8f3c1a...",
"validUntilTimestampSeconds": "1789012345"
}
Request Channel Release
Ask the liquidity service to withdraw assets from a channel or cooperatively close it. The service charges a fee and Hydra App settles it before observing the release update.
Method: RequestChannelRelease
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Channel network |
operation | ChannelReleaseOperation | YES | withdraw or cooperative_close |
payment_network | Network | YES | Network used to pay the service fee |
payment_asset_id | string | YES | Asset used to pay the service fee |
fee_payment | one of OnchainFeePayment / OffchainFeePayment / SponsoredWithdrawalFeePayment | YES | Exactly one. SponsoredWithdrawalFeePayment is release-only — see above |
quote_id | string | no | (2026-09-11) The quote an earlier estimate of this same request returned — see Quotes |
Response:
| Field | Type | Description |
|---|---|---|
txid | string | Release transaction ID |
Example Request (cooperative close, offchain fee):
import {
RequestChannelReleaseRequest,
ChannelReleaseOperation,
OffchainFeePayment
} from './proto/liquidity_pb'
const request = new RequestChannelReleaseRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
const close = new ChannelReleaseOperation.CooperativeClose()
close.setChannelId('ch_def456')
const op = new ChannelReleaseOperation()
op.setCooperativeClose(close)
request.setOperation(op)
request.setPaymentNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentAssetId('0x0000000000000000000000000000000000000000000000000000000000000000')
request.setOffchainFeePayment(new OffchainFeePayment())
const response = await client.requestChannelRelease(request, {})
console.log('Release TX:', response.getTxid())
Example Response:
{ "txid": "rel_abc789..." }
Estimate Request Channel Release Fee
Estimate the service fee for a channel release before executing it.
Method: EstimateRequestChannelReleaseFee
Parameters: Same as Request Channel Release.
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated service fee |
quote_id | string (optional) | (2026-09-11) Pass this back on the matching request to be billed at fee — see Quotes |
valid_until_timestamp_seconds | uint64 (optional) | (2026-09-11) When the quote stops being honoured (Unix seconds) |
Example:
const response = await client.estimateRequestChannelReleaseFee(request, {})
console.log('Estimated fee:', response.getFee()?.getValue())
Example Response:
{ "fee": "0.00005" }
Request Channel Lease Extension
Extend the duration of an existing lease on a specific channel asset. lease_extension_seconds is an extension delta, not a replacement absolute expiry.
Check the expiry before paying to extend. Leases extend automatically as a channel is used, so an actively-traded channel's expiry moves forward on its own — a market maker routing volume through a channel typically pays only the initial lease. Read the current expiry from
AssetChannel.lease_expiry(on everywatchOnlyNode.GetChannels/GetChannelread, added 2026-07-12), and extend manually only when it is actually running down.liquidity.GetLeaseExpiriesreturns the same information for all channels at once.
Method: RequestChannelLeaseExtension
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Channel network |
channel_id | string | YES | Channel whose lease to extend |
asset_id | string | YES | Specific asset whose lease is being extended |
lease_extension_seconds | uint64 | YES | Delta to add to the current expiry |
payment_network | Network | YES | Network used to pay the service fee |
payment_asset_id | string | YES | Asset used to pay the service fee |
fee_payment | one of OnchainFeePayment / OffchainFeePayment | YES | Exactly one. The dual-fund and sponsored methods are not valid here |
quote_id | string | no | (2026-09-11) The quote an earlier estimate of this same request returned — see Quotes |
Response:
| Field | Type | Description |
|---|---|---|
channel_id | string | Channel that was extended |
asset_id | string | Asset whose lease was extended |
expiry_timestamp_seconds | int64 | New absolute expiry (Unix seconds) |
Example Request:
import {
RequestChannelLeaseExtensionRequest,
OffchainFeePayment
} from './proto/liquidity_pb'
const request = new RequestChannelLeaseExtensionRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setChannelId('ch_def456')
request.setAssetId('0x0000000000000000000000000000000000000000000000000000000000000000')
request.setLeaseExtensionSeconds(2592000) // +30 days
request.setPaymentNetwork({ protocol: 1, id: '0a03cf40' })
request.setPaymentAssetId('0x0000000000000000000000000000000000000000000000000000000000000000')
request.setOffchainFeePayment(new OffchainFeePayment())
const response = await client.requestChannelLeaseExtension(request, {})
console.log('New expiry:', response.getExpiryTimestampSeconds())
Example Response:
{
"channelId": "ch_def456",
"assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
"expiryTimestampSeconds": "1735689600"
}
Estimate Request Channel Lease Extension Fee
Estimate the service fee for a lease extension before executing it.
Method: EstimateRequestChannelLeaseExtensionFee
Parameters: Same as Request Channel Lease Extension.
Response:
| Field | Type | Description |
|---|---|---|
fee | DecimalString | Estimated service fee |
quote_id | string (optional) | (2026-09-11) Pass this back on the matching request to be billed at fee — see Quotes |
valid_until_timestamp_seconds | uint64 (optional) | (2026-09-11) When the quote stops being honoured (Unix seconds) |
Example:
const response = await client.estimateRequestChannelLeaseExtensionFee(request, {})
console.log('Estimated fee:', response.getFee()?.getValue())
Example Response:
{ "fee": "0.00002" }
Get Liquidity Service Info
Server-wide bounds, durations, per-asset fee configuration, and the provider's node public key on each network. Read-only, no side effects — call it once at start and cache it.
Method: GetLiquidityServiceInfo — Parameters: None.
Response:
| Field | Type | Description |
|---|---|---|
min_duration_secs / max_duration_secs | uint64 | The lease-duration window the service accepts |
min_capacity_usd / max_capacity_usd | DecimalString | Capacity bounds, in USD |
asset_configs | LiquidityAssetConfig[] | Per-asset fee configuration — see below |
node_pubkeys | LiquidityNetworkNode[] | { network, node_pubkey } — the provider's node id per network |
quote_validity_secs | uint64 | How long every quote the service issues stays valid — see Quotes |
LiquidityAssetConfig:
| Field | Type | Description |
|---|---|---|
protocol | Protocol | Protocol family of the network |
network_id | string | Network identifier |
asset_id | string | The asset |
fee_ratio_per_hour | DecimalString | Hourly fee ratio — "0.001" is 0.1% per hour |
pricing_ticker | string (optional) | External pricing ticker used for the USD conversion |
node_pubkeysis where a bot gets the peer id to connect to before requesting liquidity — it is the same value the peer tables publish, read from the live service.
Get Leaseable Asset Info
Liquidity bounds and fee ratio for one (network, asset) pair on the provider. This is the call that answers "can I lease this, and how much".
Method: GetLeaseableAssetInfo
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Channel network |
asset_id | string | YES | Asset to lease |
Response: info (LeaseableAssetInfo):
| Field | Type | Description |
|---|---|---|
available_liquidity | DecimalString | What the provider can lease of this asset right now |
min_capacity / max_capacity | DecimalString | Per-lease size bounds, in the asset's own units |
min_duration_secs / max_duration_secs | uint64 | Duration bounds for this asset |
fee_ratio | DecimalString | The fee ratio applied to this asset |
Sizing a lease against
available_liquidityis what avoids thelease_too_bigestimate variant — that variant reports the same three numbers after the fact.
Get Leases
The caller's active leases on a network.
Method: GetLeases
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to list leases for |
Response: leases (AssetChannelLease[]):
| Field | Type | Description |
|---|---|---|
network | Network | The network the lease is on |
channel_id | string | The leased channel |
asset_id | string | The leased asset |
expiry | Timestamp (optional) | When the lease expires. Absent means not yet calculated, not "never expires" |
Get Lease Expiries
Every lease expiry the node holds for a network, in one call, keyed by channel then asset.
Method: GetLeaseExpiries
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network to read expiries for |
Response:
| Field | Type | Description |
|---|---|---|
channels | map<string, ChannelAssetExpiries> | channel_id → { assets: map<asset_id, Timestamp> } |
This one reads the local cache — no round-trip to the providerThat makes it cheap enough to poll on a UI timer, and it is the right call for "which of my leases are running down".
GetLeasesasks the provider and is the authority when the two disagree.Usage extends a lease on its own, so an actively-traded channel's expiry moves forward without you paying anything. Watch this rather than counting down from the duration you bought.
Duration Reference
lease_duration_seconds and lease_extension_seconds are expressed in seconds.
| Duration | Seconds | Calculation |
|---|---|---|
| 1 hour | 3,600 | 60 × 60 |
| 1 day | 86,400 | 24 × 60 × 60 |
| 1 week | 604,800 | 7 × 24 × 60 × 60 |
| 30 days | 2,592,000 | 30 × 24 × 60 × 60 |
| 90 days | 7,776,000 | 90 × 24 × 60 × 60 |
const days = 30
const leaseSeconds = days * 24 * 60 * 60 // 2,592,000
Choosing a fee payment method
| Variant | Available on | When to use |
|---|---|---|
offchain_fee_payment | Liquidity / Release / Lease Extension | You have offchain (channel) balance to pay the fee — fastest, lowest overhead |
onchain_fee_payment (UTXO) | Liquidity / Release / Lease Extension on Bitcoin-style protocols | You're paying the fee from on-chain funds and need a refund address for change |
onchain_fee_payment (Account) | Liquidity / Release / Lease Extension on EVM-style protocols | You're paying the fee from an EVM account; refund_address is optional |
dual_fund_fee_payment | Liquidity only | You're contributing client-side funds (client_amount > 0) and want the fee settled inside the dual-fund flow |
sponsored_deposit_fee_payment | Liquidity only | (2026-09-11) The wallet holds the token but no native asset — the service broadcasts the funding and takes its fee out of the deposit. See SponsoredDepositFeePayment |
sponsored_withdrawal_fee_payment | Release only | (2026-09-11) The wallet holds a channel balance but no gas — the only method that can exit that channel at all. See SponsoredWithdrawalFeePayment |
Common Workflows
Open a channel with server-provided liquidity (offchain fee)
async function openChannelWithLiquidity(
client: LiquidityServiceClient,
network: { protocol: number; id: string },
assetId: string,
serverAmount: string,
leaseDays: number
) {
const request = new RequestChannelLiquidityRequest()
request.setNetwork(network)
const open = new ChannelLiquidityRequestOperation.Open()
const liq = new AssetLiquidity()
liq.setServerAmount({ value: serverAmount })
liq.setClientAmount({ value: '0' })
open.getAssetLiquidityMap().set(assetId, liq)
const op = new ChannelLiquidityRequestOperation()
op.setOpen(open)
request.setOperation(op)
request.setLeaseDurationSeconds(leaseDays * 24 * 60 * 60)
request.setPaymentNetwork(network)
request.setPaymentAssetId(assetId)
request.setOffchainFeePayment(new OffchainFeePayment())
// 1. Estimate first
const estimate = await client.estimateRequestChannelLiquidityFee(request, {})
console.log('Estimated fee:', estimate.getFee()?.getValue())
// 2. Execute
const result = await client.requestChannelLiquidity(request, {})
return {
channelId: result.getChannelId(),
txid: result.getTxid()
}
}
Cooperatively close a channel through the service
async function cooperativeClose(
client: LiquidityServiceClient,
network: { protocol: number; id: string },
channelId: string,
paymentAssetId: string
) {
const request = new RequestChannelReleaseRequest()
request.setNetwork(network)
const close = new ChannelReleaseOperation.CooperativeClose()
close.setChannelId(channelId)
const op = new ChannelReleaseOperation()
op.setCooperativeClose(close)
request.setOperation(op)
request.setPaymentNetwork(network)
request.setPaymentAssetId(paymentAssetId)
request.setOffchainFeePayment(new OffchainFeePayment())
const result = await client.requestChannelRelease(request, {})
return result.getTxid()
}
Error Handling
| Error Code | Description | Solution |
|---|---|---|
INVALID_ARGUMENT | Missing lease_duration_seconds while server_amount > 0, or no fee_payment variant set | Provide a duration when leasing; pick exactly one fee payment variant |
INVALID_ARGUMENT | dual_fund_fee_payment or sponsored_deposit_fee_payment used outside of RequestChannelLiquidity | Switch to onchain_fee_payment or offchain_fee_payment |
FAILED_PRECONDITION | The quote_id has expired, or no longer fits the request | Re-run the matching Estimate*Fee, show the user the new figure, and send the fresh quote_id — see Quotes |
RESOURCE_EXHAUSTED | Insufficient liquidity available on the provider side | Reduce server_amount or retry later |
FAILED_PRECONDITION | Channel does not exist, asset not present in channel, or release/extension not permitted | Verify channel_id and asset_id against watchOnlyNode.GetChannel |
UNAVAILABLE | Liquidity service temporarily unavailable | Retry with exponential backoff |
Best Practices
- Always estimate first. Call the matching
Estimate*RPC before the operation. - Provide
lease_duration_secondsonly when needed. Required if any asset hasserver_amount > 0; otherwise omit. - Pick the right fee payment. Offchain is cheapest; dual-fund and sponsored-deposit are only valid on
RequestChannelLiquidity.- Pass the
quote_idfrom your estimate. Without it the service prices the request at execution, and the user is billed a figure they never saw.
- Pass the
- Reuse channels when possible.
deposit/deposit_any/open_or_depositavoid the cost of opening a new channel. - Treat
lease_extension_secondsas a delta, not a target expiry. - Watch
AssetChannel.lease_expiryinstead of assuming the duration you paid for. Usage extends a lease automatically, so the expiry moves; poll it (orliquidity.GetLeaseExpiries) and extend only when it is genuinely running down. - On
withdraw, name the side.asset_amountsneedsserver_amountand/orclient_amountper asset — at least one — since 2026-07-23.