Skip to content

Sponsored writes fail when funds are in address balance (Enoki: 'Invalid bcs bytes for TransactionData') #93

@funkiirabu

Description

@funkiirabu

Summary

Sponsored writes (save_deposit, repay_debt, send_transfer, swap_execute) fail at the Enoki execute step with:

gas_station_error: Request to gas station failed
{"error":"Invalid bcs bytes for TransactionData"}

…whenever the user's source funds live in their Sui address balance (the new gasless-stablecoin / Address Balances feature) rather than as coin objects.

Root cause

@mysten/sui@2.17.0's coinWithBalance resolver (transactions/intents/CoinWithBalance.mjs) sources from the address balance when addressBalance >= totalRequired, emitting 0x2::coin::redeem_funds + a FundsWithdrawal reservation input. That FundsWithdrawal is a new TransactionData field. Enoki's sponsor endpoint accepts the kind bytes (200), but its gas station can't deserialize the resulting TransactionDataInvalid bcs bytes.

Notably coinWithBalance takes the address-balance branch even when coin objects exist, as long as the address balance alone covers the amount.

Evidence

  • devInspectTransactionBlock of the exact composed save_deposit kind bytes succeeds on mainnet → the PTB is valid on-chain; the fullnode parses FundsWithdrawal fine. Only Enoki's gas station rejects it.
  • Repro with a wallet holding coin objects + $0 address balance → clean PTB (MergeCoins/SplitCoins), no FundsWithdrawal, would sponsor fine.
  • Repro wallet with 100% address-balance USDC → PTB contains coin::redeem_funds + FundsWithdrawal → Enoki rejects.
  • Enoki app config verified healthy (keys unchanged, gas pool funded, 47 prior sponsored txs OK) — not a config/credit issue.

Why this matters

Gasless stablecoin transfers (Audric Pay receives, bridges, Circle) deposit USDC into the recipient's address balance. As these spread, more wallets become address-balance-only, and every sponsored write against those funds silently breaks.

Options

  • A. Enoki adds FundsWithdrawal TransactionData support — the real, permanent fix. On Mysten's side (escalation filed separately). Tracks the address-balance-only case.
  • B. Self-funded fallback when the user holds SUI (sign + submit to fullnode directly, bypass Enoki). Stopgap; breaks gasless promise; most zkLogin users have no SUI.
  • C. SDK coin-object-only sponsored path (this issue's deliverable). In sponsoredContext, source from coin objects only; if insufficient, throw a clear typed error (ADDRESS_BALANCE_UNSPONSORABLE) instead of the cryptic Enoki failure.

Fix C — planned scope

  • Centralize in packages/sdk/src/wallet/coinSelection.ts (selectAndSplitCoin): when sponsoredContext, fetch coin objects via getCoins → merge → split; never call coinWithBalance.
  • Throw ADDRESS_BALANCE_UNSPONSORABLE with actionable copy when coin objects don't cover the amount.
  • Engine preflight/guard surfaces the message before the confirm card.
  • Covers save_deposit / repay_debt / send_transfer (all via selectAndSplitCoin). Follow-ups: swap_execute (Cetus addSwapToTx uses coinWithBalance directly) + SUI path (selectSuiCoin).

Limitation: C does NOT unblock address-balance-only wallets (no coin objects to use). Those depend on A or B.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions