Wallet Integration skill for Codex

Connect, switch and sign without confusing users. Codex builds connect flows that keep the app usable before connection, discover every installed wallet through EIP-6963, and give wrong-network, pending-request and rejected states their own UI. Sign-In with Ethereum is verified on the server with nonce, domain and expiry and accepts smart-account signatures, and typed-data prompts are named for humans.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- wallet-integration

Installs to ~/.agents/skills/wallet-integration/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $wallet-integration, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and implements wallet connection and signing UX for EVM dapps - EIP-6963 wallet discovery, connect modals with RainbowKit, ConnectKit or Reown AppKit, WalletConnect on mobile, wrong-network handling with wallet_switchEthereumChain, Sign-In with Ethereum (EIP-4361) sessions verified server-side, EIP-712 typed-data prompts users can read, EIP-5792 batched calls, and handling of rejection and pending-request errors. Use when building connect-wallet flows, adding SIWE login, fixing wallet detection or chain-switch bugs, or making signature requests clearer and safer.

Use it when

  • A dapp needs a connect-wallet flow or a better one
  • Users should log in with their wallet via SIWE
  • Chain switching, wallet detection or pending-request bugs appear
  • Signature prompts are unreadable or use eth_sign

Use something else when

  • You need contract read and write hooks rather than connection UX: use viem-wagmi
  • Users should transact without gas or with an embedded smart wallet: use account-abstraction

How to check the result

A manual matrix of an extension wallet, a WalletConnect mobile wallet and a smart-account wallet completes connect, switch, SIWE login and a typed-data signature, and each rejection path recovers.

The mistake it prevents

Verifying SIWE signatures only in the browser, or with ecrecover alone, so sessions can be forged and smart-account users cannot sign in.

~/.agents/skills/wallet-integration/SKILL.md

--- name: wallet-integration description: Designs and implements wallet connection and signing UX for EVM dapps - EIP-6963 wallet discovery, connect modals with RainbowKit, ConnectKit or… license: MIT ---

Wallet UX is right when every installed wallet shows up without guesswork, the app works read-only before connecting, users always know which network and account they are on, every signature request is human-readable and justified, and a rejection or timeout leaves the UI in a clear, recoverable state.

Inspect first

  1. Read AGENTS.md, package.json and the existing provider tree. Identify the stack: wagmi + a connect kit, a vendor SDK (embedded wallets), or raw window.ethereum.
  2. List supported chains and where contract addresses live per chain.
  3. Find the auth model: no login, SIWE session, or a third-party auth provider.
  4. Check the connect kit’s supported wagmi major before upgrading anything; see $viem-wagmi for hook names that differ between wagmi v2 and v3.

Choose the stack

NeedDefault
React app, standard wallets, good modalwagmi + RainbowKit or ConnectKit
Many wallets incl. mobile, email/social login optionsReown AppKit (WalletConnect)
Users without wallets (consumer app)Embedded wallet SDK or smart accounts: see $account-abstraction
Non-React or minimalwagmi core or viem with EIP-6963 discovery

Avoid reading window.ethereum directly: with several extensions installed they fight over it. EIP-6963 announces each wallet separately; wagmi’s injected connector and the kits already use it.

Getting a WalletConnect / Reown project ID requires the user to create one in the Reown dashboard; ask for it, put it in a public env var, and restrict allowed origins there.

Connection states to design

StateUI
Not connectedFull read-only app; connect button; never block browsing on connection
ConnectingModal shows which wallet is waiting; cancel returns to idle
Request already pending (-32002)Tell the user to open their wallet extension; do not re-fire the request
Connected, right chainShort address (ENS name if any), network badge, disconnect
Connected, wrong chainBanner with one-click switch; disable writes, keep reads working
Wallet locked or disconnected (4900/4901)Fall back to not-connected without losing form input

Reconnect on reload only if the user connected before (connect kits handle this with storage); never auto-open the connect modal on first visit.

Network switching

import { useSwitchChain } from "wagmi";

const { switchChain, isPending, error } = useSwitchChain();
// onClick:
switchChain({ chainId: base.id });
  • Under the hood: wallet_switchEthereumChain; error 4902 means the wallet does not know the chain and wagmi falls back to wallet_addEthereumChain using the chain definition.
  • Some wallets (many mobile and WalletConnect sessions) do not allow programmatic switching; show manual instructions as a fallback.
  • Pass chainId explicitly on every read and write so a wallet on another chain cannot send a transaction to the wrong deployment.

Sign-In with Ethereum (EIP-4361)

