Skip to content

feat(payments): reuse cached credentials in send [AMB-3309] - #57

Merged
wthrajat merged 3 commits into
mainfrom
feat/AMB-3309-reuse-cached-send-credentials
Oct 2, 2026
Merged

wthrajat merged 3 commits into
mainfrom
feat/AMB-3309-reuse-cached-send-credentials

Conversation

@bufo24

@bufo24 bufo24 commented Oct 2, 2026

Copy link
Copy Markdown
Member

Problem

Transactions.send() derives node credentials on every call when a password is passed: two GraphQL round trips plus two Argon2id passes (m=64 MiB, t=3, p=4). Only prepareSend() filled the cache, and only a send() without a password read it.

Measurement (litd-mainnet-144d77c6, 100 sat payments to one Lightning address, one at a time)

Time from the send() call to the REST request reaching the node:

Mode Runs Range Median
send() with password (derives every time) 10 8.4 s to 47.6 s (8.4 s to 14.6 s without one outlier) 10.3 s
prepareSend() once, then send() without password 5 1.2 s to 3.3 s 1.4 s

Change

  • send() and prepareSend() now share one private path (#prepare). A cache hit is used even when a password is passed. A miss derives once and stores the result, so the first payment warms the cache.
  • Concurrent sends for one wallet share the existing in-flight derivation (#pending).
  • Kept: a failed derivation is never cached; forgetSend() during a derivation stops the result from being stored (that one send() still pays with what it derived); sandbox wallets work; retryPayment reads the same cache.
  • Docs updated: README, docs/INTEGRATION.md, AGENTS.md, JSDoc on send, prepareSend, isSendReady, forgetSend, PaymentsConfig.send and SendParams.password.
  • No new options or APIs.

Behavior changes to review

  1. A decrypted node admin macaroon now stays in process memory after the first send(), with no expiry. Before, this happened only after an explicit prepareSend().
  2. A wrong password no longer fails a send() when credentials are already cached. It still must be non-empty.
  3. If node credentials rotate, callers must call forgetSend(walletId).

Also: a teamId override passed to send() is ignored on a cache hit.

Open question

Do we want an opt-out (for example a per-call flag to skip the cache) for callers that must not keep the macaroon in memory? Not added here; forgetSend() after a run of sends is the current way.

Test plan

  • pnpm build passes
  • pnpm typecheck passes (the repo has no lint script)
  • pnpm format:check passes
  • pnpm test: core 11/11, payments 82/82 pass
  • New tests were written first and failed on the old code. They cover: cache hit with a different password (no derivation calls), first send warms the cache, consecutive sends derive once, concurrent sends share one derivation, send plus in-flight prepareSend share one, failed derivation not cached, forgetSend mid-derivation not stored, second sandbox send skips the wallet lookup.
  • No live payments were run.

Linear: https://linear.app/amboss/issue/AMB-3309

🤖 Generated with Claude Code

bufo24 and others added 3 commits October 2, 2026 17:15
…AMB-3309]

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e [AMB-3309]

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A send that joined another caller's in-flight derivation inherited that
caller's failure, so a wrong-password send (or a passwordless prepare)
could fail a concurrent send with the right password. A joiner now
derives with its own credentials when the derivation it joined rejects.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@bufo24
bufo24 force-pushed the feat/AMB-3309-reuse-cached-send-credentials branch from 067132b to 694cc81 Compare October 2, 2026 16:16
@wthrajat
wthrajat merged commit 3791990 into main Oct 2, 2026
2 checks passed
@apotdevin apotdevin mentioned this pull request Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants