Skip to main content

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:

  1. Execute contract interactions with sponsored gas
  2. Implement social recovery for account security
  3. 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