No description
  • Rust 99%
  • Nix 0.6%
  • Shell 0.3%
  • Just 0.1%
Find a file
tsk 810eb4cca4
Some checks failed
Checks / format (push) Successful in 1m0s
Checks / format (pull_request) Successful in 1m3s
Checks / dependencies (pull_request) Successful in 1m9s
Checks / cdk-compatible (pull_request) Failing after 12m47s
Checks / dependencies (push) Successful in 1m19s
Checks / checks (push) Failing after 19m59s
Checks / checks (pull_request) Failing after 23m50s
Checks / cdk-compatible (push) Successful in 16m23s
fix: bound pending recovery and reclaim rejected settlement state
Cap new Cashu reservations globally and per intent, preserving existing retries.
Rotate recovery through a durable cursor with bounded attempts and elapsed time.

Checkpoint pre-melt cleanup before deleting isolated source wallet storage,
then remove settlement ownership and unused hold records atomically. Add an
idempotent cleanup hook for caller-owned wallets. Preserve started melts and
uncollected destination quotes, and bound orphaned receiving-wallet quotes.

Cover admission races, restart, cleanup interruption, provider storage, manual
hold reuse, quote capacity, and collection of late funds without receipts.
2026-10-09 16:21:54 +00:00
.github/workflows refactor(storage)!: leave database coordination to applications 2026-10-09 15:32:36 +00:00
crates/payments fix: bound pending recovery and reclaim rejected settlement state 2026-10-09 16:21:54 +00:00
scripts refactor(storage)!: leave database coordination to applications 2026-10-09 15:32:36 +00:00
tests/regtest feat: receive L402 payments through a trusted Cashu mint 2026-10-09 15:55:20 +00:00
.gitignore feat: introduce satsettle payment intents and settlement 2026-10-09 11:12:31 +00:00
Cargo.lock refactor(storage)!: leave database coordination to applications 2026-10-09 15:32:36 +00:00
Cargo.toml refactor(storage)!: leave database coordination to applications 2026-10-09 15:32:36 +00:00
deny.toml fix: replace vulnerable Lightning transport and legacy crypto 2026-10-09 14:18:16 +00:00
DEVELOPMENT.md feat: receive L402 payments through a trusted Cashu mint 2026-10-09 15:55:20 +00:00
flake.lock feat: introduce satsettle payment intents and settlement 2026-10-09 11:12:31 +00:00
flake.nix refactor(storage)!: leave database coordination to applications 2026-10-09 15:32:36 +00:00
justfile refactor(storage)!: leave database coordination to applications 2026-10-09 15:32:36 +00:00
LICENSE feat: introduce satsettle payment intents and settlement 2026-10-09 11:12:31 +00:00
NOTICE.md feat: introduce satsettle payment intents and settlement 2026-10-09 11:12:31 +00:00
README.md fix: bound pending recovery and reclaim rejected settlement state 2026-10-09 16:21:54 +00:00
rust-toolchain.toml feat: introduce satsettle payment intents and settlement 2026-10-09 11:12:31 +00:00

satsettle

A Rust library that creates one durable payment intent with Cashu, L402, and Lightning x402 offers. Choose automatic settlement or manual claim/reject for exclusive purchases such as usernames. Each buyer gets a separate intent; your application reserves the resource before claiming the winning payment.

This is an embedded, single-process library. It includes CDK wallets, an LDK Server client, protocol codecs, and persistent recovery journals. Applications provide HTTP handlers (if needed), authentication, rate limits, scheduling, and fulfillment. Manual capture starts authenticated LDK event listeners; the application still schedules recovery and makes allocation decisions.

Use it

The initial version is consumed as a path or Git dependency. Publishing to crates.io requires replacing the pinned Git LDK Server dependency with a compatible published release.

[dependencies]
satsettle = { path = "../402_payments/crates/payments" }
use satsettle::{CreateIntent, Engine, IdempotencyKey, PaymentProof};

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let payments = Engine::open(".state/payments").await?;
payments.recover().await?;

// Generate for a new checkout; save it with the order and reuse it on retries.
let key = IdempotencyKey::generate();
let intent = payments.create_intent(CreateIntent {
    idempotency_key: key,
    reference: "order-123".into(),
    amount_sats: 10,
    // Supply and retain an absolute deadline; don't change it on retries.
    expires_at: 2_000_000_000,
    cashu_mint: Some("https://your-mint.example".into()),
    capture: satsettle::CaptureMode::Automatic,
    cashu_settlement: None,
    lightning: None,
}).await?;

