Shell Chain Native Account Abstraction Guide

Shell Chain implements account abstraction at the protocol layer. Every user account is treated as a smart account from the start: the chain validates post-quantum signatures natively, uses 32-byte native addresses end-to-end inside the PQVM, and exposes each address as 0x + 64 lowercase hex.

See also: Quickstart Guide · JSON-RPC API Reference · Post-Quantum Cryptography Guide


1. What "native AA" means on Shell Chain

Shell Chain does not rely on ERC-4337's EntryPoint / Bundler architecture. Instead, transaction validation is part of the base protocol:

  • Default path: built-in post-quantum signature validation
  • Upgradeable path: account-specific validation contract logic
  • Stable account identity: address stays the same across key rotation
  • PQVM execution: the PQVM operates on Shell's full 32-byte addresses (no truncation to 20 bytes)

In practice, this means the chain can support:

  • first-use account creation from a PQ public key
  • key rotation without changing account identity
  • custom validation logic such as multisig or social recovery

2. Address format

2.1 Native 32-byte address derivation

Shell Chain derives account addresses from the signing algorithm and public key:

preimage = version(1 byte) || algo_id(1 byte) || pubkey(n bytes)
address  = blake3(preimage)
  • version = 0x01 for the first derivation scheme
  • algo_id = SignatureType::as_u8()
  • the final address is the full 32-byte BLAKE3 output

There is no internal 20-byte address model. The PQVM, RPC layer, genesis format, and storage all operate on the same native 32-byte address.

2.2 External address encoding

Shell Chain exposes one valid representation of each 32-byte address:

0x<64 lowercase hex characters>

Example:

  • canonical form: 0x0b871be37a4af6348d28a90dbecc96820aa50d5178b7cbd7ced3d710e5a7eda9

2.3 Why Shell Chain keeps one canonical form

The 0x + 64 lowercase hex format is canonical for JSON-RPC, genesis, CLI, SDK APIs, and human-facing displays. It refers to the full 32-byte native address.


3. Validation model

Shell Chain uses a three-layer validation flow.

Layer Trigger Validation rule Purpose
Layer 1 First transaction from an account with no state entry Re-derive tx.from from (version, algo_id, pubkey) and verify signature Account creation / first-use safety
Layer 2 Existing account with validation_code_hash = None Verify pubkey_hash and PQ signature Normal operation with key rotation support
Layer 3 Existing account with validation_code_hash = Some(hash) Call account-specific validation logic in the PQVM Multisig / recovery / custom policies

3.1 Layer 1 — first-use validation

When the account does not yet exist in world state:

  1. the node requires sender_pubkey
  2. it derives the expected address from (version, algo_id, pubkey)
  3. it checks that the derived address matches tx.from
  4. it verifies the PQ signature

This is the only stage where address derivation itself is re-checked.

3.2 Layer 2 — default existing-account validation

Once an account exists and uses the built-in validator path:

  1. the node resolves the sender public key
  2. it compares blake3(pubkey) with account.pq_pubkey_hash
  3. it verifies the PQ signature

At this stage the chain no longer needs to re-derive the address from the new public key, which is what makes key rotation without address changes possible.

3.3 Layer 3 — custom validator path

If account.validation_code_hash is set, the chain delegates validation to account-specific PQVM logic instead of the built-in PQ verifier.

This is the hook for advanced account policies such as:

  • multisig
  • social recovery
  • time locks
  • contract-defined signature / authorization gates

4. Custom validator contract interface

Shell Chain's native AA path calls a validation function with the transaction hash and signature material:

interface IAccountValidator {
    function validateTransaction(
        bytes32 txHash,
        bytes calldata sig,
        bytes calldata pubkey
    ) external returns (bytes1);
}

Validation call behavior

  • target: the account address being validated
  • gas cap: 500_000
  • input: validateTransaction(bytes32,bytes,bytes)
  • execution model: isolated validation dry-run against a world-state snapshot
  • replay guard: protocol nonce equality is still enforced before execution

Current limitation

Custom validators currently receive only txHash, sig, and pubkey. That is enough for custom signature / policy checks, but not enough to safely replace canonical nonce handling. Because of that, Shell Chain keeps the protocol nonce check as the baseline replay guard even when validation_code_hash is set. Richer custom nonce schemes are deferred until the validator ABI carries more transaction context.

Validation succeeds when the return value is interpreted as true / valid. Current node logic accepts the common "magic valid" encodings:

  • raw 0x01
  • ABI-encoded bool(true)
  • ABI-encoded bytes1(0x01)

This call path is implemented in:

  • crates/pqvm/src/aa_validation.rs
  • crates/pqvm/src/tx_validation.rs
  • contracts/DefaultPQValidator.sol

5. Key rotation and validator upgrades

The long-term AA model includes a protocol-managed account controller for:

  • rotateKey(pubkey, algo_id)
  • setValidationCode(code_hash)
  • clearValidationCode()

Why address rotation is not required

Shell Chain checks address derivation only when the account is first created. After that, validation depends on the account's stored pq_pubkey_hash or custom validator configuration.

That means a user can:

  1. keep the same account address
  2. rotate to a new keypair
  3. even move to a different supported PQ algorithm

without changing the account's on-chain identity.

Current status

The validation dispatcher, AccountManager system-contract flow, and reference validator contract are all landed. The remaining AA work is focused on wider workspace regression and final rollout validation.


6. Session Keys

Shell Chain AA Phase 2 supports session keys: short-lived delegated signing keys derived from a PQ-HD wallet seed.

Authorization model

A root account signs a SessionAuth authorization over:

PQTX_SESSION_V1 || session_pubkey || target_or_zero || value_cap || expiry_block || chain_id

The authorization binds the session key to one chain, an expiry block, a value cap, and optionally one target address. This prevents a session authorization created for one dApp or chain from being replayed elsewhere.

Wallet flow

PQ-HD wallets derive session keys from:

m/1'/1'/session_index'

The Shella wallet exposes a privileged background operation that:

  1. decrypts the local HD seed after password verification
  2. derives the requested root account and session key
  3. signs the session authorization with the root key
  4. optionally signs a 32-byte AA bundle signing hash with the session key
  5. zeroizes root/session secret material after the operation

The returned SessionAuth has an empty session_signature until a concrete AA bundle signing hash is supplied.

Client rules

  • Use SDK session helpers instead of constructing SessionAuth by hand.
  • Do not persist session secret keys.
  • Prefer short expiry blocks and per-dApp target restrictions.
  • Treat missing session_signature as an unsigned draft, not as a submit-ready AA bundle.

7. How this differs from ERC-4337

Topic Shell Chain native AA ERC-4337
Validation location Protocol-level EntryPoint contract
Bundler required No Yes
Separate alt-mempool No Usually yes
Default validator Built into the chain Wallet contract-defined
Address format 0x + 64 lowercase hex (32-byte native address) 0x... (20-byte)

Shell Chain's model is closer to a native smart-account chain than to an Ethereum add-on AA layer.


8. Rollout boundary for testnet

Native AA + 32-byte native addressing is a release-boundary change.

For the first public M9-compatible testnet:

  • a one-time testnet reset is required
  • genesis must be rebuilt with the new 32-byte address space
  • old world-state / RocksDB / mempool data is not migrated in place

The same private keys may be reused, but their exported Shell Chain addresses must be re-derived under the new address scheme.


9. Implementation status

Area Status Notes
PQ address derivation (`blake3(algo_id pubkey)`)
RPC / CLI / genesis address migration ✅ Implemented User-facing outputs now use 32-byte 0x hex
AA validation dispatcher core ✅ Implemented Layer 1 / Layer 2 / Layer 3 routing exists
Custom validator dry-run path ✅ Implemented Snapshot-based EVM validation with gas cap
Mempool / production ingress integration ✅ Implemented Revalidation and block-production paths are wired
AccountManager (rotateKey, setValidationCode) ✅ Implemented Native system-contract flow is live and tested
Reference validator contract ✅ Implemented contracts/DefaultPQValidator.sol + compiled runtime fixture
Session-key authorization ✅ Implemented SDK helpers and Shella wallet background authorization flow

10. Developer pointers

If you want to trace the implementation in code:

  • crates/primitives/src/address.rs — address derivation and 32-byte hex encoding
  • crates/pqvm/src/aa_validation.rs — native AA dispatcher and custom-validator path
  • crates/pqvm/src/tx_validation.rs — transaction validation entry points
  • crates/mempool/src/pool.rs — mempool-side validation integration

11. Summary

Shell Chain's AA model combines:

  • protocol-native smart-account validation
  • post-quantum key material
  • 32-byte 0x + 64 hex chain-specific addresses
  • future-safe key rotation and validator upgrades
  • batch transactions and native paymaster (v0.18.0 Phase 1)

The goal is to make account abstraction the default account model, not an optional overlay.


12. Batch Transactions (v0.18.0 Phase 1)

Shell Chain v0.18.0 introduces AA batch transactions — a single PQ-signed transaction that executes multiple inner calls atomically.

11.1 Transaction type 0x7E

Batch transactions use tx type 0x7E (126). The payload carries:

pub struct InnerCall {
    pub to: Option<Address>,   // None = contract creation
    pub value: U256,
    pub data: Bytes,
    pub gas_limit: u64,        // per-inner advisory cap; sum ≤ outer gas_limit
}

pub struct AaBundle {
    pub inner_calls: Vec<InnerCall>,
    pub paymaster: Option<Address>,
    pub paymaster_signature: Option<Bytes>,
}

AaBundle is an optional trailing field on SignedTransaction. The base Transaction struct is unchanged, preserving backward compatibility.

11.2 Execution semantics

  • All inner calls share one nonce (the outer transaction nonce)
  • All inner calls share one gas budget (the outer gas_limit)
  • Execution is atomic: if any inner call reverts, the entire batch reverts
  • Each inner call produces its own receipt logs
  • Per-inner gas_limit is an advisory cap; the node still enforces the sum

Maximum inner calls: 16 per batch.

11.3 Building a batch transaction with the SDK

import { buildBatchTransaction, buildSignedTransaction } from "shell-sdk";

const batch = buildBatchTransaction([
  { to: "0xContractA", value: 0n, data: calldata1, gasLimit: 100_000n },
  { to: "0xContractB", value: 1_000_000n, data: "0x", gasLimit: 21_000n },
]);

const signed = await buildSignedTransaction(adapter, {
  ...batch,
  aaBundle: batch.aaBundle,
  nonce: await provider.getNonce(address),
  gasPrice: await provider.getGasPrice(),
  gasLimit: 250_000n,
  chainId: 1337n,
});

await provider.sendTransaction(signed);

11.4 Estimating batch gas

Use shell_estimateBatch before submitting:

const estimate = await provider.estimateBatch({
  from: address,
  innerCalls: [
    { to: "0xContractA", value: "0x0", data: calldata1, gasLimit: "0x186a0" },
  ],
});
console.log(estimate.total_gas); // hex string

12. Sponsored Gas / Paymaster (v0.18.0 Phase 1)

Shell Chain v0.18.0 introduces native paymaster accounts. A paymaster is an ordinary chain account that authorizes paying gas on behalf of another account.

12.1 How paymaster authorization works

A batch transaction (or regular transaction) includes:

  • paymaster: Address — the account that will pay gas
  • paymaster_signature: Bytes — ECDSA/PQ signature by the paymaster over the canonical transaction hash

The node validates the paymaster signature during mempool admission:

  1. Checks that the paymaster address has sufficient balance
  2. Verifies the paymaster signature against the canonical hash
  3. On execution, deducts gas cost from the paymaster account instead of the sender

Scope: EOA/native paymasters are submit-ready when the paymaster signature is present and valid. Contract paymasters expose a versioned capability probe through shell_estimatePaymasterGas, but current nodes return simulation_status: "cap_only" rather than a full validatePaymasterOp staticcall simulation.

12.2 Checking paymaster policy

const policy = await provider.getPaymasterPolicy("0xpaymasterAddress");
// { address, hasPqPubkey, balance, policy: "eoa-open", maxGasSponsorship: null }

12.3 Checking contract-paymaster capability

const gas = await provider.estimatePaymasterGas({
  paymaster: "0xpaymasterAddress",
  sender: address,
  inner_calls_data: "0x",
  max_fee_per_gas: "0x3b9aca00",
  paymaster_context: "0x01",
});

if (gas.simulation_status !== "simulated") {
  // Current cap-only nodes expose the protocol cap but not validation_gas.
  disableContractPaymasterSubmission();
}

For simulation_status: "cap_only", validation_gas and within_cap are null. Wallets and dApps must not treat this as a successful paymaster validation.

12.4 Checking sponsorship after execution

const info = await provider.isSponsored("0xtxhash");
// { found: true, location: "chain", isAaBundle: true, sponsored: true,
//   paymaster: "0x...", sender: "0x...", innerCallCount: 2 }

12.5 Security and hash domains

Batch and sponsored transactions use a distinct hash domain from regular transactions. This prevents any v0.17 or earlier signature from being replayed as a v0.18.0 AA bundle. The hash domain is defined in crates/primitives/src/transaction.rs.


13. Implementation pointers (v0.18.0)

File Purpose
crates/primitives/src/transaction.rs AaBundle, InnerCall wire structs; hash domain
crates/pqvm/src/executor.rs Batch execution loop; inner-call receipts
crates/pqvm/src/tx_validation.rs Paymaster balance + signature pre-check
crates/rpc/src/api.rs shell_estimateBatch, shell_getPaymasterPolicy, shell_isSponsored
tests/e2e/aa_batch_test.rs E2E batch execution tests
tests/e2e/aa_sponsored_test.rs E2E paymaster tests