To connect to XMTP, your app needs a signer, an object that identifies a user and can sign messages on their behalf. Ethereum EOA signatures use ECDSA on the secp256k1 curve. The SDK also supports smart contract wallet verification and P-256 passkey signatures.
There are four ways to create a signer:
Key pair: Generate or provide a secp256k1 private key directly. No wallet or blockchain needed.
Externally Owned Account (EOA): Use an Ethereum wallet.
Smart Contract Wallet (SCW): Use an ERC-1271 compatible smart contract wallet.
Passkey: Use a P-256 key and the passkey signature fields.
All four use the same signer interface, so the rest of your XMTP integration is identical regardless of which you choose.
Use Client.generateKeys() (SDKClient.generateKeys() in Kotlin and Swift) to
create a LocalSigningKey. It derives an Ethereum address, signs ERC-191 text,
and can be passed directly to Client.create() or SDKClient.create(). No
additional crypto package is needed. Generating a key does not contact a backend.
Save the private key securely before creating a persistent account. toHex()
returns lowercase hex with a 0x prefix; toBytes() returns a copy of the 32
private-key bytes. LocalSigningKey.fromHex() and fromBytes() restore the same
identity. Hex imports require 0x followed by exactly 64 hexadecimal digits.
Invalid lengths, invalid hex, zero, and out-of-range private keys fail with
InvalidInput. toBytes() and toHex() are synchronous on Swift and Kotlin,
and asynchronous on Node and Browser. Generation, import, and signing remain
asynchronous on every platform.
let signer =tryawait LocalSigningKey.fromHex(key: saved)
let keyBytes = signer.toBytes()
let keyHex = signer.toHex()
The exported value controls the account. Use the Keychain, an encrypted keystore,
or another secret store. Keep it separate from the database encryption key.
toString() and normal debug output do not export the private key; use toHex()
explicitly.
Connect your wallet or key provider to the signer interface. These EOA examples
call an app function that signs SigningRequest.text as ERC-191 text and returns
raw recoverable ECDSA signature bytes.
All three methods are async. PublicIdentity has identifier and kind.
Ethereum identities use the ethereum kind and an Ethereum address. Passkey
identities use the passkey kind and a public key encoded as hex.
SignerKind selects EOA, SCW, or passkey. An SCW kind carries a required chain ID
and an optional block number. JavaScript uses bigint; Kotlin uses ULong;
Swift uses UInt64. The EOA kind carries neither field.
Return the matching Signature variant with raw bytes. An EOA returns ecdsa with 65-byte [R || S || V] data. Convert a wallet library’s hex result to bytes when needed. An SCW returns its bytes, address, chain ID, and optional block number. A passkey returns its signature, public key, authenticator data, and client data JSON.
Most Ethereum wallet providers add the ERC-191 prefix and hash the message. If you add it again, verification fails. Check the wallet provider behavior.
An SCW signer uses the same interface. Its SignerKind and Signature carry the same chain ID. The signature can include a block number for historical verification. The backend operator must configure the RPC endpoint for that chain.