Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.startale.com/llms.txt

Use this file to discover all available pages before exploring further.

In about five minutes you will deploy a smart account on Soneium Minato and send a sponsored UserOperation to a counter contract using nothing but Node.js and the Startale AA SDK.
The full SDK source is at StartaleGroup/scs-aa-sdk. Every symbol referenced below comes from @startale-scs/aa-sdk or viem.

Prerequisites

1

Node.js 18 or newer

Verify with node -v. The SDK targets modern ES2022 output.
2

An EOA private key for development

Any local key works. The signer is only used to sign UserOperations; the smart account is a separate address.
3

An SCS Portal API key and paymaster

Follow Portal setup to issue an API key, create a managed or self-funded paymaster, and copy the resulting bundlerUrl, paymasterUrl, and paymasterId.
4

A target contract on Soneium Minato

Any deployed contract with a public method works. The example uses a count() method on a Counter contract; substitute your own ABI and selector.

1. Initialize the project

mkdir aa-quickstart && cd aa-quickstart
npm init -y
npm install @startale-scs/aa-sdk viem dotenv
npm install -D typescript ts-node @types/node
These are the only runtime dependencies you need for the script in this guide. @startale-scs/aa-sdk carries the smart account and clients; viem provides chain definitions, transports, and ABI helpers; dotenv loads your bundler and paymaster URLs from .env.
Create a .env file alongside package.json:
.env
BUNDLER_URL="https://soneium-minato.bundler.scs.startale.com?apikey=YOUR_API_KEY"
PAYMASTER_URL="https://paymaster.scs.startale.com/v1?apikey=YOUR_API_KEY"
PAYMASTER_ID="pm_..."
OWNER_PRIVATE_KEY="0xYourPrivateKey"
COUNTER_CONTRACT_ADDRESS="0xYourCounterContract"

2. Wire up the script

Create index.ts:
index.ts
import "dotenv/config"
import {
  type Address,
  createPublicClient,
  encodeFunctionData,
  http,
} from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { soneiumMinato } from "viem/chains"
import {
  createSCSPaymasterClient,
  createSmartAccountClient,
  toStartaleSmartAccount,
} from "@startale-scs/aa-sdk"

const chain = soneiumMinato
const signer = privateKeyToAccount(process.env.OWNER_PRIVATE_KEY as `0x${string}`)

const publicClient = createPublicClient({ chain, transport: http() })

const paymasterClient = createSCSPaymasterClient({
  transport: http(process.env.PAYMASTER_URL!),
})

const smartAccountClient = createSmartAccountClient({
  account: await toStartaleSmartAccount({
    signer,
    chain,
    transport: http(),
    index: 0n,
  }),
  transport: http(process.env.BUNDLER_URL!),
  client: publicClient,
  paymaster: paymasterClient,
  paymasterContext: {
    paymasterId: process.env.PAYMASTER_ID!,
  },
})

console.log("smart account address:", smartAccountClient.account.address)

What every import does

SymbolSourceRole
soneiumMinatoviem/chainsPre-baked viem Chain for Soneium Minato (chain id 1946). Replace with soneium for mainnet.
privateKeyToAccountviem/accountsTurns a hex private key into a viem LocalAccount that the SDK accepts as a signer.
createPublicClient / httpviemBuilds a read-only RPC client and HTTP transport used by the smart account for nonce and code reads.
encodeFunctionDataviemEncodes a function call to ABI-encoded 0x calldata for the UserOperation calls[].data field. Used in step 3.
createSCSPaymasterClient@startale-scs/aa-sdkConnects to the SCS Paymaster RPC and exposes paymaster actions. Pass either a paymasterUrl, a viem transport, or a chainId + apiKey.
toStartaleSmartAccount@startale-scs/aa-sdkBuilds the ERC-7579 smart account from the signer. The index parameter is a bigint salt that determines the counterfactual address; using a different index with the same signer yields a different account.
createSmartAccountClient@startale-scs/aa-sdkWraps the account with bundler, paymaster, and ERC-7579 module actions. The returned StartaleAccountClient is what you call sendUserOperation on.
smartAccountClient.account.address returns the counterfactual address of the smart account. The contract is not deployed until the first UserOperation lands; the bundler will deploy it for you on the first call. With sponsored gas you do not need to fund this address yourself, but if you ever skip the paymaster you must send some ETH there first.

3. Send a sponsored UserOperation

Append the following to index.ts. Replace the ABI fragment with the ABI of the function you want to call.
index.ts (continued)
const counterAbi = [
  { name: "count", type: "function", stateMutability: "nonpayable", inputs: [], outputs: [] },
] as const

const callData = encodeFunctionData({
  abi: counterAbi,
  functionName: "count",
})

const hash = await smartAccountClient.sendUserOperation({
  calls: [
    {
      to: process.env.COUNTER_CONTRACT_ADDRESS as Address,
      value: 0n,
      data: callData,
    },
  ],
})

const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash })
console.log("UserOperation hash:", hash)
console.log("Receipt:", receipt)

What is happening here

  1. encodeFunctionData turns the count() selector into ABI-encoded calldata, exactly as if you were calling publicClient.writeContract.
  2. sendUserOperation packs the call into a UserOperation, asks the paymaster to co-sign it, signs it with your signer, and submits it to the SCS Bundler.
  3. waitForUserOperationReceipt polls the bundler until the UserOperation is mined and returns the inclusion receipt with the resolved transaction hash.
Run it:
npx ts-node index.ts
You should see the smart account address, the UserOperation hash, and the receipt with success: true.

Common adjustments

Push more entries onto the calls array. They run atomically in the order you pass them; if any reverts, the whole UserOperation reverts.
await smartAccountClient.sendUserOperation({
  calls: [
    { to: tokenA, data: approveCalldata, value: 0n },
    { to: dex, data: swapCalldata, value: 0n },
  ],
})
Swap paymasterId for a token address from the supported tokens table.
paymasterContext: {
  token: "0xfF0CBFbA43a1Ce2B8d72B2f3121558BcBd4B03a6",
}
See the ERC-20 paymaster tutorial for the full quote-and-execute flow.
Drop the paymaster and paymasterContext keys from createSmartAccountClient. You then need to fund the smart account address with ETH on Soneium Minato so that the EntryPoint can debit gas from the account itself.
toStartaleSmartAccount accepts any of LocalAccount, viem WalletClient, EthersWallet, or an EthereumProvider (EIP-1193). See Installation and setup for the full signer matrix.

Next steps

Tutorials path

Move from a script to a real React app: signer setup, provider context, contract interactions, sessions, and recovery.

Smart account setup

Wire toStartaleSmartAccount into a React provider so the account survives component remounts.

Sponsored paymaster

Build a real sponsored flow with a managed or self-funded paymaster and gas policies.

EIP-7702 delegation

Reuse the same script against an existing EOA address by delegating it to the Startale account implementation.