Flow: server issues a nonce, client builds and signs the message, server verifies and creates a session. Never verify only on the client.

// server: GET /api/siwe/nonce
import { generateSiweNonce } from "viem/siwe";
const nonce = generateSiweNonce();          // store in the session, single use, short TTL

// client
import { createSiweMessage } from "viem/siwe";
const message = createSiweMessage({
  address, chainId, domain: window.location.host, nonce,
  uri: window.location.origin, version: "1",
  statement: "Sign in to Example. This does not cost gas.",
  issuedAt: new Date(), expirationTime: new Date(Date.now() + 10 * 60_000),
});
const signature = await signMessageAsync({ message });

// server: POST /api/siwe/verify
const ok = await publicClient.verifySiweMessage({ message, signature, nonce: session.nonce, domain: req.host });
  • publicClient.verifySiweMessage checks the signature with ERC-1271 and ERC-6492 too, so smart-account users can sign in. Plain ecrecover rejects them.
  • Validate domain, nonce (then delete it), chain ID and expiry on the server.
  • Session cookie: HttpOnly, Secure, SameSite=Lax; end the session on account change (accountsChanged) as well as on disconnect.
  • A SIWE session proves wallet control at login time. Authorize later actions server-side against that session, not against an address sent by the client.

Signature prompts users can read

MethodUseNever
personal_sign (signMessage)SIWE, simple consent textHex blobs the user cannot read
eth_signTypedData_v4 (signTypedData)Orders, permits, off-chain votesDomains without chainId and verifyingContract
eth_sign-Never request it; it signs arbitrary hashes and wallets warn or block it
await signTypedDataAsync({
  domain: { name: "Example Exchange", version: "1", chainId: 8453, verifyingContract: EXCHANGE },
  types: { Order: [
    { name: "sell", type: "address" }, { name: "amount", type: "uint256" },
    { name: "minReceive", type: "uint256" }, { name: "deadline", type: "uint256" },
    { name: "nonce", type: "uint256" },
  ] },
  primaryType: "Order",
  message: { sell: USDC, amount: 100_000_000n, minReceive: 0n, deadline, nonce },
});

Field names appear in the wallet; name them for humans (minReceive, not p2). Before the prompt, show in your own UI what the signature authorizes, for how long, and that it is free or costs gas.

Batched calls (EIP-5792)

wallet_getCapabilities tells you whether the wallet supports atomic batches and paymaster sponsorship per chain. If it does, send approve + action as one wallet_sendCalls (wagmi useSendCalls) and track with wallet_getCallsStatus. If not, fall back to sequential transactions with clear “step 1 of 2” progress. Never assume support.

Error handling

Code / caseMeaningResponse
4001User rejectedQuiet inline message, keep inputs, allow retry. Not an error toast
-32002Request already pending in walletAsk the user to open the wallet
4100Not authorized for this account/methodReconnect
4902Unknown chainOffer add-chain
Insufficient funds for gasBalance too lowShow required amount and network
Simulation revertCall would failExplain the decoded reason before the wallet opens

Map viem errors with err.walk() to UserRejectedRequestError and friends rather than string-matching messages that differ by wallet.

Security rules

  • Show the full destination address (checksummed) and amount before any value transfer; never auto-submit a transaction after connect.
  • Request exact token approvals by default; unlimited only as an explicit user choice with a warning, and link to revoking.
  • Never ask for seed phrases or private keys, and never build flows where your server holds user keys unless that is the explicit product (custodial) with its own security review.
  • Pin and verify domains in SIWE; phishing sites replay messages for other domains.
  • Do not log full signatures, SIWE messages with nonces, or session tokens.

Testing

  • Use wagmi’s mock connector in component tests for connected, wrong-chain and rejected states.
  • Manual matrix before release: one desktop extension, one mobile wallet via WalletConnect, one smart-account wallet (SIWE and permit via 1271), two extensions installed at once.
  • E2E with a headless wallet harness (for example Synpress-style setups) if the team already has one; otherwise script the mock connector flows.

Done when

  • App is usable read-only; connect modal lists all EIP-6963 wallets
  • Wrong-chain, pending-request and rejection states each have UI
  • SIWE verified server-side with nonce, domain and expiry; smart accounts can log in
  • Every signature prompt is typed data or readable text with an in-app explanation
  • Batching uses EIP-5792 when supported and falls back cleanly

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Wallet Integration"
  short_description: "Connect, switch and sign without confusing users"
  default_prompt: "$wallet-integration Add a connect-wallet button with RainbowKit and handle the wrong network"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem