# Openfort Documentation Comprehensive tutorials on embedded wallets, gas sponsorship, and wallet management. # Welcome to Openfort Openfort builds secure **wallet infrastructure** to unlock better UX with internet money. Embedded wallets, account abstraction, automations, and money flows come built-in—so you can add stablecoins functionality *in days, not months*. ## Choose the right product ## Get started # Why Openfort Openfort solves the core challenges of modern wallet infrastructure: **vendor lock-in, approval bottlenecks, and transaction lifecycle complexity**. With an open-source, non-custodial signing implementation, your users' keys remain under your control and aren't locked into a single ecosystem. The architecture supports programmable policy engines and permissions, enabling automation for **AI agents, payments, and fintech use cases**. Beyond signing, Openfort manages the entire transaction pipeline—from gas estimation to reliable inclusion—so you can focus on building your product. ## Open source foundations Openfort develops, uses an maintains a non-custodial embedded wallet key management solution called [OpenSigner](https://www.opensigner.dev). OpenSigner is MIT licensed self-hostable. Openfort is the managed cloud platform built on OpenSigner. It adds enterprise features and eliminates operational overhead. Teams that need to ship faster, want turnkey authentication, or require advanced features like gas sponsorship and transaction automation use Openfort. | Feature | OpenSigner | Openfort | | :--- | :--- | :--- | | Authentication | Bring your own | Openfort auth or bring your own | | Recovery | Automatic, Passkey, Password | Automatic, Passkey, Password | | Account Abstraction | - | Built-in | | Automation | - | Onchain permissions and TEE wallets | | Maintenance | Self-managed | Zero operational overhead | | Pricing | Free | Usage-based | ```text ┌─────────────────────────────────────────────────────────────────────┐ │ YOUR APPLICATION │ └─────────────────────────────────────────────────────────────────────┘ │ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Openfort │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ Embedded Key Management Layer (OpenSigner) │ │ │ │ • Key Generation • PIN/Passkey/Automatic Recovery │ │ │ │ • Secure Signing • Non-Custodial Architecture │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ Platform & Infrastructure Services │ │ │ │ • Managed Authentication • Account Abstraction │ │ │ │ • Gas Sponsorship • Transaction Automation │ │ │ │ • Batch Transactions • Policy Engine │ │ │ └───────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ``` ## Compare with other solutions Openfort offers distinct advantages over other wallet infrastructure providers and approaches. ### Compared to other wallet SDKs * **Framework agnostic**—Works with any stack, from web to mobile SDKs, including headless options for maximum customization * **Unified experience**—Embedded wallets, account abstraction, and infrastructure in one SDK with no need for multiple vendors * **Full control**—Customize UI, flows, and permissions with complete flexibility ### Compared to self-hosted wallet solutions * **No separate infrastructure**—Runs in your app with no dedicated wallet back end to maintain * **Zero operational overhead**—No additional servers or security audits to manage * **Complete feature set**—Account abstraction wallets, authentication methods, and in-app UX ready out of the box ### Compared to managed private key services * **Own your infrastructure**—Your users stay in your ecosystem with no vendor lock-in * **Transparent pricing**—No hidden per-user or transaction-based fees that scale unpredictably * **Open source**—Inspect and contribute to avoid black-box dependencies ### Compared to building your own wallet stack * **Security handled**—Battle-tested flows for wallet creation, key management, and transaction signing * **Faster time to market**—Focus on your core product instead of low-level wallet infrastructure * **Extensible design**—Add custom flows without reinventing the wheel ## Resources # Using Openfort with AI ## Openfort CLI The [Openfort CLI](/docs/overview/building-with-cli) exposes every command as an MCP tool, allowing AI agents to **create wallets, manage policies, send transactions, and more** — directly from your AI assistant. Paste this into your agent to set up the Openfort CLI as an MCP server: :::code-group ```bash [Claude Code] claude -p "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Amp] amp --execute "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Codex CLI] codex exec "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ::: ### Quick Setup Register the CLI as an MCP server with a single command: ```bash openfort mcp add ``` This auto-detects your installed AI agents (Claude Code, Cursor, Amp) and registers the CLI with all of them. To target a specific agent: ```bash openfort mcp add --agent claude-code openfort mcp add --agent cursor openfort mcp add --agent amp ``` Or configure manually: ```json { "mcpServers": { "openfort": { "command": "npx", "args": ["@openfort/cli", "--mcp"], "env": { "OPENFORT_SECRET_KEY": "sk_test_..." } } } } ``` :::warning The CLI MCP server runs commands with your credentials. Make sure `OPENFORT_SECRET_KEY` (and optionally `OPENFORT_WALLET_SECRET` and `OPENFORT_PUBLISHABLE_KEY`) are available as environment variables, or run `openfort login` and `openfort backend-wallet setup` first. ::: ### Available Tools Every CLI command is exposed as an MCP tool. Subcommand paths are joined with underscores: | Tool | Description | | --- | --- | | `accounts_list` | List all accounts across chains | | `accounts_evm_create` | Create a new EVM backend wallet | | `accounts_evm_send-transaction` | Send a gasless EVM transaction | | `accounts_solana_create` | Create a new Solana backend wallet | | `accounts_solana_transfer` | Transfer SOL or SPL tokens | | `contracts_create` | Register a smart contract | | `policies_create` | Create an access-control policy | | `policies_evaluate` | Pre-flight check if an operation is allowed | | `sponsorship_create` | Create a gas sponsorship | | `transactions_create` | Create a transaction intent | | `transactions_estimate` | Estimate gas cost | | `users_list` | List users | | `sessions_create` | Create a session key | | `message_hash` | Hash a message with keccak256 | :::tip Use both MCP servers together — the docs server for context and the CLI server for execution. For example, an AI agent can look up how policies work via the docs MCP, then create one via the CLI MCP. ::: ## Openfort Docs ### Docs skill Install the [Openfort Agent Skills](https://github.com/openfort-xyz/agent-skills) to give AI coding agents (Amp, Claude Code, etc.) access to Openfort documentation, source code via MCP, and examples. ```bash npx skills add openfort-xyz/agent-skills ``` Once installed, the agent uses it automatically when relevant tasks are detected. Check out [`openfort-xyz/agent-skills`](https://github.com/openfort-xyz/agent-skills) for more info on available skills. Or manually: ```bash $ git clone https://github.com/openfort-xyz/agent-skills.git $ cp -r agent-skills/skills/openfort ~/.config/agents/skills/ ``` Or add to your project's [`.agents/skills/`](https://github.com/openfort-xyz/agent-skills/tree/main/skills) directory for project-specific access. ### Read docs as Markdown Every page on this site is available as plain Markdown — append `.md` to any URL: ```text https://www.openfort.io/docs/products/embedded-wallet/react.md ``` This is useful for copying documentation into AI conversations or for custom integrations. AI user agents are automatically detected and served raw Markdown instead of rendered HTML. This provides better token efficiency and easier parsing for LLMs. For LLM consumption, two [`llms.txt`](https://llmstxt.org/) files are served at the root: | URL | Contents | |---|---| | [/llms.txt](https://www.openfort.io/docs/llms.txt) | Concise index of all pages with titles and descriptions | | [/llms-full.txt](https://www.openfort.io/docs/llms-full.txt) | Complete documentation in a single file | ### MCP server The docs include a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for programmatic navigation of documentation and source code. :::code-group ```bash [Claude Code] claude mcp add --transport http openfort-docs https://www.openfort.io/api/mcp ``` ```bash [Codex CLI] codex mcp add openfort-docs --url https://www.openfort.io/api/mcp ``` ```bash [Amp] amp mcp add openfort-docs https://www.openfort.io/api/mcp ``` ::: Or configure manually: ```json { "mcpServers": { "openfort-docs": { "url": "https://www.openfort.io/api/mcp" } } } ``` #### Available Tools | Tool | Description | | --- | --- | | `list_pages` | List all documentation pages with their paths | | `read_page` | Read the content of a specific documentation page | | `search_docs` | Search documentation for a query string | #### Source Code Navigation The documentation MCP server also provides access to source code repositories: | Tool | Description | | --- | --- | | `list_sources` | List available source code repositories | | `list_source_files` | List files in a directory | | `read_source_file` | Read a source code file | | `get_file_tree` | Get a recursive file tree | | `search_source` | Search source code for a pattern | # Openfort CLI The Openfort CLI lets you manage blockchain wallets, access-control policies, gas sponsorship, transaction intents, and other Openfort resources directly from the terminal. It supports both EVM and Solana chains.
Key Concepts Before using the CLI, it helps to understand the core resource model: * **EOA (Externally Owned Account)** — A standard EVM backend wallet created with `accounts evm create`. This is the base key pair you sign with. * **Delegated Account** — An EOA upgraded to a smart account via EIP-7702 (`accounts evm update`). Delegated accounts support account abstraction (gasless transactions). Each delegated account is chain-specific and has its own `acc_` ID separate from the EOA. * **Policy** — A rule set that governs which operations are allowed (e.g., sponsoring gas on a specific chain). * **Sponsorship** — Links a policy to a gas payment strategy (e.g., `pay_for_user`), enabling gasless transactions. * **Transaction Intent** — A record of a desired on-chain action. Created via the API, it may require signing before broadcast. **Sending Transactions: Two Approaches** There are two ways to send transactions. Choose based on your use case: | Approach | Command | Best for | | :--- | :--- | :--- | | **Sponsored (recommended)** | `openfort accounts evm send-transaction` | Gasless transactions. Handles everything automatically: finds or creates a delegated account, signs EIP-7702 authorization, creates the transaction intent, signs and submits it. Pass an **EOA** account ID. | | **Manual (advanced)** | `openfort transactions create` + `openfort transactions sign` | When you need fine-grained control over each step, external signing, or non-backend wallets. Requires a **delegated** account ID. You must handle signing separately. | :::tip For most use cases, use `openfort accounts evm send-transaction`. It wraps the entire delegation and signing flow into a single command. The `openfort transactions` commands are for advanced scenarios where you need to separate transaction creation from signing. :::
## Installation ```bash npm install -g @openfort/cli ``` After installation, the `openfort` command is available globally. ## Authenticate ```bash openfort login ``` This opens your browser to the Openfort dashboard. After you authorize, the CLI stores your API key, publishable key, and project ID in a local credentials file at `~/.config/openfort/credentials` (or `$XDG_CONFIG_HOME/openfort/credentials` if set). :::tip All commands except `login` require authentication. The CLI reads your credentials automatically from the stored file. After logging in, run `openfort backend-wallet setup` to set up signing keys. ::: ## Example: Send a Sponsored Transaction This walkthrough creates a backend wallet and sends a gasless transaction on Base Sepolia (chain ID 84532). :::steps ### Authenticate ```bash openfort login ``` ### Generate wallet keys ```bash openfort backend-wallet setup ``` ### Create a backend wallet ```bash openfort accounts evm create # Returns: acc_ ``` ### Register the target smart contract ```bash openfort contracts create \ --name "My Token" \ --address 0xbabe0001489722187FbaF0689C47B2f5E97545C5 \ --chainId 84532 ``` ### Create a policy and sponsorship for gas ```bash # Create a policy that allows sponsoring transactions on Base Sepolia openfort policies create \ --scope project \ --rules '[{"action":"accept","operation":"sponsorEvmTransaction","criteria":[{"type":"evmNetwork","operator":"in","chainIds":[84532]}]}]' # Returns: ply_ # Create a sponsorship linked to the policy openfort sponsorship create \ --policyId ply_ \ --strategy pay_for_user \ --name "Base Sepolia Gas" \ --chainId 84532 # Returns: pol_ ``` ### Send a gasless transaction Use `accounts evm send-transaction` with the **EOA** account ID. The command auto-delegates the account via EIP-7702, signs, and broadcasts: ```bash openfort accounts evm send-transaction acc_ \ --chainId 84532 \ --interactions '[{"to":"0xbabe0001489722187FbaF0689C47B2f5E97545C5","data":"0x40c10f190000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000","value":"0"}]' \ --policy pol_ ``` ### Verify the transaction ```bash openfort transactions get ``` ::: ## Backend Wallet Key Setup Before you can create wallets, sign data, or send transactions, you need to generate and register backend wallet signing keys (ECDSA P-256): ```bash openfort backend-wallet setup ``` This generates an ECDSA P-256 key pair, registers it with Openfort, and saves the keys to your credentials file. You can also rotate or revoke keys: ```bash openfort backend-wallet rotate openfort backend-wallet revoke ``` ## Accounts (Embedded Wallets) Manage backend wallets on both EVM and Solana chains. ### List all accounts ```bash openfort accounts list openfort accounts list --chainType EVM --custody Developer --limit 10 ``` | Option | Description | | :--- | :--- | | `--limit`, `-l` | Max number of results | | `--skip` | Number of results to skip | | `--chainType` | Filter by chain type: `EVM` or `SVM` | | `--custody` | Filter by custody model: `Developer` or `User` | ### EVM Wallets ```bash # Create a new EVM backend wallet (EOA) openfort accounts evm create # List EOA wallets openfort accounts evm list --limit 5 # List delegated (smart) accounts openfort accounts evm list-delegated --limit 5 # List smart accounts openfort accounts evm list-smart --limit 5 # Get wallet details openfort accounts evm get # Sign data with an EOA openfort accounts evm sign --data 0x1234abcd # Import an existing private key openfort accounts evm import --privateKey 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Export a private key openfort accounts evm export # Delete a wallet openfort accounts evm delete ``` #### Upgrading to a Delegated Account (EIP-7702) Upgrade an EOA to a delegated smart account on a specific chain. This registers the delegation with Openfort — the on-chain EIP-7702 delegation happens automatically on the first transaction. ```bash openfort accounts evm update \ --chainId 84532 \ --implementationType CaliburV9 ``` :::info The `--implementationType` specifies the smart account implementation. Availability varies by chain. The delegated account receives a new `acc_` ID which you can retrieve with `accounts evm list-delegated`. ::: #### Sending a Sponsored Transaction This is the **recommended** way to send transactions. Pass an **EOA** account ID — the command automatically handles delegation, EIP-7702 authorization signing, transaction intent creation, and submission: ```bash openfort accounts evm send-transaction \ --chainId 84532 \ --interactions '[{"to":"0x...","data":"0x...","value":"0"}]' \ --policy pol_abc123 ``` The `--policy` flag references a sponsorship ID (starts with `pol_`) that covers gas fees. Without it, the account's own native tokens are used. ### Solana Wallets ```bash # Create a new Solana backend wallet openfort accounts solana create # List Solana wallets openfort accounts solana list # Get wallet details openfort accounts solana get # Sign data openfort accounts solana sign --data SGVsbG8gV29ybGQ= # Import / Export openfort accounts solana import --privateKey openfort accounts solana export # Delete a wallet openfort accounts solana delete # Transfer SOL (--token defaults to "sol", --cluster defaults to mainnet-beta) openfort accounts solana transfer \ --to FDx9mf... \ --amount 1000000 \ --cluster devnet # Transfer USDC openfort accounts solana transfer \ --to FDx9mf... \ --amount 500 \ --token usdc \ --cluster mainnet-beta # Transfer SPL token by mint address openfort accounts solana transfer \ --to FDx9mf... \ --amount 500 \ --token \ --cluster mainnet-beta ``` ## Smart Contracts Register and manage smart contracts for use in transactions and policies. When you register a contract by address, Openfort automatically fetches and stores its ABI if the contract is verified on-chain. ```bash # List contracts openfort contracts list --limit 10 # Register a contract (ABI auto-fetched if verified on-chain) openfort contracts create \ --name USDC \ --address 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 \ --chainId 137 # Register with explicit ABI openfort contracts create \ --name "My Contract" \ --address 0x... \ --chainId 137 \ --abi '[{"type":"function","name":"transfer",...}]' # Get contract details openfort contracts get # Update a contract openfort contracts update --name "USDC v2" --chainId 137 --address 0x... --abi '[{"type":"function","name":"transfer",...}]' # Delete a contract openfort contracts delete ``` | Option | Description | | :--- | :--- | | `--name` | Human-readable contract name | | `--address` | Contract address on-chain | | `--chainId` | Chain ID where the contract is deployed | | `--abi` | Contract ABI as a JSON string (optional — auto-fetched for verified contracts) | ## Policies Create access-control policies that govern which transactions are allowed or sponsored. ```bash # List policies openfort policies list --scope project --enabled true # Create a policy to sponsor transactions on a chain openfort policies create \ --scope project \ --description "Sponsor transactions on Base Sepolia" \ --priority 1 \ --rules '[{"action":"accept","operation":"sponsorEvmTransaction","criteria":[{"type":"evmNetwork","operator":"in","chainIds":[84532]}]}]' # Get / Update / Delete openfort policies get openfort policies update --enabled false openfort policies delete # Evaluate a policy (pre-flight check) openfort policies evaluate --operation sponsorEvmTransaction --accountId acc_abc123 ``` | Option | Description | | :--- | :--- | | `--scope` | Policy scope: `project`, `account`, or `transaction` | | `--description` | Human-readable description | | `--priority` | Priority order (higher = evaluated first) | | `--rules` | JSON array of policy rules | | `--enabled` | Enable or disable the policy | ## Gas Sponsorship Set up gas gas sponsorship so your users don't pay for transactions. A sponsorship links a policy to a payment strategy. ```bash # List sponsorships openfort sponsorship list --enabled true # Create a sponsorship linked to a policy openfort sponsorship create \ --policyId ply_abc123 \ --name "Base Sepolia Gas Sponsor" \ --strategy pay_for_user \ --chainId 84532 # Get details openfort sponsorship get # Update openfort sponsorship update --name "Updated Sponsor" --strategy pay_for_user --policyId ply_abc123 # Enable / Disable openfort sponsorship enable openfort sponsorship disable # Delete openfort sponsorship delete ``` | Strategy | Description | | :--- | :--- | | `pay_for_user` | Fully sponsor the user's gas fees (default) | | `charge_custom_tokens` | Charge the user in custom tokens | | `fixed_rate` | Charge a fixed rate | :::info After creating a sponsorship, you receive a `pol_` ID. Pass this as `--policy` to `send-transaction` or `transactions create` to enable gas sponsorship. ::: ## Transactions Transaction intents represent desired on-chain actions. There are two levels of commands: ### Recommended: `accounts evm send-transaction` For **sponsored/gasless transactions**, use `accounts evm send-transaction` with an **EOA** account ID. It handles the full lifecycle automatically: 1. Finds or creates a delegated account for the EOA on the target chain 2. Signs the EIP-7702 authorization (if this is the first transaction) 3. Creates the transaction intent 4. Signs and submits the transaction ```bash openfort accounts evm send-transaction \ --chainId 84532 \ --interactions '[{"to":"0x...","data":"0x...","value":"0"}]' \ --policy pol_abc123 ``` ### Advanced: `transactions` commands For **fine-grained control** or external signing workflows, use the `transactions` commands directly. These work with **delegated** account IDs (obtained from `accounts evm update` or `accounts evm list-delegated`). ```bash # Create a transaction intent (returns a signableHash in nextAction) openfort transactions create \ --account \ --chainId 84532 \ --interactions '[{"to":"0x...","data":"0x...","value":"0"}]' \ --policy pol_abc123 # Sign the signableHash with the EOA openfort accounts evm sign --data # Submit the signature to broadcast openfort transactions sign --signature ``` :::warning When using `transactions create` with a newly delegated account, you may need to pass `--signedAuthorization` with a signed EIP-7702 authorization for the first transaction. Without it, the on-chain delegation won't be established and the transaction will fail with a signature error. Use `accounts evm send-transaction` to avoid this complexity. ::: ### Other transaction commands ```bash # List transactions openfort transactions list --limit 20 # Get transaction details and status openfort transactions get # Estimate gas cost before sending openfort transactions estimate \ --account \ --chainId 84532 \ --interactions '[{"to":"0x...","data":"0x...","value":"0"}]' \ --policy pol_abc123 # Sign with optimistic return (don't wait for on-chain confirmation) openfort transactions sign --signature 0x... --optimistic true ``` ## Sessions Manage session keys that let users sign transactions for a limited time without repeated approvals. ```bash # List sessions openfort sessions list --player pla_abc123 # Create a session key openfort sessions create \ --address 0x... \ --chainId 137 \ --validAfter 1700000000 \ --validUntil 1700086400 \ --player pla_abc123 \ --account acc_abc123 \ --limit 100 \ --policy pol_abc123 \ --whitelist '["con_abc123"]' # Get session details openfort sessions get # Sign a session openfort sessions sign --signature 0x... # Sign a session with optimistic return (don't wait for on-chain confirmation) openfort sessions sign --signature 0x... --optimistic true # Revoke sessions openfort sessions revoke --address 0x... --chainId 137 --player pla_abc123 --policy pol_abc123 ``` ## Users View and manage users in your project. ```bash # List users openfort users list --email user@example.com --name "John" --limit 10 # Get user details openfort users get # Delete a user openfort users delete ``` ## Paymasters Configure ERC-4337 paymaster contracts. ```bash # Create a paymaster openfort paymasters create --address 0x... --name "My Paymaster" --url https://paymaster.example.com # Get / Update / Delete openfort paymasters get openfort paymasters update --address 0x... --name "Updated Paymaster" --url https://paymaster.example.com openfort paymasters delete ``` | Option | Required | Description | | :--- | :--- | :--- | | `--address` | Yes | Paymaster contract address | | `--name` | No | Human-readable paymaster name | | `--url` | No | Paymaster service URL | :::info When updating a paymaster, `--address` is always required even if you only want to change other fields. ::: ## Subscriptions (Webhooks) Subscribe to Openfort events and configure webhook triggers. ```bash # List subscriptions openfort subscriptions list # Create a subscription with a webhook trigger openfort subscriptions create \ --topic transaction_intent.successful \ --triggers '[{"type":"webhook","target":"https://myapp.com/webhooks"}]' # Get / Delete openfort subscriptions get openfort subscriptions delete ``` ### Managing Triggers ```bash # List triggers for a subscription openfort subscriptions triggers list # Add a trigger openfort subscriptions triggers create \ --target https://myapp.com/webhooks \ --type webhook # Add an email trigger openfort subscriptions triggers create \ --target alerts@myapp.com \ --type email # Get / Delete a trigger openfort subscriptions triggers get openfort subscriptions triggers delete ``` ### Available Topics | Topic | Description | | :--- | :--- | | `transaction_intent.broadcast` | Transaction was broadcast to the network | | `transaction_intent.successful` | Transaction completed successfully | | `transaction_intent.cancelled` | Transaction was cancelled | | `transaction_intent.failed` | Transaction failed | | `balance.project` | Project balance changed | | `balance.contract` | Contract balance changed | | `balance.dev_account` | Developer account balance changed | | `user.created` | A new user was created | | `user.updated` | A user was updated | | `user.deleted` | A user was deleted | | `account.created` | A new account was created | | `test` | Test event for verifying webhooks | ## Logs Inspect API request, webhook, and subscription logs to debug a failing integration. Only mutating calls (POST/PUT/PATCH/DELETE) are recorded, and secrets in bodies are masked before storage. ```bash # Project API request logs — what calls were made and how they responded openfort logs list openfort logs list --limit 20 --method POST # Webhook delivery logs openfort logs webhook # Triggered subscription logs openfort logs subscriptions --status failed --limit 20 ``` ## Embedded Wallet Keys (Shield) Set up Shield encryption keys for embedded wallets: ```bash openfort embedded-wallet setup --project pro_abc123 ``` This registers encryption keys with the Shield service and stores `SHIELD_PUBLISHABLE_KEY`, `SHIELD_SECRET_KEY`, and `SHIELD_ENCRYPTION_SHARE` in your credentials file. ## Message Utilities Hash messages using keccak256: ```bash openfort message hash "Hello World" ``` ## Integrations The CLI includes built-in integration commands: ```bash # Generate shell completion script (bash, zsh, fish, nushell) openfort completions # Register the CLI as an MCP (Model Context Protocol) server openfort mcp add # Sync skill files to AI agents openfort skills add ``` ## Configuration ### Credentials File The CLI stores credentials at: * **Linux/macOS**: `~/.config/openfort/credentials` (or `$XDG_CONFIG_HOME/openfort/credentials`) * **Windows**: `%APPDATA%/openfort/credentials` The file contains key-value pairs: ```text OPENFORT_SECRET_KEY=sk_test_... OPENFORT_PUBLISHABLE_KEY=pk_test_... OPENFORT_PROJECT_ID=pro_... OPENFORT_WALLET_SECRET=... OPENFORT_WALLET_KEY_ID=... OPENFORT_WALLET_PUBLIC_KEY=... ``` ### Environment Variables You can override credentials via environment variables: | Variable | Description | | :--- | :--- | | `OPENFORT_SECRET_KEY` | Secret API key (required for all commands except `login`) | | `OPENFORT_WALLET_SECRET` | Wallet encryption secret (required for wallet operations) | | `OPENFORT_PUBLISHABLE_KEY` | Publishable key for client-side operations | | `OPENFORT_BASE_URL` | Custom API base URL (default: `https://api.openfort.io`) | ## Global Options Every command supports the following global options: | Option | Description | | :--- | :--- | | `--format ` | Output format (default: `toon`) | | `--filter-output ` | Filter output by key paths (e.g. `foo,bar.baz,a[0,3]`) | | `--verbose` | Show full output envelope | | `--schema` | Show JSON Schema for the command | | `--llms`, `--llms-full` | Print LLM-readable manifest | | `--mcp` | Start as MCP stdio server | | `--token-count` | Print token count of output instead of the output itself | | `--token-limit ` | Limit output to n tokens | | `--token-offset ` | Skip first n tokens of output | :::tip Use `--format json` to get machine-readable output for scripting and automation. Use `--filter-output` to extract specific fields from the response. ::: ## Learn more # Build a mobile neobank Use Openfort's React Native SDK to create a mobile neobank. Embedded wallets and sponsored gas policies allow users to manage stablecoins without seed phrases or gas fees. ## Prerequisites * [Node.js](https://nodejs.org/) (v18+) * An [Expo](https://expo.dev/) development environment * An [Openfort](https://dashboard.openfort.io/) account with a project created ## Install the Openfort CLI ```bash npm install -g @openfort/cli openfort login ``` ## Teach your agent to use Openfort Register the CLI as an MCP server so your agent can call Openfort tools directly: ```bash openfort mcp add ``` Or, if your agent supports skills: :::code-group ```bash [Claude Code] claude -p "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Amp] amp --execute "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Codex CLI] codex exec "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ::: ## Walkthrough ::::steps ### 1. Set up embedded wallet keys Generate the Shield API keys required for embedded wallets: > **Prompt your agent**: "Run `embedded-wallet setup` to generate and register the Shield API keys for this project." This produces your `SHIELD_PUBLISHABLE_KEY`. Save it — you'll need it for the provider configuration. ### 2. Create the React Native project Scaffold the app and install all required dependencies: > **Prompt your agent**: "Create a new Expo React Native app. Install `@openfort/react-native` and its required peer dependencies: `expo-secure-store`, `expo-crypto`, `expo-web-browser`, `expo-linking`, `expo-application`, `expo-apple-authentication`, and `react-native-webview`. Also install `@solana/kit`, `@solana/kora`, and `@solana/transaction-confirmation` for Solana transaction support." :::info `react-native-passkeys` is already bundled with `@openfort/react-native` — do not install it separately to avoid version conflicts. ::: ### 3. Configure the OpenfortProvider Set up the provider with passkey recovery at the root of your app: > **Prompt your agent**: "Wrap the app in an `OpenfortProvider` from `@openfort/react-native`. Configure it with passkey recovery using these settings:" ```tsx import { OpenfortProvider, RecoveryMethod } from '@openfort/react-native'; {/* App content */} ``` | Environment variable | Description | |---|---| | `OPENFORT_PUBLISHABLE_KEY` | Your Openfort publishable key (from dashboard) | | `SHIELD_PUBLISHABLE_KEY` | Generated in Step 1 | | `PASSKEY_RP_ID` | Your domain (e.g., `yourdomain.com`) | ### 4. Set up domain verification Passkeys require platform-specific domain binding. Without this, passkey creation will fail on devices. > **Prompt your agent**: "Set up passkey domain verification. For iOS, create an `apple-app-site-association` file with `webcredentials` for our team ID and bundle ID, served at `https://yourdomain.com/.well-known/apple-app-site-association`. For Android, create an `assetlinks.json` with our package name and SHA256 fingerprint at `https://yourdomain.com/.well-known/assetlinks.json`." ### 5. Create wallets on signup Use the `useEmbeddedSolanaWallet` hook to provision a wallet when a user registers: > **Prompt your agent**: "Create a signup screen that uses the `useEmbeddedSolanaWallet` hook from `@openfort/react-native`. After authentication, call `create({ recoveryMethod: 'passkey' })` to provision a Solana embedded wallet. Display the wallet address on success." ```tsx import { useEmbeddedSolanaWallet } from '@openfort/react-native'; const { create, status, activeWallet } = useEmbeddedSolanaWallet(); await create({ recoveryMethod: 'passkey', onSuccess: ({ account }) => { console.log('Wallet created:', account.address); }, }); ``` ### 6. Create a gas sponsorship policy Set up a policy so users don't pay transaction fees. Use the Openfort CLI: > **Prompt your agent**: "Create a project-scoped policy using `policies create` that accepts `sponsorSolTransaction` operations. Then create a gas sponsorship linked to that policy using `sponsorship create`." This ensures the Kora paymaster will sponsor your users' Solana transactions. ### 7. Transfer stablecoins Build the transfer screen using the embedded wallet provider and Kora for gas sponsorship: > **Prompt your agent**: "Create a transfer screen that accepts a Solana address and USDC amount. Use the `provider` from `useEmbeddedSolanaWallet` for signing. Construct a sponsored transaction using `@solana/kit` and `@solana/kora`: > > 1. Create a `KoraClient` pointing to `https://api.openfort.io/rpc/solana/devnet` with the publishable key as the Bearer token > 2. Get the fee payer signer via `getPayerSigner()` > 3. Build a transfer instruction for the USDC token > 4. Sign the transaction with the user's embedded wallet provider > 5. Call `signAndSendTransaction()` on the KoraClient to co-sign and broadcast" :::: ## Next steps * [React Native quickstart](/docs/products/embedded-wallet/react-native) * [Passkey recovery setup](/docs/products/embedded-wallet/react-native/quickstart/passkey) * [Solana paymaster](/docs/products/infrastructure/paymaster/solana) * [useEmbeddedSolanaWallet hook](/docs/products/embedded-wallet/react-native/hooks/useEmbeddedSolanaWallet) # Create a stablecoin payroll system Automate payments to contractors, employees, or users. Use Openfort's backend wallets and batch transactions to execute programmable payouts efficiently. ## Prerequisites * [Node.js](https://nodejs.org/) (v18+) * An [Openfort](https://dashboard.openfort.io/) account with a project created ## Install the Openfort CLI ```bash npm install -g @openfort/cli openfort login ``` ## Teach your agent to use Openfort Register the CLI as an MCP server so your agent can call Openfort tools directly: ```bash openfort mcp add ``` Or, if your agent supports skills: :::code-group ```bash [Claude Code] claude -p "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Amp] amp --execute "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Codex CLI] codex exec "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ::: ## Walkthrough :::steps ### 1. Set up backend wallet signing Generate the signing keys so your backend wallet can authorize transactions: > **Prompt your agent**: "Run `backend-wallet setup` to generate and register the ECDSA P-256 signing keys for this project." ### 2. Create a backend wallet Provision the treasury wallet that will disburse payroll: > **Prompt your agent**: "Use `accounts evm create` to create a new EVM backend wallet. This will be our payroll treasury account." This returns an account ID (`acc_...`) and an EOA address. Fund this address with USDC on your target chain. ### 3. Set up the Node.js SDK Initialize the Openfort Node SDK for programmatic payouts: > **Prompt your agent**: "Install `@openfort/openfort-node` into this project. Initialize the SDK with both the secret key and wallet secret." ```ts import Openfort from '@openfort/openfort-node'; const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY, { walletSecret: process.env.OPENFORT_WALLET_SECRET, }); ``` | Environment variable | Description | |---|---| | `OPENFORT_SECRET_KEY` | Your API secret key (`sk_test_...` or `sk_live_...`) | | `OPENFORT_WALLET_SECRET` | Wallet encryption secret (you create this) | ### 4. Configure gas sponsorship Create a policy and gas sponsorship so payroll transactions are gasless: > **Prompt your agent**: "Create a project-scoped policy using `policies create` with a rule that accepts `sponsorEvmTransaction` operations. Then create a gas sponsorship linked to that policy using `sponsorship create`." Without this, batch transactions will fail — `sendTransaction` requires a gas sponsorship to cover gas. ### 5. Batch payroll transfers Write the payroll disbursement script. The SDK's `sendTransaction` method handles the full flow: it auto-upgrades the wallet to an EIP-7702 Delegated Account (required for batching), creates the transaction intent, signs it, and submits it. > **Prompt your agent**: "Write a TypeScript function that takes an array of `{ address: string, amount: number }` payees and uses `openfort.accounts.evm.backend.sendTransaction()` to batch multiple USDC `transfer` calls into a single transaction from our treasury wallet. Each interaction should ABI-encode an ERC-20 `transfer(address, uint256)` call to the USDC contract. Use `viem` to encode the calldata." ```ts import { encodeFunctionData } from 'viem'; const USDC_ADDRESS = '0x...'; // USDC contract on your chain const TREASURY_ACCOUNT = 'acc_...'; // from Step 2 const interactions = payees.map(({ address, amount }) => ({ to: USDC_ADDRESS, data: encodeFunctionData({ abi: [{ name: 'transfer', type: 'function', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ name: '', type: 'bool' }] }], functionName: 'transfer', args: [address, BigInt(amount * 1e6)], // USDC has 6 decimals }), value: '0', })); const result = await openfort.accounts.evm.backend.sendTransaction({ account: TREASURY_ACCOUNT, chainId: 84532, // Base Sepolia for testing interactions, }); ``` To execute payroll directly from the CLI instead: > **Prompt your agent**: "Use `accounts evm send-transaction` with the treasury account ID, chain ID `84532`, and an interactions array containing ABI-encoded USDC `transfer` calls for each payee. Use the policy from Step 4 for gas sponsorship." ::: ## Pregenerate employee wallets (optional) If you want employees to have Openfort embedded wallets ready before they sign up, you can pregenerate wallets by email: > **Prompt your agent**: "Run `embedded-wallet setup` to generate Shield API keys. Then write a script using the Openfort Node SDK that calls `openfort.accounts.evm.embedded.pregenerate()` with each employee's email and the Shield credentials to reserve their wallet addresses." This requires additional credentials: | Parameter | Description | |---|---| | `shieldApiKey` | Shield API key (from `embedded-wallet setup`) | | `shieldApiSecret` | Shield API secret | | `encryptionShare` | Shield encryption share | Once pregenerated, fund these addresses and employees will have access when they log in. ## Next steps * [Backend wallets setup](/docs/products/server/setup) * [Gasless transactions on EVM](/docs/products/server/evm/gasless-transactions) * [Pregenerate wallets](/docs/products/embedded-wallet/server/pregenerate-wallets) * [Policies overview](/docs/configuration/policies) # Give an AI agent a wallet Provision secure, policy-gated wallets for your AI agents to interact with the blockchain. Use Openfort to restrict agent permissions and manage signing without exposing private keys. ## Install the Openfort CLI Install the CLI globally and log in: ```bash npm install -g @openfort/cli openfort login ``` ## Teach your agent to use Openfort Register the CLI as an MCP server so your agent can call Openfort tools directly: ```bash openfort mcp add ``` Or, if your agent supports skills, sync the agent skill files: :::code-group ```bash [Claude Code] claude -p "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Amp] amp --execute "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Codex CLI] codex exec "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ::: ## Walkthrough ::::steps ### 1. Set up backend wallet signing Before your agent can sign transactions, generate the backend wallet signing keys: > **Prompt your agent**: "Run `backend-wallet setup` to generate and register the signing keys for this project." This creates an ECDSA P-256 key pair and registers it with Openfort. The credentials are saved locally — no need to set environment variables manually. ### 2. Provision an agent wallet Create an on-chain wallet for your agent: > **Prompt your agent**: "Use `accounts evm create` to provision a new EVM wallet for this agent." This returns an account ID (e.g., `acc_...`) and an EOA address. Save the account ID — you'll need it for transactions and policies. ### 3. Define a policy Restrict what your agent can do on-chain. First register the contract your agent should interact with, then create a policy scoped to it. > **Prompt your agent**: "Register the USDC contract on Base Sepolia (address `0x036CbD53842c5426634e7929541eC2318f3dCF7e`, chain ID `84532`) using `contracts create`. Then create a project-scoped policy using `policies create` that only allows `signEvmTransaction` operations." :::info If you're using a test API key, you must use testnet chain IDs (e.g., `84532` for Base Sepolia, `11155111` for Ethereum Sepolia). Mainnet chain IDs will be rejected. ::: ### 4. Send a transaction Test the wallet by sending a gasless transaction through the policy: > **Prompt your agent**: "Use `accounts evm send-transaction` to send a transaction from account `acc_` on chain `84532`, calling the USDC contract's `transfer` function. Use the policy `ply_` for gas sponsorship." :::: ## x402 payments (advanced) Agents can handle [x402](https://www.x402.org/) payment flows — automatically paying for HTTP resources that return `402 Payment Required`. This requires additional environment variables: | Variable | Description | |---|---| | `OPENFORT_SECRET_KEY` | Your Openfort secret API key | | `OPENFORT_BACKEND_WALLET_ID` | The `acc_...` ID from Step 2 | | `X402_FACILITATOR_URL` | URL of the x402 facilitator service | | `CDP_API_KEY_NAME` | Coinbase Developer Platform API key name | | `CDP_API_KEY_SECRET` | Coinbase Developer Platform API key secret | > **Prompt your agent**: "Write a script that makes an HTTP request to ``. If it gets a 402 response, parse the `x402` payment requirements from the response headers, then use the Openfort SDK to sign a USDC `TransferWithAuthorization` (EIP-3009) and retry the request with the payment signature in the headers." ## Next steps * [Server-side security](/docs/products/server/security) * [Managing accounts](/docs/products/server/accounts) * [Policies overview](/docs/configuration/policies) # Send a gasless transaction Send a sponsored transaction from the terminal using Openfort's CLI. This walkthrough covers creating a backend wallet, registering a contract, setting up policies for both signing and gas sponsorship, and executing a gasless transaction. ## Prerequisites * [Node.js](https://nodejs.org/) (v18+) * An [Openfort](https://dashboard.openfort.io/) account with a project created ## Install the Openfort CLI ```bash npm install -g @openfort/cli openfort login ``` ## Teach your agent to use Openfort Register the CLI as an MCP server so your agent can call Openfort tools directly: ```bash openfort mcp add ``` Or, if your agent supports skills: :::code-group ```bash [Claude Code] claude -p "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Amp] amp --execute "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ```bash [Codex CLI] codex exec "Read https://github.com/openfort-xyz/agent-skills and set up Openfort CLI" ``` ::: ## Walkthrough ::::steps ### 1. Set up backend wallet signing Generate the signing keys that authorize your backend wallet to sign transactions: > **Prompt your agent**: "Run `backend-wallet setup` to generate and register the ECDSA P-256 signing keys." ### 2. Create an EOA Provision the wallet that will send the transaction: > **Prompt your agent**: "Run `accounts evm create` to create a new EVM backend wallet. Note the account ID (`acc_...`) and address." ### 3. Register the target contract Openfort validates that transaction targets are registered contracts. Register the contract you want to interact with: > **Prompt your agent**: "Use `contracts create` to register USDC on Base Sepolia — name `USDC`, address `0x036CbD53842c5426634e7929541eC2318f3dCF7e`, chain ID `84532`." :::info Openfort auto-fetches the contract ABI from on-chain verification. For proxy contracts (like USDC), this may return the proxy ABI instead of the implementation ABI. If your transaction calldata targets implementation functions (e.g., `transfer`), provide the implementation ABI explicitly via the `--abi` flag. ::: ### 4. Create policies You need two policies: one to **allow signing** and one to **sponsor gas**. > **Prompt your agent**: "Create two project-scoped policies using `policies create`: > > 1. A signing policy with the rule `{"action":"accept","operation":"signEvmTransaction"}` > 2. A sponsorship policy with the rule `{"action":"accept","operation":"sponsorEvmTransaction","criteria":[{"type":"evmNetwork","operator":"in","chainIds":[84532]}]}`" Verify both policies are working with a pre-flight check: > **Prompt your agent**: "Run `policies evaluate` for both `signEvmTransaction` and `sponsorEvmTransaction` operations to confirm they're allowed." ### 5. Create a gas sponsorship Link a gas sponsorship to the sponsorship policy: > **Prompt your agent**: "Run `sponsorship create` linked to the sponsorship policy ID from Step 4. Use the `pay_for_user` strategy and name it `Base Sepolia Gas Sponsor`." The `pay_for_user` strategy means Openfort covers the gas fees entirely. Other strategies include `charge_custom_tokens` (user pays with ERC-20) and `fixed_rate`. ### 6. Send a gasless transaction Execute the transaction. The `send-transaction` command auto-upgrades the EOA to an EIP-7702 Delegated Account if needed. > **Prompt your agent**: "Use `accounts evm send-transaction` with the account ID from Step 2, chain ID `84532`, the sponsorship ID from Step 5 as the `--policy` flag, and an interactions array targeting the registered USDC contract." The interactions array format is: ```json [{"to": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "data": "0x", "value": "0"}] ``` Where `data` is the ABI-encoded function call (e.g., `transfer(address,uint256)` encoded as hex). :::: ## Next steps * [Openfort CLI reference](/docs/overview/building-with-cli) * [Gas sponsorship configuration](/docs/configuration/gas-sponsorship) * [Gasless transactions on EVM](/docs/products/server/evm/gasless-transactions) * [Policies overview](/docs/configuration/policies) # Recipes ## Overview Explore comprehensive recipes that demonstrate how to integrate Openfort's embedded wallet infrastructure with popular DeFi protocols and blockchain applications. Each recipe includes complete source code, setup instructions, and implementation details. :::tip Jump straight to code in our [GitHub](https://github.com/openfort-xyz/recipes-hub). ::: ## Available recipes ## Use your own authentication Complete quickstart templates integrating third-party authentication providers with Openfort embedded wallets. # Openfort Affiliate Program Earn a commission every time you bring a new paying customer to Openfort. Whether you are a developer, founder, agency, or part of an ecosystem, you can refer teams to Openfort and get paid for the business you send our way. ## How it works :::steps ### Apply Chat with our team and tell us how you would like to get paid. ### Get your code We'll provide you with your personal referral code. ### Share it Send the code to acquaintances, friends, or colleagues who are building on blockchain and you believe would be a good fit for Openfort. ### Get paid When someone you referred becomes a paying customer, you earn a commission for the life of their initial subscription. ::: ## What you earn Choose how you want your commission paid out: | Payout type | Rate | Example (Pro plan) | | --- | --- | --- | | **Cash** | **20%** | **$49.80 / month** in cash | | **Additional operations** | **25%** | **25k operations / month** added on top of your plan | | **Subscription extension** | **25%** | **$62.25 / month** off your own plan | Commission is **20% in cash, or 25% taken as additional operations on Openfort or a subscription extension**, based on what your referred customer actually pays Openfort, and it accrues for as long as their initial subscription runs. :::info The example column assumes a referral on the **Pro plan ($249 / month, 100k operations)**. You keep earning that amount every month for the life of their initial subscription. ::: ## Who can join Anyone can refer. Individuals, developers, founders, communities, agencies, and partners are all welcome. ## Getting your referral credited Your referral needs to tell us your code so we can link the customer to you. They can: * Enter your code in the **referral code** field when they sign up for a paid plan * Mention your code in their chat with our team :::tip Make sure your referrals know your code before they sign up for a paid plan. ::: ## Program terms * **Qualifies on the first cleared payment.** A signup alone does not count. Commission starts once your referral makes their first cleared invoice payment. * **No self-referral.** You cannot refer your own organization or related accounts. * **$100 minimum payout.** Balances pay out once you reach $100. * **90-day holding period.** If a referred customer refunds or churns within 90 days, that commission does not count. * **Net of refunds.** Commission is calculated on the amount paid net of refunds and chargebacks. * **Initial term only.** Commission accrues for the initial subscription term. If a customer churns and later re-subscribes, the new term does not count. ## Payouts Payouts run on a **monthly** basis. Once your balance is over $100 and the 90-day holding period has passed with no refund, we pay you in your chosen form: cash, additional operations on Openfort, or a subscription extension applied directly to your account. ## Would you like to become an Openfort affiliate? Get in touch and we'll set you up with your referral code so that you can start earning your revenue share. # Embedded Wallet Embedded wallets provide a seamless experience by abstracting away the complexity of wallet management. Users can interact with your application without needing to understand private keys, seed phrases, and blockchain concepts. ## Choose your SDK # Authentication Methods At a high level, onboarding users onchain breaks down into three core questions: who is taking the onchain action (the user), through what means are they able to control this action (the signer), and where is the affected state (the account). **With Openfort, your app can authenticate users, including:** * **Email**: via password or one-time passcode (OTP) * **Phone**: via SMS one-time passcode (OTP) * **Wallet**: via Sign In With Ethereum ([SIWE](https://eips.ethereum.org/EIPS/eip-4361)) standard * **Web2 social accounts**: via [OAuth2.0 Protocol](https://oauth.net/2/) (Google, Facebook, Twitter, Discord, and more) * **Guest**: anonymous authentication that can be upgraded later These methods can be configured as either login or link options for users. Once authenticated, Openfort creates a common `User` object that includes the user's ID and profile information, treating all users equally regardless of their authentication method. ## Choosing an authentication method | Method | Description | Best for | | --- | --- | --- | | **Email & Password** | Traditional credentials-based sign up and login | Familiar UX, broad compatibility | | **Email OTP** | Passwordless login via one-time code sent to email | Low-friction onboarding | | **Phone OTP** | Passwordless login via one-time code sent via SMS | Mobile-first apps | | **Social / OAuth** | Google, Apple, Facebook, X, Discord, and more | Consumer apps, fast sign-up | | **Wallet (SIWE)** | Connect external wallets (MetaMask, WalletConnect) via Sign In With Ethereum | Crypto-native users | | **Guest** | Anonymous authentication that can be upgraded later | Try-before-you-register flows | | **Third-party** | Firebase, Supabase, Auth0, and custom auth systems | Existing auth infrastructure | ## Response types All authentication methods across all SDKs return a common response structure containing the session token and user information. ### AuthResponse ```typescript interface AuthResponse { token: string | null // Session token for authentication user: User // User profile information session?: Session // Optional session details } ``` ### User ```typescript interface User { id: string // Unique user identifier email?: string // User's email address name?: string // User's display name image?: string // URL to user's profile image emailVerified?: boolean // Whether email has been verified createdAt?: string // ISO timestamp when created updatedAt?: string // ISO timestamp when last updated isAnonymous?: boolean // Whether user is anonymous (guest) phoneNumber?: string // User's phone number phoneNumberVerified?: boolean // Whether phone has been verified linkedAccounts?: UserAccount[] // Linked accounts (external wallets and auth providers only, not embedded wallets) } ``` ### Session ```typescript interface Session { id?: string // Session identifier token: string // Session token for authentication userId: string // User ID associated with this session expiresAt?: string // ISO timestamp when session expires createdAt?: string // ISO timestamp when created } ``` ### Example response ```json { "user": { "id": "usr_cc9ed2b7-c5f5-4c43-8dca-c4b104ba1762", "email": "hello@example.com", "name": "John Doe", "emailVerified": true, "createdAt": "2024-03-20T12:00:00Z", "updatedAt": "2024-03-20T12:00:00Z", "isAnonymous": false }, "token": "eyJhbGci...", "session": { "id": "ses_...", "token": "eyJhbGci...", "userId": "usr_cc9ed2b7-c5f5-4c43-8dca-c4b104ba1762", "expiresAt": "2024-03-21T12:00:00Z", "createdAt": "2024-03-20T12:00:00Z" } } ``` The SDK automatically stores tokens after successful authentication. ## Configure authentication * [Social login providers](/docs/configuration/social-login) - Set up Google, Apple, Facebook, and other OAuth providers * [External auth providers](/docs/configuration/external-auth) - Configure Firebase, Supabase, and other third-party systems * [Password settings](/docs/configuration/password/security) - Customize password policies and email templates ## SDK implementations # Account Types When creating an embedded wallet, you choose an account type that determines the wallet's features. The available account types depend on the chain. ## Ethereum Ethereum (EVM) chains support all three account types. Set the account type with the `accountType` field of `walletConfig.ethereum` — see [Setting the account type](/docs/products/embedded-wallet/react/wallet/ethereum#setting-the-account-type). | Feature | EOA | Smart Account | Delegated Account | | --- | --- | --- | --- | | **Gas sponsorship** | No | Yes | Yes | | **Transaction batching** | No | Yes | Yes | | **Session keys** | No | Yes | Yes | | **Chain agnostic** | Yes | No ([supported chains](/docs/configuration/chains)) | Limited | | **Onchain deployment** | None | Required | Temporary | | **Key rotation** | N/A | Supported | Not supported | ### EOA Externally Owned Accounts are standard accounts controlled directly by a private key. They are simple and gas-efficient, but limited in programmability. * Chain agnostic: once created, the same address works across every EVM chain, even custom ones * No onchain deployment needed * Users pay their own gas **Set it in code:** `accountType: AccountTypeEnum.EOA` → [Ethereum wallet configuration](/docs/products/embedded-wallet/react/wallet/ethereum#eoa-wallets-on-a-custom-evm-chain) ### Smart Account Smart wallets provisioned through Openfort are [ERC-4337-compatible](https://www.erc4337.io/) smart contract accounts. They support **account abstraction**, which enables: * **Gasless transactions** (via [gas sponsorship](/docs/configuration/gas-sponsorship)) * **Transaction batching** * **Permission delegation with session keys** ERC-4337 introduces a new infrastructure layer — entry points, bundlers, and paymasters — that abstracts complex wallet logic from the base protocol. You can also use third-party implementations of smart accounts. **Set it in code:** `accountType: AccountTypeEnum.SMART_ACCOUNT` → [Setting the account type](/docs/products/embedded-wallet/react/wallet/ethereum#setting-the-account-type) ### Delegated Account (ERC-7702) With [ERC-7702](https://eips.ethereum.org/EIPS/eip-7702), EOAs can be **temporarily upgraded into smart EOAs**, enabling many of the same benefits as smart wallets. The EOA remains in control and no smart contract is permanently deployed. **Key rotation is not supported** under 7702, unlike fully programmable smart wallets. **Set it in code:** `accountType: AccountTypeEnum.DELEGATED_ACCOUNT` → [Sign EIP-7702 authorization](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization) ## Solana Solana wallets are **EOAs**. Smart accounts and delegated accounts are Ethereum-only. ### EOA The Solana embedded wallet is a standard Ed25519 keypair account. Gasless transactions are still available through [Kora gas sponsorship](/docs/products/embedded-wallet/react/wallet/actions/send-transaction/solana#gasless-solana-transactions) rather than account abstraction. **Set it in code:** no `accountType` needed → [Solana wallet configuration](/docs/products/embedded-wallet/react/wallet/solana) # Wallet Lifecycle The embedded wallet goes through several states during initialization. It's crucial to wait for the proper state before performing wallet operations. ## Embedded wallet states | State | Value | Description | | ------------------------------ | ----- | -------------------------------- | | NONE | 0 | Initial SDK state | | UNAUTHENTICATED | 1 | Before user authentication | | EMBEDDED\_SIGNER\_NOT\_CONFIGURED | 2 | Before wallet configuration | | CREATING\_ACCOUNT | 3 | Creating new account for chainID | | READY | 4 | Wallet ready for use | ## State flow A typical embedded wallet follows this lifecycle: 1. **NONE** — The SDK has been loaded but not yet initialized. 2. **UNAUTHENTICATED** — The SDK is initialized. The user needs to authenticate using one of the supported [authentication methods](/docs/products/embedded-wallet/authentication). 3. **EMBEDDED\_SIGNER\_NOT\_CONFIGURED** — The user is authenticated, but the embedded wallet signer has not been configured yet. This is where wallet creation or recovery happens. 4. **CREATING\_ACCOUNT** — The wallet is being created or recovered for a specific chain. 5. **READY** — The wallet is fully configured and ready for signing transactions and messages. ## Wallet creation After authentication, the wallet needs to be configured with a [recovery method](/docs/configuration/recovery-methods). This can happen automatically (if `recoverWalletAutomaticallyAfterAuth` is enabled) or manually by calling the create/configure method in your SDK. The wallet transitions from `EMBEDDED_SIGNER_NOT_CONFIGURED` through `CREATING_ACCOUNT` to `READY`. ## Wallet recovery When a user logs in on a new device, the wallet needs to be recovered. The recovery method used depends on what was configured during wallet creation: * **Automatic recovery**: Silent recovery via an encryption session from your backend * **Password recovery**: User enters their password to recover * **Passkey recovery**: User authenticates with their passkey (Face ID/Touch ID) See [Recovery methods](/docs/configuration/recovery-methods) for full details on each option. ## SDK-specific state monitoring Each SDK provides a way to observe the current wallet state: * **React**: [`useOpenfort`](/docs/products/embedded-wallet/react/hooks/useOpenfort) hook returns the current state * **React Native**: [`useEmbeddedEthereumWallet`](/docs/products/embedded-wallet/react-native/hooks/useEmbeddedEthereumWallet) returns `status` field * **Swift**: [`embeddedStatePublisher`](/docs/products/embedded-wallet/swift/wallet/state) provides a reactive publisher * **Unity**: [`GetEmbeddedState()`](/docs/products/embedded-wallet/unity/signer/state) returns the current state * **JavaScript**: [`getEmbeddedState()`](/docs/products/embedded-wallet/javascript/use-openfort) on the Openfort instance # Quickstart React Set up Openfort embedded wallets in React. Use the **Ethereum** or **Solana** code snippets in each step according to your chain. ## Install Openfort with the CLI :::code-group ```sh [pnpm] pnpm create openfort ``` ```sh [npm] npm create openfort ``` ```sh [yarn] yarn create openfort ``` ::: The **Openfort CLI** helps you set up a new project with all dependencies and configurations. You can select framework (Vite or Next.js), authentication providers, embedded wallet, and UI theming. :::info Want a ready-made project? Head to our [recipes section](/docs/recipes). ::: ## Install manually (step by step)
Starting from scratch? Create a new React app first :::code-group ```sh [pnpm] pnpm create vite my-openfort-app --template react-ts ``` ```sh [npm] npm create vite@latest my-openfort-app -- --template react-ts ``` ```sh [yarn] yarn create vite my-openfort-app --template react-ts ``` :::
::::steps ### Install dependencies Install [@openfort/react](https://www.npmjs.com/package/@openfort/react) and chain-specific packages. Use the snippet for your chain: :::code-group ```sh [Ethereum] pnpm add @openfort/react wagmi @tanstack/react-query viem@^2 ``` ```sh [Solana] pnpm add @openfort/react @solana/kit ``` ::: Use `npm install ...` or `yarn add ...` if you prefer those package managers. ### Get your Openfort API keys In the [API keys](https://dashboard.openfort.io/api-keys) section of the [Openfort dashboard](https://dashboard.openfort.io): * **Publishable Key**: Safe for client-side use * **Secret Key**: Server-side only To generate non-custodial wallets: 1. Scroll to the Shield section and click **Create Shield keys** 2. **Store the encryption share** safely when it appears (you'll only see it once) 3. You'll receive **Shield Publishable Key** and **Shield Secret Key** ### Choose your recovery method Decide which wallet recovery experience you want to ship: * **Automatic recovery** — no action from the user required. **Solana uses this by default**; for Ethereum you can choose it and set up a backend endpoint. * [Passkey recovery](/docs/products/embedded-wallet/react/quickstart/passkey#choose-your-recovery-method) — biometric or device authentication with no backend endpoint. * [Password recovery](/docs/products/embedded-wallet/react/quickstart/password#choose-your-recovery-method) — user sets their own password with no backend endpoint. ### Set up the recovery endpoint Required for **Automatic recovery**. Create a backend endpoint that returns a Shield encryption session. See [setting up the recovery endpoint](/docs/products/embedded-wallet/server/automatic-recovery-session). **One-click deploy:** [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/openfort-xyz/recovery-endpoint-cloudflare) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/openfort-xyz/recovery-endpoint-vercel\&project-name=shield-recovery-endpoint\&env=SHIELD_PUBLISHABLE_KEY,SHIELD_SECRET_KEY,SHIELD_ENCRYPTION_SHARE\&envDescription=Required%20Shield%20API%20keys%20from%20your%20Openfort%20project\&envLink=https://www.openfort.io/docs) **Or implement your own:** expose a POST endpoint that returns `{ "session": "" }`. See the [backend repo](https://github.com/openfort-xyz/openfort-backend-quickstart) for examples. Save your endpoint URL for the next step (e.g. `https://your-domain.com/api/protected-create-encryption-session`). ### Set up providers Wrap your app with the appropriate providers for your chain. :::info If using Next.js, add `'use client'` at the top of the file. ::: ## Providers.tsx :::code-group ```tsx [Ethereum] import React from "react"; import { AuthProvider, OpenfortProvider, RecoveryMethod } from "@openfort/react"; import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { WagmiProvider, createConfig } from "wagmi"; import { baseSepolia } from "viem/chains"; const config = createConfig( getDefaultConfig({ appName: "Openfort Demo App", chains: [baseSepolia], ssr: true, }) ); const queryClient = new QueryClient(); export function Providers({ children }: { children: React.ReactNode }) { return ( {/* [!code focus] */} {/* [!code focus] */} {/* [!code focus] */} {children} {/* [!code focus] */} {/* [!code focus] */} {/* [!code focus] */} // [!code focus] ); } ``` ```tsx [Solana] import React from "react"; import { AuthProvider, ChainTypeEnum, OpenfortProvider, RecoveryMethod } from "@openfort/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient(); export function Providers({ children }: { children: React.ReactNode }) { return ( {children} {/* [!code focus] */} ); } ``` :::
Ethereum: Need WalletConnect support? Add `walletConnectProjectId` to `getDefaultConfig`. Get a project ID from the [WalletConnect dashboard](https://cloud.reown.com/sign-in).
### You're good to go! Wrap your app in `Providers` and add the Openfort button: ```tsx [App.tsx] import React from "react"; import { Providers } from "./Providers"; import { OpenfortButton } from "@openfort/react"; export default function App() { return ( ); } ``` :::: ## Advanced provider props `OpenfortProvider` also accepts these optional props: | Prop | Type | Description | |------|------|-------------| | `overrides` | `SDKOverrides` | Override SDK internals (advanced). | | `onConnect` | `({ address, connectorId, user }) => void` | Callback fired when a wallet connects. Receives the connected address, connector ID, and user object. | | `onDisconnect` | `() => void` | Callback fired when a wallet disconnects. | | `thirdPartyAuth` | `ThirdPartyAuthConfiguration` | Configuration for third-party authentication providers. | | `walletConfig.connectOnLogin` | `boolean` | Whether to automatically recover/create a wallet after authentication. Set inside `walletConfig`, not as a direct provider prop. Defaults to `true`. | ## Utility exports `@openfort/react` also exports the following utility functions: * `createSIWEMessage` — Create a SIWE (Sign-In with Ethereum) message for custom authentication flows. * `formatAddress` — Format a wallet address for display (truncation, checksumming). * `getDefaultSolanaRpcUrl` — Get the default Solana RPC URL for a given cluster. * `invalidateBalance` — Invalidate cached balance data to trigger a refresh. ## Next steps * [Wallet actions](/docs/products/embedded-wallet/react/wallet/actions) * [UI Configuration](/docs/products/embedded-wallet/react/ui) # Openfort UI The Openfort UI delivers pre-built React components for common wallet actions and authentication flows. Use the UI hooks and modal system to: * Authenticate users with plenty of authentication options * Guide users through wallet setup and recovery * Show profile and wallet info * Switch networks and providers Whether you want a full onboarding experience or just a wallet connect button, Openfort's UI is modular and easy to embed in your layout. :::tip[Try it] This is the real `OpenfortButton` running on Base. Click it to log in (guest, email, or Google) and explore the wallet — then [configure](/docs/products/embedded-wallet/react/ui/configuration) what it does and [customize](/docs/products/embedded-wallet/react/ui/customization) how it looks. ::: To use the Openfort Button, use the `OpenfortButton` component. This component renders a button that opens the Openfort login screen when clicked. Use your `` component from the [Quickstart](/docs/products/embedded-wallet/react#set-up-providers) guide. :::info `OpenfortButton` works with both Ethereum and Solana and automatically adapts to your provider's chain type — connect, send, and add-funds screens all follow it, with no separate UI config. The switch is [`walletConfig.chainType`](/docs/products/embedded-wallet/react/wallet/solana): set it to `ChainTypeEnum.SVM` for Solana (it defaults to EVM). Setting the `solana` config block alone isn't enough — `chainType` is what selects the chain. ::: ```tsx [App.tsx] import { OpenfortButton } from '@openfort/react'; import { useState } from 'react'; import { Providers } from "./Providers"; function Content() { const [clicked, setClicked] = useState(false); const label = clicked ? "Connected!" : "Connect Wallet"; // A custom onClick replaces the default behavior — call open() to show the modal. const handleClick = (open: () => void) => { setClicked(true); open(); } return (
); } function App() { return ( {/* Your app content */} ); } export default App; ``` This button uses the default configuration of Openfort, but you can customize the theme properties of the button through the following properties: * `showAvatar`: Show the avatar of the user * `showBalance`: Show the balance of the user * `label`: The label of the button when the user is not connected The `OpenfortButton` component also provides an `onClick` callback that receives the `open` function as a parameter: `onClick?: (open: () => void) => void`. If you want to configure the options of the button, you can use the [configuration](/docs/products/embedded-wallet/react/ui/configuration) guide. ### Custom rendering For full control over the button's appearance, use `OpenfortButton.Custom` with a render prop: ```tsx import { OpenfortButton } from '@openfort/react' function App() { return ( {({ isConnected, isConnecting, show, hide, address, truncatedAddress, chainId, unsupported, ensName }) => ( )} ) } ``` The render prop receives: | Prop | Type | Description | |------|------|-------------| | `show` | `() => void` | Function to open the Openfort modal. | | `hide` | `() => void` | Function to close the Openfort modal. | | `isConnected` | `boolean` | Whether the user is authenticated and wallet is ready. | | `isConnecting` | `boolean` | Whether a connection is in progress. | | `unsupported` | `boolean` | Whether the current chain is unsupported. | | `address` | `` `0x${string}` \| undefined `` | Connected wallet address. | | `truncatedAddress` | `string \| undefined` | Shortened wallet address for display. | | `ensName` | `string \| undefined` | ENS name if resolved. | | `chainId` | `number \| undefined` | Current chain ID. | If you want to customize the button further, you can use the [customization](/docs/products/embedded-wallet/react/ui/customization) guide. For the standalone [`Avatar` and `ChainIcon` components](/docs/products/embedded-wallet/react/ui/customization#components), see Customization. # Openfort UI Configuration :::info If using Next.js App Router, add `"use client"` at the top of any file that imports OpenfortProvider, OpenfortButton, or Openfort hooks. ::: :::tip[Try it live] The preview boxes throughout this page run a real Openfort wallet on Base. Log in through any one of them and the rest stay logged in — once connected, each box opens that exact wallet screen. The label in each box is the hook or component that opens it. ::: This page covers the `uiConfig` options that control **what the wallet does** — authentication, recovery, funding, disclaimers, and behavior. For **how it looks** (theme, colors, fonts, logo, layout, and visual toggles), see [Customization](/docs/products/embedded-wallet/react/ui/customization). For wallet setup (`walletConfig`), see [Wallet configuration](/docs/products/embedded-wallet/react/wallet). ## Auth Providers The `authProviders` property is an array of `AuthProvider` authentication providers. Currently, the following providers are supported: ### Social providers * `AuthProvider.GOOGLE` * `AuthProvider.FACEBOOK` * `AuthProvider.TWITTER` * `AuthProvider.DISCORD` * `AuthProvider.APPLE` ### Email and phone providers * `AuthProvider.EMAIL_PASSWORD` - Email with password authentication * `AuthProvider.EMAIL_OTP` - Email with OTP (passwordless) authentication * `AuthProvider.PHONE` - Phone number with SMS OTP authentication ### Other providers * `AuthProvider.GUEST` - Anonymous guest authentication * `AuthProvider.WALLET` - External wallet (SIWE) authentication. Ethereum only, requires [wagmi setup](/docs/products/embedded-wallet/react/hooks/useWalletAuth). Example configuration: ```tsx import { OpenfortProvider, AuthProvider } from '@openfort/react' function App() { return ( {/* Add your wallet components here */} ) } ``` :::note Social login configuration - Social login like Google, Facebook, and Twitter require some additional configuration to let Openfort know about your app. You can find more information in the [Social login](/docs/configuration/social-login) guide. ::: Other authentication options: | Option | What it does | | --- | --- | | `authProvidersLength` | How many providers to show before collapsing the rest into "more". | | `skipEmailVerification` | Skip the email verification step. | | `phoneConfig` | Default country, preferred countries, and formatting for phone auth. | ## Wallet recovery methods Configure the wallet recovery methods available to your users. By default, all recovery methods are enabled. If you configure automatic recovery in the `OpenfortProvider` (view the [wallet recovery](/docs/products/embedded-wallet/react/wallet#wallet-recovery) guide), it will be used as the default recovery method when creating a wallet. You can set the **default method** and the **available methods** with the `walletRecovery` property: ```tsx import { OpenfortProvider, RecoveryMethod } from '@openfort/react' function App() { return ( {/* Add your wallet components here */} ) } ``` The available recovery methods are shown to the user when they create their wallet. :::info `RecoveryMethod.AUTOMATIC` is automatically removed from `allowedMethods` if no encryption session configuration (`createEncryptedSessionEndpoint` or `getEncryptionSession`) is provided in `walletConfig`. ::: ## Link wallet on sign up By default, when a user signs up using social login or email authentication, a new wallet is created for them. You can change this behavior for the user to connect their wallet instead of creating a new one by changing the `linkWalletOnSignUp` property. :::info `linkWalletOnSignUp` connects an external Ethereum wallet via SIWE. This option is only relevant when using [wagmi setup](/docs/products/embedded-wallet/react/hooks/useWalletAuth) with an Ethereum configuration. ::: * `LinkWalletOnSignUpOption.OPTIONAL`: The user can choose to link their wallet or create an embedded one. * `LinkWalletOnSignUpOption.REQUIRED`: The user must link their wallet. * `LinkWalletOnSignUpOption.DISABLED`: **(default)** The user always creates a new wallet. ## Disclaimer There are two ways to configure the disclaimer, you can either set the terms of service and privacy policy URLs: * `privacyPolicyUrl`: The privacy policy URL. * `termsOfServiceUrl`: The terms of service URL. or customize the disclaimer component: * `disclaimer`: A disclaimer to be shown in the wallet. ## Send The **Send** screen lets users transfer assets from their embedded wallet to any address. Gas is sponsored when a paymaster is configured. Open the full flow with `useUI().openSend()`, or pass a prepared transaction — `useUI().openSend({ to, amount, asset })` — to jump straight to the confirmation (approval) screen, as the demo above does. ## Receive The **Receive** screen shows the wallet's address and a QR code so others can pay it. Open it with `useUI().openReceive()`. :::info Receive is for **same-chain** transfers — funds must be sent on the wallet's own chain. For cross-chain top-ups (pay from any chain, token, or exchange), use [Funding](#funding) instead. ::: ## Sign message Prompt the user to sign a message or EIP-712 typed data with a confirmation screen, via [`useSignMessage`](/docs/products/embedded-wallet/react/wallet/actions/sign-message). Signing is gasless — no transaction. Open with `useSignMessage().signMessage(text)` or `signTypedData(typedData)`. ## Funding The wallet modal includes a **Deposit** flow that lets users top up their wallet from any chain, token, or exchange — see [Funding](/docs/configuration/funding) for the concept. The destination is the active wallet's address, resolved from the target chain's family: an EVM `targetChain` settles into the EVM wallet, a Solana `targetChain` into the Solana wallet. Override the destination chain/token and tune the pickers: ```tsx import { OpenfortProvider } from '@openfort/react' ``` The Deposit hub shows up to five methods: * **Transfer from wallet** — on mobile, one-tap deeplinks into the user's wallet app (MetaMask, Coinbase Wallet, Phantom, Trust, Rainbow, Rabby) that open a prefilled [deposit send page](/docs/configuration/funding#hosted-deposit-page); on desktop, a direct transfer through the connected browser-extension wallet. * **Transfer from address** — a cross-chain deposit address with a QR; send from any chain. * **Transfer from exchange** — Coinbase direct transfer, delivering to the destination on a supported chain. Binance is coming soon. * **Onramp with Card** — fiat on-ramp. ### Choosing which methods show By default the hub shows every available method (Apple Pay first on mobile). Use `funding.methods` to pick which appear and in what order — the same pattern as `authProviders` for the auth modal. ```tsx import { OpenfortProvider, FundingMethod } from '@openfort/react' ``` `FundingMethod` values: `APPLE_PAY`, `CARD`, `WALLET` (Transfer from wallet), `ADDRESS` (Transfer from address), `EXCHANGE` (Transfer from exchange). Device, region, and availability gating still apply — Apple Pay stays mobile-only, and the cross-chain rails are hidden until the funding backend is reachable. ### Choosing source chains & currencies The chains and currencies inside the crypto/exchange pickers aren't hardcoded — they're fetched live from the rail. By default the modal shows a curated subset; narrow it further with `sourceChains` (a CAIP-2 allowlist, also the order) and `sourceCurrencies` (a symbol allowlist where the `'native'` sentinel matches each chain's native asset — ETH, SOL, POL…). ```tsx ``` Defaults when omitted: chains `Arbitrum, Base, BNB, Ethereum, Optimism, Polygon, Solana`; currencies `['native', 'USDC', 'USDT']`. A selection is shown only if the rail can route it — a chain or currency the rail doesn't support (e.g. a non-bridgeable native asset) is silently skipped, so you never offer a route that would fail. ### Funding reference Everything the Deposit hub reads from `uiConfig`. `fundingBaseUrl` sits at the top level; the rest live under `funding`. | Option | Type | Default | What it does | | --- | --- | --- | --- | | `fundingBaseUrl` | `string` | SDK backend (`https://api.openfort.io`) | Base URL of the funding **JSON API** (serves `/v2/funding/*` — chains + sessions). Defaults to the SDK backend; set only to point the crypto/exchange rails at a custom funding service. Distinct from `funding.depositPageUrl` (the hosted deposit *page*). | | `funding.targetChain` | `string` (CAIP-2) | `eip155:8453` (Base) | Chain deposits settle on. | | `funding.targetCurrency` | `string` (address) | USDC on Base | Token deposits settle as (zero address for native). | | `funding.methods` | `FundingMethod[]` | all (Apple Pay first on mobile) | Which methods show, and in what order. | | `funding.sourceChains` | `string[]` (CAIP-2) | curated set above | Allowlist (and order) of source chains in the pickers. | | `funding.sourceCurrencies` | `string[]` | `['native', 'USDC', 'USDT']` | Allowlist of source currencies; `'native'` matches each chain's native asset. | | `funding.depositPageUrl` | `string` | `https://deposit.openfort.io` | URL of the [hosted deposit page](/docs/configuration/funding#hosted-deposit-page) the mobile "Transfer from wallet" deeplinks open. | For a custom Deposit UI driven by a hook instead of the modal, see [Funding with `useFunding`](/docs/products/embedded-wallet/react/wallet/funding). ### Onramp with cards Apple Pay and Card are fiat on-ramps surfaced inside the Deposit hub. Configure where they point: | Option | What it does | | --- | --- | | `buyWithCardUrl` | URL for the buy-with-card flow. | | `buyFromExchangeUrl` | URL for the buy-from-exchange flow. | | `buyTroubleshootingUrl` | URL for buy troubleshooting help. | ## Export key Your users are able to export the embedded wallet private key by using export key method. ## WalletConnect | Option | What it does | | --- | --- | | `appName` | App name shown in WalletConnect and the modal header. | | `walletConnectCTA` | Show the WalletConnect CTA as a `link`, `modal`, or `both`. | | `walletConnectName` | Name used for WalletConnect. | ## Onboarding & help | Option | What it does | | --- | --- | | `ethereumOnboardingUrl` | Custom URL for the Ethereum onboarding flow. | | `walletOnboardingUrl` | Custom URL for the wallet onboarding flow. | | `hideQuestionMarkCTA` | Hide the help question-mark icon. | | `hideNoWalletCTA` | Hide the "no wallet" call-to-action. | | `disableSiweRedirect` | Disable the redirect to the SIWE page after a wallet connects. | ## Advanced | Option | What it does | | --- | --- | | `language` | UI language. | | `bufferPolyfill` | Polyfill the Node.js `Buffer` API for browsers. Default `true`; set `false` if your bundler already provides it. | :::tip[Looking for appearance options?] Theme, mode, colors, fonts, logo, custom avatar, custom pages, and visual toggles (`hideBalance`, `hideTooltips`, `reducedMotion`, …) live on the [Customization](/docs/products/embedded-wallet/react/ui/customization) page. ::: ### Debug mode Pass `true` to enable all debug logging, or an object for granular control: | Flag | What it logs | |------|-------------| | `openfortReactDebugMode` | React SDK state transitions and hook lifecycle | | `openfortCoreDebugMode` | Core JS SDK operations (auth, signing) | | `shieldDebugMode` | Shield (key management) operations | | `debugRoutes` | Modal route navigation | ```tsx ``` # Openfort UI Customization This page covers the `uiConfig` options that control **how the wallet looks** — theme, colors, fonts, logo, custom components, and visual layout. For **what the wallet does** (authentication, recovery, funding, behavior), see [Configuration](/docs/products/embedded-wallet/react/ui/configuration). :::info[Ethereum] You cannot log in with external wallets (MetaMask, WalletConnect, etc.) unless you add [@openfort/react/wagmi](/docs/products/embedded-wallet/javascript/smart-wallet/libraries#using-openfortwagmi-with-openfortreact). ::: ## Theme Pick a built-in theme with `theme` and the light/dark mode with `mode` (both default to `auto`): * `theme`: `auto` · `web95` · `retro` · `soft` · `midnight` · `minimal` · `rounded` · `nouns` * `mode`: `auto` · `light` · `dark` ```jsx {/* Your app here */} ``` ## Custom theme Edit fonts, colors, and other styling with `customTheme`. CSS custom properties use the `--ck-` prefix. Openfort UI theming is inspired by ConnectKit — see the [ConnectKit docs](https://docs.family.co/connectkit/customization) for the full list of variables. ```jsx {/* Your app here */} ``` ## Custom components Bring your own components for parts of the UI: * `logo`: The logo shown in the wallet (`React.ReactNode` — a JSX element). * `customAvatar`: Custom avatar component. A `React.FC` where `CustomAvatarProps` has `address` (hex string), `ensName`, `ensImage` (optional strings), and required `size` and `radius` (numbers). Pass a component function, not a ReactNode. * `customPageComponents`: Replace specific modal pages with your own components (see below). ### Custom pages Replace specific pages in the Openfort modal with your own components using `customPageComponents`. Currently the following route can be customized: * `connected` — the profile/connected page shown after authentication (shown regardless of chain type). ```tsx import { OpenfortProvider } from '@openfort/react'; function CustomConnectedPage() { return (

My Profile

This is a custom profile page.

); } function App() { return ( , }, }} > {/* Your app */} ); } ``` ## Visual options Toggles that change how the modal looks and feels. All are chain-agnostic unless noted. | Option | What it does | | --- | --- | | `hideBalance` | Hide the balance in the wallet. | | `hideTooltips` | Hide tooltips in the wallet. | | `hideRecentBadge` | Hide the "recent" badge. | | `truncateLongENSAddress` | Truncate long ENS addresses (Ethereum only). | | `reducedMotion` | Reduce animation and motion. | | `overlayBlur` | Blur intensity applied to the background when the modal is open (`number`). | | `avoidLayoutShift` | Avoid layout shift when the modal opens by padding the body. | | `embedGoogleFonts` | Embed the current theme's Google font. Does not work with custom themes. | ## Components Standalone display components you can drop into your own UI. ### Avatar Display a user avatar based on their address. ```tsx import { Avatar } from '@openfort/react' ``` | Prop | Type | Description | |------|------|-------------| | `address` | `string` | Wallet address used to generate the avatar. | | `size` | `number` | Avatar size in pixels. | | `className` | `string` | CSS class for custom styling. | ### ChainIcon Display a chain/network icon. ```tsx import { ChainIcon } from '@openfort/react' ``` | Prop | Type | Description | |------|------|-------------| | `chainId` | `number` | Chain ID to display the icon for. | | `size` | `number` | Icon size in pixels. | | `className` | `string` | CSS class for custom styling. | ## More customization If you want to customize your authentication further, use the [authentication hooks](/docs/products/embedded-wallet/react/auth). # Authentication Methods Openfort React supports multiple authentication methods. Pick the one that fits your UX and security requirements. :::info For an overview of all authentication methods and response types, see [Authentication methods](/docs/products/embedded-wallet/authentication). ::: * Prefer the prebuilt UI? Configure providers and flows in the dashboard, then follow the setup guide at [Openfort UI Configuration](/docs/products/embedded-wallet/react/ui/configuration). * Building a custom flow? The hooks below cover every scenario, and [`useAuthCallback`](/docs/products/embedded-wallet/react/hooks/useAuthCallback) simplifies OAuth and email verification callbacks. :::info Authentication is chain-agnostic. All auth hooks below work the same whether your OpenfortProvider is configured for Ethereum or Solana. The only exception is `useConnectWithSiwe`, which is Ethereum-only. ::: ## Using Email Authentication Use [`useEmailAuth`](/docs/products/embedded-wallet/react/hooks/useEmailAuth) for traditional email/password authentication. It covers sign up, login, password reset, and email linking. ```tsx import { useEmailAuth } from "@openfort/react" function EmailLogin() { const { signInEmail, signUpEmail, isLoading } = useEmailAuth() const handleSignIn = async () => { const result = await signInEmail({ email: "user@example.com", password: "password123", }) if (result.requiresEmailVerification) { // Prompt the user to check their inbox for a verification code } } return ( ) } ``` [View full documentation →](/docs/products/embedded-wallet/react/hooks/useEmailAuth) ## Using Email OTP Authentication Use [`useEmailOtpAuth`](/docs/products/embedded-wallet/react/hooks/useEmailOtpAuth) for passwordless email authentication with one-time passwords. ```tsx import { useEmailOtpAuth } from "@openfort/react" import { useState } from "react" function EmailOtpLogin() { const { requestEmailOtp, signInEmailOtp, isRequesting, isLoading } = useEmailOtpAuth() const [email, setEmail] = useState("") const [otp, setOtp] = useState("") const [otpSent, setOtpSent] = useState(false) const handleRequestOtp = async () => { const { error } = await requestEmailOtp({ email }) if (!error) setOtpSent(true) } const handleSignIn = async () => { const { user, error } = await signInEmailOtp({ email, otp }) if (user) console.log("Signed in:", user.id) } return (
setEmail(e.target.value)} placeholder="Email" /> {otpSent && ( setOtp(e.target.value)} placeholder="Enter OTP" /> )} {!otpSent ? ( ) : ( )}
) } ``` [View full documentation →](/docs/products/embedded-wallet/react/hooks/useEmailOtpAuth) ## Using Phone OTP Authentication Use [`usePhoneOtpAuth`](/docs/products/embedded-wallet/react/hooks/usePhoneOtpAuth) for passwordless SMS authentication with one-time passwords. ```tsx import { usePhoneOtpAuth } from "@openfort/react" import { useState } from "react" function PhoneOtpLogin() { const { requestPhoneOtp, logInWithPhoneOtp, isRequesting, isLoading } = usePhoneOtpAuth() const [phoneNumber, setPhoneNumber] = useState("") const [otp, setOtp] = useState("") const [otpSent, setOtpSent] = useState(false) const handleRequestOtp = async () => { const { error } = await requestPhoneOtp({ phoneNumber }) if (!error) setOtpSent(true) } const handleSignIn = async () => { const { user, error } = await logInWithPhoneOtp({ phoneNumber, otp }) if (user) console.log("Signed in:", user.id) } return (
setPhoneNumber(e.target.value)} placeholder="+1234567890" /> {otpSent && ( setOtp(e.target.value)} placeholder="Enter OTP" /> )} {!otpSent ? ( ) : ( )}
) } ``` [View full documentation →](/docs/products/embedded-wallet/react/hooks/usePhoneOtpAuth) ## Using Social Authentication Use [`useOAuth`](/docs/products/embedded-wallet/react/hooks/useOAuth) for social login (Google, Facebook, Twitter, etc.) and account linking. `useOAuth` only authenticates. The wallet appears reactively on [`useEthereumEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useEthereumEmbeddedWallet) or [`useSolanaEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useSolanaEmbeddedWallet) once `isConnected` flips true. Both hooks expose the same connection-state shape (`isConnected`, `isConnecting`, `isDisconnected`, `isReconnecting`). Recovery is automatic via the `recoverWalletAutomatically` option (default `true`) and uses the `walletConfig.chainType` set on `OpenfortProvider` — set it to `ChainTypeEnum.SVM` for Solana, otherwise EVM is used. :::code-group ```tsx [Prebuilt] import { useUI } from "@openfort/react" function SocialLogin() { const { open } = useUI() return } ``` ```tsx [Ethereum (custom)] import { OAuthProvider, useOAuth } from "@openfort/react" import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" function SocialLogin() { const { initOAuth, isLoading } = useOAuth() const { isConnected, address } = useEthereumEmbeddedWallet() if (isConnected) return

Wallet: {address}

return ( ) } ``` ```tsx [Solana (custom)] import { OAuthProvider, useOAuth } from "@openfort/react" import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function SocialLogin() { const { initOAuth, isLoading } = useOAuth() const { isConnected, address } = useSolanaEmbeddedWallet() if (isConnected) return

Wallet: {address}

return ( ) } ``` ::: :::warning One `chainType` per provider. An app cannot headlessly OAuth into both EVM and Solana from the same `OpenfortProvider` instance. ::: Working examples: * [Headless EVM quickstart](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/headless) – minimal headless EVM setup * [Headless Solana quickstart](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/solana-headless) – minimal headless Solana setup with `chainType: SVM` [View full documentation →](/docs/products/embedded-wallet/react/hooks/useOAuth) ## Using Wallet Authentication (Ethereum only) Wallet authentication (Sign-In with Ethereum, SIWE) with external wallets (MetaMask, WalletConnect, etc.) is **Ethereum-only** and requires wagmi provider setup. Full setup, provider config, and API are in [useWalletAuth](/docs/products/embedded-wallet/react/hooks/useWalletAuth). :::code-group ```tsx [Prebuilt] import { useUI } from "@openfort/react" function WalletLogin() { const { open } = useUI() return } ``` ```tsx [Custom] import { useWalletAuth } from "@openfort/react/wagmi" import { useState } from "react" function WalletLogin() { const { availableWallets, connectWallet } = useWalletAuth() const [loading, setLoading] = useState(false) const handleConnect = async (walletId: string) => { setLoading(true) try { await connectWallet(walletId, { onConnect: () => console.log("Connected"), onError: (err) => console.error(err), }) } finally { setLoading(false) } } return ( <> {availableWallets.map((w) => ( ))} ) } ``` ::: [View full documentation →](/docs/products/embedded-wallet/react/hooks/useWalletAuth#usewalletauth-siwe-connection-flow) ## Using Guest Authentication Use [`useGuestAuth`](/docs/products/embedded-wallet/react/hooks/useGuestAuth) for anonymous users and instant onboarding. ```tsx import { useGuestAuth } from "@openfort/react" function GuestLogin() { const { signUpGuest, isLoading } = useGuestAuth() return ( ) } ``` [View full documentation →](/docs/products/embedded-wallet/react/hooks/useGuestAuth) ## Using Your Own Authentication Openfort integrates with external authentication providers like Firebase, Supabase, Auth0, and custom auth systems so you can keep your existing login flow while issuing embedded wallets for users. [View full documentation →](/docs/products/embedded-wallet/react/auth/third-party) # Using Your Own Authentication Openfort integrates with external authentication providers so you can keep your existing login while still issuing embedded wallets for users. :::warning[No UI Components with Third-Party Auth] When using third-party authentication (Firebase, Supabase, etc.), you must handle your own login UI. Openfort's built-in UI components (like `OpenfortButton`) and the `uiConfig` settings in `OpenfortProvider` do not apply—they are designed for Openfort's native authentication methods only. ::: ## Ready-Made Quickstarts Get started quickly with complete working examples that integrate third-party authentication: * [Quickstart w/ Firebase](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/firebase#firebase-quickstart) – Complete Firebase integration example * [Quickstart w/ Supabase](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/supabase#supabase-quickstart) – Complete Supabase integration example * [Quickstart w/ Better Auth](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/betterauth#better-auth-quickstart) – Complete Better Auth integration example ## Supported Providers Openfort has built-in support for these third-party authentication platforms: | Provider | Configuration | |----------|---------------| | [**Firebase**](/docs/configuration/external-auth/firebase) | `ThirdPartyOAuthProvider.FIREBASE` | | [**Better-Auth**](/docs/configuration/external-auth/better-auth) | `ThirdPartyOAuthProvider.BETTER_AUTH` | | [**Supabase**](/docs/configuration/external-auth/supabase) | `ThirdPartyOAuthProvider.SUPABASE` | | [**PlayFab**](/docs/configuration/external-auth/playfab) | `ThirdPartyOAuthProvider.PLAYFAB` | | [**AccelByte**](/docs/configuration/external-auth/accelbyte) | `ThirdPartyOAuthProvider.ACCELBYTE` | | [**LootLocker**](/docs/configuration/external-auth/lootlocker) | `ThirdPartyOAuthProvider.LOOTLOCKER` | | [**Custom OIDC**](/docs/configuration/custom-auth/oidc-token) | `ThirdPartyOAuthProvider.OIDC` | | [**Custom Auth**](/docs/configuration/custom-auth/auth-token) | `ThirdPartyOAuthProvider.CUSTOM` | ## Setup Steps ### 1. Configure Your Provider in the Dashboard Before integrating, configure your authentication provider in the [Openfort Dashboard → Providers](https://dashboard.openfort.io/providers): * **For built-in providers** (Firebase, Supabase, etc.): Add your provider's Project ID or credentials * **For OIDC providers** (Auth0, Cognito, etc.): Provide your JWKS URL and audience (`aud`) value * **For custom auth servers**: Set up a verification endpoint and authentication headers [View detailed provider setup guides →](/docs/configuration/external-auth) ### 2. Set up the `thirdPartyAuth` prop in `OpenfortProvider` Wrap your React app with `OpenfortProvider` and specify your authentication provider's `getAccessToken` function. Here's an example using Firebase: ```tsx [Providers.tsx] import React from "react" import { OpenfortProvider, RecoveryMethod, ThirdPartyOAuthProvider } from "@openfort/react" import { auth as firebaseAuth } from "./lib/firebase" export function Providers({ children }: { children: React.ReactNode }) { const firebaseGetAccessToken = async () => { const token = await firebaseAuth.currentUser?.getIdToken(false) return token ?? null } return ( {children} ) } ``` ```tsx [App.tsx] import { Providers } from "./Providers" import MainApp from "./MainApp" export default function App() { return ( ) } ``` :::info Need external wallet support alongside third-party auth? See [useWalletAuth](/docs/products/embedded-wallet/react/hooks/useWalletAuth) for adding wagmi. ::: ### 3. Sync Authentication State In your main app, synchronize the third-party authentication state with Openfort's. Here's an example using Firebase: ```tsx [MainApp.tsx] import { useEffect } from "react" import { useUser, useSignOut } from "@openfort/react" import { auth: firebaseAuth } from "./lib/firebase"; export default function MainApp() { const { getAccessToken } = useUser() // [!code focus] const { signOut: signOutOpenfort } = useSignOut() // [!code focus] // Sync Firebase's authentication state with Openfort's [!code focus] useEffect(() => { // [!code focus] firebaseAuth.onAuthStateChanged(user => { // [!code focus] if (user) { // [!code focus] getAccessToken(); // [!code focus] } else { // [!code focus] signOutOpenfort(); // [!code focus] } // [!code focus] }); // [!code focus] }, []); // [!code focus] return ( <> {/* Your app content */} ); } ``` ## How It Works 1. **User authenticates** with your provider (Firebase, Auth0, etc.) 2. **`getAccessToken()` is called** – exchanges your provider's token for an Openfort session 3. **Openfort verifies the token** and retrieves the user's session key 4. **An embedded wallet is linked** (Ethereum or Solana, depending on your provider configuration) to the user's identifier 5. **User can now sign transactions** with their embedded wallet ## Configuration Guides For detailed setup instructions for each provider type: * [Firebase Setup](/docs/configuration/external-auth/firebase) * [Supabase Setup](/docs/configuration/external-auth/supabase) * [Better-Auth Setup](/docs/configuration/external-auth/better-auth) * [PlayFab Setup](/docs/configuration/external-auth/playfab) * [AccelByte Setup](/docs/configuration/external-auth/accelbyte) * [LootLocker Setup](/docs/configuration/external-auth/lootlocker) * [Custom OIDC Setup](/docs/configuration/custom-auth/oidc-token) (for Auth0, Cognito, etc.) * [Custom Auth Server Setup](/docs/configuration/custom-auth/auth-token) # Wallet configuration Pass a `walletConfig` object to `OpenfortProvider`. The shared options below apply to every chain. For chain-specific setup, follow the Ethereum or Solana guide. For UI-specific options (`uiConfig`), see [UI configuration](/docs/products/embedded-wallet/react/ui/configuration). ## Shared options These options apply regardless of chain. Set `ethereum` and/or `solana` for chain-specific configuration. | Option | Type | Description | |--------|------|-------------| | `shieldPublishableKey` | `string` | **Required.** Shield API publishable key. | | `createEncryptedSessionEndpoint` | `string` | API endpoint that creates an encrypted session. **Mutually exclusive** with `getEncryptionSession`. | | `getEncryptionSession` | `(params) => Promise` | Callback to retrieve an encryption session. **Mutually exclusive** with `createEncryptedSessionEndpoint`. | | `requestWalletRecoverOTPEndpoint` | `string` | API endpoint for requesting wallet recovery OTP. **Mutually exclusive** with `requestWalletRecoverOTP`. | | `requestWalletRecoverOTP` | `(params) => Promise` | Callback to request wallet recovery OTP. **Mutually exclusive** with `requestWalletRecoverOTPEndpoint`. | | `passkeyDisplayName` | `string` | Display name in passkey creation dialog. | | `connectOnLogin` | `boolean` | Create and recover the wallet automatically after auth. Default: `true`. Set to `false` for manual control via `wallet.create()`. | | `chainType` | `ChainTypeEnum` | Which chain type to activate on first mount. Default: `EVM`. | | `ethereum` | `EthereumConfig` | Chain ID, RPC URLs, gas sponsorship, account type, and assets. See [Ethereum configuration](/docs/products/embedded-wallet/react/wallet/ethereum). | | `solana` | `SolanaConfig` | Cluster, RPC URLs, commitment level, and UI options. See [Solana configuration](/docs/products/embedded-wallet/react/wallet/solana). | :::info Need transaction examples? See [Wallet actions](/docs/products/embedded-wallet/react/wallet/actions) for `wallet_sendCalls` usage and gas sponsorship tips. ::: ## connectOnLogin By default, an embedded wallet is automatically created and recovered after authentication. Set `connectOnLogin` to `false` to take full control of wallet creation. ```tsx {/* Check wallet.wallets.length === 0 and call wallet.create() explicitly */} ``` :::info When disabled, wallets are not silently created or recovered during auth flows or chain switches. Check `wallet.wallets.length === 0` after auth and call `wallet.create()` yourself. ::: # Ethereum wallet configuration Configure Ethereum (EVM) embedded wallets through the `ethereum` key of `walletConfig`. For shared options and the Solana setup, see [Wallet configuration](/docs/products/embedded-wallet/react/wallet). ```tsx [Providers.tsx] import { OpenfortProvider } from "@openfort/react" import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi" import { WagmiProvider, createConfig } from "wagmi" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { baseSepolia } from "viem/chains" const config = createConfig( getDefaultConfig({ appName: "My App", chains: [baseSepolia], ssr: true, // set to true for Next.js }) ) const queryClient = new QueryClient() export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ## EthereumConfig ```ts export type FeeSponsorshipConfig = string | Record export type EthereumConfig = { /** Initial chain ID for the embedded wallet provider. * Optional when using OpenfortWagmiBridge — chain is managed by wagmi. * Required for SDK-only (no wagmi) mode; defaults to Sepolia if omitted. */ chainId?: number rpcUrls?: Record /** gas sponsorship ID for gas sponsorship / embedded signer */ ethereumFeeSponsorshipId?: FeeSponsorshipConfig accountType?: AccountTypeEnum /** Token addresses for asset inventory (chainId -> Hex[]) */ assets?: Record } ``` ## The wagmi bridge When building on Ethereum, Openfort integrates with [wagmi](https://wagmi.sh) through `OpenfortWagmiBridge` and `getDefaultConfig` from `@openfort/react/wagmi`. This bridge synchronizes the Openfort embedded wallet with wagmi's connector system so you can use standard wagmi hooks (`useAccount`, `useWalletClient`, `useSendTransaction`, etc.) with the Openfort wallet. **Why it's needed:** Openfort manages authentication, key custody, and wallet recovery independently from wagmi. The bridge keeps both systems in sync — when a user authenticates through Openfort and creates a wallet, the bridge registers it as a wagmi connector. When the user signs out, the bridge disconnects the wagmi session. Without it, wagmi hooks would not see the Openfort wallet. **How it works:** 1. `getDefaultConfig` creates a wagmi config that includes the Openfort connector alongside any external wallets (WalletConnect, injected, etc.). 2. `OpenfortWagmiBridge` wraps your component tree and listens to Openfort auth events. On login, it connects the embedded wallet to wagmi. On logout, it disconnects. 3. Once bridged, `useAccount()` returns the embedded wallet address, `useWalletClient()` returns a signer backed by Openfort, and transaction hooks route through the Openfort provider — including gas sponsorship when configured. ```tsx import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi" import { WagmiProvider, createConfig } from "wagmi" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { baseSepolia } from "viem/chains" const config = createConfig( getDefaultConfig({ appName: "My App", chains: [baseSepolia], ssr: true, // set to true for Next.js }) ) const queryClient = new QueryClient() export function Providers({ children }: { children: React.ReactNode }) { return ( {/* [!code focus] */} {children} {/* [!code focus] */} ) } ``` ## Setting the account type Set the `accountType` parameter in `walletConfig.ethereum` to `AccountTypeEnum.EOA`, `AccountTypeEnum.SMART_ACCOUNT`, or `AccountTypeEnum.DELEGATED_ACCOUNT`. :::info Learn the differences between EOA, Smart Account, and Delegated Account in [Account types](/docs/products/embedded-wallet/account-types). ::: ```tsx {children} ``` ### EOA wallets on a custom EVM chain :::info Building on smart wallets? Check [supported chains](/docs/configuration/chains). ::: EOA wallets are Ethereum-chain-agnostic in Openfort. Once created, the same address works across every chain — even ones that are not part of the built-in chain list. :::warning Set `accountType: AccountTypeEnum.EOA` in `walletConfig.ethereum` for EOA wallets on custom chains. ::: ```tsx {children} ``` :::info Need external wallets on a custom chain? See [useWalletAuth](/docs/products/embedded-wallet/react/hooks/useWalletAuth). ::: ## Next steps * [Send a transaction (Ethereum)](/docs/products/embedded-wallet/react/wallet/actions/send-transaction/ethereum) * [Sign a message (Ethereum)](/docs/products/embedded-wallet/react/wallet/actions/sign-message) * [Wallet assets](/docs/products/embedded-wallet/react/wallet/assets) # Solana wallet configuration Configure Solana (SVM) embedded wallets through the `solana` key of `walletConfig`. For shared options and the Ethereum setup, see [Wallet configuration](/docs/products/embedded-wallet/react/wallet). Solana does not use wagmi — no `WagmiProvider` or `OpenfortWagmiBridge` needed. You still need `QueryClientProvider` because `@openfort/react` uses TanStack Query internally: ```tsx [Providers.tsx] import { OpenfortProvider } from "@openfort/react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" const queryClient = new QueryClient() export function Providers({ children }: { children: React.ReactNode }) { return ( {children} // [!code focus] ) } ``` ## SolanaConfig ```ts export type SolanaConfig = { /** Solana cluster to connect to */ cluster: SolanaCluster /** RPC URLs per cluster (mirrors ethereum.rpcUrls) */ rpcUrls?: Partial> /** Commitment level for transactions (default: 'confirmed') */ commitment?: SolanaCommitment /** UI options for Solana-connected screens */ ui?: SolanaUIOptions } ``` ## Cluster configuration * `cluster` — **Required.** One of `'mainnet-beta'`, `'devnet'`, or `'testnet'`. * `rpcUrls` — Optional. Maps cluster name to an RPC endpoint. When omitted, the SDK uses default Solana RPC URLs (`getDefaultSolanaRpcUrl` from `@openfort/react`). * `commitment` — Optional. Transaction commitment level. Default: `'confirmed'`. Options: `'processed'` | `'confirmed'` | `'finalized'`. * `ui` — Optional. `{ customAvatar?: React.ComponentType<{ address: string }> }` — custom avatar component shown in Solana-connected screens, receives the wallet address. :::info On Solana, the account type is always an EOA. Smart accounts and delegated accounts are Ethereum-only — see [Account types](/docs/products/embedded-wallet/account-types). ::: ## Next steps * [Send a transaction (Solana)](/docs/products/embedded-wallet/react/wallet/actions/send-transaction/solana) * [Sign a message (Solana)](/docs/products/embedded-wallet/react/wallet/actions/sign-message-solana) # Creating a new embedded wallet To create a new embedded wallet, use `useEthereumEmbeddedWallet` (Ethereum) or `useSolanaEmbeddedWallet` (Solana). :::code-group ```tsx [Ethereum] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function CreateWallet() { const { create, isLoading } = useEthereumEmbeddedWallet() const handleCreate = async () => { const wallet = await create({ recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Ethereum wallet created:", wallet.address) } return ( ) } ``` ```tsx [Solana] import { useSolanaEmbeddedWallet } from "@openfort/react/solana" import { RecoveryMethod } from "@openfort/react" function CreateWallet() { const { create, isLoading } = useSolanaEmbeddedWallet() const handleCreate = async () => { const wallet = await create({ recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Solana wallet created:", wallet.address) } return ( ) } ``` ::: This creates a new embedded wallet with **Automatic recovery** and sets it as the active wallet. :::tip[Auto-create behavior] By default, the SDK automatically creates a wallet after authentication when none exists. To disable this and control creation manually, set `connectOnLogin: false` in [`walletConfig`](/docs/products/embedded-wallet/react/wallet#connectonlogin). ::: :::info `accountType` (Smart Account vs EOA) and `chainId` are available via `CreateEmbeddedWalletOptions`. `accountType` is currently supported for Ethereum; Solana uses EOA by default. Gas sponsorship is configured at the provider level via `walletConfig.ethereum.ethereumFeeSponsorshipId`, not per-wallet-creation. ::: ## Recovery method Recovering the embedded wallet is needed when a user logs into a new device. There are three recovery methods available: * **Automatic recovery** — Uses an encrypted session from your backend; no user action. * **Password recovery** — User sets a password; no backend required. * **Passkey recovery** — WebAuthn (Face ID, Touch ID, fingerprint); no backend required. Not sure which to use? See the [recovery methods guide](/docs/configuration/recovery-methods). When creating a wallet, choose one: `RecoveryMethod.AUTOMATIC`, `RecoveryMethod.PASSWORD`, or `RecoveryMethod.PASSKEY`. Configure the same method in `OpenfortProvider` (see examples below). ### Automatic recovery Automatic recovery requires a backend to create encryption sessions. Set `createEncryptedSessionEndpoint` or `getEncryptionSession` in `walletConfig`. See the [Automatic Recovery session](/docs/products/embedded-wallet/server/automatic-recovery-session) guide. :::code-group ```tsx [Endpoint] import { OpenfortProvider } from "@openfort/react" function App() { return ( {/* Add your wallet components here */} ) } ``` ```tsx [Callback] import { OpenfortProvider } from "@openfort/react" function App() { return ( { const res = await fetch("YOUR_BACKEND_ENDPOINT", { method: "POST", body: JSON.stringify({ user_id: userId }), headers: { Authorization: `Bearer ${accessToken}` }, }) const { session } = await res.json() return session }, }} > {/* Add your wallet components here */} ) } ``` ::: **OTP-protected sessions:** If your backend requires a one-time password, add `requestWalletRecoverOTPEndpoint` or `requestWalletRecoverOTP` to `walletConfig`. The recovery UI will show the OTP input when the backend signals it: ```tsx walletConfig={{ shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY", createEncryptedSessionEndpoint: "/api/encryption-session", requestWalletRecoverOTPEndpoint: "/api/send-recovery-otp", // OR use a callback: // requestWalletRecoverOTP: async ({ accessToken, userId, email }) => { // await sendOtpEmail(email) // }, }} ``` Then specify `RecoveryMethod.AUTOMATIC` when creating (or omit it; it defaults to automatic): :::code-group ```tsx [Ethereum] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function CreateWallet() { const { create, isLoading } = useEthereumEmbeddedWallet() const handleCreate = async () => { const wallet = await create({ recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Wallet created:", wallet.address) } return } ``` ```tsx [Solana] import { useSolanaEmbeddedWallet } from "@openfort/react/solana" import { RecoveryMethod } from "@openfort/react" function CreateWallet() { const { create, isLoading } = useSolanaEmbeddedWallet() const handleCreate = async () => { const wallet = await create({ recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Wallet created:", wallet.address) } return } ``` ::: ### Password recovery Password recovery lets users set their own password to protect the wallet. No backend is required. Configure the provider, then use `RecoveryMethod.PASSWORD` when calling `create()`: :::code-group ```tsx [Provider] import { OpenfortProvider, RecoveryMethod } from "@openfort/react" function App() { // Wrap with providers required for your chain(s), e.g. WagmiProvider for Ethereum return ( {/* Add your wallet components here */} ) } ``` ```tsx [create()] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function CreateWallet() { // Same with useSolanaEmbeddedWallet from @openfort/react/solana const { create, isLoading } = useEthereumEmbeddedWallet() const handleCreate = async (password: string) => { const wallet = await create({ recoveryMethod: RecoveryMethod.PASSWORD, password, }) console.log("Wallet created:", wallet.address) } return (
{ e.preventDefault(); handleCreate((e.target as HTMLFormElement).password.value) }}>
) } ``` ::: ### Passkey recovery Passkey recovery uses WebAuthn (Face ID, Touch ID, fingerprint). No backend is required. Configure the provider, then use `RecoveryMethod.PASSKEY` when calling `create()`. :::warning[Domain-bound credentials] WebAuthn credentials are bound to a specific domain. Credentials created for `example.com` only work on that domain — they cannot be used on other applications. If your product spans multiple domains, use automatic recovery instead. ::: :::code-group ```tsx [Provider] import { OpenfortProvider, RecoveryMethod } from "@openfort/react" function App() { // Wrap with providers required for your chain(s), e.g. WagmiProvider for Ethereum return ( {/* Add your wallet components here */} ) } ``` ```tsx [create()] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function CreateWallet() { // Same with useSolanaEmbeddedWallet from @openfort/react/solana const { create, isLoading } = useEthereumEmbeddedWallet() const handleCreate = async () => { const wallet = await create({ recoveryMethod: RecoveryMethod.PASSKEY, }) console.log("Wallet created:", wallet.address) } return ( ) } ``` ::: ### Controlling which methods appear in the UI Use `uiConfig.walletRecovery` to restrict or pre-select the recovery methods shown: ```tsx import { OpenfortProvider, RecoveryMethod } from "@openfort/react" uiConfig={{ walletRecovery: { allowedMethods: [RecoveryMethod.PASSKEY, RecoveryMethod.PASSWORD], defaultMethod: RecoveryMethod.PASSKEY, }, }} ``` :::info `RecoveryMethod.AUTOMATIC` is automatically excluded from `allowedMethods` if no encryption session configuration (`createEncryptedSessionEndpoint` or `getEncryptionSession`) is provided in `walletConfig`. ::: # Importing a wallet from a private key Use the `import` action on `useEthereumEmbeddedWallet` (Ethereum) or `useSolanaEmbeddedWallet` (Solana) to register an existing private key as a new embedded wallet. The key is split with Shamir secret sharing and the shares are persisted to Shield and your account exactly like a freshly created wallet — only the key generation step is skipped. :::info[Key format] * **Ethereum (EVM)** — hex-encoded private key (`0x` prefix optional). * **Solana (SVM)** — base58-encoded 64-byte secret key (the same string returned by `exportPrivateKey()` for a Solana wallet). ::: :::warning[Handle private keys with care] Treat the imported `privateKey` as a high-value secret. Read it from a trusted user-controlled input, avoid logging it, and never persist it in plaintext after import — once imported, all subsequent signing happens through Openfort's MPC flow. ::: :::code-group ```tsx [Ethereum] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function ImportWallet() { const { import: importWallet, isLoading } = useEthereumEmbeddedWallet() const handleImport = async (privateKey: string) => { const wallet = await importWallet({ privateKey, recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Ethereum wallet imported:", wallet.address) } return (
{ e.preventDefault() handleImport((e.target as HTMLFormElement).privateKey.value) }} >
) } ``` ```tsx [Solana] import { useSolanaEmbeddedWallet } from "@openfort/react/solana" import { RecoveryMethod } from "@openfort/react" function ImportWallet() { const { import: importWallet, isLoading } = useSolanaEmbeddedWallet() const handleImport = async (privateKey: string) => { const wallet = await importWallet({ privateKey, recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Solana wallet imported:", wallet.address) } return (
{ e.preventDefault() handleImport((e.target as HTMLFormElement).privateKey.value) }} >
) } ``` ::: `import` returns the same `EmbeddedAccount` as `create`, sets the imported wallet as the active wallet, and refreshes the wallet list — so it slots into the existing `useEthereumEmbeddedWallet` / `useSolanaEmbeddedWallet` state machine just like `create()`. ## Options `import` accepts every option from `CreateEmbeddedWalletOptions` plus the `privateKey` to import: ```ts import({ privateKey: string, // hex (EVM) or base58 (SVM) recoveryMethod?: RecoveryMethod, // 'automatic' | 'password' | 'passkey' password?: string, // required when recoveryMethod === 'password' passkeyId?: string, // optional for 'passkey' otpCode?: string, // optional for 'automatic' with OTP accountType?: AccountTypeEnum, // EVM only; defaults to Smart Account chainId?: number, // EVM only }) ``` ## Recovery method Importing a wallet stores a recovery share for it, exactly like creating one. Pick a recovery method up-front — it controls how the key is encrypted at rest. See the [recovery methods guide](/docs/configuration/recovery-methods) for help choosing. ### Automatic recovery Requires an encryption session from your backend. Configure `createEncryptedSessionEndpoint` or `getEncryptionSession` in `walletConfig` (see the [Automatic Recovery session](/docs/products/embedded-wallet/server/automatic-recovery-session) guide), then pass `RecoveryMethod.AUTOMATIC`: :::code-group ```tsx [Ethereum] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function ImportWallet({ privateKey }: { privateKey: string }) { const { import: importWallet, isLoading } = useEthereumEmbeddedWallet() const handleImport = async () => { const wallet = await importWallet({ privateKey, recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Wallet imported:", wallet.address) } return } ``` ```tsx [Solana] import { useSolanaEmbeddedWallet } from "@openfort/react/solana" import { RecoveryMethod } from "@openfort/react" function ImportWallet({ privateKey }: { privateKey: string }) { const { import: importWallet, isLoading } = useSolanaEmbeddedWallet() const handleImport = async () => { const wallet = await importWallet({ privateKey, recoveryMethod: RecoveryMethod.AUTOMATIC, }) console.log("Wallet imported:", wallet.address) } return } ``` ::: ### Password recovery The user supplies a password that encrypts the recovery share. No backend is required: :::code-group ```tsx [Ethereum] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function ImportWallet() { {/* Same with useSolanaEmbeddedWallet from @openfort/react/solana */} const { import: importWallet, isLoading } = useEthereumEmbeddedWallet() const handleImport = async (privateKey: string, password: string) => { const wallet = await importWallet({ privateKey, recoveryMethod: RecoveryMethod.PASSWORD, password, }) console.log("Wallet imported:", wallet.address) } return (
{ e.preventDefault() const form = e.target as HTMLFormElement handleImport(form.privateKey.value, form.password.value) }} >
) } ``` ::: ### Passkey recovery Pass `RecoveryMethod.PASSKEY` to bind the recovery share to a WebAuthn credential. The browser prompts the user to create a passkey (Face ID, Touch ID, fingerprint). :::warning[Domain-bound credentials] WebAuthn credentials are bound to a specific domain. A passkey created for `example.com` cannot be used on a different domain. For multi-domain products, prefer automatic recovery. ::: :::code-group ```tsx [Ethereum] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import { RecoveryMethod } from "@openfort/react" function ImportWallet({ privateKey }: { privateKey: string }) { {/* Same with useSolanaEmbeddedWallet from @openfort/react/solana */} const { import: importWallet, isLoading } = useEthereumEmbeddedWallet() const handleImport = async () => { const wallet = await importWallet({ privateKey, recoveryMethod: RecoveryMethod.PASSKEY, }) console.log("Wallet imported:", wallet.address) } return ( ) } ``` ::: # Active wallet The active wallet is the wallet currently used for signing and sending transactions. The hooks and wallet object differ per chain — pick your chain below. # Active wallet (Ethereum) The active wallet is the wallet currently used for signing and sending transactions. Use [`useEthereumEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useEthereumEmbeddedWallet) to read and manage the active Ethereum wallet. For Solana, see [Active wallet (Solana)](/docs/products/embedded-wallet/react/wallet/active-wallet/solana). ## View active wallet Use `useEthereumEmbeddedWallet` to read the active wallet: ```tsx import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" function ActiveWalletDisplay() { const { activeWallet, status } = useEthereumEmbeddedWallet() if (status !== "connected" || !activeWallet) return
Not connected
return (

Address: {activeWallet.address}

Chain: Ethereum

) } ``` ## List user wallets The hook returns a list of [`ConnectedEmbeddedEthereumWallet`](#connectedembeddedethereumwallet) objects. ```tsx import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" function WalletList() { const { wallets, activeWallet } = useEthereumEmbeddedWallet() return (
    {wallets.map((w) => (
  • {w.address} {w.id === activeWallet?.id && "(active)"}
  • ))}
) } ``` ## Change active wallet To change the active wallet, use the `setActive` method from the `useEthereumEmbeddedWallet` hook. In this example, we change the active wallet by providing the `address`. ```tsx import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" import type { Hex } from "viem" function SwitchWallet() { const { isConnecting, setActive, } = useEthereumEmbeddedWallet() const handleChangeWallet = async (address: Hex) => { await setActive({ address, }) } return ( ) } ``` You can also pass `chainId`, `recoveryParams`, `recoveryMethod`, `passkeyId`, `password`, or `otpCode` when recovering a wallet. This switches the active wallet to the one at the given address. ## ConnectedEmbeddedEthereumWallet The Ethereum wallet object represents the embedded wallet for the user on Ethereum. It contains: * Account ID and wallet address uniquely identify the wallet * Account type (e.g. Smart Account) specifies the wallet's capabilities * Recovery method used for wallet restoration * `getProvider()` for EIP-1193 signing **Properties:** * `address`: The wallet address (hex). * `id`: Wallet identifier. * `chainType`: Always `'EVM'`. * `walletIndex`: Index among the user's Ethereum wallets. * `recoveryMethod`: The recovery method (e.g. AUTOMATIC, PASSWORD, PASSKEY). * `ownerAddress`: Owner address (Smart Account only). * `implementationType`: Implementation type (e.g. Upgradeable\_v05) (Smart Account only). * `getProvider()`: Returns the EIP-1193 compatible provider. * `isAvailable`: Whether the wallet is available. * `isActive`: Whether this wallet is currently active. * `isConnecting`: Whether the wallet is connecting. * `accounts`: Account metadata (id, chainId). * `connectorType`: Connector type (e.g. embedded). * `walletClientType`: Wallet client type (e.g. openfort). * `accountId`: Account ID (Openfort embedded wallets). * `accountType`: Account type (e.g. Smart Account). * `createdAt`: Timestamp for wallet creation. * `salt`: Salt used for wallet encryption. ### Example usage ```tsx import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" function MyComponent() { const { activeWallet, wallets } = useEthereumEmbeddedWallet() return (

Ethereum wallet

Address: {activeWallet?.address}

Account ID: {activeWallet?.accountId}

Created at: {activeWallet?.createdAt}

All wallets

    {wallets.map((wallet) => (
  • {wallet.address} — {wallet.isActive ? "Active" : "Inactive"}
  • ))}
) } ``` # Active wallet (Solana) The active wallet is the wallet currently used for signing and sending transactions. Use [`useSolanaEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useSolanaEmbeddedWallet) to read and manage the active Solana wallet. For Ethereum, see [Active wallet (Ethereum)](/docs/products/embedded-wallet/react/wallet/active-wallet/ethereum). ## View active wallet Use `useSolanaEmbeddedWallet` to read the active wallet: ```tsx import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function ActiveWalletDisplay() { const { activeWallet, cluster, status } = useSolanaEmbeddedWallet() if (status !== "connected" || !activeWallet) return
Not connected
return (

Address: {activeWallet.address}

Cluster: {cluster}

) } ``` ## List user wallets The hook returns a list of [`ConnectedEmbeddedSolanaWallet`](#connectedembeddedsolanawallet) objects. ```tsx import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function WalletList() { const { wallets, activeWallet } = useSolanaEmbeddedWallet() return (
    {wallets.map((w) => (
  • {w.address} {w.id === activeWallet?.id && "(active)"}
  • ))}
) } ``` ## Change active wallet To change the active wallet, use the `setActive` method from the `useSolanaEmbeddedWallet` hook. In this example, we change the active wallet by providing the `address`. ```tsx import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function SwitchWallet() { const { isConnecting, setActive, } = useSolanaEmbeddedWallet() const handleChangeWallet = async (address: string) => { await setActive({ address }) } return ( ) } ``` You can also pass `recoveryParams`, `recoveryMethod`, `passkeyId`, `password`, or `otpCode` when recovering a wallet. ## ConnectedEmbeddedSolanaWallet * Wallet address (base58) and ID * Recovery method used for wallet restoration * `getProvider()` for Solana signing **Properties:** * `address`: The wallet address (base58). * `id`: Wallet identifier. * `chainType`: Always `'SVM'`. * `walletIndex`: Index among the user's Solana wallets. * `recoveryMethod`: The recovery method (e.g. AUTOMATIC, PASSWORD, PASSKEY). * `getProvider()`: Returns the Solana signing provider. ### Example usage ```tsx import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function MyComponent() { const { activeWallet, wallets } = useSolanaEmbeddedWallet() return (

Solana wallet

Address: {activeWallet?.address}

All wallets

    {wallets.map((wallet) => (
  • {wallet.address} — {wallet.id === activeWallet?.id ? "Active" : "Inactive"}
  • ))}
) } ``` # Export Private Key Allow users to export their wallet’s private key for backup or migration. Openfort’s SDK provides: * Secure export flows for embedded wallets * Warnings and best practices for key management * Options to restrict export based on user status Key export empowers users with control over their assets, while maintaining security and clarity in the export process. To export the private key of the embedded wallet, use the `useEthereumEmbeddedWallet` or `useSolanaEmbeddedWallet` hook. Both provide `exportPrivateKey` for securely exporting the wallet's private key. ## Code examples :::code-group ```tsx [Ethereum] import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" function ExportKey() { const { exportPrivateKey } = useEthereumEmbeddedWallet() // [!code focus] const handleExport = async () => { const privateKey = await exportPrivateKey() console.log("Private key:", privateKey) } return } ``` ```tsx [Solana] import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function ExportKey() { const { exportPrivateKey } = useSolanaEmbeddedWallet() const handleExport = async () => { const privateKey = await exportPrivateKey() console.log("Private key:", privateKey) } return } ``` ::: # Wallet actions Once your app is wrapped in `OpenfortProvider` and the user has an embedded wallet, you can perform onchain actions. The available features depend on the [account type](/docs/products/embedded-wallet/account-types) — each page below shows code for every supported account type. ## Ethereum ## Solana # Send transaction Send onchain transactions from your user's embedded wallet. ## Send a native transfer Use wagmi's `useSendTransaction` to send native ETH/MATIC transfers: ```tsx [SendNative.tsx] import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi' import { parseEther } from 'viem' function SendNative() { const { sendTransaction, data: hash, isPending, error } = useSendTransaction() const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash }) const handleSend = () => { sendTransaction({ to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', value: parseEther('0.01'), }) } return (
{isConfirmed &&

Transfer confirmed!

} {error &&

Error: {error.message}

}
) } ``` ## Send a contract call Use wagmi's `useWriteContract` for ERC-20 transfers or any contract interaction: :::info This example uses a testnet USDC token address for demonstration purposes. This is not the real mainnet USDC token. ::: :::code-group ```tsx [useSendUsdc.ts] import { useCallback } from 'react' import { parseUnits } from 'viem' import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi' const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // replace with your token const usdcAbi = [{ name: 'transfer', type: 'function', stateMutability: 'nonpayable', inputs: [ { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }, ], outputs: [{ name: 'success', type: 'bool' }], }] as const export function useSendUsdc() { const { address } = useAccount() const { writeContract, data: hash, isPending, error } = useWriteContract() const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash }) const sendUsdc = useCallback(async (to: `0x${string}`, amount: string) => { if (!address) throw new Error('Wallet not connected') const units = parseUnits(amount, 6) writeContract({ address: usdcAddress, abi: usdcAbi, functionName: 'transfer', args: [to, units], }) }, [address, writeContract]) return { sendUsdc, hash, isPending, isConfirming, isConfirmed, error, } } ``` ```tsx [TransferButton.tsx] import { useSendUsdc } from './useSendUsdc' export function TransferButton() { const { sendUsdc, isPending, isConfirming, isConfirmed, error } = useSendUsdc() const handleTransfer = () => { sendUsdc('0x742d35Cc6634C0532925a3b844Bc454e4438f44e', '10.5') } return (
{isConfirmed &&

Transfer confirmed!

} {error &&

Error: {error.message}

}
) } ``` ::: ## Gas sponsorship :::warning Gas sponsorship requires a **Smart Account** or **Delegated Account**. EOA wallets pay their own gas and cannot use gas sponsorship policies. Make sure `accountType` is set to `AccountTypeEnum.SMART_ACCOUNT` or `AccountTypeEnum.DELEGATED_ACCOUNT` in your [wallet configuration](/docs/products/embedded-wallet/react/wallet/ethereum#setting-the-account-type). ::: :::info For full details on gas sponsorship policies, see [Gas sponsorship](/docs/configuration/gas-sponsorship). ::: Keep the experience gasless by attaching a gas policy created in the Openfort dashboard: 1. Navigate to [**Dashboard → Gas sponsorship**](https://dashboard.openfort.io/policies), add a gas sponsorship, and copy the generated `pol_...` identifier. 2. Configure match rules (chain, method, contract) so only the intended `wallet_sendCalls` bundles qualify. 3. Pick a **Sponsorship mode**: **App pays** covers gas so users transact for free, or **User pays** lets users pay gas in a token you choose. On EVM you select a network and a gas-payment token — supported stablecoins (USDC, USDT, DAI, WETH) are priced automatically, or you set a fixed amount per transaction. :::info This applies to Ethereum wallets. For Solana, see [Send transaction (Solana)](/docs/products/embedded-wallet/react/wallet/actions/send-transaction/solana#gasless-solana-transactions). ::: Wire the policy into `ethereumFeeSponsorshipId` inside `walletConfig.ethereum` so the SDK appends it automatically: ```tsx [Providers.tsx] const config = createConfig( getDefaultConfig({ appName: "Openfort demo", chains: [polygonAmoy], ssr: true, }) ) export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` Dashboard edits apply immediately, matching `wallet_sendCalls` bundles will respect the latest sponsorship rules without redeploying your frontend. ## More examples * [`useAaveOperations.ts`](https://github.com/openfort-xyz/recipes-hub/tree/main/aave/frontend/src/hooks/useAaveOperations.ts) chains approvals with lending transactions. * [`useVaultOperations.ts`](https://github.com/openfort-xyz/recipes-hub/tree/main/morpho/frontend/src/hooks/useVaultOperations.ts) batches deposits and redemptions. # Sign message Request a message signature on the wallet's Ethereum provider. Openfort's `useSignMessage` opens a confirmation screen showing exactly what's being signed, then resolves with the signature — no gas, no transaction. Try it live on the [UI configuration](/docs/products/embedded-wallet/react/ui/configuration#sign-message) page. ## Personal sign :::info This method uses Ethereum’s `personal_sign` RPC method. ::: ```tsx [SignMessage.tsx] import { useSignMessage } from 'wagmi' function SignMessage() { const { signMessage, data: signature, isPending, error } = useSignMessage() return (
{signature &&

Signature: {signature}

} {error &&

Error: {error.message}

}
) } ``` :::info Smart and Delegated accounts use [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) for signature verification. The wagmi hooks are identical — the SDK handles the difference under the hood. ::: ## Typed data — EIP-712 Use `useSignTypedData` for structured, domain-separated signatures: ```tsx [SignTypedData.tsx] import { useSignTypedData } from 'wagmi' function SignTypedData() { const { signTypedData, data: signature, isPending, error } = useSignTypedData() const handleSign = () => { signTypedData({ domain: { chainId: 1, name: 'Example DApp', verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', version: '1', }, types: { Mail: [ { name: 'from', type: 'Person' }, { name: 'to', type: 'Person' }, { name: 'content', type: 'string' }, ], Person: [ { name: 'name', type: 'string' }, { name: 'wallet', type: 'address' }, ], }, message: { from: { name: 'Alice', wallet: '0x2111111111111111111111111111111111111111' }, to: { name: 'Bob', wallet: '0x3111111111111111111111111111111111111111' }, content: 'Hello!', }, primaryType: 'Mail', }) } return (
{signature &&

Signature: {signature}

} {error &&

Error: {error.message}

}
) } ``` # Switch chain Switch the active chain for the user's embedded wallet. :::info When using *Smart accounts*, the address is at first pre-computed off-chain. The account will be deployed when doing the first transaction with it. Only [supported chains](/docs/configuration/chains) can be used with Smart Accounts. ::: :::warning ERC-7702 delegation must be **set per chain**. When switching to a new chain, the delegation authorization needs to be re-established on that chain. See [EIP-7702 authorization](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization) for details. ::: Use wagmi's `useSwitchChain` hook to change the active chain: ```tsx [SwitchChainButton.tsx] import { useSwitchChain } from 'wagmi' import { polygonAmoy, baseSepolia } from 'wagmi/chains' function SwitchChainButton() { const { switchChain, isPending, error } = useSwitchChain() return (
{error &&

Error: {error.message}

}
) } ``` # Sign EIP-7702 authorization [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) allows an externally owned accounts (EOA) to delegate their execution to smart contract code This allows EOA wallets to gain account abstraction capabilities such as transaction bundling, gas sponsorship, and custom permissions. :::warning This page applies to **Delegated Accounts only**. EOA and Smart Account types do not require EIP-7702 authorization. ::: Openfort provides methods to sign EIP-7702 authorizations, which allows your embedded wallets to be upgraded into any smart contract. Despite you can sign an authorization for any smart account, when using openfort to send transactions from `delegated accounts`, only a limited list of smart account implementations is supported. [Learn more](/docs/configuration/addresses) about the supported list of smart accounts. When sending a transaction from a Delegated Account through Openfort, you must sign a one-time EIP-7702 authorization. This authorization tells the network which smart account implementation contract the EOA is delegating to. * The authorization is **signed once per chain** — after the first transaction is submitted with the authorization, subsequent transactions on the same chain do not need it again. * When switching to a **new chain**, a new authorization must be signed for that chain. :::info[Supported chains vs. other chains] On a [supported EVM chain](/docs/configuration/chains), Openfort delegates automatically to the **Calibur** implementation (the default unless you configure another) — nothing else to do. On a chain Openfort does not natively support, you can still delegate to any 7702 implementation deployed on that chain by signing the authorization yourself with viem — see [Delegate to a different implementation](#delegate-to-a-different-implementation). ::: ## Recommended: native delegation to Calibur The simplest path is to configure the embedded wallet as a Delegated Account and let the SDK attach the authorization for you. Set `accountType` to `DELEGATED_ACCOUNT` in [`walletConfig.ethereum`](/docs/products/embedded-wallet/react/wallet/ethereum#setting-the-account-type): ```tsx [Providers.tsx] {children} ``` Then send a transaction as usual — for example with wagmi's `useSendTransaction`, or the [send guide](/docs/products/embedded-wallet/react/wallet/actions/send-transaction/ethereum). On the first send, the SDK signs and attaches the one-time EIP-7702 authorization to Openfort's [Calibur](/docs/configuration/addresses) implementation, keeping your transactions visible in the [dashboard](https://dashboard.openfort.io). ## Delegate to a different implementation Use this path when you want an implementation other than Calibur — the [`Simple` 7702 account](/docs/configuration/addresses) or your own contract — **or when you're on a chain Openfort doesn't natively delegate on** but that has a 7702 implementation available. Sign the authorization yourself with the `use7702Authorization` hook and send the UserOperation with viem. :::warning Only [Openfort-supported implementations](/docs/configuration/addresses) appear in the dashboard and can be tracked. Transactions from a smart account Openfort does not natively support will not show up there. ::: ### Sign the authorization Use the `use7702Authorization` hook from the Openfort React SDK: ```tsx [Authorize7702.tsx] import { use7702Authorization } from '@openfort/react' import { usePublicClient, useAccount } from 'wagmi' import { sepolia } from 'viem/chains' function Authorize7702() { const { signAuthorization } = use7702Authorization() const publicClient = usePublicClient() const { address } = useAccount() const handleAuthorization = async () => { if (!address) throw new Error('Wallet not connected') const nonce = await publicClient.getTransactionCount({ address }) const signedAuth = await signAuthorization({ contractAddress: '0xe6Cae83BdE06E4c305530e199D7217f42808555B', chainId: sepolia.id, nonce, }) console.log('Authorization signed:', signedAuth) // Pass signedAuth to your transaction flow } return } ``` ### Parameters | Parameter | Type | Description | | --- | --- | --- | | `contractAddress` | `Address` | The implementation contract to delegate execution to | | `chainId` | `number` | The chain ID for the authorization | | `nonce` | `number` | The current transaction count of the EOA | ### Return value The `signAuthorization` function returns a `SignedAuthorization` object: ```ts type SignedAuthorization = { address: Address chainId: number nonce: number r: Hex s: Hex v: bigint yParity: number } ``` ### Use the authorization in a transaction Once signed, pass the authorization to your transaction. Here is an example using Openfort as a bundler and paymaster for gasless execution: ```tsx [useUserOperation.ts] import { useCallback } from 'react' import { http, zeroAddress } from 'viem' import { baseSepolia } from 'viem/chains' import { createPaymasterClient, createBundlerClient, entryPoint08Address, } from 'viem/account-abstraction' import { toSimpleSmartAccount } from 'viem/account-abstraction' import { useWalletClient, usePublicClient } from 'wagmi' const OPENFORT_RPC_URL = `https://api.openfort.io/rpc/${baseSepolia.id}` const OPENFORT_PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_OPENFORT_PUBLISHABLE_KEY! const OPENFORT_FEE_SPONSORSHIP_ID = process.env.NEXT_PUBLIC_OPENFORT_FEE_SPONSORSHIP_ID! export function useUserOperation() { const { data: walletClient } = useWalletClient() const publicClient = usePublicClient() return useCallback(async (authorization: SignedAuthorization) => { if (!walletClient) throw new Error('Wallet client not ready') const simpleSmartAccount = await toSimpleSmartAccount({ owner: walletClient, entryPoint: { address: entryPoint08Address, version: '0.8' }, client: publicClient, address: walletClient.account.address, }) const paymasterClient = createPaymasterClient({ transport: http(OPENFORT_RPC_URL, { fetchOptions: { headers: { 'Authorization': `Bearer ${OPENFORT_PUBLISHABLE_KEY}`, }, }, }), }) const bundlerClient = createBundlerClient({ account: simpleSmartAccount, client: publicClient, paymaster: paymasterClient, paymasterContext: { policyId: OPENFORT_FEE_SPONSORSHIP_ID, }, transport: http(OPENFORT_RPC_URL, { fetchOptions: { headers: { 'Authorization': `Bearer ${OPENFORT_PUBLISHABLE_KEY}`, }, }, }), }) const txnHash = await bundlerClient.sendUserOperation({ calls: [{ to: zeroAddress, data: '0x', value: 0n }], authorization, }) const receipt = await bundlerClient.waitForUserOperationReceipt({ hash: txnHash, }) return receipt.receipt.transactionHash }, [walletClient, publicClient]) } ``` ## Delegate an external wallet (server-side) The flow above upgrades an Openfort embedded wallet from the browser. You can also delegate a **third-party or external wallet** — for example one held in Privy or Turnkey — to an Openfort Delegated Account from your server with the [`@openfort/openfort-node`](/docs/products/server) SDK. The wallet signs a single EIP-7702 authorization; Openfort then sponsors and submits the transaction. :::warning Wallets like MetaMask or Rabby do not support signing external EIP-7702 authorizations. Use a wallet whose SDK exposes authorization signing. ::: Before you start, create an Openfort project, a [gas sponsorship](/docs/configuration/gas-sponsorship) (note the `pol_...` ID), and a [contract](/docs/configuration/addresses) (note the `con_...` ID). ```ts [delegate-external-wallet.ts] import 'dotenv/config' import Openfort, { createAccountV2 } from '@openfort/openfort-node' import { generatePrivateKey, privateKeyToAccount, signAuthorization } from 'viem/accounts' import { createWalletClient, http, serializeSignature } from 'viem' import { baseSepolia } from 'viem/chains' const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY!) const chainId = baseSepolia.id // 1. Your third-party wallet (replace key generation with your wallet SDK) const privateKey = generatePrivateKey() const account = privateKeyToAccount(privateKey) // 2. Register it as a Delegated Account const openfortAccount = await createAccountV2({ chainType: 'EVM', address: account.address, accountType: 'Delegated Account', chainId, implementationType: 'Calibur', }) // 3. Sign the EIP-7702 authorization with the external wallet const walletClient = createWalletClient({ chain: baseSepolia, transport: http(), account }) const authorization = await walletClient.prepareAuthorization({ account: account.address, contractAddress: openfortAccount.smartAccount.implementationAddress, }) const serializedAuthorization = serializeSignature( await signAuthorization({ ...authorization, privateKey }), ) // 4. Create a sponsored transaction intent that uses the authorization const txIntent = await openfort.transactionIntents.create({ chainId, account: openfortAccount.id, policy: 'pol_...', // your gas sponsorship ID signedAuthorization: serializedAuthorization, interactions: [ { contract: 'con_...', functionName: 'mint', functionArgs: [account.address] }, ], }) // 5. Sign the intent's payload with the external wallet and submit const signature = await account.sign({ hash: txIntent.nextAction.payload.signableHash }) const result = await openfort.transactionIntents.signature(txIntent.id, { signature }) console.log('Transaction hash:', result.transactionHash) ``` :::info This example generates a private key with [Viem](https://viem.sh/) for demonstration. Replace it with your third-party wallet's signing SDK. ::: ## Related * [`use7702Authorization` hook reference](/docs/products/embedded-wallet/react/hooks/use7702Authorization) * [Account types](/docs/products/embedded-wallet/account-types) * [Send Ethereum transaction](/docs/products/embedded-wallet/react/wallet/actions/send-transaction/ethereum) * [Switch chain](/docs/products/embedded-wallet/react/wallet/actions/switch-chain) # Using Session Keys with React :::warning Session keys are only available for **Smart Accounts**. By default, the Openfort SDK creates EOA (Externally Owned Account) wallets. To use session keys, you must first [configure your wallet to use a smart account](/docs/products/embedded-wallet/react/wallet). ::: Session keys are programmable access tokens with specific permissions, designed for controlled interactions with smart accounts. Examples include: * Granting access to specific areas or features. * Limiting usage to a set amount of resources (e.g., 1000 units of currency). * Time-bound validity (e.g., expiring after 3 days). Permissions can be combined, enabling fine-tuned, context-specific capabilities. :::tip Use `useGrantPermissions` to create session keys and `useRevokePermissions` to revoke them. Both follow the EIP-7715 standard. See [useGrantPermissions](/docs/products/embedded-wallet/react/hooks/useGrantPermissions) and [useRevokePermissions](/docs/products/embedded-wallet/react/hooks/useRevokePermissions) for detailed usage. ::: ## Using the `useGrantPermissions` Hook The `useGrantPermissions` hook provides a simple, client-side way to create and manage session keys using the EIP-7715 standard. ### Quick Example ```tsx import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import { useGrantPermissions } from '@openfort/react'; function SessionKeyExample() { const { grantPermissions, isLoading } = useGrantPermissions({ onSuccess: (result) => { console.log('Permissions granted for wallet:', result.address); }, }); const createSession = async () => { // Generate a new session key const sessionKey = generatePrivateKey(); const sessionAccount = privateKeyToAccount(sessionKey); // Store the session key BEFORE granting permissions // The hook does not return the private key localStorage.setItem('sessionKey', sessionKey); const result = await grantPermissions({ sessionKey, request: { signer: { type: 'account', data: { id: sessionAccount.address }, }, expiry: 60 * 60 * 24, // 24 hours permissions: [ { type: 'contract-call', data: { address: '0x2522f4fc9af2e1954a3d13f7a5b2683a00a4543a', calls: [], }, policies: [], }, ], }, }); if (result.address) { console.log('Session key registered for wallet:', result.address); } }; return ( ); } ``` For more details and advanced usage, see the [useGrantPermissions documentation](/docs/products/embedded-wallet/react/hooks/useGrantPermissions). ## Revoking a session key Use the `useRevokePermissions` hook to revoke a session key by its address: ```tsx import { useRevokePermissions } from '@openfort/react' function RevokeSession() { const { revokePermissions, isLoading } = useRevokePermissions() const handleRevoke = async (sessionKeyAddress: `0x${string}`) => { await revokePermissions({ sessionKey: sessionKeyAddress }) } return ( ) } ``` For full API reference, see [useRevokePermissions](/docs/products/embedded-wallet/react/hooks/useRevokePermissions). # Send transaction Solana wallets use [`@solana/kit`](https://www.npmjs.com/package/@solana/kit) to build, sign, and submit transactions. Openfort's embedded wallet signs transaction message bytes directly with Ed25519, so the flow is always the same: wrap the wallet's `signMessage` in a `@solana/kit` `TransactionSigner`, build a transaction message, sign it with that signer, then broadcast and confirm. :::tip[Built-in Send UI] If you render [``](/docs/products/embedded-wallet/react/ui), your users already get a **Send** action for native SOL in the wallet modal — with an optional **gasless** toggle that sponsors the fee through the [Solana Paymaster](/docs/products/infrastructure/paymaster/solana). On an empty wallet, Send routes to **Add funds** instead. The steps below are for building a custom send flow. ::: ## How signing works Two details are specific to Solana: * **Ed25519, not keccak256.** Pass `{ hashMessage: false }` to `signMessage` so the wallet signs the raw transaction bytes. The default hashing is only correct for EVM signatures. * **A signer adapter.** `@solana/kit` builds and signs transactions through a `TransactionSigner`. You bridge it to the embedded wallet with a small adapter that calls `signMessage` and trims the recovery byte (Ed25519 signatures are 64 bytes). ## 1. Create the signer adapter `createOpenfortSigner` turns the wallet's `signMessage` into a `@solana/kit` `TransactionSigner` you can reuse for every transaction: ```tsx [createOpenfortSigner.ts] import { type Address, type SignatureBytes, type SignatureDictionary, type TransactionSigner, } from '@solana/kit' import { Base58 } from 'ox' export function createOpenfortSigner( signerAddress: Address, signMessage: (message: Uint8Array) => Promise, ): TransactionSigner { return { address: signerAddress, signTransactions: async (transactions): Promise => { return Promise.all( transactions.map(async (transaction) => { const signatureBase58 = await signMessage( new Uint8Array(transaction.messageBytes), ) let signatureBytes = Base58.toBytes(signatureBase58) // Trim recovery byte if present (65 → 64 bytes for Ed25519) if (signatureBytes.length === 65) { signatureBytes = signatureBytes.slice(0, 64) } return Object.freeze({ [signerAddress]: signatureBytes as SignatureBytes, }) }), ) }, } } ``` ## 2. Build, sign, and send a transfer Build the transfer with the signer as the source, sign it with `signTransactionMessageWithSigners`, then broadcast and confirm with `sendAndConfirmTransactionFactory`: ```tsx [useSendSol.ts] import { useMemo } from 'react' import { useSolanaEmbeddedWallet } from '@openfort/react/solana' import { type Address, address, createSolanaRpc, createSolanaRpcSubscriptions, createTransactionMessage, lamports, pipe, sendAndConfirmTransactionFactory, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, signTransactionMessageWithSigners, appendTransactionMessageInstruction, assertIsTransactionWithBlockhashLifetime, } from '@solana/kit' import { getTransferSolInstruction } from '@solana-program/system' import { createOpenfortSigner } from './createOpenfortSigner' const rpc = createSolanaRpc('https://api.devnet.solana.com') const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.devnet.solana.com') export function useSendSol() { const solana = useSolanaEmbeddedWallet() return useMemo(() => { if (solana.status !== 'connected') return null return async (to: string, amountInSol: number) => { const from = solana.activeWallet.address as Address const openfortSigner = createOpenfortSigner(from, (message) => solana.provider.signMessage(message, { hashMessage: false }), ) const { value: latestBlockhash } = await rpc.getLatestBlockhash().send() const transactionMessage = pipe( createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(from, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstruction( getTransferSolInstruction({ source: openfortSigner, destination: address(to), amount: lamports(BigInt(Math.floor(amountInSol * 1_000_000_000))), }), tx, ), ) const signedTransaction = await signTransactionMessageWithSigners(transactionMessage) assertIsTransactionWithBlockhashLifetime(signedTransaction) const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions }) await sendAndConfirm(signedTransaction, { commitment: 'confirmed' }) return Object.keys(signedTransaction.signatures)[0] } }, [solana]) } ``` :::tip To sign arbitrary messages on Solana, see [Sign message (Solana)](/docs/products/embedded-wallet/react/wallet/actions/sign-message-solana). ::: ## Gasless Solana transactions Keep transactions SOL-free for users by sponsoring fees through the [Openfort Solana Paymaster](/docs/products/infrastructure/paymaster/solana), which integrates with [Kora](https://www.npmjs.com/package/@solana/kora). Openfort acts as the transaction fee payer, so users never need to hold SOL. 1. In [**Dashboard → Gas sponsorship**](https://dashboard.openfort.io/policies), add a gas sponsorship with a `sponsorSolTransaction` rule, then pick a **Sponsorship mode**: * **App pays** — your project covers the SOL fees so users transact for free. * **User pays** — users pay fees in a supported SPL token; Kora computes the exchange rate at request time. 2. Point a `KoraClient` at the Openfort RPC for your cluster (`https://api.openfort.io/rpc/solana/{cluster}`), authenticated with your publishable key. :::info Solana gas sponsorship is **project-scoped** — matching policies apply automatically, with no per-request `policyId` to pass (unlike Ethereum). ::: Build the transfer with the standard `@solana-program/token` library, set Kora as the fee payer, sign the user's part with the same `createOpenfortSigner` adapter from above (Ed25519, `{ hashMessage: false }`), then submit through Kora for co-signing and broadcast: ```tsx [useGaslessSol.ts] import { KoraClient } from '@solana/kora' import { useSolanaEmbeddedWallet } from '@openfort/react/solana' import { address, createNoopSigner, getBase64EncodedWireTransaction, partiallySignTransactionMessageWithSigners, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction, type Blockhash, } from '@solana/kit' import { findAssociatedTokenPda, getTransferInstruction, TOKEN_PROGRAM_ADDRESS, } from '@solana-program/token' import { createOpenfortSigner } from './createOpenfortSigner' const client = new KoraClient({ rpcUrl: 'https://api.openfort.io/rpc/solana/devnet', apiKey: 'Bearer YOUR_OPENFORT_PUBLISHABLE_KEY', }) export function useGaslessSol() { const solana = useSolanaEmbeddedWallet() return async (destination: string) => { if (solana.status !== 'connected') throw new Error('Wallet not connected') const from = address(solana.activeWallet.address) const userSigner = createOpenfortSigner(from, (message) => solana.provider.signMessage(message, { hashMessage: false }), ) // 1. Kora's fee-payer signer address const { signer_address } = await client.getPayerSigner() const feePayer = createNoopSigner(address(signer_address)) // 2. The SPL token to transfer const { allowed_spl_paid_tokens } = (await client.getConfig()).validation_config const mint = address(allowed_spl_paid_tokens[0]) // 3. Build the transfer with the standard SPL Token program const [source] = await findAssociatedTokenPda({ owner: from, tokenProgram: TOKEN_PROGRAM_ADDRESS, mint }) const [dest] = await findAssociatedTokenPda({ owner: address(destination), tokenProgram: TOKEN_PROGRAM_ADDRESS, mint }) const { blockhash } = await client.getBlockhash() const transactionMessage = pipe( createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayerSigner(feePayer, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash( { blockhash: blockhash as Blockhash, lastValidBlockHeight: 0n }, tx, ), (tx) => appendTransactionMessageInstruction( getTransferInstruction({ source, destination: dest, authority: userSigner, amount: 100_000n, // 0.10 USDC (6 decimals) }), tx, ), ) // 4. Sign the user's part, then submit for Kora co-signing + broadcast const userSigned = await partiallySignTransactionMessageWithSigners(transactionMessage) const { signature } = await client.signAndSendTransaction({ transaction: getBase64EncodedWireTransaction(userSigned), signer_key: signer_address, }) return signature } } ``` :::tip For the complete copy-pasteable flow (compute-budget instructions, payment instruction, and confirmation), see the [Solana Paymaster](/docs/products/infrastructure/paymaster/solana) docs and the [Solana sample app](https://github.com/openfort-xyz/openfort-js/tree/main/examples/apps/solana-sample). ::: # Sign message Sign arbitrary messages using your Openfort Solana embedded wallet. Openfort uses Ed25519 for Solana wallets, which signs raw message bytes — pass `{ hashMessage: false }` to disable the default keccak256 hashing used for EVM signatures. ```tsx [SignSolanaMessage.tsx] import { useSolanaEmbeddedWallet } from '@openfort/react/solana' function SignSolanaMessage() { const solana = useSolanaEmbeddedWallet() const handleSignMessage = async () => { if (solana.status !== 'connected') return const message = new TextEncoder().encode('Hello from Openfort!') // hashMessage: false is required for Solana Ed25519 signatures const signature = await solana.provider.signMessage(message, { hashMessage: false, }) console.log('Signature:', signature) } return ( ) } ``` :::info When signing for Solana, always pass `{ hashMessage: false }`. Openfort uses Ed25519 for Solana wallets, which expects raw message bytes rather than the keccak256 hash used for EVM signatures. ::: # Wallet assets Wallet assets represent the tokens held by a user's embedded wallet: * **Native tokens** (e.g., ETH, MATIC, AVAX) — the blockchain's native currency * **ERC-20 tokens** (e.g., USDC, DAI, USDT) — fungible tokens following the ERC-20 standard :::info Asset tracking with [`useEthereumWalletAssets`](/docs/products/embedded-wallet/react/hooks/useEthereumWalletAssets) and the `assets` configuration is **Ethereum-only** (native tokens + ERC-20). For Solana, query the native balance through the Solana provider — see [Solana](#solana) below. ::: ## Ethereum On Ethereum there are three steps: configure which tokens to track, display them with the prebuilt UI, and/or fetch them yourself with a hook. ### 1. Configure tracked tokens By default, Openfort tracks balances for popular ERC-20 tokens like USDC and DAI, plus a set of [default assets](/docs/configuration/default-assets). Add your own tokens in the `assets` map of `walletConfig.ethereum`, keyed by chain ID: ```tsx import { polygonAmoy } from "viem/chains" import { OpenfortProvider } from "@openfort/react" export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` The `assets` object uses chain IDs as keys, with each value being an array of token addresses: ```typescript assets?: { [chainId: number]: Hex[]; } ``` Openfort automatically queries each token contract to retrieve its `symbol`, `name`, and `decimals` — you only provide the address. ### 2. Display with Openfort UI Openfort's prebuilt UI components fetch and display native and ERC-20 balances automatically, with no extra code. A user's assets look like this:
Wallet Assets UI
### 3. Fetch with a hook To fetch assets yourself, use the [`useEthereumWalletAssets`](/docs/products/embedded-wallet/react/hooks/useEthereumWalletAssets) hook from `@openfort/react/ethereum`. It returns the default assets plus any tokens you configured in step 1 (or pass addresses directly to the hook). ```tsx import { useEthereumWalletAssets } from "@openfort/react/ethereum" import { formatUnits } from "viem" function WalletAssets() { const { data: assets, isLoading, error } = useEthereumWalletAssets() if (isLoading) return
Loading assets...
if (error) return
Error: {error.message}
return (

Wallet Assets

    {assets?.map((asset) => (
  • {asset.address}: {formatUnits(asset.balance, asset.metadata?.decimals ?? 18)} {asset.metadata?.symbol}
  • ))}
) } ``` For all options, see [useEthereumWalletAssets](/docs/products/embedded-wallet/react/hooks/useEthereumWalletAssets). ## Solana For native SOL balance, use the Solana RPC with the active wallet address from [`useSolanaEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useSolanaEmbeddedWallet) (e.g. `activeWallet?.address`) and your preferred RPC client. The Ethereum `assets` config and `useEthereumWalletAssets` hook do not apply to Solana. ## Related resources * [Wallet actions](/docs/products/embedded-wallet/react/wallet/actions) — send native tokens and ERC-20 transfers * [Active wallet](/docs/products/embedded-wallet/react/wallet/active-wallet) — get, list, and switch the active wallet * [ConnectedEmbeddedEthereumWallet](/docs/products/embedded-wallet/react/wallet/active-wallet/ethereum#connectedembeddedethereumwallet) — wallet object properties # Funding with `useFunding` For concepts (sessions, payment methods, statuses) see [Funding](/docs/configuration/funding). For the drop-in Deposit flow in the wallet modal, see [UI configuration](/docs/products/embedded-wallet/react/ui/configuration#funding). This page covers the `useFunding` hook for a bespoke UI. ## Custom UI with `useFunding` For a bespoke flow, drive a session yourself with the `useFunding` hook. It exposes the session state plus `fund` (create a session and set a payment method in one call), `createSession` (create a session for a destination, to set the payment method later), `track` (attach to a session created elsewhere), `payLink`, and `reset`. ```tsx import { useFunding } from '@openfort/react' function Deposit({ walletAddress }: { walletAddress: string }) { const { fund, createSession, session, status, loading, error, isAvailable, payLink, reset } = useFunding() async function depositFromPolygon() { await fund( // target — where funds land (USDC on Base) { chain: 'eip155:8453', currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', address: walletAddress }, // source — what the user sends (USDC on Polygon) { type: 'evm', source: { chain: 'eip155:137', currency: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', amount: '10000000' } } ) } if (session?.paymentMethod) { return (

Status: {status}

Send to: {session.paymentMethod.receiverAddress}

{/* render session.paymentMethod.addressUri as a QR, or session.paymentMethod.deeplinks */}
) } return ( ) } ``` The hook returns once a deposit address is available and keeps polling until the session reaches a terminal status (`succeeded`, `bounced`, or `expired`). `fund`'s payment method can source from an EVM wallet (`{ type: 'evm', source }`) or a Solana wallet (`{ type: 'solana', source }`). To fund from a centralized exchange, use `payLink` instead. ### What `useFunding` returns | Field | Type | Description | | --- | --- | --- | | `session` | `FundingSession \| null` | The current or last session, incl. `paymentMethod` once set. | | `status` | `SessionStatus \| 'idle'` | Lifecycle status; `'idle'` before the first call. | | `loading` | `boolean` | True while creating the session and minting the address. | | `error` | `Error \| null` | The last error, if any. | | `isAvailable` | `boolean` | True when the funding service is reachable. Defaults to the Openfort backend — set `fundingBaseUrl` only to point at a custom funding service. | | `fund(target, paymentMethod)` | `=> Promise` | Create a session and set a payment method in one call. | | `createSession(target)` | `=> Promise` | Create a session for a destination, to set the payment method later. | | `track(session)` | `=> Promise` | Attach to a session created elsewhere (its `id` + `clientSecret`) and poll it to a terminal status. | | `payLink(params)` | `=> Promise` | Resolve a prefilled exchange on-ramp URL (Coinbase / Binance). | | `reset()` | `=> void` | Clear session state and start over. | :::tip[Reacting to settlement] There's no completion callback — drive UI off `status`. It reaches `succeeded` when funds land (or `bounced` / `expired`). For server-side fulfilment, don't rely on the browser: use [webhooks](/docs/configuration/webhooks#funding-events). ::: :::tip Same‑chain deposits (source chain == destination chain) skip bridging entirely — the hook returns the wallet address directly, so a plain transfer works with no fees. ::: ## Next steps * Drive funding from a backend or agent with the [JavaScript SDK](/docs/products/embedded-wallet/javascript/signer/funding) or the [REST reference](/docs/configuration/funding/headless). * Render the mobile deposit page with the [hosted deposit page](/docs/configuration/funding#hosted-deposit-page). * React to settlement with [Webhooks](/docs/configuration/webhooks#funding-events). # Disconnect Wallet Let users disconnect their wallet from your app when desired. The Disconnect Wallet flow: * Ends the wallet session and clears user state * Handles cleanup for both embedded and external wallets * Can trigger UI transitions to onboarding or profile screens ## Use `useSignOut` Disconnect uses [`useSignOut`](/docs/products/embedded-wallet/react/hooks/useSignOut) from `@openfort/react`. It clears authentication state and disconnects from all services. ```tsx import { useSignOut } from "@openfort/react" function DisconnectButton() { const { signOut, isLoading } = useSignOut({ onSuccess: () => { // Redirect to login or home window.location.href = "/" }, }) return ( ) } ``` `useSignOut` returns `{ signOut, isLoading, isError, isSuccess, error }`. Call `signOut()` to end the session. For external wallets connected via the wagmi bridge, signing out also clears the wagmi connection. # Hooks React hooks for authentication, wallet management, and UI control. :::note Try the hooks in our [Openfort playground](https://playground.openfort.io/). ::: ## Authentication | Hook | Description | |------|-------------| | [`useUser`](/docs/products/embedded-wallet/react/hooks/useUser) | Current user, auth flags, and access token | | [`useEmailAuth`](/docs/products/embedded-wallet/react/hooks/useEmailAuth) | Email/password authentication | | [`useEmailOtpAuth`](/docs/products/embedded-wallet/react/hooks/useEmailOtpAuth) | Email OTP (passwordless) authentication | | [`usePhoneOtpAuth`](/docs/products/embedded-wallet/react/hooks/usePhoneOtpAuth) | Phone OTP (SMS) authentication | | [`useOAuth`](/docs/products/embedded-wallet/react/hooks/useOAuth) | OAuth provider authentication | | [`useGuestAuth`](/docs/products/embedded-wallet/react/hooks/useGuestAuth) | Guest authentication | | [`useConnectWithSiwe`](/docs/products/embedded-wallet/react/hooks/useConnectWithSiwe) | SIWE wallet authentication (from `@openfort/react/wagmi`) | | [`useAuthCallback`](/docs/products/embedded-wallet/react/hooks/useAuthCallback) | OAuth and email verification callbacks | | [`useSignOut`](/docs/products/embedded-wallet/react/hooks/useSignOut) | Sign out and clear session | | [`useWalletAuth`](/docs/products/embedded-wallet/react/hooks/useWalletAuth) | Connect and link external wallets with SIWE (from `@openfort/react/wagmi`) | ## Wallets | Hook | Chain | Description | |------|-------|-------------| | [`useEthereumEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useEthereumEmbeddedWallet) | Ethereum | Address, chainId, provider, isConnected, plus create, setActive, export key. Accepts optional `options` with `chainId` and `recoveryParams`. | | [`useSolanaEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useSolanaEmbeddedWallet) | Solana | Embedded wallet: create, list, setActive, export key, cluster, rpcUrl. Accepts optional `options` with `cluster` (counterpart of Ethereum `chainId`) and `recoveryParams`. | ## Assets | Hook | Description | |------|-------------| | [`useEthereumWalletAssets`](/docs/products/embedded-wallet/react/hooks/useEthereumWalletAssets) | Multi-token asset inventory for Ethereum (from `@openfort/react/ethereum`) | ## Permissions (Ethereum only) | Hook | Description | |------|-------------| | [`useGrantPermissions`](/docs/products/embedded-wallet/react/hooks/useGrantPermissions) | Grant session key permissions (EIP-7715) | | [`useRevokePermissions`](/docs/products/embedded-wallet/react/hooks/useRevokePermissions) | Revoke session key permissions | | [`use7702Authorization`](/docs/products/embedded-wallet/react/hooks/use7702Authorization) | Sign EIP-7702 authorizations | ## UI | Hook | Description | |------|-------------| | [`useUI`](/docs/products/embedded-wallet/react/hooks/useUI) | Modal control: open, close, openProfile | ## Core | Hook | Description | |------|-------------| | [`useOpenfort`](/docs/products/embedded-wallet/react/hooks/useOpenfort) | Universal. Core SDK client: user, embeddedState, client, logout, updateUser, etc. | ## Utility These hooks are exported from `@openfort/react/wagmi` (Ethereum/Wagmi only). | Hook | Description | |------|-------------| | `useChains` | Access configured blockchain chains | | `useChainIsSupported` | Check if a chain ID is supported | ### useChains Access all blockchain chains configured in the Wagmi config. ```tsx import { useChains } from '@openfort/react/wagmi' function ChainSelector() { const chains = useChains() return ( ) } ``` ### useChainIsSupported Check if a specific chain ID is supported in the current configuration. ```tsx import { useChainIsSupported } from '@openfort/react/wagmi' function ChainStatus({ chainId }: { chainId?: number }) { const isSupported = useChainIsSupported(chainId) return {isSupported ? 'Supported' : 'Unsupported'} } ``` ## Types | Type | Description | |------|-------------| | `CreateEmbeddedWalletOptions` | Options for `create()` on both `useEthereumEmbeddedWallet` and `useSolanaEmbeddedWallet`. See [Create wallet](/docs/products/embedded-wallet/react/wallet/create). | | `CreateEmbeddedWalletResult` | Shape of `onSuccess` callback data: `{ account: EmbeddedAccount, error?: string }`. | | `OpenfortHookOptions` | Generic options for hooks: `onSuccess`, `onError`, `throwOnError`. Used by create, auth, and permissions hooks. See [Error handling](/docs/products/embedded-wallet/react/errors#openforthookoptions). | | `SetRecoveryOptions` | Shared options for `setRecovery()` on both embedded wallet hooks: `previousRecovery`, `newRecovery`. | ## Constants | Export | Description | |--------|-------------| | `embeddedWalletId` | Identifier for the Openfort embedded wallet ('xyz.openfort') | | `OPENFORT_VERSION` | Current SDK version string | :::note Imports are tree-shaken by entry point: `@openfort/react`, `@openfort/react/ethereum`, `@openfort/react/solana`, `@openfort/react/wagmi`. Only import from the subpath you need. ::: # `useGuestAuth` Create guest accounts for instant onboarding without requiring authentication. ## Usage ```tsx import { useGuestAuth } from '@openfort/react'; function GuestEntry() { const { signUpGuest, isLoading } = useGuestAuth(); const handleClick = async () => { const { user } = await signUpGuest(); if (user) console.log('Guest created:', user.id); }; return ; } ``` ## Return type ```ts type GuestAuthReturn = { signUpGuest(options?: GuestOptions): Promise isLoading: boolean isError: boolean isSuccess: boolean error?: OpenfortError | null } type GuestResult = { user?: User wallet?: UserWallet error?: OpenfortError } ``` ## Hook options Configure default behavior when initializing the hook. ```ts type UseGuestHookOptions = { // Log out the user if wallet recovery fails. Defaults to true. logoutOnError?: boolean // Automatically recover the wallet after authentication. Defaults to true. recoverWalletAutomatically?: boolean onSuccess?: (data: GuestResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` ## Parameters ### signUpGuest ```ts type GuestOptions = { // Overrides the hook-level default. logoutOnError?: boolean // Overrides the hook-level default. recoverWalletAutomatically?: boolean onSuccess?: (data: GuestResult) => void onError?: (error: OpenfortError) => void } ``` # `useEmailAuth` Handle email/password authentication: sign in, sign up, password reset, and email verification. ## Usage ```tsx import { useEmailAuth } from '@openfort/react'; function LoginForm() { const { signInEmail, signUpEmail, isLoading } = useEmailAuth(); const handleLogin = async (email: string, password: string) => { const { error, user, requiresEmailVerification } = await signInEmail({ email, password }); if (requiresEmailVerification) { // Show a message asking the user to verify their email. } }; return null; } ``` ## Return type ```ts type UseEmailAuthReturn = { signInEmail(options: SignInEmailOptions): Promise signUpEmail(options: SignUpEmailOptions): Promise verifyEmail(options: VerifyEmailOptions): Promise linkEmail(options: LinkEmailOptions): Promise requestResetPassword(options: RequestResetPasswordOptions): Promise resetPassword(options: ResetPasswordOptions): Promise reset(): void requiresEmailVerification: boolean isAwaitingInput: boolean isLoading: boolean isError: boolean isSuccess: boolean error?: OpenfortError | null } type EmailAuthResult = { user?: User wallet?: UserWallet requiresEmailVerification?: boolean error?: OpenfortError } ``` ## Hook options Configure default behavior when initializing the hook. ```ts type UseEmailHookOptions = { // Default redirect URL for email verification. emailVerificationRedirectTo?: string // Log out the user if wallet recovery fails. Defaults to true. logoutOnError?: boolean // Automatically recover the wallet after authentication. Defaults to true. recoverWalletAutomatically?: boolean onSuccess?: (data: EmailAuthResult | EmailVerificationResult) => void onError?: (error: OpenfortError) => void } ``` ## Parameters ### signInEmail ```ts type SignInEmailOptions = { email: string password: string emailVerificationRedirectTo?: string // Overrides the hook-level default. logoutOnError?: boolean // Overrides the hook-level default. recoverWalletAutomatically?: boolean onSuccess?: (data: EmailAuthResult) => void onError?: (error: OpenfortError) => void } ``` ### signUpEmail ```ts type SignUpEmailOptions = { email: string password: string name?: string emailVerificationRedirectTo?: string // Overrides the hook-level default. logoutOnError?: boolean // Overrides the hook-level default. recoverWalletAutomatically?: boolean onSuccess?: (data: EmailAuthResult) => void onError?: (error: OpenfortError) => void } ``` ### verifyEmail ```ts type VerifyEmailOptions = { email: string // Verification code from the email. state: string onSuccess?: (data: EmailVerificationResult) => void onError?: (error: OpenfortError) => void } ``` ### linkEmail Link an email to an existing account. A verification email will be sent to the provided email address. ```ts type LinkEmailOptions = { email: string emailVerificationRedirectTo?: string onSuccess?: (data: EmailAuthResult) => void onError?: (error: OpenfortError) => void } ``` ### requestResetPassword ```ts type RequestResetPasswordOptions = { email: string emailVerificationRedirectTo?: string onSuccess?: (data: EmailAuthResult) => void onError?: (error: OpenfortError) => void } ``` ### resetPassword ```ts type ResetPasswordOptions = { email: string password: string // Reset code from the email. state: string onSuccess?: (data: EmailAuthResult) => void onError?: (error: OpenfortError) => void } ``` # `useEmailOtpAuth` Authenticate users with email OTP (one-time password) for passwordless login. ## Usage ```tsx import { useEmailOtpAuth } from '@openfort/react'; function EmailOtpLogin() { const { requestEmailOtp, signInEmailOtp, isRequesting, isLoading } = useEmailOtpAuth(); const [email, setEmail] = useState(''); const [otp, setOtp] = useState(''); const [otpSent, setOtpSent] = useState(false); const handleRequestOtp = async () => { const { error } = await requestEmailOtp({ email }); if (!error) setOtpSent(true); }; const handleSignIn = async () => { const { user, error } = await signInEmailOtp({ email, otp }); if (user) console.log('Signed in:', user.id); }; return (
setEmail(e.target.value)} placeholder="Email" /> {otpSent && ( setOtp(e.target.value)} placeholder="Enter OTP" /> )} {!otpSent ? ( ) : ( )}
); } ``` ## Return type ```ts type EmailOtpAuthReturn = { requestEmailOtp(options: RequestEmailOtpOptions): Promise signInEmailOtp(options: LoginWithEmailOtpOptions): Promise reset(): void isRequesting: boolean isLoading: boolean isError: boolean isSuccess: boolean isAwaitingInput: boolean error?: OpenfortError | null } type EmailOtpAuthResult = { user?: User wallet?: UserWallet error?: OpenfortError } ``` ## Hook options Configure default behavior when initializing the hook. ```ts type UseEmailOtpHookOptions = { // Log out the user if wallet recovery fails. Defaults to true. logoutOnError?: boolean // Automatically recover the wallet after authentication. Defaults to true. recoverWalletAutomatically?: boolean onSuccess?: (data: EmailOtpAuthResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` ## Parameters ### requestEmailOtp Request an OTP code to be sent to the user's email. ```ts type RequestEmailOtpOptions = { email: string // Inherited from CreateWalletPostAuthOptions logoutOnError?: boolean recoverWalletAutomatically?: boolean // Inherited from OpenfortHookOptions onSuccess?: (data: EmailOtpAuthResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` ### signInEmailOtp Sign in with the email and OTP code. ```ts type LoginWithEmailOtpOptions = { email: string otp: string // Overrides the hook-level default. logoutOnError?: boolean // Overrides the hook-level default. recoverWalletAutomatically?: boolean onSuccess?: (data: EmailOtpAuthResult) => void onError?: (error: OpenfortError) => void } ``` ## Link an email to an existing user Unlike phone OTP which has a `linkPhoneOtp` function, email OTP doesn't include a link method. To add an email to an existing authenticated user, use [`useEmailAuth.linkEmail()`](/docs/products/embedded-wallet/react/hooks/useEmailAuth) instead. ## Related * [useEmailAuth](/docs/products/embedded-wallet/react/hooks/useEmailAuth) - Email/password authentication and email linking * [usePhoneOtpAuth](/docs/products/embedded-wallet/react/hooks/usePhoneOtpAuth) - Phone OTP authentication (includes phone linking) # `usePhoneOtpAuth` Authenticate users with phone OTP (SMS one-time password) for passwordless login. ## Usage ```tsx import { usePhoneOtpAuth } from '@openfort/react'; function PhoneOtpLogin() { const { requestPhoneOtp, logInWithPhoneOtp, isRequesting, isLoading } = usePhoneOtpAuth(); const [phoneNumber, setPhoneNumber] = useState(''); const [otp, setOtp] = useState(''); const [otpSent, setOtpSent] = useState(false); const handleRequestOtp = async () => { const { error } = await requestPhoneOtp({ phoneNumber }); if (!error) setOtpSent(true); }; const handleSignIn = async () => { const { user, error } = await logInWithPhoneOtp({ phoneNumber, otp }); if (user) console.log('Signed in:', user.id); }; return (
setPhoneNumber(e.target.value)} placeholder="+1234567890" /> {otpSent && ( setOtp(e.target.value)} placeholder="Enter OTP" /> )} {!otpSent ? ( ) : ( )}
); } ``` ## Return type ```ts type PhoneOtpAuthReturn = { requestPhoneOtp(options: RequestPhoneOtpOptions): Promise logInWithPhoneOtp(options: LoginWithPhoneOtpOptions): Promise linkPhoneOtp(options: LoginWithPhoneOtpOptions): Promise // Same type as logInWithPhoneOtp reset(): void isRequesting: boolean isLoading: boolean isError: boolean isSuccess: boolean isAwaitingInput: boolean error?: OpenfortError | null } type PhoneAuthResult = { user?: User wallet?: UserWallet error?: OpenfortError } ``` ## Hook options Configure default behavior when initializing the hook. ```ts type UsePhoneHookOptions = { // Log out the user if wallet recovery fails. Defaults to true. logoutOnError?: boolean // Automatically recover the wallet after authentication. Defaults to true. recoverWalletAutomatically?: boolean onSuccess?: (data: PhoneAuthResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` ## Parameters ### requestPhoneOtp Request an OTP code to be sent to the user's phone via SMS. ```ts type RequestPhoneOtpOptions = { phoneNumber: string // Inherited from CreateWalletPostAuthOptions logoutOnError?: boolean recoverWalletAutomatically?: boolean // Inherited from OpenfortHookOptions onSuccess?: (data: PhoneAuthResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` ### logInWithPhoneOtp Sign in with the phone number and OTP code. ```ts type LoginWithPhoneOtpOptions = { phoneNumber: string otp: string // Overrides the hook-level default. logoutOnError?: boolean // Overrides the hook-level default. recoverWalletAutomatically?: boolean onSuccess?: (data: PhoneAuthResult) => void onError?: (error: OpenfortError) => void } ``` ### linkPhoneOtp Link a phone number to an existing authenticated user. The user must already be authenticated (for example, via email or OAuth) before linking a phone number. `linkPhoneOtp` uses the same `LoginWithPhoneOtpOptions` type as `logInWithPhoneOtp`. Wallet recovery fields (`logoutOnError`, `recoverWalletAutomatically`) are present in the type but ignored by `linkPhoneOtp`. Note: Unlike `logInWithPhoneOtp`, the `linkPhoneOtp` function does not create or recover a wallet—it only links the phone number to the existing user account. #### Link phone example ```tsx import { usePhoneOtpAuth, useUser } from '@openfort/react'; function LinkPhoneNumber() { const { user } = useUser(); const { requestPhoneOtp, linkPhoneOtp, isRequesting, isLoading } = usePhoneOtpAuth(); const [phoneNumber, setPhoneNumber] = useState(''); const [otp, setOtp] = useState(''); const [otpSent, setOtpSent] = useState(false); if (!user) return

Please sign in first

; const handleRequestOtp = async () => { const { error } = await requestPhoneOtp({ phoneNumber }); if (!error) setOtpSent(true); }; const handleLinkPhone = async () => { const { user, error } = await linkPhoneOtp({ phoneNumber, otp }); if (user) console.log('Phone linked successfully'); }; return (
setPhoneNumber(e.target.value)} placeholder="+1234567890" /> {otpSent && ( setOtp(e.target.value)} placeholder="Enter OTP" /> )} {!otpSent ? ( ) : ( )}
); } ``` ## Related * [useEmailOtpAuth](/docs/products/embedded-wallet/react/hooks/useEmailOtpAuth) - Email OTP authentication * [useEmailAuth](/docs/products/embedded-wallet/react/hooks/useEmailAuth) - Email/password authentication # `useOAuth` Authenticate with third-party providers like Google, Apple, Facebook, Discord, and Twitter. ## Usage ```tsx import { OAuthProvider, useOAuth } from '@openfort/react'; function SocialLogin() { const { initOAuth, linkOauth, isLoading } = useOAuth(); const loginWithGoogle = () => initOAuth({ provider: OAuthProvider.GOOGLE }); const linkDiscord = () => linkOauth({ provider: OAuthProvider.DISCORD }); return null; } ``` ## Reading the wallet after sign-in `useOAuth` is fire-and-forget — it authenticates the user, then returns. The wallet appears reactively on the chain hook a moment later, once the SDK has recovered or created it. * **Ethereum:** read `address` / `isConnected` from [`useEthereumEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useEthereumEmbeddedWallet). * **Solana:** read `address` / `isConnected` from [`useSolanaEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useSolanaEmbeddedWallet). * Both hooks share the same connection-state shape (`isConnected`, `isConnecting`, `isDisconnected`, `isReconnecting`); use the `status` discriminated union when you need granular states like `'fetching-wallets'`, `'creating'`, or `'needs-recovery'`. * Recovery is controlled by `recoverWalletAutomatically` (default `true`) and uses the `walletConfig.chainType` set on `OpenfortProvider`. **The default `chainType` is `EVM`** — Solana apps must set `chainType: ChainTypeEnum.SVM`, or the wallet hook will stay disconnected after OAuth. See the [social authentication overview](/docs/products/embedded-wallet/react/auth#using-social-authentication) for end-to-end snippets, or the [headless EVM](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/headless) and [headless Solana](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/solana-headless) quickstart apps for runnable references. ## Return type ```ts type UseOAuthReturn = { initOAuth(options: InitOAuthOptions): Promise linkOauth(options: InitOAuthOptions): Promise storeCredentials(options: StoreCredentialsOptions): Promise isLoading: boolean isError: boolean isSuccess: boolean error?: OpenfortError | null } type InitOAuthReturnType = { error?: OpenfortError } type StoreCredentialsResult = { user?: User wallet?: UserWallet error?: OpenfortError } ``` ## Hook options Configure default behavior when initializing the hook. ```ts type UseOAuthOptions = { // Default OAuth redirect URL. redirectTo?: string // Log out the user if wallet recovery fails. Defaults to true. logoutOnError?: boolean // Automatically recover the wallet after authentication. Defaults to true. recoverWalletAutomatically?: boolean onSuccess?: (data: StoreCredentialsResult | InitOAuthReturnType) => void onError?: (error: OpenfortError) => void } ``` ## Parameters ### initOAuth / linkOauth ```ts type InitOAuthOptions = { provider: OAuthProvider redirectTo?: string onSuccess?: (data: InitOAuthReturnType) => void onError?: (error: OpenfortError) => void } enum OAuthProvider { GOOGLE = "google", FACEBOOK = "facebook", DISCORD = "discord", TWITTER = "twitter", EPIC_GAMES = "epic_games", APPLE = "apple", LINE = "line", } ``` ### storeCredentials Store OAuth credentials manually for custom OAuth flows. ```ts type StoreCredentialsOptions = { userId: string token: string // Overrides the hook-level default. logoutOnError?: boolean // Overrides the hook-level default. recoverWalletAutomatically?: boolean onSuccess?: (data: StoreCredentialsResult) => void onError?: (error: OpenfortError) => void } ``` # `useConnectWithSiwe` :::info **Ethereum only.** Requires [wagmi setup](/docs/products/embedded-wallet/react/hooks/useWalletAuth). ::: Connect to Openfort using Sign-In with Ethereum (SIWE) after a wallet has been connected via wagmi. Use for custom wallet connection flows. ## Usage ```tsx import { useConnect } from 'wagmi'; import { useConnectWithSiwe } from '@openfort/react/wagmi'; function CustomWalletConnect() { const { connect, connectors } = useConnect(); const { connectWithSiwe } = useConnectWithSiwe(); const handleConnect = async () => { // First connect the wallet using wagmi await connect({ connector: connectors[0] }); // Then authenticate with SIWE await connectWithSiwe({ onConnect: () => console.log('Connected!'), onError: (message) => console.error('Error:', message), }); }; return ; } ``` ## Return type ```ts type UseConnectWithSiweReturn = { connectWithSiwe: ConnectWithSiwe } type ConnectWithSiwe = (options?: SiweConnectOptions) => Promise type SiweConnectOptions = { // Override the connector type (defaults to the connected wallet's type). connectorType?: string // Override the wallet client type (defaults to the connected wallet's ID). walletClientType?: string // Specify a specific address to use for SIWE authentication. address?: `0x${string}` // Callback when SIWE authentication fails. onError?: (error: string, openfortError?: OpenfortError) => void // Callback when SIWE authentication succeeds. onConnect?: () => void // Link wallet to existing account instead of creating new one. // Defaults to true if user is already authenticated. link?: boolean } ``` ## Behavior * If a user is already authenticated, the wallet will be linked to their existing account * If no user is authenticated, a new account will be created using the wallet * The hook requires a wallet to be connected via wagmi before calling * Automatically handles chain switching if the account chain differs from the current chain ## Related * [useWalletAuth](/docs/products/embedded-wallet/react/hooks/useWalletAuth) — Setup with wagmi # `useWalletAuth` :::warning **External wallet (SIWE) connection is Ethereum-only.** Solana has no external wallet support via the wagmi bridge. For Solana, use only Openfort embedded wallets. ::: Use [@openfort/react/wagmi](/docs/products/embedded-wallet/react) for React apps that need external wallet support (MetaMask, WalletConnect, Coinbase Wallet, etc.) alongside the embedded wallet. ::::steps ### Install dependencies ```sh pnpm add @openfort/react @tanstack/react-query viem wagmi ``` ### Provider setup `OpenfortWagmiBridge` (from `@openfort/react/wagmi`) must sit inside `WagmiProvider` and wrap `OpenfortProvider`. Wagmi uses TanStack Query for caching, so wrap with `QueryClientProvider`. Nesting order: `QueryClientProvider` → `WagmiProvider` → `OpenfortWagmiBridge` → `OpenfortProvider` → your app ```tsx [Providers.tsx] import { AuthProvider, OpenfortProvider, RecoveryMethod } from "@openfort/react" import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { WagmiProvider, createConfig } from "wagmi" import { baseSepolia } from "viem/chains" const config = createConfig( getDefaultConfig({ appName: "Openfort Demo App", chains: [baseSepolia], walletConnectProjectId: "YOUR_WALLETCONNECT_PROJECT_ID", }) ) const queryClient = new QueryClient() export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ) } ```
WalletConnect support If using WalletConnect, add a project ID from the [WalletConnect dashboard](https://cloud.walletconnect.com): ```tsx const config = createConfig( getDefaultConfig({ appName: "Openfort demo", chains: [baseSepolia], ssr: true, walletConnectProjectId: "YOUR_WALLET_CONNECT_PROJECT_ID", }) ) ```
### @openfort/react/wagmi API reference **OpenfortWagmiBridge** — React component that bridges wagmi wallet state into Openfort. Must be inside `WagmiProvider` and wrap `OpenfortProvider`. No props except `children`. :::info[How the bridge works] `OpenfortWagmiBridge` registers the Openfort embedded wallet as a native wagmi connector. Once it's in the tree, the embedded wallet and any connected external wallet (MetaMask, WalletConnect, etc.) both work through the same wagmi hooks — `useSendTransaction`, `useWriteContract`, `useBalance`, `useSignMessage`, `useWalletClient`. One API, regardless of wallet type. No Openfort-specific wrappers needed. ::: **getDefaultConfig(opts)** — Returns wagmi `CreateConfigParameters`. Params: `appName`, `appIcon?`, `appDescription?`, `appUrl?`, `walletConnectProjectId?`, `coinbaseWalletPreference?`, `chains` (default: mainnet, polygon, optimism, arbitrum), plus any `CreateConfigParameters` overrides. Uses `getDefaultConnectors` for connectors when `connectors` not provided. **getDefaultConnectors(opts)** — Returns `CreateConnectorFn[]`. Params: `app: { name, icon?, description?, url? }`, `walletConnectProjectId?`, `coinbaseWalletPreference?`. Includes Safe (in iframes), Injected (MetaMask), Coinbase Wallet, WalletConnect (if project ID provided). **useWalletAuth(hookOptions?)** — Returns `{ availableWallets, connectWallet, linkWallet, walletConnectingTo, isLoading, isError, isSuccess, error }`. Optional `hookOptions` (default `{}`) supports `onSuccess`, `onError`, `throwOnError` like other auth hooks. Use `connectWallet(connectorId, callbacks?)` or `linkWallet(connectorId, callbacks?)` for the connect/link flow. See [SIWE connection flow](#usewalletauth-siwe-connection-flow) below.

useWalletAuth — SIWE connection flow

`useWalletAuth` from `@openfort/react/wagmi` is the recommended way to connect external wallets with Sign-In with Ethereum. It handles connect + SIWE sign-in in one flow. ```tsx import { useWalletAuth } from "@openfort/react/wagmi" function WalletConnectList() { const { availableWallets, connectWallet, linkWallet, walletConnectingTo, isLoading, isError, error, } = useWalletAuth({ onSuccess: () => console.log("Connected"), onError: (err) => console.error(err), }) return (
{walletConnectingTo &&

Connecting to {walletConnectingTo}…

} {availableWallets.map((w) => ( ))}
) } ``` | Parameter | Type | Description | |-----------|------|-------------| | `hookOptions` | `OpenfortHookOptions?` | Optional. `onSuccess`, `onError`, `throwOnError`. Default `{}`. | | Return | Type | Description | |--------|------|-------------| | `availableWallets` | `AvailableWallet[]` | List of connectable wallets (excludes Openfort embedded). See [AvailableWallet](#availablewallet-type) below. | | `connectWallet(connectorId, callbacks?)` | `(string, WalletAuthCallbacks?) => Promise` | Connect + SIWE sign-in (new session) | | `linkWallet(connectorId, callbacks?)` | `(string, WalletAuthCallbacks?) => Promise` | Connect + SIWE link to existing account | | `walletConnectingTo` | `string \| null` | Connector id currently in progress, or `null` when idle. Use for “Connecting to MetaMask…”-style UI. | | `isLoading` | `boolean` | `true` while a connect/link is in progress. | | `isError` | `boolean` | `true` when the last connect/link failed. | | `isSuccess` | `boolean` | `true` when the last connect/link succeeded. | | `error` | `OpenfortError \| null` | Error from the last failed connect/link, or `null`. | **WalletAuthCallbacks** (per call) — `{ onConnect?: () => void, onError?: (error: string, openfortError?: OpenfortError) => void }`. Called in addition to `hookOptions.onSuccess` / `hookOptions.onError`. On success: hook sets status to `'success'`, calls `onSuccess(hookOptions, {})`, then the per-call `onConnect`. On error: hook sets status to `'error'`, calls `onError(hookOptions, error)`, then the per-call `onError(message, openfortError)`. String errors are normalized to `OpenfortError` for `hookOptions.onError` and `error`. If the app is already connected and the bridge disconnects before connecting the new wallet, state is reset and both hook and per-call error callbacks are invoked. :::info[API compatibility] Existing code that only uses `availableWallets`, `connectWallet`, and `linkWallet` continues to work. New code can optionally use `hookOptions`, `walletConnectingTo`, and `isLoading` / `isError` / `isSuccess` / `error` for loading and error UI. :::

AvailableWallet type

```ts type AvailableWallet = { id: string name: string icon?: string connector: OpenfortEthereumBridgeConnector // Openfort bridge connector for the external wallet } ``` `connectWallet` creates a new session; `linkWallet` links the external wallet to the current user's account. ### Using wagmi hooks for transactions When using `OpenfortWagmiBridge`, wagmi's native hooks work with the Openfort embedded wallet. Example with `useWriteContract` for an ERC-20 transfer: ```tsx import { useWriteContract } from "wagmi" import { parseUnits } from "viem" const usdcAbi = [ { name: "transfer", type: "function", stateMutability: "nonpayable", inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }], outputs: [{ name: "success", type: "bool" }] }, ] as const function TransferButton() { const { writeContract, data: hash, isPending } = useWriteContract() return ( ) } ``` `useSendTransaction`, `useAccount`, `useBalance`, `useSignMessage`, and `useWalletClient` also work through the bridge. ### Using wallet\_sendCalls directly Use `useWalletClient` from wagmi for direct `wallet_sendCalls` RPC: ```tsx import { useWalletClient } from "wagmi" import { encodeFunctionData, parseUnits } from "viem" const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" const usdcAbi = [ { name: "transfer", type: "function", stateMutability: "nonpayable", inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }], outputs: [{ name: "success", type: "bool" }] }, ] as const function useSendUsdc() { const { data: walletClient } = useWalletClient() return async (to: `0x${string}`, amount: string) => { if (!walletClient?.chain || !walletClient.account) throw new Error("Wallet client not ready") const data = encodeFunctionData({ abi: usdcAbi, functionName: "transfer", args: [to, parseUnits(amount, 6)] }) return walletClient.request({ method: "wallet_sendCalls", params: [{ version: "1.0", chainId: `0x${walletClient.chain.id.toString(16)}`, from: walletClient.account.address, calls: [{ to: usdcAddress, value: "0x0", data }], }], }) } } ``` ### Gas sponsorship Wire `ethereumFeeSponsorshipId` into `walletConfig.ethereum`. Create policies at [Dashboard → Gas sponsorship](https://dashboard.openfort.io/policies): ```tsx walletConfig={{ shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY", ethereum: { chainId: 84532, ethereumFeeSponsorshipId: "pol_...", }, }} ``` ### EOA wallets on a custom chain For EOA wallets on a custom chain, define the chain with `defineChain` and pass it to `getDefaultConfig`. Set `accountType: AccountTypeEnum.EOA` in `walletConfig.ethereum` and `enforceSupportedChains: false` in `uiConfig`: ```tsx [Providers.tsx] import { defineChain } from "viem" import { WagmiProvider, createConfig, http } from "wagmi" import { AccountTypeEnum, AuthProvider, OpenfortProvider, RecoveryMethod } from "@openfort/react" import { getDefaultConfig, OpenfortWagmiBridge } from "@openfort/react/wagmi" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" const customMainnet = defineChain({ id: 12345, name: "Custom Mainnet", network: "custom", nativeCurrency: { name: "Cust", symbol: "CUST", decimals: 18 }, rpcUrls: { default: { http: ["https://rpc.custom.xyz"] }, }, blockExplorers: { default: { name: "customscan", url: "https://customscan.io" }, }, testnet: true, }) const wagmiConfig = createConfig( getDefaultConfig({ appName: "Your App Name", chains: [customMainnet], transports: { [customMainnet.id]: http() }, walletConnectProjectId: "YOUR_WALLETCONNECT_PROJECT_ID", }) ) const queryClient = new QueryClient() export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ### Ethereum-only UI options that require wagmi * `AuthProvider.WALLET` — external wallet auth via SIWE * `enforceSupportedChains` — enforces wagmi's chain list * `walletConnectCTA` / `walletConnectName` — WalletConnect UI options * `truncateLongENSAddress` — ENS display * `linkWalletOnSignUp` — link external wallet on signup :::: ## Related * [Quickstart](/docs/products/embedded-wallet/react) — Provider setup with wagmi * [useConnectWithSiwe](/docs/products/embedded-wallet/react/hooks/useConnectWithSiwe) — Lower-level SIWE hook * [Wallet actions](/docs/products/embedded-wallet/react/wallet/actions) — Send transactions with wagmi hooks # `useAuthCallback` Handle OAuth and email verification redirects by parsing query parameters automatically. ## Usage ```tsx import { useAuthCallback } from '@openfort/react'; function CallbackPage() { const { provider, email, isLoading, isSuccess, error } = useAuthCallback({ enabled: true, onSuccess: (result) => { if (result.type === 'storeCredentials') { // Handle successful OAuth authentication. } else if (result.type === 'verifyEmail') { // Handle successful email verification. } }, }); if (isLoading) return
Processing...
; if (isSuccess) return
Success!
; return null; } ``` ## Return type ```ts type UseAuthCallbackReturn = { // Detected auth provider from URL. provider: UIAuthProvider | null // Email from verification URL. email: string | null verifyEmail(options: VerifyEmailOptions): Promise storeCredentials(options: StoreCredentialsOptions): Promise isLoading: boolean isError: boolean isSuccess: boolean error?: OpenfortError | null } type CallbackResult = | (StoreCredentialsResult & { type: 'storeCredentials' }) | (EmailVerificationResult & { type: 'verifyEmail' }) enum UIAuthProvider { GOOGLE = 'google', TWITTER = 'twitter', X = 'twitter', // alias for TWITTER FACEBOOK = 'facebook', DISCORD = 'discord', APPLE = 'apple', EMAIL_PASSWORD = 'emailPassword', EMAIL_OTP = 'emailOtp', PHONE = 'phone', WALLET = 'wallet', GUEST = 'guest', } ``` ## Parameters ### Hook options ```ts type UseAuthCallbackOptions = { // Automatically process URL parameters. Defaults to true. enabled?: boolean // Log out the user if wallet recovery fails. Defaults to true. logoutOnError?: boolean // Automatically recover the wallet after authentication. Defaults to true. recoverWalletAutomatically?: boolean onSuccess?: (data: CallbackResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` # `useSignOut` Sign out users and reset the provider state. ## Usage ```tsx import { useSignOut } from '@openfort/react'; function LogoutButton() { const { signOut, isLoading } = useSignOut(); return ( ); } ``` ## Return type ```ts type SignOutReturn = { signOut(options?: SignOutOptions): Promise<{}> isLoading: boolean isError: boolean isSuccess: boolean error: OpenfortError | null | undefined } type SignOutOptions = { onSuccess?: (data: {}) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` # `useUser` :::info If using Next.js App Router, add `"use client"` at the top of the file. ::: Access the current authenticated user and refresh authentication tokens. ## Usage ```tsx import { useUser } from '@openfort/react'; function Profile() { const { user, linkedAccounts, isAuthenticated, isConnected, getAccessToken } = useUser(); if (!isConnected) return ; return (

User ID: {user?.id}

Email: {user?.email}

Linked accounts: {linkedAccounts?.length}

); } ``` ## Return type ```ts type UseUserReturn = { // Current user object (from @openfort/openfort-js). user: User | null // Linked accounts associated with the user. linkedAccounts: UserAccount[] // True while initial auth state is being resolved. isLoading: boolean // Whether the user is logged in. isAuthenticated: boolean // Whether the user is authenticated and wallet is ready. isConnected: boolean // Returns the current access token. getAccessToken(): Promise // Forces a token refresh. validateAndRefreshToken(): Promise } // User type is imported from @openfort/openfort-js type User = { id: string email?: string phoneNumber?: string createdAt?: string // ... additional user properties } // UserAccount is aliased from ListAccountsGet200ResponseInner in @openfort/openfort-js type UserAccount = { provider: string createdAt: number updatedAt: number accountId: string chainType: string chainId: string connectorType: string walletClientType: string } ``` # `useEthereumEmbeddedWallet` :::info If using Next.js App Router, add `"use client"` at the top of the file. ::: `useEthereumEmbeddedWallet` is your Ethereum wallet hook — it combines wallet state, connection status, and provider access with wallet management. If you're coming from wagmi, think of it as `useAccount` + `useWalletClient` + wallet CRUD in one hook. It accepts an optional `options` argument with `chainId` and `recoveryParams`. For parity, the Solana hook `useSolanaEmbeddedWallet(options?)` uses `options.cluster` (network override) and `options.recoveryParams`. ## Day-to-day properties Properties you read on every render: address, chain, connection status, and provider. | Property | Type | Description | |----------|------|-------------| | `address` | `string \| undefined` | Current wallet address (hex). | | `chainId` | `number \| undefined` | Current chain ID. | | `embeddedWalletId` | `string \| undefined` | Set when connected; use for connector checks (e.g. vs wagmi `connector?.id`). | | `isConnected` | `boolean` | Whether a wallet is connected. | | `isConnecting` | `boolean` | True while connecting or transitioning. | | `isDisconnected` | `boolean` | True when disconnected. | | `isReconnecting` | `boolean` | True when reconnecting after loss of connection. | | `provider` | `OpenfortEmbeddedEthereumWalletProvider \| undefined` | EIP-1193 provider for signing. Use with viem's `custom(provider)` when not using wagmi. | ### Connected wallet state properties The hook exposes this connection-state shape (subset of the full return type). Shared with `useSolanaEmbeddedWallet`: ```ts export type ConnectedWalletState = { /** embeddedWalletId when connected. */ embeddedWalletId?: string /** True when currently connected. */ isConnected: boolean /** True when actively connecting or transitioning. */ isConnecting: boolean /** True when disconnected. */ isDisconnected: boolean /** True when reconnecting after loss of connection. */ isReconnecting: boolean } ``` ## Wallet management operations Operations used during onboarding or when switching wallets. | Property | Type | Description | |----------|------|-------------| | `create` | `(options?) => Promise` | Create a new embedded wallet. | | `setActive` | `(options) => Promise` | Switch the active wallet. | | `wallets` | `ConnectedEmbeddedEthereumWallet[]` | List of user's embedded wallets. | | `activeWallet` | `ConnectedEmbeddedEthereumWallet \| null` | Currently active wallet. | | `exportPrivateKey` | `() => Promise` | Export the active wallet's private key. | | `setRecovery` | `(options) => Promise` | Change recovery method. | ## Usage **Read address and connection status** (like `useAccount`): ```tsx import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" function AccountInfo() { const { address, chainId, isConnected } = useEthereumEmbeddedWallet() if (!isConnected) return
Not connected
return
{address} on chain {chainId}
} ``` **Manage wallets** (create, list, switch): ```tsx import { useEthereumEmbeddedWallet } from "@openfort/react/ethereum" function WalletList() { const { wallets, activeWallet, setActive } = useEthereumEmbeddedWallet() return (
{wallets.map((w) => ( ))}
) } ``` ## Return type The hook returns `EthereumWalletState` (`EthereumWalletStateBase & WalletDerived & ConnectedWalletState`). All status variants include the actions and the derived/connected fields; the status-specific fields below vary by `status`. ```ts type EthereumWalletState = { // --- Actions (all statuses) --- create(options?: CreateEmbeddedWalletOptions): Promise wallets: ConnectedEmbeddedEthereumWallet[] setActive(options: SetActiveEthereumWalletOptions): Promise setRecovery(options: SetRecoveryOptions): Promise exportPrivateKey(): Promise // --- WalletDerived --- isLoading: boolean isError: boolean isSuccess: boolean // --- ConnectedWalletState --- embeddedWalletId?: string isConnected: boolean isConnecting: boolean isDisconnected: boolean isReconnecting: boolean // --- Discriminated by status (union) --- status: 'disconnected' | 'fetching-wallets' | 'connecting' | 'reconnecting' | 'creating' | 'needs-recovery' | 'connected' | 'error' activeWallet: ConnectedEmbeddedEthereumWallet | null address?: `0x${string}` chainId?: number displayAddress?: string provider?: OpenfortEmbeddedEthereumWalletProvider error?: string } ``` Status-specific shape: * **`disconnected`** | **`fetching-wallets`** | **`creating`**: `activeWallet: null`; no `address`, `chainId`, `displayAddress`, `provider`, `error`. * **`connecting`** | **`reconnecting`**: `activeWallet` set; `address`, `displayAddress`; `chainId` optional. * **`needs-recovery`**: `activeWallet` set; `address`, `chainId`, `displayAddress` optional. * **`connected`**: `activeWallet` set; `address`, `chainId`, `displayAddress`, `provider` all set. * **`error`**: `activeWallet` set or null; `error: string`; `address`, `chainId`, `displayAddress` optional. `ConnectedEmbeddedEthereumWallet` (items in `wallets` / `activeWallet`): ````ts type SimpleAccount = { id: string; chainId?: number } type ConnectedEmbeddedEthereumWallet = { id: string address: `0x${string}` ownerAddress?: string implementationType?: string chainType: typeof ChainTypeEnum.EVM walletIndex: number recoveryMethod?: RecoveryMethod getProvider(): Promise isAvailable: boolean isActive: boolean isConnecting: boolean accounts: SimpleAccount[] connectorType?: string walletClientType?: string accountId?: string accountType?: AccountTypeEnum createdAt?: number salt?: string } ``` `WalletDerived` adds `isLoading`, `isError`, `isSuccess`; `ConnectedWalletState` adds `embeddedWalletId`, `isConnected`, `isConnecting`, `isDisconnected`, `isReconnecting`. ## Hook options ```ts export type UseEmbeddedEthereumWalletOptions = { chainId?: number recoveryParams?: RecoveryParams } ```` ## Parameters ### create ```ts export type CreateEmbeddedWalletOptions = { chainId?: number recoveryMethod?: RecoveryMethod passkeyId?: string password?: string otpCode?: string accountType?: AccountTypeEnum feeSponsorshipId?: string } & OpenfortHookOptions ``` `CreateEmbeddedWalletOptions` and `CreateEmbeddedWalletResult` are from `@openfort/react` shared types; `OpenfortHookOptions` adds `onSuccess`, `onError`, `throwOnError`. ### setActive ```ts export type SetActiveEmbeddedWalletOptionsBase = { recoveryParams?: RecoveryParams recoveryMethod?: RecoveryMethod passkeyId?: string password?: string otpCode?: string } export type SetActiveEthereumWalletOptions = SetActiveEmbeddedWalletOptionsBase & { address: `0x${string}` chainId?: number } ``` ### setRecovery ```ts export type SetRecoveryOptions = { previousRecovery: RecoveryParams newRecovery: RecoveryParams } ``` ## Embedded only / external wallet `useEthereumEmbeddedWallet` is for Openfort embedded wallets only. External wallet state comes from wagmi — use `useAccount()`, `useConnect()`, and `useDisconnect()`, and check the connector (e.g. `connector?.id !== embeddedWalletId`) to detect or switch to an external wallet. Apps that want a single active account (embedded or external) or “prefer Openfort” should implement that in the app using `setActive` / `setActiveEmbeddedAddress`, wagmi’s disconnect/connect, and connector checks; the SDK does not auto-disconnect external wallets. ## Related * [Wallet actions](/docs/products/embedded-wallet/react/wallet/actions) — Send transactions with wagmi * [Create wallet](/docs/products/embedded-wallet/react/wallet/create) * [Active wallet](/docs/products/embedded-wallet/react/wallet/active-wallet) # `useSolanaEmbeddedWallet` :::info If using Next.js App Router, add `"use client"` at the top of the file. ::: Manage Solana embedded wallets: create, list, set active, set recovery method, and export private key. The hook accepts an optional `options` argument of type `UseEmbeddedSolanaWalletOptions`; when `options.cluster` is set, the returned `cluster` and `rpcUrl` are derived from it (with `rpcUrl` resolved via the SDK’s shared RPC helper when there is no matching context). ## Day-to-day properties | Property | Type | Description | |----------|------|-------------| | `address` | `string \| undefined` | Current wallet address (base58). | | `cluster` | `SolanaCluster \| undefined` | Current cluster; when `options.cluster` is passed to the hook, this reflects that override. | | `embeddedWalletId` | `string \| undefined` | Set when connected (this hook is embedded-only). | | `isConnected` | `boolean` | True when currently connected. | | `isConnecting` | `boolean` | True when actively connecting or transitioning. | | `isDisconnected` | `boolean` | True when disconnected. | | `isReconnecting` | `boolean` | True when reconnecting after loss of connection. | | `provider` | `OpenfortEmbeddedSolanaWalletProvider \| undefined` | Provider for signing. | | `rpcUrl` | `string \| undefined` | RPC URL for the current cluster; when `options.cluster` is passed, resolved via the SDK’s default RPC helper (see [Default Solana RPC URLs](#default-solana-rpc-urls)). | ### Connected wallet state properties The hook exposes this connection-state shape (subset of the full return type). Shared with `useEthereumEmbeddedWallet`: ```ts export type ConnectedWalletState = { /** embeddedWalletId when connected (this hook is embedded-only). */ embeddedWalletId?: string /** True when currently connected. */ isConnected: boolean /** True when actively connecting or transitioning. */ isConnecting: boolean /** True when disconnected. */ isDisconnected: boolean /** True when reconnecting after loss of connection. */ isReconnecting: boolean } ``` ## Usage **Read the active wallet after sign-in** — the most common case. Gate on `isConnected` so you don't render against a half-initialized wallet (mirrors `useEthereumEmbeddedWallet`): ```tsx import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function ActiveWallet() { const { isConnected, address, activeWallet, provider } = useSolanaEmbeddedWallet() if (!isConnected) return null // safe to use address / activeWallet / provider here return

Wallet: {address}

} ``` For granular UI states (loading, recovery prompts, errors) read the `status` discriminated union instead — see [Return type](#return-type). **List and switch wallets** (cluster from provider/context): ```tsx import { useSolanaEmbeddedWallet } from "@openfort/react/solana" function WalletList() { const { wallets, activeWallet, setActive, create } = useSolanaEmbeddedWallet() return (
{wallets.map((w) => ( ))}
) } ``` **With options** (parity with Ethereum’s `useEthereumEmbeddedWallet({ chainId: 1 })`): pass `cluster` to override the cluster from context, and optionally `recoveryParams` for wallet access: ```tsx const { cluster, rpcUrl, activeWallet } = useSolanaEmbeddedWallet({ cluster: 'mainnet-beta' }) // Or with recovery params const { cluster, rpcUrl } = useSolanaEmbeddedWallet({ cluster: 'devnet', recoveryParams: { ... } }) ``` ## Return type The hook returns `SolanaWalletState` (`SolanaWalletStateBase & SolanaWalletDerived & ConnectedWalletState`). All status variants include the actions and the derived/connected fields; the status-specific fields below vary by `status`. ```ts type SolanaCluster = 'mainnet-beta' | 'devnet' | 'testnet' type SolanaWalletState = { // --- Actions (all statuses) --- create(options?: CreateEmbeddedWalletOptions): Promise wallets: ConnectedEmbeddedSolanaWallet[] setActive(options: SetActiveSolanaWalletOptions): Promise setRecovery(options: SetRecoveryOptions): Promise exportPrivateKey(): Promise // --- SolanaWalletDerived (WalletDerived + rpcUrl) --- isLoading: boolean isError: boolean isSuccess: boolean rpcUrl?: string // --- ConnectedWalletState --- embeddedWalletId?: string isConnected: boolean isConnecting: boolean isDisconnected: boolean isReconnecting: boolean // --- Discriminated by status (union) --- status: 'disconnected' | 'fetching-wallets' | 'connecting' | 'reconnecting' | 'creating' | 'needs-recovery' | 'connected' | 'error' activeWallet: ConnectedEmbeddedSolanaWallet | null address?: string cluster?: SolanaCluster displayAddress?: string provider?: OpenfortEmbeddedSolanaWalletProvider error?: string } ``` Status-specific shape: * **`disconnected`** | **`fetching-wallets`** | **`creating`**: `activeWallet: null`; no `address`, `cluster`, `displayAddress`, `provider`, `error`. * **`connecting`** | **`reconnecting`**: `activeWallet` set; `address`, `cluster`, `displayAddress` set. * **`needs-recovery`**: `activeWallet` set; `address`, `cluster`, `displayAddress` optional. * **`connected`**: `activeWallet` set; `address`, `cluster`, `displayAddress`, `provider` all set. * **`error`**: `activeWallet` set or null; `error: string`; `address`, `cluster`, `displayAddress` optional. `ConnectedEmbeddedSolanaWallet` (items in `wallets` / `activeWallet`): ```ts type ConnectedEmbeddedSolanaWallet = { id: string address: string chainType: typeof ChainTypeEnum.SVM walletIndex: number recoveryMethod?: RecoveryMethod getProvider(): Promise } ``` ## Hook options ```ts type UseEmbeddedSolanaWalletOptions = { /** Optional Solana cluster override (mirrors Ethereum’s chainId in useEthereumEmbeddedWallet). When set, overrides the cluster from SolanaContext; returned cluster and rpcUrl are derived from this. */ cluster?: SolanaCluster /** Optional recovery params for wallet access. */ recoveryParams?: RecoveryParams } ``` :::info[Parity with Ethereum] **Ethereum:** `useEthereumEmbeddedWallet(options?)` with `options.chainId` and `options.recoveryParams`.\ **Solana:** `useSolanaEmbeddedWallet(options?)` with `options.cluster` and `options.recoveryParams`.\ `cluster` is the Solana counterpart of `chainId` for choosing the network from the hook. ::: ### Default Solana RPC URLs Default Solana RPC URLs are not defined in the hook; they live in the SDK as a single source of truth in `utils/rpc`: * **`getDefaultSolanaRpcUrl(cluster)`** — returns the default URL for a cluster (`'mainnet-beta'`, `'devnet'`, or `'testnet'`), or `undefined` for unknown clusters. When you pass `options.cluster` to the hook and no matching context supplies an RPC URL, the hook uses this helper to resolve `rpcUrl`. ## Parameters ### create The hook uses the shared `CreateEmbeddedWalletOptions` type (same as Ethereum). `chainId` and `accountType` are optional and apply to EVM; Solana create typically omits them. ```ts export type CreateEmbeddedWalletOptions = { chainId?: number recoveryMethod?: RecoveryMethod passkeyId?: string password?: string otpCode?: string accountType?: AccountTypeEnum feeSponsorshipId?: string } & OpenfortHookOptions ``` `CreateEmbeddedWalletOptions` and `CreateEmbeddedWalletResult` are from `@openfort/react` shared types; `OpenfortHookOptions` adds `onSuccess`, `onError`, `throwOnError`. ### setActive ```ts export type SetActiveEmbeddedWalletOptionsBase = { recoveryParams?: RecoveryParams recoveryMethod?: RecoveryMethod passkeyId?: string password?: string otpCode?: string } export type SetActiveSolanaWalletOptions = SetActiveEmbeddedWalletOptionsBase & { address: string } ``` ### setRecovery ```ts export type SetRecoveryOptions = { previousRecovery: RecoveryParams newRecovery: RecoveryParams } ``` ## Solana configuration reference ### Provider chainType Requires `walletConfig.chainType: ChainTypeEnum.SVM` on `OpenfortProvider`. The default is `EVM`, so without it the hook stays at `isConnected: false` even after a successful OAuth / email / passkey sign-in. See [provider setup](/docs/products/embedded-wallet/react/#providerstsx) and the [headless Solana quickstart](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/solana-headless). ### Cluster One of `'mainnet-beta'`, `'devnet'`, or `'testnet'`. Set in `walletConfig.solana.cluster` or override per-hook via `options.cluster`. ### Commitment levels Set `walletConfig.solana.commitment` to control how long the SDK waits for transaction confirmation. Default: `'confirmed'`. | Level | Description | |-------|-------------| | `processed` | Processed by the leader node. Fastest but may be reverted. | | `confirmed` | Confirmed by a supermajority of validators. Good balance of speed and safety. | | `finalized` | Finalized and cannot be reverted. Slowest but most secure. | ### Custom RPC URLs Provide custom RPC endpoints per cluster via `walletConfig.solana.rpcUrls`: ```tsx solana: { cluster: "mainnet-beta", rpcUrls: { "mainnet-beta": "https://your-rpc.example.com", "devnet": "https://your-devnet-rpc.example.com", }, } ``` When no custom URL is configured, the SDK falls back to `getDefaultSolanaRpcUrl(cluster)` (exported from `@openfort/react`). ## @solana/kit integration Use the provider from `activeWallet.getProvider()` with a `TransactionSigner` adapter to sign transactions. See [Wallet actions](/docs/products/embedded-wallet/react/wallet/actions#send-sol-or-sign-transactions) for the full `createOpenfortSigner` pattern and SOL transfer example. ## Related * [Create wallet](/docs/products/embedded-wallet/react/wallet/create) * [Active wallet](/docs/products/embedded-wallet/react/wallet/active-wallet) # `useEthereumWalletAssets` :::info **Ethereum only.** Import from `@openfort/react/ethereum`. ::: Returns wallet assets (tokens, NFTs) for the connected Ethereum address using ERC-7811 via Openfort's authenticated RPC proxy. :::warning Fetches [default assets](/docs/configuration/default-assets) plus any assets defined in [wallet configuration](/docs/products/embedded-wallet/react/wallet/assets#erc-20-tokens) or passed as options. ::: ## Usage ```tsx import { useEthereumWalletAssets } from '@openfort/react/ethereum'; import { formatUnits } from 'viem'; function WalletAssets() { const { data: assets, isLoading, error } = useEthereumWalletAssets(); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return (
    {assets?.map((asset) => (
  • {formatUnits(asset.balance, asset.metadata?.decimals ?? 18)} {asset.metadata?.symbol}
  • ))}
); } ``` ## Return type The return type is a discriminated union based on the `multiChain` option: ```ts type UseEthereumWalletAssetsResult = | (WalletAssetsReturnBase & { multiChain: true; data: readonly MultiChainAsset[] | null }) | (WalletAssetsReturnBase & { multiChain: false; data: readonly Asset[] | null }) type WalletAssetsReturnBase = { isLoading: boolean isError: boolean isSuccess: boolean isIdle: boolean // True when wallet not connected or chain not available error: OpenfortError | undefined refetch(): Promise } // Asset is a discriminated union by `type` type Asset = { type: 'native' address?: 'native' balance: bigint metadata?: { decimals?: number symbol: string name?: never fiat: { value: number; currency: string } } raw?: NativeAsset } | { type: 'erc20' address: Hex // e.g. `0x${string}` balance: bigint metadata: { decimals?: number symbol: string name: string fiat?: { value: number; currency: string } } raw?: Erc20Asset } type MultiChainAsset = Asset & { chainId: number } ``` ## Parameters ```ts type UseEthereumWalletAssetsOptions = { assets?: Record // Additional assets to track per chain multiChain?: boolean // When true, fetches across all configured chains staleTime?: number // Cache duration in ms (default: 30000) } // assets format: { [chainId: number]: Hex[] } // Example USDC on mainnet: { 1: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'] } ``` # `useUI` Control the Openfort modal: open/close and navigate between views. ## Usage ```tsx import { useUI } from '@openfort/react'; function Header() { const { isOpen, open, close, openProfile, openProviders } = useUI(); return ( <> {isOpen && } ); } ``` ## Return type ```ts type UseUIReturn = { isOpen: boolean open(): void close(): void setIsOpen(open: boolean): void openProfile(): void openSwitchNetworks(): void openProviders(): void openWallets(): void } ``` # `useOpenfort` :::info If using Next.js App Router, add `"use client"` at the top of the file. ::: Access the core Openfort context, including the SDK client instance, embedded wallet state, and user information. :::warning This hook provides low-level access to the Openfort SDK. For most use cases, prefer using the specialized hooks like [`useUser`](/docs/products/embedded-wallet/react/hooks/useUser), [`useEthereumEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useEthereumEmbeddedWallet), [`useSolanaEmbeddedWallet`](/docs/products/embedded-wallet/react/hooks/useSolanaEmbeddedWallet), or the authentication hooks. ::: ## Usage ```tsx import { useOpenfort } from '@openfort/react'; function AdvancedComponent() { const { client, embeddedState, user, isLoading } = useOpenfort(); // Access the underlying Openfort SDK client for advanced operations const handleAdvancedOperation = async () => { const accessToken = await client.getAccessToken(); // Use the token for custom API calls }; if (isLoading) return
Loading...
; return (

Embedded state: {embeddedState}

User ID: {user?.id}

); } ``` ## Return type The hook returns the core Openfort context value (`OpenfortCoreContextValue`): ```ts import type { ChainTypeEnum, EmbeddedAccount, EmbeddedState, Openfort, OpenfortError, User, } from '@openfort/openfort-js' import type { UserAccount } from '@openfort/react' type UseOpenfortReturn = { // The Openfort SDK client instance client: Openfort // Current chain type (EVM | SVM) chainType: ChainTypeEnum setChainType: (chainType: ChainTypeEnum) => void // Sign up as guest user signUpGuest: () => Promise // Current embedded wallet state embeddedState: EmbeddedState // Whether the SDK is loading/initializing isLoading: boolean // Whether wallet recovery is needed needsRecovery: boolean // Current authenticated user user: User | null // Update the user object updateUser: (user?: User) => Promise // Linked authentication accounts (external wallets and auth providers only, not embedded wallets) linkedAccounts: UserAccount[] // Embedded wallet accounts embeddedAccounts?: EmbeddedAccount[] // Whether embedded accounts are loading isLoadingAccounts: boolean // Current active embedded wallet address. Set by useEthereumEmbeddedWallet.setActive and synced from SDK on load. activeEmbeddedAddress: string | undefined setActiveEmbeddedAddress: (address: string | undefined) => void // Sign out the current user logout: () => void // Refresh embedded accounts data updateEmbeddedAccounts: (options?: { silent?: boolean }) => Promise // Current wallet flow status walletStatus: WalletFlowStatus // Update wallet flow status setWalletStatus: (status: WalletFlowStatus) => void // Set when auto-recovery fails. Null on success or when cleared by a new auth session. // Use this to show recovery error UI. recoveryError: Error | null } // Embedded wallet states enum EmbeddedState { NONE = 0, UNAUTHENTICATED = 1, EMBEDDED_SIGNER_NOT_CONFIGURED = 2, CREATING_ACCOUNT = 3, READY = 4, } type WalletFlowStatus = | { status: 'idle'; error?: never } | { status: 'awaiting-input'; error?: never } | { status: 'loading'; error?: never } | { status: 'success'; error?: never } | { status: 'error'; error: OpenfortError | null } | { status: 'creating' | 'connecting'; address?: `0x${string}`; error?: never } ``` ## Store selectors (advanced) The SDK exports store selectors and context for advanced usage with the underlying zustand store: * `StoreContext` — React context providing access to the `OpenfortStore` * `useOpenfortCore` — Low-level hook for direct store access * `selectActiveAddress` — Selector for the active wallet address * `selectChainType` — Selector for the current chain type * `selectEmbeddedState` — Selector for the embedded wallet state * `selectIsAuthenticated` — Selector for authentication status * `selectIsLoading` — Selector for loading status * `selectUser` — Selector for the current user * `selectWalletStatus` — Selector for wallet flow status These are exported from `@openfort/react` and can be used for performance-sensitive components that need to subscribe to specific slices of state. ## Example: Custom API calls with access token ```tsx import { useOpenfort } from '@openfort/react'; function CustomApiExample() { const { client } = useOpenfort(); const fetchUserData = async () => { const accessToken = await client.getAccessToken(); const response = await fetch('https://your-api.com/user-data', { headers: { 'Authorization': `Bearer ${accessToken}`, }, }); return response.json(); }; return ; } ``` ## Example: Check embedded wallet state ```tsx import { useOpenfort } from '@openfort/react' import { EmbeddedState } from '@openfort/openfort-js' function WalletStateChecker() { const { embeddedState, needsRecovery } = useOpenfort(); const getStateMessage = () => { switch (embeddedState) { case EmbeddedState.NONE: return 'No wallet configured'; case EmbeddedState.UNAUTHENTICATED: return 'Please sign in'; case EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED: return 'Wallet signer not configured'; case EmbeddedState.CREATING_ACCOUNT: return 'Creating wallet...'; case EmbeddedState.READY: return needsRecovery ? 'Wallet needs recovery' : 'Wallet ready'; default: return 'Unknown state'; } }; return

{getStateMessage()}

; } ``` # `useGrantPermissions` Grant permissions to session keys using EIP-7715 for scoped, time-limited transactions. Both `useGrantPermissions` and `useRevokePermissions` use the Ethereum bridge directly (no SIWE dependency). External wallets (e.g. MetaMask) connected via wagmi work for session key grant/revoke. ## Usage ```tsx import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import { useGrantPermissions } from '@openfort/react'; function CreateSessionKey() { const { grantPermissions, isLoading } = useGrantPermissions(); const handleCreate = async () => { const sessionKey = generatePrivateKey(); const sessionAccount = privateKeyToAccount(sessionKey); // Store the session key. You need it later to sign transactions. localStorage.setItem('sessionKey', sessionKey); const result = await grantPermissions({ sessionKey: sessionKey, request: { signer: { type: 'account', data: { id: sessionAccount.address }, }, expiry: 60 * 60 * 24, // 24 hours permissions: [ { type: 'contract-call', data: { address: '0x2522f4fc9af2e1954a3d13f7a5b2683a00a4543a', calls: [], }, policies: [], }, ], }, }); console.log('Permissions granted:', result.address); }; return ; } ``` ## Return type ```ts type UseGrantPermissionsReturn = { grantPermissions(params: GrantPermissionsParams, options?: GrantPermissionsHookOptions): Promise data: GrantPermissionsResult | null reset(): void isLoading: boolean isError: boolean isSuccess: boolean error: OpenfortError | null | undefined } type GrantPermissionsResult = { address: `0x${string}` // Wallet that granted permissions } & GrantPermissionsReturnType // From viem/experimental type GrantPermissionsHookResult = { error?: OpenfortError } & Partial ``` ## Hook options Configure default behavior when initializing the hook. ```ts type GrantPermissionsHookOptions = { onSuccess?: (data: GrantPermissionsHookResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` ## Parameters ### grantPermissions Both hooks support an optional `walletClient` override for non-wagmi external wallets. Resolution order: explicit `walletClient` → wagmi bridge → embedded wallet fallback. ```ts type GrantPermissionsParams = { request: GrantPermissionsParameters // From viem/experimental sessionKey: Hex // Required session key (hex-encoded private key) } // GrantPermissionsParameters includes: type GrantPermissionsParameters = { signer: { type: 'account' data: { id: string } // Session account address } expiry: number // Duration in seconds permissions: Permission[] } type Permission = { type: 'contract-call' | 'native-token-transfer' | 'erc20-transfer' | 'erc721-transfer' | 'erc1155-transfer' data: { address: string calls?: [] } policies?: [] } ``` ## Related * [Session Keys Guide](/docs/products/embedded-wallet/react/wallet/actions/session-keys) * [useRevokePermissions](/docs/products/embedded-wallet/react/hooks/useRevokePermissions) # `useRevokePermissions` Revoke permissions previously granted to session keys. ## Usage ```tsx import { useRevokePermissions } from '@openfort/react'; function RevokeSessionKey() { const { revokePermissions, isLoading } = useRevokePermissions(); const handleRevoke = async (sessionKeyAddress: `0x${string}`) => { const { error } = await revokePermissions({ sessionKey: sessionKeyAddress, }); if (!error) console.log('Session key revoked'); }; return ( ); } ``` ## Return type ```ts type UseRevokePermissionsReturn = { revokePermissions(params: RevokePermissionsParams, options?: RevokePermissionsHookOptions): Promise data: SessionResponse | null reset(): void isLoading: boolean isError: boolean isSuccess: boolean error: OpenfortError | null | undefined } type RevokePermissionsHookResult = { error?: OpenfortError } & Partial // SessionResponse from @openfort/openfort-js type SessionResponse = { id: string object: 'session' address: string // ... session details } ``` ## Hook options Configure default behavior when initializing the hook. ```ts type RevokePermissionsHookOptions = { onSuccess?: (data: RevokePermissionsHookResult) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` ## Parameters ### revokePermissions Both hooks support an optional `walletClient` override for non-wagmi external wallets. Resolution order: explicit `walletClient` → wagmi bridge → embedded wallet fallback. ```ts type RevokePermissionsParams = { sessionKey: Hex // Session key (permissionContext) to revoke } ``` ## Related * [Session Keys Guide](/docs/products/embedded-wallet/react/wallet/actions/session-keys) * [useGrantPermissions](/docs/products/embedded-wallet/react/hooks/useGrantPermissions) # `use7702Authorization` Sign EIP-7702 authorization payloads, allowing EOAs to temporarily behave like smart accounts for gasless transactions and batch operations. :::tip See the complete implementation in [Sign EIP-7702 authorization](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization). ::: ## Usage ```tsx import { use7702Authorization } from '@openfort/react'; import { usePublicClient } from 'wagmi'; import { sepolia } from 'viem/chains'; function Authorize7702() { const { signAuthorization } = use7702Authorization(); const publicClient = usePublicClient(); const handleAuthorization = async () => { const nonce = await publicClient.getTransactionCount({ address: '0x...' }); const signedAuth = await signAuthorization({ contractAddress: '0xe6Cae83BdE06E4c305530e199D7217f42808555B', chainId: sepolia.id, nonce, }); console.log('Authorization signed:', signedAuth); }; return ; } ``` ## Return type ```ts type Use7702AuthorizationReturn = { signAuthorization(params: SignAuthorizationParameters, options?: SignOptions): Promise } type SignAuthorizationParameters = { contractAddress: Address // Implementation contract to authorize chainId: number // Chain ID for the authorization nonce: number // Current transaction count } type SignOptions = { hashMessage?: boolean // Hash message before signing (default: false) arrayifyMessage?: boolean // Arrayify message before signing (default: false) } type SignedAuthorization = { address: Address chainId: number nonce: number r: Hex s: Hex v: bigint yParity: number } ``` ## Related * [Sign EIP-7702 authorization](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization) * [useGrantPermissions](/docs/products/embedded-wallet/react/hooks/useGrantPermissions) # Error handling The React SDK uses `OpenfortError` and `OpenfortErrorType` for typed error handling. All SDK errors extend `OpenfortError` and include a `type` for programmatic handling. ## Import ```tsx import { OpenfortError, OpenfortErrorType } from '@openfort/react' ``` ## OpenfortError ```ts class OpenfortError extends Error { type: OpenfortErrorType data: Record constructor(message: string, type: OpenfortErrorType, data?: Record) } ``` * `type` — Error type for branching (e.g. `OpenfortErrorType.AUTHENTICATION_ERROR`) * `data` — Extra context as key/value pairs for debugging ## Error types (OpenfortErrorType) | Type | Category | |------|----------| | `AUTHENTICATION_ERROR` | Authentication failures | | `WALLET_ERROR` | Wallet creation, recovery, signing | | `CONFIGURATION_ERROR` | Missing or invalid config | | `VALIDATION_ERROR` | Input validation failures | | `UNEXPECTED_ERROR` | Unexpected or unknown errors | ## Basic usage ```tsx import { useEthereumEmbeddedWallet } from '@openfort/react/ethereum' import { OpenfortError, OpenfortErrorType } from '@openfort/react' function CreateWallet() { const { create } = useEthereumEmbeddedWallet() const handleCreate = async () => { try { await create() } catch (error) { if (error instanceof OpenfortError) { switch (error.type) { case OpenfortErrorType.AUTHENTICATION_ERROR: // Redirect to login break case OpenfortErrorType.WALLET_ERROR: // Show wallet error break case OpenfortErrorType.CONFIGURATION_ERROR: // Show config error break default: console.error(error.message) } } else { throw error } } } return } ``` ## OpenfortHookOptions Many hooks accept `onSuccess`, `onError`, and `throwOnError`: ```ts type OpenfortHookOptions = { onSuccess?: (data: T) => void onError?: (error: OpenfortError) => void throwOnError?: boolean } ``` * `onSuccess` — Called when the operation succeeds. * `onError` — Called when the operation fails. Errors are caught and passed here. * `throwOnError` — When `true`, errors propagate to the nearest React error boundary instead of being passed to `onError`. ```tsx const { create } = useEthereumEmbeddedWallet() await create({ onSuccess: ({ account }) => console.log('Created:', account.id), onError: (error) => toast.error(error.message), }) ``` # Event System The Openfort SDK provides a comprehensive event system that allows you to monitor and respond to various authentication, wallet, and UI lifecycle events in your application. The SDK uses a typed event emitter that you can access via the `openfortEvents` export. All events are strongly typed and provide detailed payload information. ```typescript import { openfortEvents, OpenfortEvents } from '@openfort/react' // Subscribe to an event openfortEvents.on(OpenfortEvents.ON_AUTH_SUCCESS, (authResponse) => { console.log('User authenticated:', authResponse.player.id) }) // Remove a specific listener const handler = (authResponse) => console.log('Auth success') openfortEvents.on(OpenfortEvents.ON_AUTH_SUCCESS, handler) openfortEvents.off(OpenfortEvents.ON_AUTH_SUCCESS, handler) // Remove all listeners for an event openfortEvents.removeAllListeners(OpenfortEvents.ON_AUTH_SUCCESS) ``` ## Authentication Events ### `ON_AUTH_INIT` Called when an authentication process begins, before the user completes authentication. **Payload:** ```typescript type AuthInitPayload = { method: 'email' | 'phone' | 'oauth' | 'siwe' | 'idToken' | 'guest' provider?: string // OAuth provider name (e.g., 'google', 'discord') } ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_AUTH_INIT, (payload) => { console.log(`Authentication started with ${payload.method}`) if (payload.provider) { console.log(`Using provider: ${payload.provider}`) } // Show loading indicator, track analytics, etc. }) ``` **Use Cases:** * Display loading states * Track authentication attempts in analytics * Log authentication method usage *** ### `ON_AUTH_SUCCESS` Called after the user successfully authenticates and receives access tokens. **Payload:** ```typescript type AuthResponse = { player: AuthPlayerResponse token: string refreshToken: string } type AuthPlayerResponse = { id: string object: 'player' createdAt: number linkedAccounts: LinkedAccountResponse[] player?: { id: string name: string description?: string metadata?: Record accounts?: Array<{ id: string address: string chainId: number // ... more account fields }> } } ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_AUTH_SUCCESS, (authResponse) => { console.log('User authenticated successfully!') console.log('Player ID:', authResponse.player.id) console.log('Linked accounts:', authResponse.player.linkedAccounts) // Navigate to dashboard, update UI, etc. router.push('/dashboard') }) ``` **Use Cases:** * Navigate user to authenticated area * Store user preferences * Initialize user-specific features * Track successful authentication in analytics *** ### `ON_AUTH_FAILURE` Called when authentication fails due to an error. **Payload:** ```typescript Error // Standard JavaScript Error object ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_AUTH_FAILURE, (error) => { console.error('Authentication failed:', error.message) // Show error message to user toast.error(`Failed to authenticate: ${error.message}`) // Track error in monitoring Sentry.captureException(error) }) ``` **Use Cases:** * Display error messages to users * Log authentication errors * Retry authentication logic * Error tracking and monitoring *** ### `ON_LOGOUT` Called after the user successfully logs out. **Payload:** None **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_LOGOUT, () => { console.log('User logged out') // Clear local state, navigate to login, etc. localStorage.clear() router.push('/login') }) ``` **Use Cases:** * Clear local application state * Navigate to login page * Reset UI components * Clear sensitive data from memory *** ### `ON_OTP_REQUEST` Called when an OTP (one-time password) is requested for login. **Payload:** ```typescript { method: 'email' | 'phone' provider?: string } ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_OTP_REQUEST, (payload) => { console.log(`OTP requested via ${payload.method}`) // Show OTP input screen, start countdown timer, etc. }) ``` **Use Cases:** * Show OTP input UI at the right moment * Start a countdown timer for resend * Track OTP request analytics *** ### `ON_OTP_FAILURE` Called when an OTP request fails to send. **Payload:** ```typescript Error // Standard JavaScript Error object ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_OTP_FAILURE, (error) => { console.error('OTP request failed:', error.message) toast.error('Failed to send verification code. Please try again.') }) ``` **Use Cases:** * Display error message when OTP delivery fails * Offer retry option * Track delivery failures *** ## Wallet Events ### `ON_EMBEDDED_WALLET_CREATED` Called after a new embedded wallet is created for the user. **Payload:** ```typescript type EmbeddedAccount = { id: string user: string address: string chainId?: number // EVM only cluster?: string // SVM only ('devnet') chainType: 'EVM' | 'SVM' accountType: 'Externally Owned Account' | 'Smart Account' ownerAddress?: string implementationType?: string factoryAddress?: string salt?: string createdAt?: number recoveryMethod?: 'password' | 'automatic' | 'passkey' recoveryMethodDetails?: { passkeyId?: string passkeyEnv?: { name?: string os?: string osVersion?: string device?: string } } } ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_EMBEDDED_WALLET_CREATED, (wallet) => { console.log('New wallet created!') console.log('Address:', wallet.address) console.log('Chain type:', wallet.chainType) if (wallet.chainType === 'EVM') { console.log('Chain ID:', wallet.chainId) } else { console.log('Cluster:', wallet.cluster) } console.log('Account type:', wallet.accountType) console.log('Recovery method:', wallet.recoveryMethod) // Display wallet address to user setWalletAddress(wallet.address) // Track wallet creation analytics.track('wallet_created', { chainType: wallet.chainType, accountType: wallet.accountType, }) }) ``` **Use Cases:** * Display new wallet information * Store wallet address in UI state * Track wallet creation metrics * Initialize wallet-dependent features *** ### `ON_EMBEDDED_WALLET_RECOVERED` Called when an existing embedded wallet is recovered. **Payload:** ```typescript type EmbeddedAccount = { // Same structure as ON_EMBEDDED_WALLET_CREATED } ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_EMBEDDED_WALLET_RECOVERED, (wallet) => { console.log('Wallet recovered!') console.log('Address:', wallet.address) console.log('Recovery method used:', wallet.recoveryMethod) // Update UI with recovered wallet setWalletAddress(wallet.address) // Track recovery analytics.track('wallet_recovered', { recoveryMethod: wallet.recoveryMethod, }) }) ``` **Use Cases:** * Display recovered wallet information * Track successful wallet recovery * Verify recovery method * Resume wallet operations *** ### `ON_SWITCH_ACCOUNT` Called when switching between different accounts or chains. **Payload:** ```typescript string // The new account address ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_SWITCH_ACCOUNT, (address) => { console.log('Switched to account:', address) // Update UI with new account setCurrentAccount(address) // Refresh account balance fetchBalance(address) // Update chain-specific components updateChainInfo() }) ``` **Use Cases:** * Update UI when user switches accounts * Refresh account-specific data * Update balance displays * Sync with blockchain state *** ### `ON_SIGNED_MESSAGE` Called whenever the wallet signs a message. This includes: * Personal message signing (`signMessage`) * Typed data signing (`signTypedData` / EIP-712) * Transaction signatures * Session key signatures * Internal signing operations **Payload:** ```typescript type SignedMessagePayload = { message: string | Uint8Array // The message that was signed signature: string // The resulting signature (hex string) } ``` **Example:** ```typescript openfortEvents.on(OpenfortEvents.ON_SIGNED_MESSAGE, (payload) => { console.log('Message signed!') console.log('Message:', payload.message) console.log('Signature:', payload.signature) // Track signing activity analytics.track('message_signed', { messageLength: typeof payload.message === 'string' ? payload.message.length : payload.message.length, }) // Store signature for later use saveSignature(payload.signature) // Show success notification toast.success('Message signed successfully!') }) ``` **Use Cases:** * Track all signing operations * Audit trail for signatures * Display signing confirmations * Monitor transaction signing * Debug signing issues * Implement custom signing workflows **Note:** This event is emitted for ALL signing operations, including: * Manual `openfort.signMessage()` calls * EIP-712 typed data signing * Smart account user operation signatures * EOA transaction signatures * Session key registration/revocation signatures ## TypeScript Types All events are fully typed. Import the types you need: ```typescript import type { OpenfortEvents, OpenfortEventMap, AuthInitPayload, AuthResponse, EmbeddedAccount, SignedMessagePayload, } from '@openfort/react' ``` The `OpenfortEventMap` provides the complete type mapping: ```typescript interface OpenfortEventMap { [OpenfortEvents.ON_AUTH_INIT]: [AuthInitPayload] [OpenfortEvents.ON_AUTH_SUCCESS]: [AuthResponse] [OpenfortEvents.ON_AUTH_FAILURE]: [Error] [OpenfortEvents.ON_LOGOUT]: [] [OpenfortEvents.ON_SWITCH_ACCOUNT]: [string] [OpenfortEvents.ON_SIGNED_MESSAGE]: [SignedMessagePayload] [OpenfortEvents.ON_EMBEDDED_WALLET_CREATED]: [EmbeddedAccount] [OpenfortEvents.ON_EMBEDDED_WALLET_RECOVERED]: [EmbeddedAccount] [OpenfortEvents.ON_OTP_REQUEST]: [{ method: 'email' | 'phone'; provider?: string }] [OpenfortEvents.ON_OTP_FAILURE]: [Error] } ``` ## Best Practices ### 1. Always Clean Up Listeners Remove event listeners when components unmount to prevent memory leaks: ```typescript useEffect(() => { const handler = (data) => console.log(data) openfortEvents.on(OpenfortEvents.ON_AUTH_SUCCESS, handler) return () => { openfortEvents.off(OpenfortEvents.ON_AUTH_SUCCESS, handler) } }, []) ``` ### 2. Handle Errors Gracefully Always handle the `ON_AUTH_FAILURE` event to provide user feedback: ```typescript openfortEvents.on(OpenfortEvents.ON_AUTH_FAILURE, (error) => { // Show user-friendly error message toast.error(getUserFriendlyError(error)) // Log for debugging console.error('Auth error:', error) // Track in error monitoring Sentry.captureException(error) }) ``` ### 3. Use Events for Analytics Events are perfect for tracking user behavior: ```typescript openfortEvents.on(OpenfortEvents.ON_AUTH_SUCCESS, (auth) => { analytics.track('user_authenticated', { player_id: auth.player.id, linked_accounts: auth.player.linkedAccounts.length, }) }) openfortEvents.on(OpenfortEvents.ON_SIGNED_MESSAGE, () => { analytics.track('message_signed') }) ``` ### 4. Avoid Heavy Operations in Event Handlers Keep event handlers lightweight and asynchronous when needed: ```typescript openfortEvents.on(OpenfortEvents.ON_AUTH_SUCCESS, async (auth) => { // Good: Non-blocking UI update setUser(auth.player) // Good: Async heavy operation doesn't block fetchUserData(auth.player.id).catch(console.error) }) ``` ### 5. Type Your Event Handlers Use proper TypeScript types for type safety: ```typescript import type { AuthResponse, EmbeddedAccount } from '@openfort/react' const handleAuthSuccess = (auth: AuthResponse) => { console.log(auth.player.id) // Fully typed! } const handleWalletCreated = (wallet: EmbeddedAccount) => { console.log(wallet.address) // Fully typed! } openfortEvents.on(OpenfortEvents.ON_AUTH_SUCCESS, handleAuthSuccess) openfortEvents.on(OpenfortEvents.ON_EMBEDDED_WALLET_CREATED, handleWalletCreated) ``` ## Troubleshooting ### Events Not Firing 1. **Check listener registration**: Ensure listeners are registered before triggering actions 2. **Verify imports**: Make sure you're importing from `@openfort/react` 3. **Check cleanup**: Ensure you're not removing listeners prematurely ### Memory Leaks If you notice memory issues: 1. Always clean up listeners in useEffect cleanup or componentWillUnmount 2. Use `removeAllListeners` for the specific event, not globally 3. Consider using weak references for long-lived listeners # Getting Started with React Native ::::steps ## Create your app (optional) If you're starting from scratch, create a new React Native app with Expo. If you already have an app, skip this step. ### Create a new Expo app You can use Expo's `create-expo-app` to create a blank template Typescript app. :::code-group ```sh [With expo-router] npx --yes create-expo-app@latest my-app --template tabs cd my-app ``` ```sh [Without expo-router] npx --yes create-expo-app@latest my-app --template expo-template-blank-typescript cd my-app ``` ::: ### Run the app From your project directory, run one of: ```bash npm run android npm run ios ``` ## Install dependencies Install the latest version of the [Openfort React Native SDK](https://www.npmjs.com/package/@openfort/react-native) and its required dependencies: :::code-group ```sh [With expo-router] npx --yes expo install expo-router expo-apple-authentication expo-application expo-crypto expo-secure-store expo-constants npm install react-native-get-random-values @openfort/react-native ``` ```sh [Without expo-router] npx --yes expo install expo-apple-authentication expo-application expo-crypto expo-secure-store expo-constants npm install react-native-get-random-values @openfort/react-native ``` ::: ## Configure Metro Create or update your `metro.config.js` to include the necessary Node.js module shims: ```tsx // metro.config.js const { getDefaultConfig } = require("expo/metro-config"); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); const resolveRequestWithPackageExports = (context, moduleName, platform) => { // Package exports in `jose` are incorrect, so we need to force the browser version if (moduleName === "jose") { const ctx = { ...context, unstable_conditionNames: ["browser"], }; return ctx.resolveRequest(ctx, moduleName, platform); } return context.resolveRequest(context, moduleName, platform); }; config.resolver.resolveRequest = resolveRequestWithPackageExports; module.exports = config; ``` ## Set up entry point Create `entrypoint.ts`. Ensure `react-native-get-random-values` is imported first. Without it, the library `crypto` does not load properly and causes an error. :::code-group ```ts [With expo-router] // entrypoint.ts import "react-native-get-random-values"; import "expo-router/entry"; ``` ```ts [Without expo-router] // entrypoint.ts import "react-native-get-random-values"; import { registerRootComponent } from 'expo'; import App from './App'; registerRootComponent(App); ``` ::: Also, update your `package.json` to use the corresponding entry point: ```json { ... "main": "entrypoint.ts", ... } ``` ## Wrap your app with OpenfortProvider Add the provider at your root to initialize the SDK. :::code-group ```tsx [With expo-router] // app/_layout.tsx import { OpenfortProvider } from "@openfort/react-native"; import { Slot } from "expo-router"; export default function RootLayout() { return ( <> {/* Your other providers */} {/* Render nested routes */} {/* Your other providers */} ); } ``` ```tsx [Without expo-router] // App.tsx import { OpenfortProvider } from "@openfort/react-native"; import { View, Text, StyleSheet } from "react-native"; export default function App() { return ( <> {/* Your other providers */} {/* Your app content */} Hello, World! {/* Your other providers */} ); } const styles = StyleSheet.create({ containerCentered: { flex: 1, justifyContent: "center", alignItems: "center", }, heading: { fontSize: 18, }, }); ``` ::: ## You can now start using Openfort! Advance to [this next guide](/docs/products/embedded-wallet/react-native/quickstart/automatic) to get the API keys and create your first wallet. :::: ## SDK Exports The React Native SDK exports the following: | Export | Description | |--------|-------------| | `OpenfortProvider` | Root provider component | | `AuthBoundary` | Auth state-based rendering component | | `useOpenfort` | SDK initialization state hook | | `useOpenfortClient` | Direct access to Openfort client | | `useUser` | Current user and access token hook | | `useEmailAuth` | Email/password authentication hook | | `useEmailAuthOtp` | Email OTP authentication hook | | `usePhoneAuthOtp` | Phone OTP authentication hook | | `useOAuth` | OAuth provider authentication hook | | `useGuestAuth` | Guest authentication hook | | `useWalletAuth` | SIWE wallet authentication hook | | `useSignOut` | Sign out hook | | `useEmbeddedEthereumWallet` | Ethereum wallet management hook | | `useEmbeddedSolanaWallet` | Solana wallet management hook | | `usePasskeyPrfSupport` | Check if device supports passkey PRF (Android 14+, iOS 18+) | ### Re-exported from `@openfort/openfort-js` | Export | Description | |--------|-------------| | `OpenfortClient` | Core Openfort class (alias for `Openfort`) | | `OpenfortEvents` | Event name type (use `openfortEvents` for the emitter instance) | | `openfortEvents` | Global event emitter | | `OAuthProvider` | OAuth provider enum | | `AccountTypeEnum` | Account type enum (SMART\_ACCOUNT, EOA) | | `ChainTypeEnum` | Chain type enum (EVM, SVM) | | `EmbeddedState` | Embedded wallet state enum | | `RecoveryMethod` | Recovery method enum (AUTOMATIC, PASSWORD, PASSKEY) | | `RecoveryParams` | Recovery parameters type | | `OpenfortError` | Base error class | | `AuthInitPayload` | Authentication initialization payload type | | `AuthResponse` | Authentication response type | | `EmbeddedAccount` | Embedded account type | | `OpenfortConfiguration` | SDK configuration type | | `OpenfortEventMap` | Event map type for event emitter | | `Provider` | Provider type | | `ShieldConfiguration` | Shield configuration type | | `SignedMessagePayload` | Signed message payload type | | `ThirdPartyOAuthProvider` | Third-party OAuth provider type | For detailed component documentation, see the [Components](/docs/products/embedded-wallet/react-native/components) section. # Quickstart with Automatic recovery Build a functional authentication and wallet sample app using the Openfort React Native SDK. This guide assumes you've completed the [Getting Started guide](/docs/products/embedded-wallet/react-native) and have your `OpenfortProvider` configured. In this guide, we are going to help you set up your wallet recovery method to our `Automatic` method. These are our wallet recovery methods you can choose from: * **Using Automatic Recovery** * [Switch to Password Recovery](/docs/products/embedded-wallet/react-native/quickstart/password) * [Switch to Passkey Recovery](/docs/products/embedded-wallet/react-native/quickstart/passkey) Not sure what wallet recovery method you need? Don't miss [our guide](/docs/configuration/recovery-methods). ::::steps ## Get your Openfort keys From this point on, you'll need an [Openfort account](https://dashboard.openfort.io/auth/register) to handle project keys and settings. In the [Openfort Dashboard](https://dashboard.openfort.io), select your desired app and navigate to the [API keys](https://dashboard.openfort.io/api-keys). There, you'll get the following keys: **Project keys**: Required for any wallet. | Key | Exposure | Description | | --- | --- | --- | | **Publishable key** | Public (client) | Initialize the SDK (for example, in `OpenfortProvider`). | | **Secret key** | Secret (server) | Privileged backend actions (for example, sessions, wallet management). Never bundle in the app or commit to git. | **Shield Keys**: Required for non-custodial wallets. | Key | Exposure | Description | | --- | --- | --- | | **Encryption part** (one-time) | Secret | Server-only key that encrypts/decrypts sensitive wallet data. Store in a key manager; if lost, data can't be recovered. | | **Publishable key** | Public (client) | Public key the app uses to enable non‑custodial wallet features. Safe to include in client code. | | **Secret key** | Secret (server) | Server-only key for sensitive wallet operations (for example, starting encryption or recovery). Never ship in the app; store in a secret manager. | Get the **Project Publishable Key** and the **Shield Publishable Key** and save them for the configuration steps below. Before you begin, make sure you have set up your **publishable key** app from the Openfort Dashboard. :::warning A properly set up publishable key is required for mobile apps and other non-web platforms to allow your app to interact with the Openfort API. Please follow [this guide](/docs/configuration/allowed-domains#native-apps) to configure an app client. ::: Save the secret keys safely for your backend. ## Set up the recovery endpoint (only for Automatic recovery) Create a backend endpoint that issues a Shield encryption session used to recover wallets. ### Option 1: One‑click deploy Use our prebuilt endpoint and deploy in one click on Cloudflare or Vercel as a function. [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/openfort-xyz/recovery-endpoint-cloudflare) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/openfort-xyz/recovery-endpoint-vercel\&project-name=shield-recovery-endpoint\&env=SHIELD_PUBLISHABLE_KEY,SHIELD_SECRET_KEY,SHIELD_ENCRYPTION_SHARE\&envDescription=Required%20Shield%20API%20keys%20from%20your%20Openfort%20project\&envLink=https://www.openfort.io/docs) ### Option 2: Implement your own backend Use this option if you have your backend set up and can add an extra endpoint.
Endpoint examples for you to copy
Expose a GET endpoint `/api/protected-create-encryption-session` that returns `{ "session": "" }`. Use one of the examples below.
:::code-group ```ts [Express.js] import express, { Request, Response } from 'express'; // Initialize the Openfort client // [!code focus] import Openfort from "@openfort/openfort-node"; // [!code focus] const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY); // [!code focus] // Use your own middleware (JWT, sessions, API key, etc.) to authenticate the user here export const authenticateUser = (req: Request, res: Response, next: NextFunction) => { // Example: req.user = { id: 'user_123', email: 'user@example.com' }; return next(); }; const router = express.Router(); router.get('/api/protected-create-encryption-session', authenticateUser, async (req: Request, res: Response) => { // [!code focus] try { // [!code focus] const session = await openfort.createEncryptionSession( // [!code focus] process.env.OPENFORT_SHIELD_PUBLISHABLE_KEY as string, // [!code focus] process.env.OPENFORT_SHIELD_SECRET_KEY as string, // [!code focus] process.env.OPENFORT_SHIELD_ENCRYPTION_SHARE as string // [!code focus] ); // [!code focus] res.status(200).json({ session }); // [!code focus] } catch (e) { // [!code focus] console.error(e); // [!code focus] res.status(500).json({ error: 'Internal server error' }); // [!code focus] } // [!code focus] }); // [!code focus] export default router; ``` ```ts [Next.js (/api/protected-create-encryption-session.ts)] import type { NextApiRequest, NextApiResponse } from 'next'; // Initialize the Openfort client // [!code focus] import Openfort from "@openfort/openfort-node"; // [!code focus] const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY); // [!code focus] // Use your own authentication middleware (JWT, sessions, API key, etc.) to authenticate the user here function authenticateUser(req: NextApiRequest): boolean { // Example: req.user = { id: 'user_123', email: 'user@example.com' }; return true; } export default async function handler(req: NextApiRequest, res: NextApiResponse) { // [!code focus] // Authenticate user // [!code focus] if (!authenticateUser(req)) { // [!code focus] return res.status(401).json({ error: 'Unauthorized' }); // [!code focus] } // [!code focus] // [!code focus] try { // [!code focus] const session = await openfort.createEncryptionSession( // [!code focus] process.env.OPENFORT_SHIELD_PUBLISHABLE_KEY as string, // [!code focus] process.env.OPENFORT_SHIELD_SECRET_KEY as string, // [!code focus] process.env.OPENFORT_SHIELD_ENCRYPTION_SHARE as string // [!code focus] ); // [!code focus] res.status(200).json({ session }); // [!code focus] } catch (e) { // [!code focus] console.error(e); // [!code focus] res.status(500).json({ error: 'Internal server error' }); // [!code focus] } // [!code focus] } // [!code focus] ``` :::
After setting up the `/api/protected-create-encryption-session` endpoint, save the full endpoint URL for the next step. ## Configure environment variables ```bash # .env # Publishable keys OPENFORT_PUBLISHABLE_KEY=YOUR_PROJECT_PUBLISHABLE_KEY OPENFORT_SHIELD_PUBLISHABLE_KEY=YOUR_SHIELD_PUBLISHABLE_KEY # Automatic recovery endpoint OPENFORT_SHIELD_RECOVERY_ENDPOINT=https://your-backend.com/api/protected-create-encryption-session # gas sponsorship ID OPENFORT_POLICY_ID=YOUR_POLICY_ID ``` Create `app.config.js` to manage your Openfort keys securely: ```js // app.config.js export default { expo: { name: "openfort-sample", slug: "openfort-sample", version: "1.0.0", platforms: ["ios", "android"], extra: { openfortPublishableKey: process.env.OPENFORT_PUBLISHABLE_KEY || "YOUR_PROJECT_PUBLISHABLE_KEY", openfortShieldPublishableKey: process.env.OPENFORT_SHIELD_PUBLISHABLE_KEY || "YOUR_SHIELD_PUBLISHABLE_KEY", openfortShieldRecoveryEndpoint: process.env.OPENFORT_SHIELD_RECOVERY_ENDPOINT || "https://your-backend.com/api/protected-create-encryption-session", openfortPolicyId: process.env.OPENFORT_POLICY_ID || "YOUR_POLICY_ID", }, }, }; ``` ## Replace your Openfort Provider Replace your basic provider setup with a more comprehensive configuration: :::code-group ```tsx [With expo-router] // app/_layout.tsx import { OpenfortProvider, RecoveryMethod } from "@openfort/react-native"; import Constants from "expo-constants"; import { Stack } from "expo-router"; export default function RootLayout() { return ( ); } ``` ```tsx [Without expo-router] // App.tsx import { OpenfortProvider, RecoveryMethod } from "@openfort/react-native"; import Constants from "expo-constants"; import MainApp from "./MainApp"; export default function App() { return ( ); } ``` ::: ## Create the login and user screens Create the login component that handles multiple authentication methods and the user dashboard component that allows wallet creation and message signing: :::code-group ```tsx [components/LoginScreen.tsx] // components/LoginScreen.tsx import { OAuthProvider, useGuestAuth, useOAuth } from "@openfort/react-native"; import { useEffect } from "react"; import { Button, Text, View, StyleSheet } from "react-native"; export default function LoginScreen() { const { signUpGuest } = useGuestAuth(); const { initOAuth, error: authError } = useOAuth(); const SELECTED_PROVIDERS = [ OAuthProvider.Google, OAuthProvider.Twitter, OAuthProvider.Discord, OAuthProvider.Apple, ] useEffect(() => { if (authError) { console.error("[Openfort RN] Error logging in with OAuth:", authError); } }, [authError]); const handleSignUpGuest = () => { signUpGuest() .catch((error) => { console.error("[Openfort RN] Error signing up guest:", error); }); }; const handleLoginWithOAuth = async (provider: OAuthProvider) => { initOAuth({ provider }); }; return ( Openfort Expo Example