Quick Start
Instantiate smart account & sending an user operation
This guide walks you through the bare essentials of initializing a Startale smart account and sending a basic user operation. It’s the fastest way to get started with account abstraction using the @startale-scs/aa-sdk
.
Setup
Create a new work directory and initialise a NodeJs project:
mkdir aa_test
cd aa_test
npm init -y
Install the dependencies:
npm i viem @startale-scs/aa-sdk typescript ts-node dotenv
Dependency explanation:
viem
and@startale-scs/aa-sdk
are used fo setting up and interacting with the smart contract accounttypescript
andts-node
compile and run the scriptdotenv
parses the .env file and makes the variables available
Create and fill the .env file
touch .env
echo "MAINNET_BUNDLER_URL=<your bundler URL> \
OWNER_PRIVATE_KEY=<your TEST private key>" > .env
Open the file and replace the text in <>
angle brackets with your data.
- You can obtain the Bundler url and api key by registering on the SCS portal.
- Use a NON-PRODUCTION private key, or generate one only for this purpose.
You can use this command to obtain the key (in the project directory):
node -e "import('viem/accounts').then(({generatePrivateKey}) => console.log(generatePrivateKey()))"
Add Typescript configuration
Create a tsconfig.json
and add the configuration.
Here's an example, but you can modify it to your needs:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@startale-scs/aa-sdk": ["./node_modules/@startale-scs/aa-sdk/dist/_types"]
},
"typeRoots": [
"./node_modules/@types",
"./node_modules/@startale-scs/aa-sdk/dist/_types",
"./src/types"
]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Create the script
Create a new file:
touch main.ts
Add imports
import "dotenv/config";
import { createPublicClient, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { soneiumMinato } from "viem/chains";
import { createSmartAccountClient, toStartaleSmartAccount } from "@startale-scs/aa-sdk";
Define variables
const bundlerUrl = process.env.MAINNET_BUNDLER_URL;
const privateKey = process.env.OWNER_PRIVATE_KEY;
if (!bundlerUrl || !privateKey) {
throw new Error("Missing environment variables");
}
const chain = soneiumMinato;
const publicClient = createPublicClient({ chain, transport: http() });
const signer = privateKeyToAccount(privateKey as Hex);
Instantiate the smart account and client
const startaleSmartAccount = await toStartaleSmartAccount({
signer,
chain,
transport: http(),
index: BigInt(0)
});
console.log("Smart account address: ", startaleSmartAccount.address);
const smartAccountClient = createSmartAccountClient({
account: startaleSmartAccount,
transport: http(bundlerUrl),
client: publicClient,
});
Note
Same
signer
and sameindex
value will always yield the same Smart Account address.
Construct and send the user operation
First we construct the call. In this case we're only sending 0 ETH back to the signer address.
This can be any contract call, but in that case the data needs to be constructed using viem
's encodeFunctionData()
.
const COUNTER_CONTRACT_ADDRESS = "0x2cf491602ad22944D9047282aBC00D3e52F56B37";
const counterCall = {
to: signer.address,
value: 0n,
data: "0x",
}
Next, send that call as a user operation:
const hash = await smartAccountClient.sendUserOperation({
calls: [
{
to: signer.address,
value: 0n,
data: "0x",
},
],
});
console.log("UserOp hash:", hash);
Running your script
The above code should be wrapped in an async function and called.
async function main() {
...
}
main();
You can now run the script:
npx ts-node main.ts
Note
First run will fail with the following message:
"sender balance and deposit together is 0 but must be at least XXXXX to pay for this operation"
That is expected, as your smart account doesn't have any funds.
You can now either send some ETH to the address logged (remember, same signer and same index will always instantiate the same account), or add a paymaster to sponsor the account.
To add a paymaster, follow the paymaster tutorial.
Full code example
In the full example we've added some UI libraries to make the output nicer.
To run it, you'll need to install them:
npm i ora chalk
Here's the main.ts
example:
import "dotenv/config";
import ora from "ora";
import chalk from "chalk";
import { createPublicClient, encodeFunctionData, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { soneiumMinato } from "viem/chains";
import { createSmartAccountClient, toStartaleSmartAccount } from "@startale-scs/aa-sdk";
const bundlerUrl = process.env.MAINNET_BUNDLER_URL;
const privateKey = process.env.OWNER_PRIVATE_KEY;
if (!bundlerUrl || !privateKey) {
throw new Error("Missing environment variables");
}
const chain = soneiumMinato;
const publicClient = createPublicClient({ chain, transport: http() });
const signer = privateKeyToAccount(privateKey as Hex);
const main = async () => {
console.log(chalk.blue("Startale Smart Account Example"));
const spinner = ora("Setting up...").start();
try {
const smartAccountClient = createSmartAccountClient({
account: await toStartaleSmartAccount({ signer, chain, transport: http(), index: BigInt(0) }),
transport: http(bundlerUrl),
client: publicClient,
});
const address = smartAccountClient.account.address;
spinner.succeed(`Smart Account initialized: ${address}`);
console.log("Make sure this account is funded before sending transactions!");
spinner.start("Sending basic User Operation...");
const hash = await smartAccountClient.sendUserOperation({
calls: [
{
to: signer.address,
value: 0n,
data: "0x",
},
],
});
console.log("UserOp hash:", hash);
} catch (err) {
spinner.fail(chalk.red(`Error: ${(err as Error).message}`));
}
process.exit(0);
};
main().catch((err) => {
console.error(chalk.red(`Unexpected error: ${(err as Error).message}`));
process.exit(1);
});
Updated 2 days ago