DEV Community

Swift Bot Labs
Swift Bot Labs

Posted on

Ref: KB-2410-09 | An agent paid real APIs over Lightning (Wavelength mainnet) vs x402 on Base: field test

Ref: KB-2410-09

Swift Bot Labs, run on 2026-10-27 between 10:29 and 15:54 UTC. Everything below is from our own runs on Bitcoin mainnet and Base mainnet. Raw logs, driver scripts and the account proof: https://github.com/swiftbotlab/wavelength-field-test

TL;DR

  • A headless agent can pay real L402 APIs from Wavelength on Bitcoin mainnet. But most API calls cost under 1,000 sats, and Wavelength refuses those by default ("below the 1000 sat minimum for pay swaps"). They only go through after you opt into server credit, which starts with a 1,000-sat top-up held by the operator.
  • Speed: a real payment settled in about 6 s, and the full 402 -> paid 200 round trip took 10-13 s. The same kind of call over x402 (USDC on Base) took a median 3.1 s.
  • Cost: getting 10,553 sats into the wallet from Base cost 8.9% (swap) plus 2.4% (boarding) and took 88 minutes. Each small call then cost 1-2 sats in fees, although the CLI reported fee_sat: 0 every time.
  • One seller took 5 sats and then answered 404 to the paid request. L402 has no refund path.
  • Limits match the launch post: 300,000 sats per account and 50,000 sats per coin, so no single receive can exceed 50,000.
  • The practical weak spot was the chain backend: public Esplora rate limits first hid our deposit, then kept a receiving wallet from using its coins for 22 minutes.
  • A wallet whose identity we never submitted for mainnet access connected, received and sent. In our test the docs' allowlist was not enforced, which fits the launch post's "live on mainnet for everyone".
  • Exit: one command (wavecli send <address> --onchain --sweep-all) took the remaining 9,298 sats out in the next hourly round, fee 528 sats (5.7%); 54 minutes from command to ETH on Base. Round trip: $9.22 of ETH in, about $6.83 of ETH back. Of the $2.39 difference only about $0.09 bought API calls (109 sats plus 5 sats of fees); the rest went to the swap in ($0.82), boarding ($0.20), the exit fee ($0.42), the swap out (about $0.16), and $0.70 still sitting as server credit.

Facts checked against public sources

claim public source (read 2026-10-27) what our account showed
Wavelength runs on Bitcoin mainnet Lightning Labs, "Ride the Wave: Integrate Bitcoin Payments with Wavelength on Mainnet" (Oct 23, 2026, https://lightning.engineering/posts/2026-10-23-wavelength-mainnet-launch/): "now live on mainnet for everyone" getinfo network mainnet; deposit and round transactions confirmed in Bitcoin blocks 973383 and 973392
Fees: 0.25% on each side plus routing same post: "Lightning payments have a 0.25% service fee for both sending and receiving, plus normal Lightning routing fees. Moving funds between on-chain and Wavelength carries a small service fee, which is dynamic" our sub-1,000-sat calls went over server credit and cost 1-2 sats each (not a percentage); boarding cost 255 sats; the cooperative exit quoted 528 sats; the CLI's fee_sat field said 0 in every case (section 3)
Per-account cap 300,000 sats same post: "each mainnet account can hold up to 300,000 sats, and each individual VTXO can hold up to 50,000 sats" getinfo max_user_balance 300,000; any single receive above 50,000 sats is refused (section 5)
Alpha same post: "This is still an alpha" the rough edges in sections 2-5 fit that label
BTC-only for now; stablecoins via Taproot Assets announced post: "with stablecoins coming soon"; docs (https://wavelength.lightning.engineering/introduction/what-is-wavelength-sdk.md): "Stablecoin support is on the way through Taproot Assets" our wallet held and paid only sats
Mainnet access is allowlisted docs ("Mainnet access") say the server accepts only approved clients; the launch post says "live on mainnet for everyone" a wallet we never submitted connected, received and sent (section 4)

1. Setup (first-hand)

  • Client: Wavelength v0.2.0, linux-arm64 release from github.com/lightninglabs/wavelength. Checked the tarball's SHA-256 against manifest-v0.2.0.txt and both release signatures with GPG before running anything.
  • Daemon: waved --network=mainnet --allow-mainnet --wallet.type=lwwallet --wallet.esploraurl=https://mempool.space/api --server.host=wavelength.lightning.finance:443 --swap.serveraddress=swap.wavelength.lightning.finance:443. The agent drives wavecli --json from Python; no browser and no human clicks in the payment path.
  • Mainnet endpoints are not in the docs ("Mainnet has no public preset"). The signet preset names (signet.wavelength.lightning.finance, swap.signet.wavelength.lightning.finance) are in the binary; the matching mainnet hostnames resolve in DNS, and the daemon connected to them.
  • Mainnet access: the docs say the mainnet server only accepts allowlisted clients. We filed the access form with our identity_pubkey on 2026-10-27 ~07:00 UTC. At 10:29 UTC, before any approval email, wavecli getinfo already reported server_connected: true on mainnet with the server limits below. A second, fresh wallet whose identity was never submitted also showed server_connected: true. So in our test the allowlist was not enforced at connection time. (Whether receiving and sending enforce it: see section 4.)

getinfo server_info on mainnet (2026-10-27 10:29 UTC):

field value
max_user_balance 300,000 sats
max_vtxo_amount 50,000 sats
min_boarding_amount 1,000 sats
min_operator_fee 1,000 sats
dust_limit 1,000 sats
fee_rate 0
min_confirmations (boarding) 6
boarding_exit_delay / vtxo_exit_delay 512 / 144 blocks

The max_user_balance of 300,000 sats matches the published per-account cap.

2. Funding the agent wallet (stablecoin -> BTC -> Wavelength)

The agent holds USDC/ETH on Base, not BTC, which is the realistic starting point for many agents.

  • No route paid a Lightning invoice from Base for us: SideShift's Lightning settlement was disabled on 2026-10-27, so we used the on-chain boarding address (wavecli recv --onchain), the fallback in our plan.
  • Swap: SideShift fixed quote, 0.004 ETH on Base -> 0.00010553 BTC (10,553 sats) to the boarding address. Base deposit tx 0xddbd4f2397cb70a2fc85261b8f24d61aa25047d9c13dad7d9a8a91a5f2403ae7 (10:33 UTC). BTC settlement tx 44803060c283e5a1f5f2d48b897f32ac96b782e6ad8721f9cba48db2196fee1f (broadcast 10:34 UTC, 6.99 sat/vB).
  • Cost: at the prices shown on SideShift at the time (ETH $2,305.71, BTC $79,650.65), 0.004 ETH = $9.22 and 10,553 sats = $8.41. About $0.82 (8.9%) went to spread + the Bitcoin network fee. For a small agent wallet, getting sats in costs far more than paying for calls.
  • Time: Base deposit 10:33 UTC -> BTC sent by SideShift 10:34:43 -> first confirmation 10:51:05 (block 973383) -> 6th confirmation 11:36:00 (block 973388, the server's min_confirmations) -> the wallet registered the boarding at 11:44:16 (after the Esplora fix below) -> the server's next scheduled round (registration opens at :55, cutoff at :00 each hour) signed it at 12:00:08 and completed at 12:02:21. From BTC sent to first spendable sat: 88 minutes, mostly Bitcoin block times on the day (blocks came 10-45 minutes apart) plus the wait for the hourly round.
  • Snag: the light wallet (lwwallet) reads the chain through a public Esplora API and, by default, polls it every 30 s. With two wallets running, mempool.space started answering HTTP 429 and the daemon logged "Address index filter failed; scanning blocks", so it saw the confirmed deposit late. Moving to blockstream.info did not help for long: within minutes it rate-limited us too (its notice: 700 requests per hour per IP), and the daemon would not even start ("start tip poller: get initial tip height: HTTP 429"). What fixed it: back on mempool.space with --wallet.pollinterval=120s, one Esplora provider per wallet, and no other polling of the same host from that machine; the deposit was then picked up within three minutes. An agent running unattended should bring its own Esplora or a keyed endpoint.

3. Paying L402 APIs from the agent

Boarding left 10,298 sats spendable out of 10,553 deposited (255 sats, 2.4%, went to boarding, although the wallet's activity entry shows fee_sat: 0). The round that boarded us was signed at 12:00:08 UTC and completed at 12:02:21.

All three endpoints price their calls below 1,000 sats, so every call went over the opt-in credit rail (--max-credit 1500 --max-credit-topup 1000). The first call moved 1,000 sats from the wallet into server credit; the later calls were paid from that credit.

Distinct real payments (each one is a separate Lightning payment in the wallet's ledger):

time (UTC) endpoint price pay (wavecli send, until preimage) 402 -> 200 total result
12:02:41 api.lightningenable.com ip-geolocation 2 sats 14.3 s (includes the 1,000-sat credit top-up) 19.2 s 200, 598 bytes
12:04:14 api.lightningenable.com currency-exchange /rates 5 sats 5.5 s 10.4 s 404 with an empty body
12:04:54 api.agentpaywire.com /v1/search 100 sats 6.0 s 12.8 s 200, 4,083 bytes
12:13:13 api.lightningenable.com ip-geolocation 2 sats 6.0 s 10.8 s 200, 597 bytes

What the numbers do not show on their own:

  • Same invoice, no double payment. lightningenable kept handing our client the same invoice and macaroon for about 10 minutes, even after we paid it. Our driver therefore "paid" the same invoice again on repeat calls; wavecli send returned the original completed payment in about 0.4 s and the ledger shows no second payment. For an agent that retries, this idempotency (per payment hash) is the right behaviour.
  • Paid, got nothing. The currency-exchange endpoint took the 5 sats, returned a valid preimage, and then answered the authorised retry with HTTP 404 and an empty body, every time, including minutes later. L402 itself has no refund path; the agent simply lost 5 sats. An agent should probe a paid endpoint with its cheapest call first.
  • Tokens stay valid. The paid macaroon:preimage for ip-geolocation and agentpaywire still returned 200 minutes later; agentpaywire's 100 sats bought 100 credits valid for 30 days, and four reuses answered in 1.7-3.8 s with no new payment.
  • Fees. Every send reported fee_sat: 0, but the server credit fell by the price plus 1-2 sats each time (1,000 -> 997 after the 2-sat call, 991 after the 5-sat call, 889 after the 100-sat call, 886 after the second 2-sat call): 5 sats of fees on 109 sats of payments. On calls this small that is 1-50% per call, not 0.25%.
  • Speed. A real credit-rail payment settled in 5.5-6.0 s; the full 402 -> paid 200 round trip took 10-13 s, about three to four times the x402 median below.

4. Receiving (charge side)

We had no outside Lightning wallet holding sats, so the receiver was a second Wavelength wallet (B) on the same mainnet server; the plan said "an external wallet", and this is the honest substitute.

  • B issued a 1,000-sat BOLT-11 invoice with wavecli recv --offchain --amt 1000; A paid it. The daemon chose the in-Ark rail (SEND_RAIL_IN_ARK) by itself: fee 0, A's send completed in 11.8 s.
  • B's ledger showed RECV COMPLETE at once, but the 1,000 sats only became spendable 22 minutes later. To accept an out-of-round payment, the receiver's own chain backend must find the round commitment behind the sender's coins; B's public Esplora (mempool.emzy.de) did not serve that transaction, and the daemon kept logging "resolve commitment ...: context deadline exceeded" until we pointed it at blockstream.info, after which the coins materialized within a minute.
  • B then paid 1,000 sats back to A over the same rail: fee 0, 14.2 s.
  • B's identity was never submitted to the mainnet access form, yet it connected, received and sent. In our test the allowlist was not enforced for any of these steps. (Our own wallet A was submitted, and no approval email had arrived by the end of the test.)
  • The received coins carry an expiry (batch_expiry=975408, about two weeks of blocks): an idle agent wallet has to refresh its coins before then. The server advertises free_refresh_window_blocks: 648; we did not test a refresh.

5. Limits and errors

Exact errors returned by wavecli --json on mainnet (2026-10-27):

test result
recv --offchain --amt 300001 (above the 300,000 cap) FAILED_PRECONDITION: "amount exceeds the operator's maximum VTXO size: receive of 0.00300001 BTC exceeds the per-VTXO maximum of 0.00050000 BTC" (retryable: false)
recv --offchain --amt 300000 (exactly the cap) same error: the per-VTXO maximum (50,000 sats) applies first
recv --offchain --amt 50001 same error, per-VTXO maximum 0.00050000 BTC
recv --offchain --amt 5000 BOLT-11 invoice returned
pay a 2-sat L402 invoice with default flags INVALID_ARGUMENT: "invoice amount_sat 2 is below the 1000 sat minimum for pay swaps (operator VTXO minimum)"
same invoice with --max-credit 1500 --max-credit-topup 1000 (dry run) quote OK on rail SEND_RAIL_CREDIT: amount 2, fee 2 sats, plus a one-time 1,000-sat top-up into server credit
pay an expired invoice (our second wallet's 1,000-sat invoice, created 11:09:48, 1 h expiry) INVALID_ARGUMENT: "quote in-ark swap: receive intent expired" (retryable: false), same with --dry-run and a real send; nothing left the wallet. Wavelength's own invoices expire after 1 hour; the L402 sellers' invoices we saw carry 24 hours (x=86399)

What this means in practice: the published 300,000-sat account cap is real, but a single Lightning receive is limited to 50,000 sats, and anything under 1,000 sats (which is most pay-per-call API pricing) cannot go through the normal swap path at all. Small calls work only through opt-in server credit: the agent pre-funds 1,000 sats of credit with the operator and pays from it. The CLI does not do this by default; an agent has to pass --max-credit and --max-credit-topup.

6. The same kind of call over x402 (USDC on Base)

Endpoint: api.agentpaywire.com/v1/search answers one 402 with both options: L402 (100 sats for 100 credits valid 30 days) and x402 v2 (exact, 0.01 USDC per call on Base, facilitator api.cdp.coinbase.com/platform/v2/x402). Our agent paid it 5 times with x402 (EIP-3009 TransferWithAuthorization signed locally; the seller's facilitator paid the gas):

# time (UTC) 402 (ms) sign (ms) paid request (ms) total (ms) Base tx
1 10:37:00 950 11 2,536 3,497 0xb3a7e48a5e82c408b0ca025b19fb7722a43ae9b60a6093131c0516fe3e561e2c
2 10:37:10 1,005 5 2,132 3,142 0xb492262fe74ab005cedec2446af84492ca996139e6bc07b0a736503cd87afbba
3 10:37:17 2,346 6 1,733 4,085 0xbde5927d26b91506d77fcb54b3e0eecfd5edc0fff3c60a8bcd4a44abf6345034
4 10:37:21 780 6 2,188 2,975 0x1fba3de7db40be3520fce9544170f2c77e7d1b869b0d40b903ff7195bdfe0db8
5 10:37:26 955 5 1,773 2,733 0x99c47ab3afdade97f50e88f73036e6af63f3ca97a0e52b181e5a48b355756893

All 5 returned HTTP 200 with the paid result (4,056 bytes). Median 3.1 s from first request to paid response; cost 0.01 USDC per call, no gas for us. Steps: GET -> 402 -> read accepts -> sign one typed-data message -> retry with PAYMENT-SIGNATURE. The x402 side needed USDC already on Base; no channel, no boarding, no wait.

7. Comparison

L402 on Wavelength (Bitcoin mainnet) x402 (USDC on Base)
Setup before the first paid call install + verify the daemon, create a wallet, request mainnet access, get BTC, board it (6 confirmations + a round), unlock a Base wallet holding USDC; nothing to install for the protocol itself
Funding cost for us 8.9% of $9.22 lost in the ETH->BTC swap (spread + Bitcoin fee), plus 255 sats (2.4%) at boarding none beyond having USDC on Base
Steps per call GET -> 402 -> pay invoice (wavecli send, waits for the preimage) -> retry with macaroon:preimage GET -> 402 -> sign typed data locally -> retry with PAYMENT-SIGNATURE
Price of the same search endpoint 100 sats for 100 credits valid 30 days 0.01 USDC per call
Fees on a tiny call 1-2 sats per call from server credit (5 sats on 109 sats paid), reported as fee_sat: 0; payments between two Wavelength wallets: 0 0 for the payer (facilitator pays gas)
Time 402 -> 200 10.4-12.8 s for a real payment (19.2 s for the first, which included the credit top-up), n=4 median 3.1 s (2.7-4.1 s, n=5)
Where it got stuck invoices < 1,000 sats refused unless credit is enabled; single receive capped at 50,000 sats; public Esplora rate limits hid our deposit; a receiver could not use incoming coins for 22 min; one seller took 5 sats and answered 404 nothing in our 5 runs

7b. Getting the money back out

  • wavecli send 32Jcu66LAonGfFqnJoH4srozgZpGQAyxBa --onchain --sweep-all --amt 0 at 12:40:15 UTC (the address is a SideShift BTC -> ETH-on-Base deposit address). The quote showed a 528-sat fee (5.7% of 9,298 sats); the CLI's entry again said fee_sat: 0.
  • It waited for the next scheduled round: a leave-only round at 13:00:03 put 8,770 sats in commitment tx c4634f0c8d1c89c0d55aa4d3a20fffd6359d70401a4dde59c451dc95f59ff824 (1.02 sat/vB).
  • SideShift saw the deposit at 13:01 and paid out 0.00296125 ETH on Base at 13:34 (tx 0xfe7b2b57cfe0e53bb77ada70bf776da24c84d061fc43aaf0f7a5783c6c4a98b2).
  • 886 sats of server credit stayed with the operator: the on-chain sweep left it untouched, and we found no command that withdraws credit; it stays usable for later invoices.

8. What we did not test / limits of this report

  • No outside Lightning wallet: the receive test used a second Wavelength wallet on the same server, so it exercised the in-Ark rail, not a real Lightning route into Wavelength.
  • No payment of 1,000 sats or more to an outside Lightning node: the swap rail that the 0.25% fee applies to was not exercised, so that fee is not measured here.
  • No coin refresh, no unilateral exit, no failure of the operator.
  • Small sample: 4 distinct real L402 payments, 5 x402 payments, one day, one machine (Linux ARM64, outbound only).
  • The x402 side ran against one seller and its facilitator; other x402 sellers may behave differently.

Top comments (0)