Skip to main content

Smart Accounts

Every user in Pons Network has a Smart Account on each destination chain. This account is the cornerstone of Pons security. It ensures that resolvers can only do exactly what users authorize, and nothing more.

Why Smart Accounts?

Traditional bridges transfer assets to a user's EOA (Externally Owned Account). But this creates problems:

  • User needs gas tokens on the destination chain
  • User must be online to execute actions
  • Multiple transactions required

Smart Accounts solve this. They:

  • Receive bridged assets on behalf of the user
  • Execute user-authorized actions automatically
  • Pay fees from the bridged assets (no gas tokens needed)
  • Enforce strict security guarantees

Design Principles

🎯

Deterministic

Same address on every chain. Predictable before deployment. Derived from owner address + salt.

Counterfactual

Doesn't exist until first use. No deployment cost upfront. Created lazily when needed.

🔐

Self-Custodial

Only the owner's signature can authorize actions. No admin keys. No upgrades. No backdoors.

Security Model

How Resolvers Are Constrained

The critical question: How can we let resolvers execute actions without letting them steal funds?

The answer is cryptographic signatures combined with on-chain validation.

What the Signature Contains

When a user signs an action, they commit to exactly what will happen:

✍️

EIP-712 Signed Action

  • CallsExact contract addresses and calldata to execute
  • Expected AmountMinimum assets that must arrive before execution
  • Fee ConfigurationMaximum fees the user agrees to pay
  • DeadlineAction expires after this timestamp
  • NonceUnique timestamp-based ID, prevents replay attacks

What Resolvers CANNOT Do

The Smart Account enforces strict constraints. A resolver cannot:

Attack VectorProtection
Execute different callsSignature covers exact calldata
Take more fees than agreedFee amounts are signed
Replay an actionEach nonce can only be used once
Execute after deadlineDeadline check reverts late submissions
Execute with less assetsExpected amount check protects user
Drain remaining fundsOnly signed calls can execute

What Resolvers CAN Do

Resolvers have limited power:

  • Submit the user's pre-signed action
  • Provide gas for the transaction
  • Optionally provide tokens upfront (reimbursed from fees)

That's it. They're essentially a gas-paying relay.

Smart Account Capabilities

1. Receive Bridged Assets

The Smart Account is the recipient of cross-chain transfers:

// User's Smart Account address is deterministic
const smartAccountAddress = await pons.calculateSmartAccountAddress(
userAddress, // Owner
0n // Salt
);
// Same address on every supported chain!

2. Execute Arbitrary Calls

Users can authorize any contract interaction:

const action = new ActionBuilder()
// Swap on Uniswap
.addCall(UNISWAP_ROUTER, swapCalldata)
// Stake on Lido
.addCall(LIDO_STAKING, stakeCalldata)
// Mint an NFT
.addCall(NFT_CONTRACT, mintCalldata)
.build();

3. Batch Multiple Operations

Execute complex multi-step transactions atomically:

All steps execute in one transaction, or none do.

4. Pay Fees Without Gas Tokens

No Gas Tokens Needed

The resolver pays gas in native tokens. The Smart Account reimburses the resolver from the user's bridged assets. User never needs destination chain gas.

Signature Verification Deep Dive

EIP-712 Typed Data

Actions use EIP-712 structured signing for security and UX:

const typedData = {
domain: {
name: 'PonsSmartAccount',
version: '1',
chainId: destinationChainId,
verifyingContract: smartAccountAddress,
},
types: {
Action: [
{ name: 'calls', type: 'Call[]' },
{ name: 'expectedAmount', type: 'uint256' },
{ name: 'feeConfig', type: 'FeeConfig' },
{ name: 'deadline', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
],
Call: [
{ name: 'target', type: 'address' },
{ name: 'data', type: 'bytes' },
{ name: 'value', type: 'uint256' },
],
// ...
},
message: action,
};

On-Chain Verification

The Smart Account verifies signatures on-chain:

function execute(Action calldata action, bytes calldata signature) external {
// 1. Verify signature is from owner
require(
_verifySignature(action, signature) == owner,
"Invalid signature"
);

// 2. Check deadline hasn't passed
require(block.timestamp <= action.deadline, "Expired");

// 3. Check nonce hasn't been used (prevents replay)
require(!usedNonces[action.nonce], "Nonce already used");
usedNonces[action.nonce] = true;

// 4. Check sufficient assets arrived
require(
_getBalance() >= action.expectedAmount,
"Insufficient amount"
);

// 5. Pay fees to operators
_payFees(action.feeConfig);

// 6. Execute exactly what user signed
for (uint i = 0; i < action.calls.length; i++) {
_executeCall(action.calls[i]);
}
}

Edge Cases & Protections

What if the resolver never executes?

  • User can execute the action themselves
  • Or wait for another resolver
  • Action remains valid until deadline

What if assets arrive but action fails?

  • Assets stay in the Smart Account (user's account)
  • User can withdraw directly with a new signature
  • User retains full control

What if resolver tries to frontrun?

  • Signature is specific to exact calldata
  • Resolver can't modify the action
  • MEV attacks don't affect user outcome

Summary

👤

For Users

Complete security: Your funds can only be used exactly as you authorize. Resolvers are constrained by cryptographic proofs. You never lose custody.

👨‍💻

For Developers

Simple integration: Users sign once, everything else is automatic. No need to handle gas tokens, network switching, or multi-step flows.


Next: Economics & Game Theory →