Skip to main content
Create a smart account and send a crosschain transaction in a Node.js script. No browser or wallet connection required.

Prerequisites

You don’t need an API key to get started. For production use, you’ll need one. For production setups that sponsor user fees or use dashboard API keys, see the Security guide to proxy Orchestrator requests via a backend and avoid exposing your key.
You will need some testnet funds. To get testnet ETH, you can use a faucet from Quicknode or Alchemy. To get testnet USDC, use Circle Faucet. Install the Rhinestone SDK:
npm install viem @rhinestone/sdk
1

Create a Wallet

Let’s create a smart account with a single owner:
import { RhinestoneSDK } from '@rhinestone/sdk'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { baseSepolia, arbitrumSepolia } from 'viem/chains'
import {
  createPublicClient,
  createWalletClient,
  encodeFunctionData,
  erc20Abi,
  type Hex,
  http,
  parseEther,
} from 'viem'

const fundingPrivateKey = process.env.FUNDING_PRIVATE_KEY
if (!fundingPrivateKey) {
  throw new Error('FUNDING_PRIVATE_KEY is not set')
}

const sourceChain = baseSepolia
const targetChain = arbitrumSepolia

// You can use an existing PK here
const privateKey = generatePrivateKey()
console.log(`Owner private key: ${privateKey}`)
const account = privateKeyToAccount(privateKey)

const rhinestone = new RhinestoneSDK({
  apiKey: process.env.RHINESTONE_API_KEY,
})
const rhinestoneAccount = await rhinestone.createAccount({
  owners: {
    type: 'ecdsa',
    accounts: [account],
  },
})
const address = rhinestoneAccount.getAddress()
console.log(`Smart account address: ${address}`)
2

Fund the Account

Send ETH to the smart account. The Orchestrator handles deployment and token conversion on the destination chain.
const publicClient = createPublicClient({
  chain: sourceChain,
  transport: http(),
})
const fundingAccount = privateKeyToAccount(fundingPrivateKey as Hex)
const fundingClient = createWalletClient({
  account: fundingAccount,
  chain: sourceChain,
  transport: http(),
})

const txHash = await fundingClient.sendTransaction({
  to: address,
  value: parseEther('0.001'),
})
await publicClient.waitForTransactionReceipt({ hash: txHash })
3

Send a Cross-chain Transaction

Finally, let’s make a cross-chain token transfer:
const usdcAmount = 1n

const transaction = await rhinestoneAccount.sendTransaction({
  sourceChains: [sourceChain],
  targetChain,
  calls: [
    {
      to: 'USDC',
      value: 0n,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'transfer',
        args: ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', usdcAmount],
      }),
    },
  ],
  tokenRequests: [
    {
      address: 'USDC',
      amount: usdcAmount,
    },
  ],
})
console.log('Transaction', transaction)

const transactionResult = await rhinestoneAccount.waitForExecution(transaction)
console.log('Result', transactionResult)
After running that, you will get a smart account deployed on both Base Sepolia and Arbitrum Sepolia, and make a cross-chain USDC transfer.Note that you don’t need to manage the gas tokens or do the ETH → USDC swap when making a transfer. The Orchestrator will handle that for you!
Building a browser app? Use the Reown + Rhinestone example to get wallet connection working with MetaMask or any WalletConnect-compatible wallet.
Going to production? Never expose your Rhinestone API key in the browser. Read the Security guide to proxy Orchestrator requests safely via a backend.

Next Steps

Sponsor fees

Cover gas, bridging, and swap fees for your users across any chain.

Smart account setup

Configure signers: passkeys, embedded wallets, multisig, and more.

Smart Sessions

Add session keys for one-click UX and automated transactions.

EIP-7702

Add smart account features to an existing EOA without changing its address.