Smart Account Setup
This tutorial shows you how to create and manage smart accounts using the Startale AA SDK. We'll build a React provider that handles smart account creation, client initialization, and state management.
Overview
Smart accounts are the foundation of Account Abstraction. Unlike EOAs (Externally Owned Accounts), smart accounts:
- Can be controlled by custom logic
- Support sponsored transactions via paymasters
- Enable modular functionality through ERC-7579 modules
- Allow flexible signature validation
Creating the Smart Account Provider
Let's build a comprehensive provider that manages smart account state:
providers/StartaleAccountProvider.tsx
import { createContext, useContext, useEffect, useState } from 'react'
import { useDynamicContext, useIsLoggedIn } from '@dynamic-labs/sdk-react-core'
import {
type StartaleAccountClient,
type StartaleSmartAccount,
createSCSPaymasterClient,
createSmartAccountClient,
toStartaleSmartAccount,
} from '@startale-scs/aa-sdk'
import { http, createPublicClient } from 'viem'
import { soneiumMinato } from 'viem/chains'
import { AA_CONFIG } from '../config/aa'
const chain = soneiumMinato
const publicClient = createPublicClient({
transport: http(AA_CONFIG.RPC_URL),
chain
})
type StartaleContextType = {
// Account & Client
startaleAccount?: StartaleSmartAccount
startaleClient?: StartaleAccountClient
startaleTokenClient?: StartaleAccountClient
// State
isLoading: boolean
error?: string
// Actions
createAccount: () => Promise<void>
logout: () => void
}
const StartaleContext = createContext<StartaleContextType | null>(null)
export function useStartale() {
const ctx = useContext(StartaleContext)
if (!ctx) throw new Error('useStartale must be used within StartaleProvider')
return ctx
}
Account Creation Logic
providers/StartaleAccountProvider.tsx (continued)
export function StartaleProvider({ children }: { children: React.ReactNode }) {
const { primaryWallet } = useDynamicContext()
const authenticated = useIsLoggedIn()
// State
const [startaleAccount, setStartaleAccount] = useState<StartaleSmartAccount>()
const [startaleClient, setStartaleClient] = useState<StartaleAccountClient>()
const [startaleTokenClient, setStartaleTokenClient] = useState<StartaleAccountClient>()
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string>()
const createAccount = async () => {
if (!primaryWallet) {
throw new Error('No wallet connected')
}
setIsLoading(true)
setError(undefined)
try {
// Get wallet client from Dynamic
const walletClient = await primaryWallet.getWalletClient()
// Create Startale smart account
const account = await toStartaleSmartAccount({
signer: walletClient,
chain,
transport: http(),
index: BigInt(813367), // Unique account index
})
setStartaleAccount(account)
console.log('Smart account created:', account.address)
// Initialize clients
await initializeClients(account)
} catch (err) {
console.error('Account creation failed:', err)
setError((err as Error).message)
} finally {
setIsLoading(false)
}
}
const initializeClients = async (account: StartaleSmartAccount) => {
// Create paymaster client
const paymasterClient = createSCSPaymasterClient({
transport: http(AA_CONFIG.PAYMASTER_URL),
})
// Sponsored transactions client
const sponsoredClient = createSmartAccountClient({
account,
transport: http(AA_CONFIG.BUNDLER_URL),
client: publicClient,
paymaster: paymasterClient,
paymasterContext: {
calculateGasLimits: true,
paymasterId: 'pm_test_managed'
},
})
// ERC-20 token payment client
const tokenClient = createSmartAccountClient({
account,
transport: http(AA_CONFIG.BUNDLER_URL),
client: publicClient,
paymaster: paymasterClient,
paymasterContext: {
calculateGasLimits: true,
token: AA_CONFIG.ASTR_TOKEN_ADDRESS
},
})
setStartaleClient(sponsoredClient)
setStartaleTokenClient(tokenClient)
console.log('Clients initialized successfully')
}
const logout = () => {
setStartaleAccount(undefined)
setStartaleClient(undefined)
setStartaleTokenClient(undefined)
setError(undefined)
}
// Auto-create account when wallet connects
useEffect(() => {
if (authenticated && primaryWallet && !startaleAccount) {
createAccount()
}
}, [authenticated, primaryWallet?.address])
// Cleanup on logout
useEffect(() => {
if (!authenticated) {
logout()
}
}, [authenticated])
return (
<StartaleContext.Provider
value={{
startaleAccount,
startaleClient,
startaleTokenClient,
isLoading,
error,
createAccount,
logout,
}}
>
{children}
</StartaleContext.Provider>
)
}
Using the Smart Account
Here's how to use the smart account in your components:
components/AccountInfo.tsx
import { useStartale } from '../providers/StartaleAccountProvider'
export function AccountInfo() {
const {
startaleAccount,
startaleClient,
isLoading,
error
} = useStartale()
if (isLoading) return <div>Creating smart account...</div>
if (error) return <div>Error: {error}</div>
if (!startaleAccount) return <div>No account created</div>
return (
<div className="account-info">
<h3>Smart Account Details</h3>
<p><strong>Address:</strong> {startaleAccount.address}</p>
<p><strong>Status:</strong> {startaleClient ? 'Ready' : 'Initializing...'}</p>
</div>
)
}
Key Concepts
Account Index
The index
parameter creates deterministic account addresses. Using the same signer and index will always generate the same smart account address.
// Different indices create different accounts for the same signer
const account1 = await toStartaleSmartAccount({ signer, index: BigInt(0) })
const account2 = await toStartaleSmartAccount({ signer, index: BigInt(1) })
Paymaster Contexts
Different paymaster contexts enable different payment methods:
// Sponsored transactions (dApp pays gas)
const sponsoredContext = {
calculateGasLimits: true,
paymasterId: 'pm_test_managed'
}
// ERC-20 token payments (user pays with tokens)
const tokenContext = {
calculateGasLimits: true,
token: '0x...' // ERC-20 token address
}
Client Types
- Regular Client: For sponsored transactions
- Token Client: For ERC-20 token-based gas payments
- Both support the same operations but with different payment methods
Next Steps
With your smart account set up, you can now:
- Execute contract interactions with sponsored gas
- Implement social recovery for account security
- Add smart sessions for seamless UX
Troubleshooting
Account creation fails:
- Check that your wallet is properly connected via Dynamic Labs
- Verify the RPC URL and network configuration
- Ensure the bundler and paymaster URLs are correct
Client initialization errors:
- Verify API keys in bundler and paymaster URLs
- Check network connectivity
- Ensure paymaster context is properly formatted