// Present intent.offers to the payer, then accept their selected proof.
let receipt = payments.submit_payment(
    &intent.id,
    PaymentProof::Cashu("cashuB...".into()),
).await?;
// Fulfill by intent.id in your application's own durable transaction.
# Ok(())
# }

The compilable checkout example shows all three methods, HTTP request binding, and separate Cashu payouts. LDK Server creates the shared invoice; incoming Lightning funds stay on that node. Cashu payments with cashu_settlement: None are redeemed into the configured mint's wallet and may be paid out to a NUT-18 request. Direct Cashu settlement is described below. The library does not convert existing LDK balances to Cashu or convert balances as part of the payout API.

Both intents and payouts require IdempotencyKey, a UUID-backed type. Use IdempotencyKey::generate() for a new operation, saved_text.parse::<IdempotencyKey>()? to restore one, or IdempotencyKey::from(existing_uuid) to wrap a UUID. Keep application order IDs in reference. UUIDs serialize as lowercase, hyphenated strings; parsing a different UUID spelling does not create a different key.

Manual capture: one username, two buyers

Set CreateIntent.capture = CaptureMode::Manual. Give each buyer a separate intent and stable key. A shared reference such as alice@xyz identifies the application resource; it does not enforce exclusivity inside this library. The compilable username example shows an application-owned durable reservation and crash recovery.

  1. For Lightning, configure lightning normally. Its L402/x402 offers use a hold invoice. For Cashu, configure cashu_settlement with Ldk(node) and call authorize_cashu(intent_id, token). The original proofs are melted without a preliminary swap; the source mint holds them pending.
  2. Call authorizations() to read funded holds in durable observation order. An Authorization is not a receipt. sequence defines which arrival this engine recorded first; simultaneous payments are ordered by the store.
  3. Atomically reserve the username in your application database, recording the winning intent and authorization IDs. Then call claim(authorization.id).
  4. Assign the username only after receiving a receipt or observing Paid. Call reject(losing_intent.id) for the other buyer.

claim() durably selects one authorization per intent before revealing its preimage. Repeating that ID is safe. Unavailable or task cancellation can mean that funds were already claimed: retain the application reservation and retry that same ID or run recover(). The intent stays Claiming until the node proves success or failure. Only Failed is evidence that the selected claim failed; it is safe for the application to release that reservation. A Rejected intent that never started a claim can also release its reservation. There is no automatic switch to another buyer. Persist application reservations and reconcile them on startup, including reservations made just before a crash preceding claim().

reject() is irreversible and cannot undo a claim decision. It also rejects late arrivals. Rejected means the service will never claim that intent; it does not mean the losing Cashu proofs are already spendable. Their source mint must process the failed Lightning payment first. Continue recover() and inspect pending_settlements() until the source reports the original proofs unspent. Rejected submissions are never remelted. Return the original token to the buyer if needed; no replacement token or swap is required. Each hold uses a fresh preimage; duplicate payments to the same invoice are failed.

Manual capture supports directly paid Lightning and direct Cashu melts to an LDK node under your control. Wallet-receive Cashu and trusted-mint destinations are rejected in this mode because they do not expose a manual claim decision. An L402/x402 payer cannot provide its preimage before the claim; the application must process node authorizations independently of that payer's pending HTTP request. Use submit_payment() for automatic intents and authorize_cashu() for manual Cashu submissions.

The node macaroon needs Bolt11ReceiveForHash, SubscribeEvents, GetPaymentDetails, ListPayments, Bolt11ClaimForId, Bolt11FailForId, and GetNodeInfo access (node:read is required for deadline checks). Listeners start before invoices are exposed and reconnect after disconnection. LDK's claimable events are not replayed. A separate reconciler scans paginated node records at startup, after reconnection, and every five seconds to discover pending holds owned by this store. Each batch reads at most 32 pages; larger histories resume from the saved cursor in the next batch and take longer to reconcile. Recovered authorizations use observation order, not node arrival order. When an event deadline is missing, checking the authorization persists one conservative bound of the node's current height plus 2016 blocks (LDK's maximum incoming HTLC lock). Repeated observations and restarts never extend it; a later event may shorten it. Reconciliation never claims a hold automatically. Persisted decisions recover across restarts. Claim deadlines are block heights; holds can expire before an application decides. At or past the deadline, a still-pending node record is treated as failed, excluding it from authorizations and resolving a selected claim as Failed. An authenticated successful record takes precedence. If the node height cannot be read, the result remains uncertain. Keep holds short and run allocation/recovery promptly. close().await stops listeners before releasing storage; dropping the engine also aborts listeners.

Intent status progresses through Authorized and Claiming to Paid, or to Rejected/Failed. Undecided expired holds are rejected by recovery. Recovery reports also expose authorized, claims_pending, and rejections_pending.

Direct Cashu settlement

Set cashu_settlement to melt the payer's original proofs directly to a trusted destination, without first receiving/swapping them:

use satsettle::{CashuSettlement, Network, SettlementDestination};

let settlement = CashuSettlement {
    destination: SettlementDestination::CashuMint {
        mint: "https://your-trusted-mint.example".into(),
        network: Network::Bitcoin,
    },
    max_fee_sats: 3,
};
// Or: destination: SettlementDestination::Ldk(your_ldk_config)
// Then set CreateIntent.cashu_settlement = Some(settlement).

See the direct settlement example for creation, submission, recovery handling and change. It is a compilable handler sketch; its main does not make payments. This mode does not require HTTP request binding or enabling L402/x402.

Destination Completion condition
Ldk(config) An authenticated ListPayments lookup finds a successful inbound payment with the invoice's hash and the full received amount.
CashuMint { mint, network } The destination invoice is paid and CDK has verified and durably stored the newly issued tokens. A completed local issuance transaction is required.

The source mint's PAID state or preimage alone never completes the intent. LDK credentials need both Bolt11Receive and ListPayments permissions. Tokens issued at the trusted mint become available through cashu_balance() and the normal payout API.

With direct settlement enabled, cashu_mint: None accepts compatible HTTPS source mints on port 443. Source wallets use CDK's standard mint connector and HTTP transport with HTTP polling. Direct melts use CDK's skip_swap option to spend the original proofs. cashu_mint: Some(url) restricts the source to that configured mint; loopback HTTP is supported for development.

The library does not filter resolved IP addresses, pin DNS, or impose a response body size limit. Applications accepting payer-selected mint URLs must enforce network access restrictions through their deployment or an outbound proxy. The source must support BOLT11 melts of sat-denominated, unlocked Cashu v4 tokens. A reachable mint is not a guarantee of liquidity or payment.

The Cashu offer requests amount_sats + max_fee_sats. Before sending, the library checks that the source's input fee plus Lightning fee reserve fits that budget and that the token covers both fees and the full purchase amount. It never tops up from another wallet. The cap limits the quoted fees; an untrusted mint can still withhold change. Excess and unused fee reserve remain source-mint ecash.

submit_payment() returns a receipt only after destination confirmation. An interrupted or uncertain attempt returns Error::Unavailable; an otherwise unpaid intent reports IntentStatus::Settling. Inspect pending_settlements() and call recover() at startup and periodically. Recovery retains the same invoice, quote, and input proofs. It can finish from destination evidence even when the source is offline. Pending melts are not resubmitted; a definitively unpaid, compensated preparation may retry the same quote and proofs. Never generate a replacement payment merely because a call timed out. An expired or unresolvable attempt that may have sent remains pending for reconciliation.

Source proofs live in isolated per-attempt CDK wallets, never in the receiving wallet's spendable balance. After settlement, settlement_change(receipt.id) recovers and exports any available change as PaymentProof::Cashu. It returns the same bearer token on retries, without another swap. Deduplicate its delivery or redemption by receipt ID. None means no change is currently recovered; later recovery may find it. Only newly derived change proofs are exported, never the original payer proofs. There is no automatic refund or conversion of change.

This is a fresh storage format (schema 3). Start with a new data directory; earlier schemas are rejected, and no migration or legacy-key compatibility is provided. Existing data is never deleted automatically.

Mint-backed L402 receiving

Use LightningReceiver::CashuMint to accept automatic L402 payments into a trusted Cashu wallet without running an LDK node:

use satsettle::{LightningReceiver, Network};

let receiver = LightningReceiver::CashuMint {
    mint: "https://your-trusted-mint.example".into(),
    network: Network::Bitcoin,
};
// Set LightningOptions.receiver = receiver and protocols = LightningProtocols::L402.
// Compute the request binding with LightningOptions::http_binding as usual.

See the mint L402 example for complete configuration. LDK callers use LightningReceiver::Ldk(config) or config.into(); existing serialized LDK settings remain readable. A mint receiver requires CaptureMode::Automatic and LightningProtocols::L402. Manual capture and x402 are rejected before creating a quote. Managed trusted wallets are the default; with supplied wallets, configure a wallet for this mint explicitly.

The payer receives an invoice and a request-bound macaroon. Quote identifiers remain private in the storage backend. A receipt requires a valid macaroon:preimage credential and verified ecash issuance committed in the receiving wallet. A mint's PAID or ISSUED response is insufficient. Retry the same intent and proof after Unavailable; the quote and receipt are idempotent.

recover() can collect a paid quote to safeguard funds even when the payer has not authenticated. It does not mark that intent paid or issue a receipt until a valid payer credential has been verified. If verification succeeded before an interrupted collection, recovery can finish that receipt after a restart. Collected ecash is available through cashu_balance() and the payout API.

The payer must obtain the Lightning preimage. A Lightning wallet normally returns it after paying, but a Cashu mint used to melt tokens may not expose it. Such a payer can pay the invoice yet remain unable to authenticate with L402. Use the direct Cashu settlement flow for Cashu payers without preimage support. Changing the receiver to LDK does not fix this payer-side limitation, and mint payment status never substitutes for credentials.

Quote creation continues to its durable checkpoint after caller cancellation. After a crash, recovery can bind the original quote already stored by CDK. If a quote response never reached durable local storage, its identity is unknown: Satsettle returns Unavailable and does not create a replacement invoice. An unbound request blocks new quote creation at that mint until it is reconciled; retain both the coordination records and the original wallet for recovery.

Automatic payment semantics

  • A caller idempotency key plus immutable terms creates one intent. Repeating those terms returns the original offers; changing them returns Conflict. The intent ID is a deterministic SHA256 of the canonical UUID string, not an authorization secret.
  • Both Lightning protocols prove the same payment hash. Submitting either or both returns the same receipt. The first accepted proof determines its method. The invoice must be the exact invoice issued for this intent, even if another intent has the same price and request binding.
  • Cashu tokens are bearer payments. The library binds a canonical token identity to an intent before redemption or melting and rejects its use for another intent. The Cashu payment-request ID is informational, not a cryptographic token binding.
  • A distinct second payment produces a receipt with additional = true. Refunds or credits are an application decision. Additional receipts do not mean the application should fulfill the intent again.
  • Receipt.applied_sats is the amount applied to the purchase, not a wallet accounting statement. Cashu prices are net of fees. Wallet-receive mode retains excess value. Direct settlement exposes source change separately. Neither mode automatically refunds excess or accumulates partial payments.
  • Intent expiry stops new Cashu submissions and new offers. A Cashu receive or direct settlement journaled before expiry can still recover. Already-issued Lightning invoices retain their own protocol expiry and 60-second grace, so a payer can submit a valid proof even after the application deadline. The application must decide how to fulfill such a late-paid order.
  • Protocol proofs must still validate when resubmitted. Once an L402 credential or invoice expires, read durable paid state with status() instead. A paid intent never becomes unpaid merely because its offer expired.

Payment, fulfillment, and payout are separate. The library does not guarantee exactly-once application side effects. Use intent.id as the deduplication key in an application transaction or transactional outbox. Authorize every API call to the intent and keep payment material out of backend logs.

Recovery and payouts

Cashu submission journals retain the token privately before contacting the mint. CDK performs its own wallet recovery; the intent journal then records the receipt. Retrying after a crash between those commits does not redeem or credit twice. Run recover() at startup and periodically. Invalid pending tokens are discarded; temporary mint failures remain pending. New Cashu submissions are limited to 1,024 unresolved attempts globally and eight per intent, atomically in storage; existing-token retries remain available at capacity. Admission returns Error::Unavailable when full. Cleanup still in progress consumes capacity. Each recovery call rotates through at most eight Cashu submissions with a shared 30-second budget; its counts describe the work attempted in that call. The cursor survives restarts, so an offline early attempt cannot monopolize recovery. Unknown outcomes are never evicted to make room. Recovery also prunes expired L402 keys.

A rejection before any melt starts durably schedules cleanup of the settlement, destination ownership, and isolated source wallet, including its seed and SQLite files. Interrupted cleanup resumes during recovery. Observed manual holds must be rejected before their records are removed. Started melts and collected change retain their recovery state. A rejected attempt targeting a Cashu mint also retains its bounded cleanup reservation while the destination quote is uncollected: the source has seen its invoice and could pay it later. Recovery collects such late funds without granting a payment receipt, then releases the reservation. Unpaid quotes are not evicted by age. Each receiving wallet additionally caps uncollected quotes at 1,024, including orphaned responses after a crash, so repeated interrupted quote creation cannot bypass admission limits. For automatic LDK intents, recovery does not discover paid invoices when the payer never submits a proof; LDK still owns those funds. Mint-backed L402 recovery safeguards paid ecash separately from payer authentication, as described above. Recovery does not run payout schedules.

payout(PayoutRequest) delivers to a Cashu payment request using the same wallet lock as receives. Supply a stable idempotency key and a total fee cap. Successful retries return Delivered without sending again. Delivery means a transport acknowledgment, not proof that the destination redeemed the tokens.

Before a send, the library journals the payout request, then persists the CDK operation ID before confirming. A crash, timeout, fee failure, or lost response can leave the caller's key Unresolved. That key is deliberately never retried by creating a fresh payment. Inspect pending_payouts(); use reclaim_payout() only when you intend to invalidate the unredeemed tokens at the destination. An unresolved caller key remains a conservative historical marker, even after CDK has recovered or compensated the operation. After checking the outcome, the application may explicitly choose a new payout key. Do not blindly generate a new key after a failure.

Payouts support CDK's HTTP POST and encrypted Nostr NIP-17 request transports. The application can implement threshold or interval scheduling by checking cashu_balance() and supplying a distinct, persisted key for each intended send.

Caller-managed trusted wallets

Pass existing CDK wallets when the application owns their seed, database and configuration:

use satsettle::{Engine, EngineOptions};
use std::sync::Arc;

# async fn open(wallet: Arc<cdk::Wallet>) -> Result<Engine, satsettle::Error> {
let options = EngineOptions::default().with_trusted_wallets([wallet]);
let engine = Engine::open_with_options(".state/payments", options).await?;
# Ok(engine)
# }

Each wallet routes its own mint URL and must use sats. Duplicate mints and unconfigured trusted mints are errors; there is no fallback to a new managed wallet. Supply one wallet per trusted mint, or an empty list to disable managed trusted wallets. Receiving, trusted-mint settlement, balance and payout APIs all use this routing. Engine::open retains the managed-wallet default.

Satsettle still persists its coordination and receive-evidence journals, but creates no seed or wallet database for a supplied wallet. Reopen with the same mapping, durable wallet state and seed, and back up both the caller's wallet storage and Satsettle's directory together. Existing managed wallet directories cannot be switched to unrelated supplied wallets; this API is not a migration.

Keep mutation and recovery exclusive to this Engine for its entire lifetime. An Arc<Wallet> does not make external CDK operations or another Engine safe to run concurrently. Caller-owned databases, seeds and permissions are the caller's responsibility. Direct-settlement source wallets remain isolated; these trusted routes never receive arbitrary source proofs.

Caller-managed source wallets

EngineOptions::with_source_wallet_provider(Arc<dyn SourceWalletProvider>) delegates direct-settlement wallet construction to the caller. The asynchronous wallet() method receives a SourceWalletRequest with an opaque stable attempt ID, source mint, and unit, and returns an Arc<cdk::Wallet>. Retries, recovery and change export use the same attempt ID. The default remains a persistent SQLite wallet with a separate seed for every attempt.

A provider must resolve each attempt to its own durable database namespace and seed across restarts. Neither a new Arc nor another connection to the same database proves isolation. The Engine checks mint/unit and rejects known reuse of a wallet database handle across attempts or with a supplied trusted wallet; the provider must prevent aliases through other handles or processes. Keep mutation/recovery exclusive to the Engine and retain the state while melts, original-proof releases, or change recovery remain unresolved. Satsettle does not directly delete provider storage or create a fallback source database. Implement the required asynchronous discard() hook to remove rejected pre-melt attempts, including their seed and database namespace. It must be idempotent and return success for missing state; failed cleanup stays pending and counts against capacity. Wallet setup and cleanup calls are serialized and survive cancellation. An in-memory wallet alone cannot recover a crash safely.

Source URL validation still runs before provider lookup: unconfigured source mints require HTTPS on port 443. Deployment egress restrictions must block internal services, and any caller-configured connector must preserve that policy and its redirect restrictions. The default uses CDK's standard HTTP client; Satsettle does not add a custom transport. Provider injection retains the direct skip_swap melt, durable rejection decisions, original-proof release, and separation from trusted balances and payouts.

Caller-managed coordination storage

EngineOptions::with_store(Arc<dyn storage::PaymentStore>) supplies all payment coordination state. Engine::open(directory) retains the embedded redb default; storage::RedbStore::open(directory) also exposes that implementation explicitly. A supplied backend owns intents, receipts, authorizations, decisions, settlement and payout records, per-mint CashuJournals and the LightningJournal containing L402 keys and replay barriers, and private mint-invoice checkpoints. The Engine opens no fallback redb database. CDK wallet databases and seeds remain separate: configure trusted wallets and a source-wallet provider as needed, or retain the managed SQLite wallets.

Each storage method is an atomic domain operation. Success must mean durable commit, including related indexes. A cancelled caller can lose an acknowledgment; subsequent reads must wait for its write to finish, and retries must preserve immutable terms, receipt ownership and capture decisions. Store records include bearer tokens, preimages and signing keys: encrypt or restrict access as appropriate and back them up with the wallets. Preserve backend state and routing on restart.

An application backend may commit its resource reservation and the equivalent of decide_claim(authorization_id) in one database transaction. It can then call Engine::claim with that authorization to perform or recover the network effects. The durable decision must be visible through the backend before any claim RPC. A database transaction cannot include a Lightning RPC or a mint payment atomically; keep reservation fulfillment idempotent and recover uncertain network outcomes. Sharing a database does not authorize concurrent Engines or external wallet use.

The application owns database configuration, migrations and worker coordination. Satsettle ships the embedded adapter and storage traits; it does not provide a PostgreSQL adapter, leases or automatic failover. Keep one Engine owner for each store and its wallets. Before transferring ownership, stop the old owner and ensure its in-flight wallet operations have finished. Distributed failover needs application-provided fencing that covers wallet writes as well as journal writes; a shared database or an expiring lease alone does not provide that guarantee.

Backend implementers can enable the storage-contract-tests Cargo feature and call storage::contracts::run(factory). The async factory receives a namespace; start each namespace empty and reopen the same durable state on repeated calls. The suite checks concurrent receipt and decision ownership, immutable terms, settlement revisions, uncertain payouts, receipt evidence, credentials and restart recovery. Add backend-specific crash, cancellation and transaction isolation tests. The embedded adapter retains its existing layout and adds a versioned table for mint-invoice checkpoints. Custom backends must implement the mint-invoice CAS and receipt prerequisites as well as the existing storage contracts.

Storage and deployment

One Engine owns a backend and its wallets; share it using Arc. With the embedded backend, opening the same data directory twice fails. Use a local filesystem with reliable locking and fsync. Application operations are serialized in v0.1, including network I/O; event listeners persist observations concurrently. Put bounded concurrency, timeouts and rate limits around the public boundary. Dropping an in-flight future does not undo payments.

The store uses redb with immediate durability and versioned tables. Managed CDK wallets use SQLite. Managed directories are mode 0700 and files 0600; unsafe existing permissions, symlinks, and missing wallet seeds fail closed. Back up the entire directory while the engine is stopped, including wallet seeds, SQLite state, L402 root keys, hold preimages, authorizations, capture decisions, pending token material, intent receipts, and payout journals. Do not delete journals to reset a payment. v0.1 does not prune intents or receipts or automatically migrate Geata data; monitor disk use and start with a new directory.

Errors and public Debug implementations omit bearer material. Applications must also suppress secret-bearing dependency logs, particularly CDK wallet and macaroon debug logs. Do not enable blanket dependency trace logging on payment services. A private directory is access control, not encryption at rest.

Supported scope

  • Rust 1.95.0, edition 2024; Linux x86_64 and aarch64 Nix outputs. The Linux host platform is tested in CI. Other platforms are not promised in this version.
  • Cashu sats from one configured mint per intent, using CDK 0.18.0. V4 cashuB tokens need DLEQ proofs and no spending conditions; 16 KiB maximum.
  • L402 with request-bound V2 macaroons and LDK Server invoices on mainnet, testnet3, signet, and regtest. Manual capture supports all four networks. X402 and Both are rejected for signet/regtest before issuing an invoice.
  • x402 v2 exact/lnbtc over BOLT11, mainnet or testnet3. This is not general EVM/USDC x402 support. The HTTP binding helper retains Geata's http:1 or geata:http:2 profile; clients need to support the selected profile. These binding codecs do not install HTTP handlers.
  • LDK Server client pinned to 21a903ccb89496285c58a55ffdce42a9f0353a05, with authenticated TLS and method-specific macaroon permissions described above.
  • Automatic mint-backed L402 receiving through trusted Cashu wallets. The payer must supply a valid preimage; manual capture and x402 require LDK.

See DEVELOPMENT.md for Nix, Cargo, tests and architecture, and NOTICE.md for the Geata source attribution.