Unified Accounts Cross-Chain System
Unified accounts — user has one identifier and one "logical" balance automatically used on any chain. Evolution from "multi-chain" (many wallets) to chain-abstracted (one wallet, transparent multi-chain). Non-trivial to implement correctly: solve several independent problems simultaneously.
Technical Level
Naive: same address on each chain via CREATE2, state sync via bridge. Works but creates complexity — fragmented state, sync delay and cost.
Advanced separates:
- Identity layer — who you are (one identifier all chains)
- Intent layer — what you want (abstracted from specific chain)
- Execution layer — how it executes (routing, bridging, execution)
- Settlement layer — final settlement
Deterministic Addresses via CREATE2
First step — same address all EVM chains:
// Factory with same address on all chains
contract AccountFactory {
function deployAccount(
bytes32 salt,
bytes calldata initCode
) external returns (address account) {
// CREATE2: address depends only on factory + salt + initCode
// Same factory on all EVM → same Account address
assembly {
account := create2(0, add(initCode, 32), mload(initCode), salt)
}
}
function predictAddress(
bytes32 salt,
bytes32 initCodeHash
) external view returns (address) {
return address(uint160(uint256(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
initCodeHash
)))));
}
}
Keyless deployment via ERC-2470 Singleton Factory ensures same factory address across all EVM. Same salt + initCode = same account address.
Cross-Chain State Synchronization
Second step — sync account data between chains. Pattern: Primary chain + sync via bridge.
contract UnifiedAccountPrimary {
mapping(address => bool) public owners;
uint256 public nonce;
function addOwnerAndSync(
address newOwner,
uint64[] calldata targetChains,
address[] calldata targetAccounts
) external onlyOwner {
owners[newOwner] = true;
for (uint i = 0; i < targetChains.length; i++) {
bytes memory payload = abi.encode(
"ADD_OWNER",
newOwner,
++nonce
);
bridge.sendMessage(targetChains[i], targetAccounts[i], payload);
}
emit OwnerAdded(newOwner);
}
}
contract UnifiedAccountReplica {
uint256 public lastSyncedNonce;
function receiveSync(
bytes calldata payload,
bytes32 originMessageId
) external onlyBridge {
(string memory action, address target, uint256 nonce) =
abi.decode(payload, (string, address, uint256));
require(nonce == lastSyncedNonce + 1, "Invalid nonce");
lastSyncedNonce = nonce;
if (keccak256(bytes(action)) == keccak256(bytes("ADD_OWNER"))) {
_addOwner(target);
}
}
}
Intent-Based Execution
User intent (stake, swap, transfer) abstracted from chain. System routes and executes:
interface UserIntent {
action: "stake" | "swap" | "transfer" | "borrow";
targetProtocol: string;
targetChain?: number;
inputToken: string;
inputAmount: string;
minOutputAmount?: string;
}
class IntentRouter {
async resolveIntent(intent: UserIntent, userProfile: UserProfile): Promise<ExecutionPlan> {
const targetChain = intent.targetChain ||
await this.findOptimalChain(intent, userProfile);
const fundingSource = await this.findBestFundingSource(
intent.inputToken,
intent.inputAmount,
userProfile.balances,
targetChain
);
const steps: ExecutionStep[] = [];
if (fundingSource.chainId !== targetChain) {
steps.push({
type: "bridge",
fromChain: fundingSource.chainId,
toChain: targetChain,
token: intent.inputToken,
amount: intent.inputAmount,
bridgeProtocol: await this.selectBridge(fundingSource.chainId, targetChain),
});
}
steps.push({
type: intent.action,
chainId: targetChain,
protocol: intent.targetProtocol,
});
return { steps, estimatedGas: await this.estimateTotalGas(steps) };
}
}
Gasless Cross-Chain Operations
Full UX requires gas abstraction. User doesn't think about gas:
async function executeGasless(
intent: UserIntent,
feeToken: "USDC" | "USDT"
): Promise<string> {
const plan = await intentRouter.resolveIntent(intent, userProfile);
const totalCostInFeeToken = await priceOracle.convertGasCost(
plan.estimatedGas,
plan.steps.map(s => s.chainId),
feeToken
);
const userOp = await buildSponsordUserOp(plan, feeToken, totalCostInFeeToken);
const signedOp = await userAccount.signUserOperation(userOp);
return relayer.submitIntent(signedOp);
}
Stack
| Component | Solution |
|---|---|
| Smart accounts | ERC-4337 (ZeroDev Kernel or Safe) |
| Cross-chain messaging | LayerZero / Axelar / CCIP |
| Gas abstraction | ERC-4337 Paymaster |
| Intent routing | Custom service + 1inch/LiFi |
| State sync | Merkle proofs + bridge |
| Frontend | wagmi v2 + viem + React |
Timelines
- Basic identity (CREATE2 addresses, basic sync): 4-6 weeks
- Intent routing + cross-chain execution: 6-8 weeks
- Gas abstraction + fee token: 3-4 weeks
- Security audit: 6-8 weeks
- Total: 4-6 months







