Appearance
Using smart wallets
Once you have configured smart wallets in the Privy Dashboard, you can use them in your application with just a few lines of code.
Setup
1. Set up your build configuration
Integrating passkey login in Expo requires that your app:
- uses an expo development build.
- has a custom
metro.config.js
file to customize the Metro bundler settings - enables package exports for the Metro bundler:
- uses the
bundler
setting for Typescript'smoduleResolution
To enable package exports for metro, update your metro.config.js
like so:
ts
//...other config logic
// Enable package exports for select libraries
...
const resolveRequestWithPackageExports = (context, moduleName, platform) => {
if (moduleName.startsWith('@privy-io/')) {
const ctx = {
...context,
unstable_enablePackageExports: true,
};
return ctx.resolveRequest(ctx, moduleName, platform);
}
return context.resolveRequest(context, moduleName, platform);
};
config.resolver.resolveRequest = resolveRequestWithPackageExports;
...
module.exports = config;
Also configure your tsconfig.json
like so:
json
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
// Allows us to use conditional/deep imports on published packages
"moduleResolution": "Bundler"
}
}
2. Install peer dependencies
sh
npx expo install viem permissionless
3. Import and wrap your app with the SmartWalletsProvider
The SmartWalletsProvider
must wrap any component or page that will use smart wallets. We recommend rendering it as close to the root of your application as possible, nested within your PrivyProvider
.
tsx
import { PrivyProvider } from "@privy-io/expo";
import { SmartWalletsProvider } from "@privy-io/expo/smart-wallets";
export default function Providers({children}: {children: React.ReactNode}) {
return (
<PrivyProvider
appId={Constants.expoConfig?.extra?.privyAppId}
clientId={Constants.expoConfig?.extra?.privyAppClientId}
>
<SmartWalletsProvider>
{children}
</SmartWalletsProvider>
</PrivyProvider>
);
}
Creating smart wallets
Once the SmartWalletsProvider
component is rendered and a smart wallet configuration has been set up for your app in the Dashboard, Privy will automatically generate smart wallets for your users once they have an embedded wallet. The embedded wallet is used as the primary signer controlling the smart wallet.
You can configure your app to create embedded wallets manually; smart wallets will be created following the same configuration.
Getting the address
Once a smart wallet has been created for a user, you can get the address for the smart wallet by finding the account of type: 'smart_wallet'
from the user's linked_accounts
array, and inspecting the entry's address
field like so:
tsx
const {user} = usePrivy();
const smartWallet = user.linked_accounts.find((account) => account.type === 'smart_wallet');
console.log(smartWallet.address);
// Logs the smart wallet's address
console.log(smartWallet.type);
// Logs the smart wallet type (e.g. 'safe', 'kernel', 'light_account', 'biconomy')
Signatures and transactions
To use the smart wallet to sign messages and send transactions, import the useSmartWallets
hook and use the client
returned by the hook. This client
is a drop-in replacement for a viem WalletClient and supports many of the same methods, including signMessage
, signTypedData
, and sendTransaction
.
tsx
import {useSmartWallets} from '@privy-io/expo/smart-wallets';
...
const {client} = useSmartWallets();
Signing messages
To sign messages with the smart wallet, simply call the client
's signMessage
or signTypedData
method like so:
tsx
const {client} = useSmartWallets();
const signature = await client.signMessage({
account: client.account,
message: 'Hello world',
});
Sending transactions
To send transactions with the smart wallet, call the client's sendTransaction
method with your desired transaction request. If your app has a paymaster URL registered in the Dashboard, Privy will automatically use that paymaster to attempt to sponsor the gas fees for the user's transaction.
tsx
const {client} = useSmartWallets();
const txHash = await client.sendTransaction({
account: client.account,
chain: base,
to: 'insert-recipient-address',
value: 0.1,
});
Batching transactions
Smart wallets support sending a batch of transactions in a single, atomic submission to the network.
To send a batched transactions with a smart wallet, call the client's sendTransaction
method with a calls
array the transactions to batch together. Each call
may have the following fields:
Field | Type | Description |
---|---|---|
to | string | The recipient of the transaction or the address of the smart contract being called. |
value | bigint | The value in wei for the transaction. |
data | string | Encoded calldata for the transaction, if calling a smart contract. We suggest using viem's encodeFunctionData to prepare this value. |
As an example, you might batch together a transaction to approve a USDC spender and to transfer USDC like so:
tsx
const {client} = useSmartWallets();
const txHash = await client.sendTransaction({
account: client.account,
calls: [
// Approve transaction
{
to: USDC_ADDRESS,
data: encodeFunctionData({
abi: USDC_ABI,
functionName: 'approve',
args: ['insert-spender-address', BigInt(1e6)],
}),
},
// Transfer transaction
{
to: USDC_ADDRESS,
data: encodeFunctionData({
abi: USDC_ABI,
functionName: 'transfer',
args: ['insert-recipient-address', BigInt(1e6)],
}),
},
],
});
TIP
The smart wallet client will default to using the first configured chain that has a smart wallet network configuration.