robotSmart Account Integration

Integrate the Goliath Bridge from ERC-4337 smart-account wallets (Safe, Coinbase Smart Wallet, Biconomy, Alchemy AA, ZeroDev, thirdweb) — ERC-1271 signatures, bundler tx hashes, and how to avoid the u

The native XCN withdrawal flow accepts EIP-712 intents signed by deployed ERC-1271 smart accounts — not just EOAs. Third-party apps built on Safe, Coinbase Smart Wallet, Biconomy, Alchemy AA, ZeroDev, thirdweb, or any AA stack that implements isValidSignature(bytes32,bytes) can drive the XCN withdraw flow end-to-end without the owner EOA ever touching the intent.

This page is for third-party integrators using smart-account wallets. The Goliath → Ethereum — Native XCN walkthrough still applies — the endpoints, typed-data fields, poll loop, and status lifecycle are unchanged. What's new is how senderAddress is resolved (smart-account contract address, not owner EOA), how the intent signature is verified (ERC-1271 on-chain call, not ECDSA recovery), and what the backend accepts as originTxHash (the final execution tx, not the userOpHash).

circle-info

If you're using an EOA wallet, skip this page — the standard Native XCN flow is all you need.

Hard Rules

Read all six before wiring code. Missing any one of these stalls the withdrawal.

  1. senderAddress = deployed smart-account contract address on Goliath.

    • Not the owner EOA. Not an ERC-4337 session key. Not a factory address.

    • The bridge enforces that the observed value transfer originated from this exact address.

  2. The smart account must already be deployed at senderAddress on Goliath before signing the intent.

    • Counterfactual (predicted-but-not-yet-deployed) accounts are rejected with 503 SIGNATURE_VERIFICATION_UNAVAILABLE.

    • ERC-6492 pre-deploy signatures are not supported. Deploy first, then sign.

  3. The smart account must implement isValidSignature(bytes32,bytes) returns (bytes4) and return magic 0x1626ba7e for a valid signature.

    • Every modern AA stack uses this selector — you should not need to change anything wallet-side.

    • The legacy isValidSignature(bytes,bytes) variant with magic 0x20c13b0b is not accepted.

  4. originTxHash is the final execution tx hash on Goliath — never the userOpHash.

    • userOpHash is a 32-byte hex string that's syntactically indistinguishable from a real tx hash, but it's not a transaction, so the bridge can never find it. Your intent will silently stall until it expires.

    • Resolve with eth_getUserOperationReceipt(userOpHash) → receipt.transactionHash before calling /bind-origin.

  5. Direct child call only — the final execution tx must contain a direct internal CALL from senderAddress to relayerWalletAddress with value = amountAtomic.

    • EntryPoint.handleOps(...) wrapping that direct call is fine — the bridge walks the call tree.

    • Multi-hop (smartAccount → Router → relayer) is rejected.

  6. Always read relayerWalletAddress from the intent response. It rotates between networks and at operator discretion. Hardcoded relayer addresses silently strand funds after a rotation.

How the Bridge Verifies Smart-Account Withdrawals

Only two steps differ from the EOA path:

Step
EOA path
Smart-account path

Intent signature

ecrecover(digest, signature) == senderAddress

On-chain eth_call to senderAddress.isValidSignature(digest, signature) — must return 0x1626ba7e

Origin-funds proof

Compare tx.from / tx.to / tx.value on the bound tx hash

Walk the call tree (Hedera mirror /api/v1/contracts/results/{txHash}/actions) for a CALL node matching (senderAddress → relayerWalletAddress, value = amountAtomic)

Everything else — typed-data shape, API endpoints, 1-hour security hold, fee schedule, polling, status lifecycle — is identical to the EOA flow.

Typed-Data Shape (unchanged)

Use the same EIP-712 domain, primary type, and field order as the EOA flow:

Signatures from a different domain are rejected with 400 SIGNATURE_DOMAIN_REJECTED.

End-to-End Flow

Steps 2–6 must complete inside the 30-minute intent window. Step 7 can run as long as needed (~1 hour because of the security hold).

Example A — viem + permissionless.js (Safe via bundler)

