Run a Universal Account end-to-end from a single Node script in ~5 minutes.
This quickstart runs a Universal Account end-to-end from one Node script (no frontend, no login UI). Install the SDK, initialize a UA from a dev wallet, fetch its unified balance, and send a cross-chain transfer.For dApp flows with social login or a connected browser wallet, jump to the Reference implementation, Integrate with Browser Wallets, or Using Magic’s API Wallet.
Notice: Universal Accounts are upgrading to V2. This will require a change in your app’s account system.With this migration underway, you need your users to withdraw all funds from their old (existing) account(s). The next version will be deployed shortly, allowing you to resume operations with users creating new accounts.Withdrawals will still be available past this date.Assets can be withdrawn to any account the user controls, but only via the createTransferTransaction method.
A dev/test wallet private key — used to sign transactions in this script.
Dev / test only. The script below loads a private key from an environment variable. That’s fine for a local smoke test, but this pattern is not production-safe:
Never commit .env files or hard-code keys to source control.
Production keys belong in a KMS, HSM, or secret manager (AWS KMS, GCP KMS, HashiCorp Vault, etc.).
For a user-facing app, the signer should be a real wallet flow — Particle Auth, a connected browser wallet, or an embedded wallet provider. See From server to browser below.
Create script.ts. Initialize an ethers.Wallet from your test key, then pass its address to the Universal Account constructor:
script.ts
import "dotenv/config";import { Wallet, getBytes } from "ethers";import { CHAIN_ID, UniversalAccount } from "@particle-network/universal-account-sdk";// Dev/test wallet — do NOT use a production key hereconst wallet = new Wallet(process.env.PRIVATE_KEY!);const ua = new UniversalAccount({ projectId: process.env.PROJECT_ID!, projectClientKey: process.env.CLIENT_KEY!, projectAppUuid: process.env.APP_ID!, ownerAddress: wallet.address, tradeConfig: { slippageBps: 100, // 1% slippage tolerance },});
Only the EOA address is needed at construction. The wallet comes back in for signing in step 4.
getPrimaryAssets() returns the Primary Assets the UA holds across every supported chain, plus an aggregated USD total. A non-zero totalAmountInUSD confirms the UA is wired end-to-end:
The UA does not need USDT, gas, or any specific asset on Arbitrum. The SDK sources liquidity from the Primary Assets the UA holds across chains and routes them automatically.
Run the script:
Terminal
npx tsx script.ts
That’s it! You’ve sent your first chain-abstracted transaction from a terminal.
In a real app, the signer is rarely a private key on your backend. The UA construction and transaction APIs stay identical — only the signer changes. Swap ethers.Wallet for one of: