Skip to content

Gas sponsorship

AttestMesh nodes never hold ETH. Funding ephemeral, attested machines with gas money would be an operational and security headache — so every state-changing call a node makes is an EIP-4337 UserOperation, paid by a paymaster under the operator’s policy.

sidecar ──► bundler (eth_sendUserOperation)
├─► paymaster policy ──► sponsorship webhook « operator's gate »
│ approve / deny
└─► EntryPoint ──► ClusterMember.validateUserOp ──► execute
  1. The sidecar wraps the real call (registration, send, setCskCommitment) as ClusterMember.execute(cluster, 0, calldata) and requests sponsorship.
  2. The paymaster consults the operator’s sponsorship webhook before signing.
  3. The bundler submits; the node’s account validates the signature on chain.

The very first UserOperation a node sends is its own registration — at which point its account has no owner key on chain yet. validateUserOp handles this with a bootstrap path: for a dstack_register call, it recovers the signer from the attestation proof inside the operation itself, and the registration installs that key as the permanent owner. One sponsored operation takes a node from “nothing on chain” to “registered member with a working smart account.”

Every later operation verifies against the installed owner — the same TEE-derived key, re-derived on each boot. (The account’s nonce comes from EntryPoint.getNonce; see field notes for the bug that taught us to fetch it correctly.)

The sponsorship webhook is a small Cloudflare Worker that answers one question: is this UserOperation AttestMesh traffic the operator wants to pay for? It checks:

  • the outer shape — must be ClusterMember.execute into a known cluster (deployed by the recognized factories, or an owner-allowlisted app id on the Path A flow);
  • the inner selector — must be on the allowlist of cluster operations: registration, wireguard key publication, encrypted send, the CSK commitment, and the cluster-admin operations (allowlist management, ownership transfers);
  • the chain id and the policy’s shared token.

Deny means the operator doesn’t pay — it is a spending control. Actual access control lives on chain (membership gates, owner gates) and stands on its own even if the webhook approved garbage.

  • The webhook deploys with deploy/webhook.sh, which also asserts the custom-domain route — a stale route surfaces as bundler 401s and is the single most-debugged failure in this system’s history.
  • Costs are small: a node’s entire lifecycle (registration, a handful of envelopes, CSK commitment) is a few sponsored operations; steady state adds envelopes only when membership changes.