For AA stacks that submit UserOperations through a bundler, the bundler tx hash returned by eth_getUserOperationReceipt is the execution tx hash.

Example B — ethers v6 + @safe-global/protocol-kit (direct on-chain)

For Safe accounts that execute on-chain through executeTransaction (no bundler), the receipt hash from the final executeTransaction is the execution tx hash — no eth_getUserOperationReceipt call is needed.

Failure Modes

HTTP
Error code
Meaning
Client action

400

SIGNATURE_INVALID

ERC-1271 eth_call reverted, or returned no data

Confirm the smart account exposes isValidSignature(bytes32,bytes) and the signature is ABI-encoded as the wallet SDK returned it

400

SIGNATURE_MISMATCH

ERC-1271 returned a non-magic bytes4 (e.g. 0xffffffff)

Re-sign; verify senderAddress equals the smart-account proxy, not the owner EOA

400

SIGNATURE_DOMAIN_REJECTED

Signed against a non-canonical domain

Use chainId: 327 (mainnet) or 8901 (testnet), name: "GoliathBridge", version: "1"

503

SIGNATURE_VERIFICATION_UNAVAILABLE

Transient RPC error or the smart account is not deployed (eth_getCode(senderAddress) == 0x)

Back off 1–5 s, deploy the smart account if it isn't yet, then retry

409

DUPLICATE_ORIGIN_TX

A different intent already bound this tx hash

Create a fresh intent

410

INTENT_EXPIRED

Missed the 30 min window

Start over from step 2

Two extra codes surface only via the operator-driven admin recovery path, listed here for completeness:

HTTP
Error code
Meaning

409

PROOF_UNAVAILABLE

Trace data not yet indexed — retry after a short backoff

422

PROOF_MISMATCH

Trace shows the wrong sender, recipient, or value — contact support

userOpHash vs. Execution Tx Hash — the #1 Gotcha

/bridge/xcn-withdraw-intent/bind-origin expects a 32-byte hex string in originTxHash, and the bridge asks the Goliath relay and the Hedera mirror to find the matching transaction. A userOpHash is the same shape (0x…, 64 hex chars), but it's not a transaction — it's the hash of a UserOperation struct that the bundler inlined inside its own handleOps(...) call. No indexer will ever return a tx with that hash.

If you bind a userOpHash:

  • The intent sits in PENDING_ORIGIN_TX and never progresses.

  • After 30 minutes it flips to INTENT_EXPIRED.

  • The 5000 XCN you actually sent is now stuck in the relayer wallet — there's no intent to pair it with.

Always resolve to the execution tx hash first:

If you're executing via Safe's on-chain executeTransaction (no bundler), the ethers receipt hash is already the execution tx hash. If you're going through a bundler, you must call eth_getUserOperationReceipt.

Common Pitfalls

  • Owner-EOA address in the intent body, smart-account signature in signature. If senderAddress is the owner EOA but the signature came from the Safe's ERC-1271 path, you'll get 400 SIGNATURE_MISMATCH. senderAddress must be the smart-account contract address on both the signed intent and the value transfer.

  • Counterfactual Safes. If the Safe has never been used, eth_getCode(safeAddress) returns 0x and the bridge responds 503 SIGNATURE_VERIFICATION_UNAVAILABLE. Send a cheap initialization op (or any deploy tx) before binding the intent. ERC-6492 pre-deploy signatures are not supported.

  • Routers and meta-tx forwarders. smartAccount → Router → relayer is rejected as SENDER_MISMATCH. The relayer transfer must be a direct child call of the smart account. If you need a router for business logic, build the composed call so the smart account still calls the relayer directly as a leaf.

  • Value-unit mismatch. amountAtomic is the 18-decimal wei string. Smart-account SDKs (especially thirdweb) sometimes assume tinybars on Hedera-family chains — do not apply the 8-decimal scale to XCN. Pass the same integer into the intent amountAtomic and into the value of the native transfer. See XCN Decimal Handling for background.

  • Hardcoded relayer address. Every example above reads relayerWalletAddress from the intent response. If you hardcode the relayer, a key rotation silently strands every withdrawal after the rotation until you redeploy.

Next

Last updated