Skip to main content
The SDK ships no wallet stack. It is not coupled to Privy, Turnkey, AppKit, wagmi, or anything else, and it never asks a user to connect. Wallet discovery, connection, and disconnection are your app’s job. That is deliberate. A partner may be a wallet provider themselves, and every partner already has a wallet story before they reach Passage. So the SDK inverts it: you implement one small interface, EvmWallet, and the SDK drives it. Do that once and every on-chain flow works - Superstate swaps, Ondo buys, token-sale approvals, ownership proofs.
The SDK uses and ships viem types, the de-facto TypeScript standard for Ethereum, so the interface lines up with whatever you are already using. A SolanaWallet seam will follow the same shape when Solana lands.

Two shapes, one seam

There are two interfaces, and the smaller one is a subset of the larger. EvmWallet extends EvmSigner, so implementing the full interface satisfies both. If all you need is the requirements checklist binding a wallet to an offer option, EvmSigner is enough and your users never see a gas prompt.

Implement EvmWallet

Every method that takes a chain gets it as an explicit parameter. Switch the wallet to that chain before acting - the SDK does not assume your wallet is already pointed at the right one.

Adapter: wagmi and AppKit

This is the canonical adapter, and it is the one the partner demo runs. Any stack that can sign a message and send a transaction works the same way.
wagmiEvmWallet.ts

Let wallet errors propagate

Do not catch and translate your wallet library’s errors. Let them throw. The SDK catches them and classifies them into a typed WalletError, so you never parse a wallet error string yourself: Flows surface it as the cause on the step that failed, so you can tell a user rejection apart from an infrastructure problem. See Errors and edge cases for the full breakdown.

Proving wallet ownership

Two identities are involved in every Passage purchase, and they are not the same thing:
  • The CoinList session (from OAuth) identifies who is investing.
  • The wallet is where the assets are delivered.
An ownership proof is what links them, which is why the user signs a message before they can transact. Some offers go further: the issuer only settles to wallets they have allowlisted. Superstate assets work this way, and the same allowlists are what make permissioned DeFi pools and RWA lending markets reachable. Ownership proof is provider-agnostic, so it lives on the platform namespace rather than being duplicated per provider:
coinlist.wallets also reads and removes those bindings: list({ offerId, offerOptionId }) returns the wallets bound to an option, and remove({ offerId, addressId }) unbinds one.
You rarely need to write the sequence above. RequirementsChecklistContainer runs it for you when an offer has an external_wallet or whitelisted_wallet requirement, and the Superstate Swap allowlist flow wraps it in one idempotent call.

CheckoutWalletSelection: what checkout takes

EvmWallet is what the SDK drives. CheckoutWalletSelection is what you hand CheckoutContainer: the wallets the buyer may spend from, plus the two lambdas that connect and disconnect an external one.
There is deliberately no separate wallet model here: EvmWallet already carries the address, so an { address, signer } pair would be a second wallet type to keep in sync with the first. Everything a wallet row displays beyond the address - the network name - comes from the checkout’s execution chain rather than from the wallet, because that is the chain the transaction goes to whatever the wallet is currently pointed at.

Next step

Building the Checkout flow

Hand your wallets to CheckoutContainer and let it render whichever provider the offer belongs to.