This page is the bridge between Installation and the feature tutorials. By the end you will have a smart account, an account client wired to the SCS bundler and paymaster, and a place to keep them in your app’s state.
What you will build
StartaleAccountClient is the object you actually call sendUserOperation and ERC-7579 module actions on. The smart account and paymaster clients are wired into it once at setup time.
Account creation
import { http , createPublicClient } from "viem"
import { soneiumMinato } from "viem/chains"
import { toStartaleSmartAccount } from "@startale-scs/aa-sdk"
const publicClient = createPublicClient ({ chain: soneiumMinato , transport: http () })
const account = await toStartaleSmartAccount ({
signer ,
chain: soneiumMinato ,
transport: http (),
index: 0 n ,
})
Parameter Required Source Notes signeryes your auth provider LocalAccount, viem WalletClient, EIP-1193 EthereumProvider, or EthersWallet. See Installation .chainyes viem/chainsUse soneiumMinato for testnet and soneium for mainnet. transportyes viemRPC transport used internally for chain reads (nonces, code). indexoptional your code A bigint salt that determines the counterfactual address. Different index values with the same signer produce different accounts. accountAddressoptional your code Pin a known smart account address. Useful for EIP-7702 delegation and for skipping address derivation. validators, executors, prevalidationHooks, hook, fallbacksoptional @startale-scs/aa-sdkERC-7579 modules to install at deployment time. See Smart sessions and Social recovery for module examples.
Why index matters. Smart accounts are deployed via CREATE2, so the (signer, index) pair determines the account address before deployment. Use a stable index per user (often 0n) to keep one account per signer; vary it when you intentionally want sub-accounts under the same signer.
Wrap the account, the paymaster, and the bundler transport in a single client:
import { http } from "viem"
import {
createSCSPaymasterClient ,
createSmartAccountClient ,
} from "@startale-scs/aa-sdk"
const paymasterClient = createSCSPaymasterClient ({
transport: http ( process . env . PAYMASTER_URL ! ),
})
const smartAccountClient = createSmartAccountClient ({
account ,
transport: http ( process . env . BUNDLER_URL ! ),
client: publicClient ,
paymaster: paymasterClient ,
paymasterContext: {
paymasterId: process . env . PAYMASTER_ID ! ,
},
})
Symbol Source Role createSCSPaymasterClient@startale-scs/aa-sdkConnects to the SCS Paymaster RPC. Returns a SCSPaymasterClient. createSmartAccountClient@startale-scs/aa-sdkBuilds a StartaleAccountClient with bundler, paymaster, and ERC-7579 module actions. paymasterContext.paymasterIdSCS Portal The id of the paymaster you provisioned. Both managed and self-funded paymasters return one.
Account client (ERC-20 gas)
To accept ERC-20 tokens as gas instead of sponsoring, swap the paymasterId field for a token address:
const tokenAccountClient = createSmartAccountClient ({
account ,
transport: http ( process . env . BUNDLER_URL ! ),
client: publicClient ,
paymaster: paymasterClient ,
paymasterContext: {
token: "0xfF0CBFbA43a1Ce2B8d72B2f3121558BcBd4B03a6" , // USDC on Soneium Minato
},
})
The supported tokens per network are listed in Supported networks . The full token paymaster flow, including quotes and approvals, is covered in the ERC-20 paymaster tutorial .
Persisting the client in a React app
A common pattern is to lift the smart account and clients into a context provider so they survive component remounts and are available everywhere in the tree.
import { createContext , useContext , useEffect , useState , type ReactNode } from "react"
import { http , createPublicClient } from "viem"
import { soneiumMinato } from "viem/chains"
import {
createSCSPaymasterClient ,
createSmartAccountClient ,
toStartaleSmartAccount ,
type StartaleSmartAccount ,
type StartaleAccountClient ,
} from "@startale-scs/aa-sdk"
type StartaleContextValue = {
account ?: StartaleSmartAccount
sponsoredClient ?: StartaleAccountClient
tokenClient ?: StartaleAccountClient
isReady : boolean
error ?: string
}
const StartaleContext = createContext < StartaleContextValue >({ isReady: false })
export function StartaleProvider ({ signer , children } : { signer : unknown ; children : ReactNode }) {
const [ value , setValue ] = useState < StartaleContextValue >({ isReady: false })
useEffect (() => {
if ( ! signer ) return
let cancelled = false
;( async () => {
try {
const publicClient = createPublicClient ({ chain: soneiumMinato , transport: http () })
const account = await toStartaleSmartAccount ({
signer: signer as never ,
chain: soneiumMinato ,
transport: http (),
index: 0 n ,
})
const paymasterClient = createSCSPaymasterClient ({
transport: http ( process . env . NEXT_PUBLIC_PAYMASTER_URL ! ),
})
const sponsoredClient = createSmartAccountClient ({
account ,
transport: http ( process . env . NEXT_PUBLIC_BUNDLER_URL ! ),
client: publicClient ,
paymaster: paymasterClient ,
paymasterContext: {
paymasterId: process . env . NEXT_PUBLIC_PAYMASTER_ID ! ,
},
})
const tokenClient = createSmartAccountClient ({
account ,
transport: http ( process . env . NEXT_PUBLIC_BUNDLER_URL ! ),
client: publicClient ,
paymaster: paymasterClient ,
paymasterContext: {
token: process . env . NEXT_PUBLIC_GAS_TOKEN_ADDRESS as `0x ${ string } ` ,
},
})
if ( ! cancelled ) setValue ({ account , sponsoredClient , tokenClient , isReady: true })
} catch ( err ) {
if ( ! cancelled ) setValue ({ isReady: false , error: ( err as Error ). message })
}
})()
return () => {
cancelled = true
}
}, [ signer ])
return < StartaleContext.Provider value = { value } > { children } </ StartaleContext.Provider >
}
export const useStartale = () => useContext ( StartaleContext )
Hook / type Source Role StartaleSmartAccount@startale-scs/aa-sdkType alias for the account returned by toStartaleSmartAccount. StartaleAccountClient@startale-scs/aa-sdkType alias for the client returned by createSmartAccountClient.
Environment variables in this snippet use a framework-agnostic placeholder. The SDK does not require any specific prefix; pick whichever your framework expects (process.env.X, import.meta.env.X, runtime config, etc.).
Useful account methods
Once you have the account, you can call these without building a UserOperation first:
account . address // counterfactual address
await account . getAddress () // resolved onchain address (deploys if needed)
await account . getNonce ({ key: 0 n }) // ERC-7579 two-dimensional nonce
await account . isDelegated () // EIP-7702 delegation check
await account . unDelegate () // EIP-7702 reset
Next steps
Contract interactions Send single and batched calls through sendUserOperation.
Sponsored paymaster Send sponsored UserOperations end to end with policies.
ERC-20 paymaster Charge users in ASTR, USDC, or other tokens.
Smart sessions Reduce signature friction with scoped session keys.