@tuwaio/siwx-evm
@tuwaio/siwx-evm is the EVM Layer 2 (L2) package of SIWX (Sign-In With X), the authentication project of TUWA Stage 1 (“Core Auth & Primitives”, next to Orbit Utils). Built on @tuwaio/siwx-core, viem and @wagmi/core, it signs CAIP-122 messages with EVM wallets and verifies eip155 signatures: EIP-191 for EOA wallets, EIP-1271 for deployed smart contract wallets and ERC-6492 for smart contract wallets that are not deployed yet. It does not use ethers.js or web3.js.
🏛️ Core Capabilities
- Signing:
createEvmSiwxSignerturns a wagmiConfigor a viemWalletClientinto the(message) => signaturefunction thatuseSiwxfrom@tuwaio/siwx-reactexpects. - EOA verification:
verifyEip191recovers the signer of apersonal_signsignature and compares it with the message address. It runs locally, without RPC calls. - Smart contract wallets:
verifyEip1271checks Safe, Coinbase Smart Wallet / Base Account and other ERC-4337 accounts with viem’sverifyMessage:isValidSignature(EIP-1271) of a deployed account, or the ERC-6492 wrapper of an account that is not deployed yet, in oneeth_call. - Every chain:
publicClientis one viemPublicClient, used only for messages of its own chain, or a function that returns the client of a chain number. A contract wallet is always checked on the chain it signed for. - Combined check:
verifyEvmSignaturetries EIP-191 first and falls back to the contract wallet check when there is a client for the message chain;result.method(eip191,eip1271orerc6492) tells which one succeeded. - No throwing: every verifier parses the message, requires an
eip155chain, checks the format, expiration andnotBeforewithvalidateMessage, and returns{ success, data, error }.
💾 Installation
pnpm add @tuwaio/siwx-evm @tuwaio/siwx-core @tuwaio/orbit-core @wagmi/core viem[!IMPORTANT]
@tuwaio/siwx-core,viem(>=2) and@wagmi/core(>=3) are peer dependencies and must be installed alongside@tuwaio/siwx-evm.@wagmi/coreis needed even if you only sign with a viemWalletClient.
🚀 Usage
Signing with wagmi or viem
import { createEvmSiwxSigner } from '@tuwaio/siwx-evm';
import type { Config } from '@wagmi/core';
import type { WalletClient } from 'viem';
declare const wagmiConfig: Config;
declare const walletClient: WalletClient;
declare const message: string;
// wagmi: signs with the connected account.
const signer = createEvmSiwxSigner(wagmiConfig);
// viem: signs with the client account, or with an explicit account.
const viemSigner = createEvmSiwxSigner(walletClient, '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B');
// Opens the wallet prompt. Rejects with "[SIWX-EVM] Signing failed: …" (original error in `cause`).
const signature = await signer(message);Verifying a signature
import { type EvmVerifyClient, verifyEvmSignature } from '@tuwaio/siwx-evm';
import { createPublicClient, http } from 'viem';
import { base, mainnet } from 'viem/chains';
declare const message: string;
declare const signature: `0x${string}`;
// Needed only for smart contract wallets: one client per chain your users sign in on
const clients = new Map<number, EvmVerifyClient>(
[mainnet, base].map((chain) => [chain.id, createPublicClient({ chain, transport: http() })]),
);
const result = await verifyEvmSignature(message, signature, { publicClient: (chainId) => clients.get(chainId) });
if (result.success) {
console.log(result.method, result.data?.address); // "eip191", "eip1271" or "erc6492", "eip155:8453:0x…"
} else {
console.error(result.error);
}- Use
verifyEip191alone when you only accept EOA wallets, andverifyEip1271when you know the account is a contract. - EOA wallets need no client on any chain. A smart contract wallet that signs on a chain without a client fails verification; a single client of another chain is never used, because the same account address can have other owners there.
- The verifiers do not check the domain, URI, nonce or other policy rules. On a server, use
@tuwaio/siwx-server, which adds the policy and single-use nonces, or runvalidatePolicyfrom@tuwaio/siwx-coreyourself. skipExpiration: truedisables the expiration check. Keep it off in production.
🌐 External Services
The package contacts no hosts of its own. verifyEip1271, and verifyEvmSignature when it falls back to the contract wallet check, send one eth_call to the RPC endpoint of the client of the message chain (with viem’s http() and no URL, that is the default public RPC of the chain). Signing goes through the wallet of the wagmi Config or WalletClient.
📚 API Reference
Every export, with signatures and types generated from the source, is documented at siwx.docs.tuwa.io/packages/siwx-evm .
📄 License
Licensed under the Apache-2.0 License. See the LICENSE file for details.