Repository navigation
feat(core): type a wallet balance shortfall as Error::InsufficientFunds (#152) - #224
Open
Nic-dorman wants to merge 1 commit into
Open
Nic-dorman wants to merge 1 commit into
Nic-dorman wants to merge 1 commit into
Conversation
Nic-dorman
force-pushed
the
fix/typed-insufficient-funds
branch
from
October 23, 2026 09:36
f6fc3ab to
795606a
Compare
…ds (#152) A wallet without enough payment tokens failed with the generic Error::Payment(String), so consumers (the mobile FFI/SDK, the CLI) could only tell "top up your wallet" from any other payment failure by matching message text. Since #152 was filed, file and data uploads moved to evmlib's journaled payment API, which returns InsufficientTokensForQuotes already rendered as a String, and ant-protocol re-exports neither evmlib::wallet::Error nor its variant (and ant-core may not depend on evmlib directly). So the variant cannot be matched by type on any path; evmlib's report text is the contract. Error::from_payment_failure parses its have/need amounts wherever the report sits in the message and is used at every wallet-backed payment site: the journaled native path, wave-batch and Merkle batch payment, the non-native upload adapter, and single-chunk payment (whose caller no longer re-wraps the typed error). The e2e_native_payment Anvil test now asserts InsufficientFunds { have: 0, need: 100 } for an unfunded wallet, pinning evmlib's wording through the real path: with the old mapping it fails with the Payment string. classify_error treats the new variant as an application error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nic-dorman
force-pushed
the
fix/typed-insufficient-funds
branch
from
October 23, 2026 18:52
795606a to
388c381
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #152. Upstream half of V2-601 (mobile SDK).
A wallet-backed payment with too few payment tokens failed with the generic
data::Error::Payment(String). Consumers such as ant-ffi, the mobile SDK and the CLI could only tell "top up your wallet" apart from any other payment failure by matching message text.Why it isn't the fix #152 describes
#152 proposed matching
evmlib::wallet::Error::InsufficientTokensForQuotes(have, need)by type before the generic stringify. That's no longer possible on any path:native_payment.rs→Wallet::prepare_payment). It returnsResult<_, String>and already calls.to_string()on that variant (evmlib-0.10.0/src/wallet/journal.rs:214).evmlib::wallet::Errornor its variant, onlyPayForQuotesErrorandWallet. ant-core's Cargo.toml forbids a direct evmlib dependency, so even the paths that still hold the typed error can't name it.That leaves evmlib's report text as the only contract, so the fix pins it with a test.
Change
data::Error::InsufficientFunds { have: Amount, need: Amount }(atto). Display:insufficient funds: wallet has {have} atto, payment needs {need} atto.Error::from_payment_failure(String)parses evmlib'sInsufficient tokens to pay for quotes. Have: … atto, need: … attowherever it appears in the message (callers prefix context). Anything else staysPaymentwith the message verbatim, and truncated or malformed reports are not guessed at.native_payment.rs, used by file and data uploads)pay_for_quotes(batch.rs×2)pay_for_merkle_tree(merkle.rs)upload.rs×2)payment.rsnow passes the typed error through instead of re-wrapping it asPayment("On-chain payment failed: …")classify_errortreats it asApplicationError, so the adaptive limiter isn't pushed down. The exhaustiveness guard and the covers-all-variants table are extended.PartialUpload, with this error's text as itsreason. That's the existing upload-level wrapper, and keeping it preserves spend reporting.Payment, as noted out of scope in Payment errors collapse insufficient-funds into generic Error::Payment(String) — add a typed InsufficientFunds variant #152.Linear issue
Closes V2-1507
Risk tier
Client-side error typing only. The payment flow, amounts, wire messages and node interaction are unchanged.
Compatibility
ant_core::data::Error, which gains a variant and is not#[non_exhaustive]. A downstream exhaustivematchmust add an arm.ant-sdk/ffi/rust/ant-ffi/src/lib.rsFrom<ant_core::data::Error>) has another =>catch-all, so it still compiles. But until it adds an arm, a shortfall maps toClientError::InternalErrorwhere it used to map toPaymentError. That arm (→ a newClientError::InsufficientFunds) is V2-601's follow-up.payment error: Insufficient tokens to pay for quotes. Have: …toinsufficient funds: wallet has … atto, payment needs … atto.Semver impact
Test evidence
Rebased 2026-10-23 onto
mainat50bc653(0.3.12 promoted), 66 commits newer. Two changes came with it:CHANGELOG.mdconflicted with the new[Unreleased]entries; both sides were kept.mainhas since added an exhaustiveError::duplicate()(shared in-flight reads, perf(read): share in-flight chunk fetches across concurrent reads #218), so it gains anInsufficientFunds { have, need }arm.Every direct wallet payment call site on the new
mainstill maps errors throughError::from_payment_failure. Re-run on Linux (dev2, stable):cargo fmt --all -- --check,cargo clippy --all-targets --all-features -- -D warningsand the browser-wasm clippy: pass.cargo test -p ant-core --lib data: pass.e2e_native_payment: 4 passed, includinginsufficient_funds_then_funding_unblocks_native_payment.Original evidence follows.
Real-chain guard:
e2e_native_payment::insufficient_funds_then_funding_unblocks_native_payment(Anvil) now assertsErr(InsufficientFunds { have: 0, need: 100 })for an unfunded wallet through the real journaled path. With the oldmap_err(Error::Payment)restored, it fails withexpected InsufficientFunds, got Err(Payment("Insufficient tokens to pay for quotes. Have: 0 atto, need: 100 atto")). That confirms both evmlib's live wording and that the test catches a regression.cargo test -p ant-core --features native,test-utils --test e2e_native_payment: 4/4 pass.Unit tests:
have/needaboveu64::MAXto prove the U256 round-trip.Paymentverbatim.classify_error_covers_all_variants.cargo fmt --all -- --check✓ ·cargo clippy --all-targets --all-features -- -D warnings✓ ·cargo test -p ant-core --lib: 804 passed ·cargo test -p ant-cli: 30 passed. All run on Windows 11.Prod compat smoke on mainnet (2026-10-23), rebased head
388c3814againstmainccd4106d. Run on Linux (dev2, stable 1.99.1) with--lockedrelease builds.The wallet is a freshly generated key, and
ant wallet balancereports0. It holds neither tokens nor gas, so nothing can be paid.Uploading a random 3 MiB file (3 chunks) collects real mainnet quotes and then fails at payment:
main--no-merkleFile upload failed: payment error: Insufficient tokens to pay for quotes. Have: 0 atto, need: 228374654296875000 attoFile upload failed: insufficient funds: wallet has 0 atto, payment needs 228374654296875000 atto--merkleFile upload failed: payment error: Insufficient tokens to pay for quotes. Have: 0 atto, need: 185711296875000000 attoFile upload failed: insufficient funds: wallet has 0 atto, payment needs 294520417968750000 attoBoth builds exit 1 before sending any transaction. The
needamounts differ between runs because quotes vary.This confirms that evmlib's wording on the live network still matches what
from_payment_failureparses, on both payment paths.A mainnet
ant file downloadof a public canary file (3cba93c8…, 5,277,908 B) with this build also succeeded, and the byte count matches.New dependency
none
ADR
n/a
Mitigation / rollback
Revert the commit. Consumers that already match
InsufficientFundsgo back to seeingPayment(String).🤖 Generated with Claude Code