Signing & transactions
Sigil wallets are EOA-based. Every wallet holds one private key per curve (secp256k1 for EVM chains, ed25519 for Solana), protected by Shamir secret sharing. The SDK exposes three signing primitives: message signing, typed-data signing, and raw transaction sending.
Multi-network signing
Sigil supports 8 networks across 2 curves:
| Network | Curve | Chain type |
|---|---|---|
| Ethereum | secp256k1 | EVM |
| Polygon | secp256k1 | EVM |
| Arbitrum | secp256k1 | EVM |
| Base | secp256k1 | EVM |
| Optimism | secp256k1 | EVM |
| BSC | secp256k1 | EVM |
| Avalanche | secp256k1 | EVM |
| Solana | ed25519 | SVM |
All EVM networks share the same secp256k1 key, so the wallet address is identical across all EVM chains. Solana uses a separate ed25519 key with its own address.
signMessage — message signing
Signs an arbitrary message with EIP-191 personal_sign semantics (EVM)
or the equivalent on Solana. Pass { network } to select which
network’s key signs the message.
// Vanillaconst sig = await sigil.signMessage('hello', { network: 'ethereum' });
// Reactconst { signMessage } = useSignMessage();const sig = await signMessage('hello', { network: 'arbitrum' });The default network is your primary network (Ethereum unless configured
otherwise). For most EVM use cases you can omit { network } entirely
since all EVM chains share the same key.
signTypedData — EIP-712 structured signing
For off-chain orders, EIP-2612 permit, Permit2, SIWE, and any
protocol that requires EIP-712 typed-data signatures.
const { signature, digest } = await sigil.signTypedData({ domain: { name: 'USD Coin', version: '2', chainId: 42161, verifyingContract: '0x...' }, types: { Permit: [/* ... */] }, primaryType: 'Permit', message: { owner, spender, value, nonce, deadline },});Returns { signature, digest }. The signature is 65 bytes
(r || s || v). digest is the 32-byte EIP-712 hash that was signed,
useful for server-side ecrecover validation.
sendTransaction — EVM transaction sending
Sends a raw EIP-1559 transaction from the wallet’s EOA. The user pays gas from their native balance on the target chain.
// Vanillaconst r = await sigil.sendTransaction({ chain: 'arbitrum', to: BRIDGE_CONTRACT, data: encodeDeposit(amountUSDC), value: '0', preview: { title: 'Deposit to Hyperliquid', description: `${usdc(amountUSDC)} USDC` },});// -> { chain, chainId, txHash, from }// Reactconst { sendTransaction, isLoading } = useSendTransaction();
const r = await sendTransaction({ chain: 'base', to: NFT_CONTRACT, data: encodeMint(1), preview: { title: 'Mint #42' },});What happens when you call it
- SDK asks the iframe to handle the transaction (the iframe holds the end-user’s session token).
- Iframe POSTs to Sigil’s
/v1/wallets/evm/prepare-txwith the chain, to, data, and value. Sigil resolves nonce, gas estimation, and fee parameters. - Iframe shows the user a confirmation screen with your
preview(title + description + fields). The user approves. - Iframe reconstructs the SSS-protected key in memory, signs the EIP-1559 envelope, wipes the buffer.
- Iframe POSTs
/v1/wallets/evm/broadcast-txwith the signed transaction. Sigil forwards it to the chain’s RPC. - SDK returns
{ chain, chainId, txHash, from }.
The function returns once the RPC accepted the broadcast. Finality is your responsibility — chains differ in confirmation times.
Parameters
| Field | Required | Notes |
|---|---|---|
chain | yes | 'ethereum', 'polygon', 'arbitrum', 'base', 'optimism', 'bsc', 'avalanche'. |
to | yes | Contract or recipient address. |
data | no | 0x-prefixed call data. Omit for pure native transfers. |
value | no | Decimal wei as a string. Default '0'. |
gasLimit | no | Override the gas estimate. Useful when some contracts under-estimate. |
preview | no | { title, description?, fields? } shown on the confirm screen. |
What you write
The bytes for the calls you want. That’s specific to your product —
which contracts, which arguments — and is the same code you’d write
without Sigil. Use viem /
ethers / your own ABI encoder; the SDK
just takes the resulting (to, data) pair.
Errors
sendTransaction rejects with Error whose message describes what
failed. Common cases:
user rejected— the user clicked Reject on the iframe confirm screen.signer_mismatch: ...— the signature recovered to a different EOA than expected. Usually means the device share inlocalStorageis from a previous wallet. The iframe wipes the stale share automatically; the next attempt routes through the recovery flow.chain_unsupported— the chain handle isn’t wired in Sigil.estimate_gas_reverted— the destination contract reverted during gas estimation. PassgasLimitexplicitly if you’ve confirmed the call is correct.broadcast_failed— the RPC rejected the signed tx. Most common reasons: insufficient funds (the wallet can’t afford gas + value), or a nonce gap from a previous in-flight tx.
Routing through a smart account
sendTransaction automatically routes through a smart account when
one is provisioned for the target chain — see
Smart accounts for the model. The quick
version: call sigil.enableSmartAccount('base') once per chain, and
from that point on any sendTransaction({ chain: 'base', ... })
becomes a sponsored ERC-4337 UserOp instead of an EOA EIP-1559 tx.
await sigil.enableSmartAccount('base');
const r = await sigil.sendTransaction({ chain: 'base', to: NFT_CONTRACT, data: encodeMint(1), preview: { title: 'Mint #42' },});// Sigil paid the gas. r.from = smart-account address.Watch for the gas_cap_exceeded error code — that’s the wallet
having burned its €10 monthly sponsorship budget. The Errors page
lists the full set; Smart accounts
covers the cap behaviour.
Server-to-server signing (TEE mode)
For backend-initiated signing without user interaction, Sigil offers a server-to-server (S2S) API. In TEE mode, the signing key is held inside a trusted execution environment and never leaves it.
S2S signing is covered in the Server-to-server API documentation. It uses API key authentication instead of the iframe flow, and supports the same networks and curves as client-side signing.
Recipes
- Hyperliquid Bridge2 deposit — depositing and signing HL Core L1 actions.