Skip to content

Wallet actions

Once your app is wrapped in OpenfortProvider and the user has an embedded wallet, you can send transactions through the smart account.

Choose your preferred approach:

  • Switch to use wagmi hooks (recommended) - Use wagmi's built-in hooks for better state management and type safety
  • Using provider directly - Use the wallet client directly without additional dependencies

This page shows the direct provider approach using the wallet_sendCalls RPC method directly through the wallet client.

Send an ERC-20 transfer

useSendUsdc.ts
import { useCallback } from 'react';
import { encodeFunctionData, parseUnits } from 'viem';
import { baseSepolia } from 'wagmi/chains';
import { useWalletClient } 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' }],
}];
 
export function useSendUsdc() {
  const { data: walletClient } = useWalletClient();
 
  return useCallback(async (to: `0x${string}`, amount: string) => {
    if (!walletClient || !walletClient.account) throw new Error('Wallet client not ready');
 
    const units = parseUnits(amount, 6);
    const data = encodeFunctionData({ abi: usdcAbi, functionName: 'transfer', args: [to, units] });
    const chainIdHex = `0x${(walletClient.chain?.id ?? baseSepolia.id).toString(16)}`;
 
    return await walletClient.request({
      method: 'wallet_sendCalls',
      params: [{
        version: '1.0',
        chainId: chainIdHex,
        from: walletClient.account.address,
        calls: [{ to: usdcAddress, value: '0x0', data }],
      }],
    });
  }, [walletClient]);
}

Use the hook in your component like this:

TransferButton.tsx
import { useState } from 'react';
import { useSendUsdc } from './useSendUsdc';
 
export function TransferButton() {
  const [isLoading, setIsLoading] = useState(false);
  const [txHash, setTxHash] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const sendUsdc = useSendUsdc();
 
  const handleTransfer = async () => {
    try {
      setIsLoading(true);
      setError(null);
      const result = await sendUsdc('0x742d35Cc6634C0532925a3b8D24ec3Ad3E5b6ca', '10.5');
      setTxHash(result);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Transfer failed');
    } finally {
      setIsLoading(false);
    }
  };
 
  return (
    <div>
      <button onClick={handleTransfer} disabled={isLoading}>
        {isLoading ? 'Sending...' : 'Send USDC'}
      </button>
      {txHash && <p>Transaction: {txHash}</p>}
      {error && <p>Error: {error}</p>}
    </div>
  );
}

For multi-step flows (e.g. ERC-20 approvals before deposits) follow the composition in the recipes mentioned above.

Sponsor gas from the dashboard

Keep the experience gasless—or recover costs in ERC-20 tokens—by attaching a gas policy created in the Openfort dashboard.

  1. Navigate to Dashboard → Sponsor policies, create a policy, and copy the generated pol_... identifier.
  2. Configure match rules (chain, method, contract) so only the intended wallet_sendCalls bundles qualify.
  3. Choose Pay gas for user to sponsor execution or Charge dynamic/fixed amount of ERC-20 to charge users before the transaction is sent.

Wire the policy into ethereumProviderPolicyId inside walletConfig so the SDK appends it automatically.

Providers.tsx
const config = createConfig(
  getDefaultConfig({
    appName: "Openfort demo",
    chains: [polygonAmoy],
    ssr: true,
  })
);
 
export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <OpenfortProvider
          publishableKey={"YOUR_OPENFORT_PUBLISHABLE_KEY"}
          walletConfig={{
            shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",
            createEncryptedSessionEndpoint: "YOUR_BACKEND_ENDPOINT",
            ethereumProviderPolicyId: {
              [polygonAmoy.id]: 'pol_...',
            },
          }}
        >
          {children}
        </OpenfortProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}

Dashboard edits apply immediately—matching wallet_sendCalls bundles will respect the latest sponsorship rules without redeploying your frontend.

More examples