Skip to content

Batched user operations for backend<>app interaction - #40

Merged
ManulParihar merged 10 commits into
mainfrom
feature/userop
Sep 25, 2026
Merged

ManulParihar merged 10 commits into
mainfrom
feature/userop

Conversation

@ManulParihar

Copy link
Copy Markdown
Member

What this does

Some SDK methods send several calls in one user operation, and they read chain state first to decide which calls to include. Until now only the mobile app could run that logic. This PR puts it in one shared place, so the backend can build the exact same calls and send them to the app to sign.

Why

The backend creates the calldata, the app signs it with its passkey, and the backend confirms the purchase from the contract event (via the Alchemy webhook). For batched methods, the backend would otherwise have to copy the SDK's logic and keep that copy in sync with the SDK.

What's new

For the backend (kokio-sdk/admin)

A new admin.calls surface. Each method returns calls and never signs or sends anything.

  • admin.calls.buyDataBundleWithTransfer(eSIMWallet, bundle, asset, maxAmountIn, paymentReference)
    Returns a token transfer for whatever the eSIM wallet is short of the price, followed by the purchase. If the eSIM wallet already holds enough, it returns only the purchase.
  • admin.calls.acceptAndBindESIMWallet(eSIMWallet, newDeviceWallet, { grantAccessToFunds })
    Returns the calls a new device signs to take over an eSIM wallet: accept the transfer, then bind it. Fund access is off unless grantAccessToFunds: true is passed.

For the app (kokio-sdk)

  • kokio.eSIMWallet.acceptAndBindESIMWallet({ grantAccessToFunds }) is new. Moving an eSIM to a new device now takes one passkey prompt instead of two or three.
  • kokio.eSIMWallet.buyDataBundleWithTransfer works the same as before, and now uses the shared code.
  • To sign calls the backend built: kokio.deviceWallet.sendUserOperation(calls).

How it fits together

  1. The backend builds the calls with admin.calls.* and returns them from its endpoint.
  2. The app signs them with deviceWallet.sendUserOperation(calls).
  3. The backend confirms from the contract event: DataBundleBoughtWithToken for a purchase, or the device wallet changing for a transfer.

Things to know

  • A purchase's transfer amount is worked out when the calls are built. If the eSIM wallet's balance or the price changes before the user signs, the operation reverts. On a retry, build the calls again with the same payment reference.
  • acceptAndBindESIMWallet only works for the device wallet named in the transfer request. It reverts if the old device cancelled the transfer first.
  • No contract changes. No breaking changes to existing SDK methods.

Tests

  • Unit tests for both call builders (with and without a shortfall, with and without fund access) and for the new admin.calls surface.
  • The consumer user flow on a Base Sepolia fork now includes:
    • a purchase the backend builds and the app signs, confirmed by its event
    • moving an eSIM to a new device, with the backend building the new device's calls
  • All unit tests and fork consumer tests pass. The live Base Sepolia run was not repeated. It now needs 3 USDCt and one more admin transaction than before.

Docs

  • New page: docs/admin/calls.md
  • docs/mobile/esim-wallet.md covers acceptAndBindESIMWallet and how to sign calls built by the backend.

Version

3.1.0 to 3.2.0

@ManulParihar
ManulParihar merged commit f88ea31 into main Sep 25, 2026
@ManulParihar
ManulParihar deleted the feature/userop branch September 25, 2026 09:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant