File: frontend/components/forms/ApproveForm.tsx lines 22, 62 and 70
Issue: The expiry defaults to a year and is converted with a raw literal:
expirationDays: "365",
...
return currentLedger + parseInt(days || "365") * 17280;
365 × 17,280 is 6,307,200 ledgers. The network maximum is 3,110,400 on both testnet and mainnet (see #398), so the default is 2.03x the ceiling. The schema at line 22 bounds the field but not against the network limit.
This is the input that makes #399 the common case rather than a corner case. Accepting the default produces an allowance whose stored expiration_ledger the network cannot honour: approve succeeds because it clamps the TTL it requests, and then the spender's first partial transfer_from or burn_from reverts with an opaque host error, while a full spend works. So the default path through the Allowances UI creates allowances that fail in the most confusing possible way, and the user who did nothing but accept the default is the one who hits it.
The 17280 literal is also the last raw copy outside recentTokens.ts, which open issue #367 covers.
Fix: Read the ceiling from the contract or the network rather than assuming it — env.storage().max_ttl() is exposed on-chain and the value is available from the STATE_ARCHIVAL config setting off-chain — and bound expirationDays in the Zod schema against it, with a validation message naming the maximum. Default to 30 days, which covers the actual use case and leaves generous headroom. Import LEDGERS_PER_DAY rather than re-typing 17280. Land it with #399 so the contract stops accepting the bad value at the same time the UI stops producing it.
File:
frontend/components/forms/ApproveForm.tsxlines 22, 62 and 70Issue: The expiry defaults to a year and is converted with a raw literal:
365 × 17,280 is 6,307,200 ledgers. The network maximum is 3,110,400 on both testnet and mainnet (see #398), so the default is 2.03x the ceiling. The schema at line 22 bounds the field but not against the network limit.
This is the input that makes #399 the common case rather than a corner case. Accepting the default produces an allowance whose stored
expiration_ledgerthe network cannot honour:approvesucceeds because it clamps the TTL it requests, and then the spender's first partialtransfer_fromorburn_fromreverts with an opaque host error, while a full spend works. So the default path through the Allowances UI creates allowances that fail in the most confusing possible way, and the user who did nothing but accept the default is the one who hits it.The
17280literal is also the last raw copy outsiderecentTokens.ts, which open issue #367 covers.Fix: Read the ceiling from the contract or the network rather than assuming it —
env.storage().max_ttl()is exposed on-chain and the value is available from theSTATE_ARCHIVALconfig setting off-chain — and boundexpirationDaysin the Zod schema against it, with a validation message naming the maximum. Default to 30 days, which covers the actual use case and leaves generous headroom. ImportLEDGERS_PER_DAYrather than re-typing 17280. Land it with #399 so the contract stops accepting the bad value at the same time the UI stops producing it.