> ## Documentation Index
> Fetch the complete documentation index at: https://developers.particle.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Web (JS/TS): Universal Accounts SDK

> Implementing Universal Accounts in your application.

<Warning>
  Notice: Universal Accounts are upgrading to V2. This will require a change in account system for your app.

  We've begun the migration and need your users to withdraw all funds from the old account. The next version will be deployed shortly after and you can resume operations.

  Withdrawals will be available past this date, but you will need to migrate your users to the new account system.

  Assets can be withdrawn to any account the user controls, but only via the `createTransferTransaction` method.

  This does not apply to 7702-based Universal Accounts.
</Warning>

The **Universal Accounts SDK** provides **chain abstraction** for your app by integrating Universal Accounts. These offer your users a single account, balance, and interaction point across **EVM chains** and **Solana**.

Our SDK integrates with existing connection flows with minimal setup.

<Card title="Learn More About Universal Accounts" icon="user" href="/intro/universal-accounts">
  What are Universal Accounts, how do they work, and what problems do they solve?
</Card>

<button onClick={() => window.openNavatticDemo && window.openNavatticDemo()} className="mt-4 px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg font-medium transition-colors cursor-pointer">
  Try Interactive Demo
</button>

To integrate Universal Accounts:

<Info>
  The following examples use a React-based app alongside [**Particle Auth**](/social-logins/auth/introduction) for authentication and wallet connection.

  However, the **Universal Accounts SDK is provider-agnostic**. You can also use [**Particle Connect**](/social-logins/connect/introduction), **Web3Modal**, **RainbowKit**, or any signer.

  The SDK can also be used server-side to construct and sign transactions programmatically.
</Info>

<Steps>
  <Step title="Connect a user's account">
    A user logs in by connecting a wallet or via a social login.
  </Step>

  <Step title="Understand account modes">
    Universal Accounts support two execution modes. Choosing the right one determines how the user’s account behaves.
  </Step>

  <Step title="Initialize Universal Accounts">
    Once connected, pass the user's EOA address to the SDK and configure your project details.
  </Step>

  <Step title="Use the UA instance">
    Use the returned Universal Account instance to fetch data and send transactions across chains.

    When sending a transaction, the SDK creates a **UserOperation** and returns a `rootHash`.\
    This hash must be **signed by the connected EOA**, then passed back into `sendTransaction()` to broadcast.
  </Step>
</Steps>

Under the hood, all routing, bridging, and gas abstraction are handled by Particle Network's infrastructure.

## Getting Started

### Installation

Once your app is set up, install the **Universal Accounts SDK**:

<Info>
  The SDK depends on `ethers.js` internally, but you are not required to use it directly. You can use any provider or signer that fits your setup.
</Info>

<CodeGroup>
  ```bash yarn theme={null}
  yarn add @particle-network/universal-account-sdk ethers
  ```

  ```bash npm theme={null}
  npm install @particle-network/universal-account-sdk ethers
  ```
</CodeGroup>

## Account Modes

Universal Accounts can operate in two modes, depending on your wallet setup and execution environment:

### 7702 Mode (Default)

Within this mode:

* Your user’s existing EOA is upgraded to act directly as a Universal Account.
* The EOA address and Universal Account address are the same.
* Assets already held at the user's EOA are immediately usable on their Universal Account—without transferring assets.
* No smart account deployments needed.

This mode provides the lowest friction and is recommended for most applications.

### Smart Account Mode

Within this mode:

* A separate smart account is attached to your user's EOA.
* The smart account has its own address, separate from the EOA.
* Users must transfer assets to the smart account before using it.

This mode exists primarily for compatibility with JSON-RPC wallets.

## Initializing and Configuring Universal Accounts

To configure Universal Accounts:

1. Import the UniversalAccount class in your app:

```ts theme={null}
import {
  UniversalAccount,
  UNIVERSAL_ACCOUNT_VERSION,
} from "@particle-network/universal-account-sdk";
```

2. The Universal Accounts SDK requires Particle project credentials from the [Particle Dashboard](https://dashboard.particle.network/). To retrieve your project credentials:

<AccordionGroup>
  <Accordion title="Access the Particle Dashboard">
    <div className="flex justify-center">
      <img className="block h-64 dark:hidden" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/login.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=82a68a9e5fce546a26db56e74d7c3b94" alt="Login into Particle." width="458" height="453" data-path="social-logins/images/login.png" />

      <img className="hidden h-64 dark:block" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/login.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=82a68a9e5fce546a26db56e74d7c3b94" alt="Login into Particle." width="458" height="453" data-path="social-logins/images/login.png" />
    </div>
  </Accordion>

  <Accordion title="Create or open a project">
    <div className="flex justify-center">
      <img className="block h-64 dark:hidden" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/project.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=1b5cd03116f224e1cba3a88a04a1f0a5" alt="Create Particle project." width="939" height="657" data-path="social-logins/images/project.png" />

      <img className="hidden h-64 dark:block" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/project.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=1b5cd03116f224e1cba3a88a04a1f0a5" alt="Create Particle project." width="939" height="657" data-path="social-logins/images/project.png" />
    </div>
  </Accordion>

  <Accordion title="Create a web application (or skip if already created)">
    <div className="flex justify-center">
      <img className="block h-64 dark:hidden" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/web-app.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=8f51019e7b9968aca5528541f819eb4e" alt="Create web app." width="584" height="369" data-path="social-logins/images/web-app.png" />

      <img className="hidden h-64 dark:block" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/web-app.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=8f51019e7b9968aca5528541f819eb4e" alt="Create web app." width="584" height="369" data-path="social-logins/images/web-app.png" />
    </div>
  </Accordion>

  <Accordion title="Retrieve project credentials">
    <div className="flex justify-center">
      <img className="block h-64 dark:hidden" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/credentials.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=c196b87c62dc17aae0f624035e3a2c19" alt="Find app credentials." width="1039" height="800" data-path="social-logins/images/credentials.png" />

      <img className="hidden h-64 dark:block" src="https://mintcdn.com/particlenetwork-fccf74d2/hR-XK15Ve4E3bk8E/social-logins/images/credentials.png?fit=max&auto=format&n=hR-XK15Ve4E3bk8E&q=85&s=c196b87c62dc17aae0f624035e3a2c19" alt="Find app credentials." width="1039" height="800" data-path="social-logins/images/credentials.png" />
    </div>
  </Accordion>
</AccordionGroup>

3. Then, initialize Universal Accounts using your preferred mode:

<Note>
  **EIP-7702 is the default mode for Universal Accounts.**\
  In this mode, the EOA used for authentication is upgraded to act directly as the Universal Account. However, if you wish to use Smart Account mode, simply set "useEIP7702" to "false".\
  \
  The section below dives deeper into the differences between both modes.
</Note>

```ts theme={null}
const ua = new UniversalAccount({
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  projectClientKey: process.env.NEXT_PUBLIC_CLIENT_KEY!,
  projectAppUuid: process.env.NEXT_PUBLIC_APP_ID!,
  smartAccountOptions: {
    // 7702 mode: the EOA address itself becomes the Universal Account
    useEIP7702: true,
    name: "UNIVERSAL",
    version: UNIVERSAL_ACCOUNT_VERSION,
    ownerAddress: wallet.address,
  },
  // Optional: defaults to auto-slippage
  tradeConfig: {
    slippageBps: 100, // 1% slippage tolerance
    // usePrimaryTokens: [SUPPORTED_TOKEN_TYPE.SOL], // Only for swaps
  },
});
```

4. You can now use the `ua` instance to fetch Universal Account data (addresses and unified balances) and to send transactions across supported chains.

### About 7702 Mode (Default)

In 7702 mode, the EOA address used for authentication is the Universal Account.

All Universal Account features—such as chain abstraction, unified balances, and gas abstraction—are applied directly to the user’s original EOA.

<Warning>
  **Current limitation:**

  * 7702 mode is only available in server-side environments and embedded wallets that support the authorization methods.
  * JSON-RPC wallets are not supported at the moment.
</Warning>

Using 7702 mode requires you to authorize the user's EOA to act as the Universal Account on the specified chain before transacting.

Once this delegation is successful, all subsequent transactions will automatically execute on that chain.\
\
To authorize the 7702 mode automatically on new chains, run the below pattern when sending transactions:

```ts theme={null}
const universalAccount = new UniversalAccount(universalAccountConfig);

const transaction = await universalAccount.createConvertTransaction({
    expectToken: { type: SUPPORTED_TOKEN_TYPE.USDT, amount: '0.0001' },
    chainId: CHAIN_ID.BSC_MAINNET,
});

// Handle 7702 Authorization
const authorizations: EIP7702Authorization[] = [];
const nonceMap = new Map<number, string>();
for (const userOp of transaction.userOps) {
    if (!!userOp.eip7702Auth && !userOp.eip7702Delegated) {
        let signature = nonceMap.get(userOp.eip7702Auth.nonce);
        if (!signature) {
            const authorization = wallet.authorizeSync(userOp.eip7702Auth);
            signature = authorization.signature.serialized;
            nonceMap.set(userOp.eip7702Auth.nonce, signature);
        }
        authorizations.push({
            userOpHash: userOp.userOpHash,
            signature: signature,
        });
    }
}
```

<CardGroup cols="2">
  <Card title="Serverside examples using EIP-7702" icon="binary" href="https://github.com/Particle-Network/universal-account-example/blob/main/examples/7702-convert-evm.ts">
    Find serverside examples using Universal Accounts in EIP-7702 mode.
  </Card>

  <Card title="EIP-7702 compatible embedded wallets" icon="wallet" href="/universal-accounts/ua-reference/desktop/eip7702-wallets">
    Demos and integration notes for Dynamic, Magic, and Privy embedded wallets with 7702 mode.
  </Card>
</CardGroup>

### Verifying EIP-7702 delegation

To check whether EIP-7702 delegation is active for a Universal Account, query the registered deployments:

```ts theme={null}
const deployments = await universalAccount.getEIP7702Deployments();
console.log(deployments);
```

This call returns an array of EIP-7702 deployment records, including the delegated contract addresses and their current status for each chain.

### Smart Account Mode (JSON-RPC wallets)

This mode exists for compatibility with JSON-RPC wallets, but does not provide the same zero-friction experience as 7702 mode.\
\
As mentioned above, if you need to support Smart Account mode, simply change the following variable upon initialization:

```ts theme={null}
useEIP7702: false
```

In this mode:

* A separate smart account is created and attached to the EOA.
* The smart account has its own address.
* Users must transfer assets to the smart account before use.

### Mode Comparison

| Mode           | Account Address | Asset Transfer Required | JSON-RPC Support |
| -------------- | --------------- | ----------------------- | ---------------- |
| 7702 (default) | Same as EOA     | No                      | No               |
| Smart Account  | Separate        | Yes                     | Yes              |

## Check Out UA Initialization in This Sample Repository

<Card title="Check Out UA Initialization in This Sample Repository" icon="link" href="https://github.com/soos3d/auth-universal-accounts/blob/117dc53daea1b2bc9017e595bc5337e204171f69/auth-universal-demo/app/page.tsx#L43">
  Sample Next.js app using Particle Auth with Universal Accounts.
</Card>

#### Control which tokens are used for swaps

When initializing a **Universal Account**, you can control which tokens are eligible to be used as the source for swap operations.

To do this, set the `usePrimaryTokens` field inside the `tradeConfig` object. This lets you restrict swap logic to specific tokens (e.g. only allow SOL to be spent, not USDT or ETH).

Example:

```ts theme={null}
tradeConfig: {
  usePrimaryTokens: [SUPPORTED_TOKEN_TYPE.SOL],
}
```

This is useful if you want to:

* Ensure predictable token usage during swaps.
* Prevent certain tokens from being auto-selected as swap input.
* Customize the user experience around token prioritization.

## Fetching a Universal Account's Addresses

A **Universal Account** is composed of multiple addresses, each relevant to a specific interaction layer:

1. **Owner Address**: The EOA that owns the Universal Account and signs transactions (e.g., from MetaMask or via a social login).
2. **EVM Universal Address**: The **UA** address used on EVM-compatible chains.
3. **Solana Universal Address**: The **UA** address used on Solana.

<Note>
  The EVM and Solana Universal Addresses are distinct due to the way deposits work on each network.

  You can deposit any **EVM token** to the **EVM Universal Address**, and any **Solana token** to the **Solana Universal Address**. EVM and Solana assets will be accessible through the **same UA instance**, and balance lookups and transactions will remain unified at the SDK level.
</Note>

You can retrieve all relevant addresses from an initialized Universal Account instance as follows:

```ts theme={null}
const smartAccountOptions = await ua.getSmartAccountOptions();

const accountInfo = {
  ownerAddress: smartAccountOptions.ownerAddress, // EOA that owns the Universal Account
  evmUaAddress: smartAccountOptions.smartAccountAddress!, // EVM UA
  solanaUaAddress: smartAccountOptions.solanaSmartAccountAddress!, // SOL UA
};

console.log("Smart Account info:", accountInfo);
```

<Card title="UA Info Management" icon="file-user" href="https://github.com/soos3d/auth-universal-accounts/blob/117dc53daea1b2bc9017e595bc5337e204171f69/auth-universal-demo/app/page.tsx#L70">
  This repository includes a sample Next.js app with social logins via Particle Auth alongside Universal Accounts.
</Card>

## Primary Assets & Unified Balance

Universal Accounts can hold all assets across supported chains.\
Among these, **Primary Assets** are special: they have the deepest liquidity and can be used as the basis for **cross-chain swaps, liquidity routing, and gas payments**.

> **Why this matters:** You can give users a *single, unified balance* of these spendable assets—regardless of which chain they're actually on.

<Note>
  The full list of supported Primary Assets is available on the [Supported chains and Primary Assets](/universal-accounts/cha/chains#primary-assets) page.
</Note>

### Fetch Unified Balance (quick way)

The easiest way to display the maximum amount a user can spend in one go (the sum of their Primary Assets across chains) is by fetching their **Unified Balance**:

```ts theme={null}
const primaryAssets = await ua.getPrimaryAssets();
console.log("Unified Balance:", primaryAssets.totalAmountInUSD);
```

This gives you a single number (totalAmountInUSD) that represents the cross-chain total balance of Primary Assets in USD.

### Inspect Primary Assets in Detail

If you need more than just the total, `getPrimaryAssets()` also returns a detailed list of assets:

```ts theme={null}
const primaryAssets = await ua.getPrimaryAssets();
console.log("Primary Assets:", JSON.stringify(primaryAssets, null, 2));
```

The following is the structure of the response:

```ts theme={null}
{
  assets: AssetInfo[],       // assets breakdown (per-token and chain)
  totalAmountInUSD: number   // unified total
}
```

Each `AssetInfo` entry aggregates a token across chains, including per-chain breakdowns.

<Accordion title="Expand to see the full structure">
  | Field              | Description                                 |
  | ------------------ | ------------------------------------------- |
  | `tokenType`        | Token identifier (e.g., "eth", "usdt")      |
  | `price`            | Current USD price                           |
  | `amount`           | Total amount across chains (human-readable) |
  | `amountInUSD`      | Total USD value                             |
  | `chainAggregation` | Per-chain balance breakdowns                |
</Accordion>

#### `chainAggregation` format

Each `chainAggregation` entry details the balance and metadata of the token on a specific chain:

<Accordion title="Expand to see the full structure">
  | Field                       | Description                                      |
  | --------------------------- | ------------------------------------------------ |
  | `token.chainId`             | Chain ID                                         |
  | `token.address`             | Token contract address                           |
  | `amount`                    | Token amount (human-readable float)              |
  | `amountInUSD`               | USD value                                        |
  | `rawAmount`                 | Token amount in raw units (integer, stringified) |
  | `token.decimals`            | ERC-20 decimals                                  |
  | `token.realDecimals`        | Adjusted decimals for display                    |
  | `token.isMultiChain`        | Part of multi-chain registry                     |
  | `token.isMultiChainDefault` | Default canonical version across chains          |
</Accordion>

<Note>
  For native assets like `ETH`, the `token.address` field will be `0x0000000000000000000000000000000000000000`.
</Note>

<CardGroup cols="2">
  <Card title="Fetch Primary Assets in a Sample App" icon="binary" href="https://github.com/soos3d/auth-universal-accounts/blob/117dc53daea1b2bc9017e595bc5337e204171f69/auth-universal-demo/app/page.tsx#L99">
    See how to call `getPrimaryAssets()` in a real Next.js app using Particle Auth and Universal Accounts.
  </Card>

  <Card title="Parse and Display Asset Balances" icon="brackets-curly" href="https://github.com/soos3d/auth-universal-accounts/blob/main/auth-universal-demo/app/components/BalanceCard.tsx">
    Check out how Primary Asset data is parsed and rendered in this sample app.
  </Card>
</CardGroup>

## Sending a Transfer Transaction

The `Universal Accounts SDK` lets you send tokens to any address across supported chains using the `createTransferTransaction()` method. Like other transactions, transfers don’t require the user to hold assets or gas tokens on the destination chain—**liquidity and gas are abstracted** behind the scenes.

Once you construct the transfer, the SDK returns a `rootHash` to sign. You sign it with the connected EOA (e.g., from Particle Auth), then call `sendTransaction()` to broadcast:

```ts theme={null}
import { CHAIN_ID, UniversalAccount } from "@particle-network/universal-account-sdk";
import { useEthereum } from "@particle-network/authkit";

const { provider } = useEthereum();

const transaction = await ua.createTransferTransaction({
  token: {
    chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
    address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT on Arbitrum
  },
  amount: "0.1", // Amount to send (human-readable string)
  receiver: receiverAddress, // Target address
});

const signature = await provider.signMessage(transaction.rootHash);
const result = await ua.sendTransaction(transaction, signature);

console.log("Explorer URL:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

<Info>
  For native assets like `ETH`, the token address will be `0x0000000000000000000000000000000000000000`.
</Info>

The returned `TransactionResult` will include the transaction ID, as well as metadata like token movements and fee breakdowns. You can find more details about this in the [TransactionResult](/universal-accounts/ua-reference/desktop/web#transactionresult) section below.

<Card title="View a Sample Transfer Transaction" icon="arrows-turn-right" href="https://github.com/soos3d/auth-universal-accounts/blob/f026f3e3af14ce0ca2c7904e916307b1277b8158/auth-universal-demo/app/components/SendTransactionCard.tsx#L90">
  See how to send cross-chain transfers in a demo Next.js app leveraging Universal Accounts and Particle Auth.
</Card>

## Sending a Custom Payable Transaction

The **Universal Accounts SDK** supports sending contract interactions, including payable transactions, through the `createUniversalTransaction()` method. In this example, we interact with a smart contract on the **Base Mainnet** that requires exactly 0.0000001 ETH to execute a `checkIn()` function.

By specifying an `expectTokens` array, the SDK ensures the account has the necessary **ETH** on Base—even if the user’s assets are on other chains or in different tokens (e.g., USDC, USDT). The SDK will handle all additional required cross-chain routing and token conversion under the hood.

Once the transaction is created, it will return a `rootHash` value representing the payload to be signed. You can then use a signer (e.g., Particle Auth) to sign this hash and broadcast it using `sendTransaction()`.

The following code snippet shows how to use the **Universal Accounts SDK** to send a payable transaction:

```tsx theme={null}
import { CHAIN_ID, SUPPORTED_TOKEN_TYPE } from "@particle-network/universal-account-sdk";
import { Interface, parseEther, toBeHex } from "ethers";
import { useEthereum } from "@particle-network/authkit";

// Extract the provider from Particle Auth
const { provider } = useEthereum();

const contractAddress = "0x14dcD77D7C9DA51b83c9F0383a995c40432a4578";
const interf = new Interface(["function checkIn() public payable"]);

const transaction = await ua.createUniversalTransaction({
  chainId: CHAIN_ID.BASE_MAINNET,
  expectTokens: [
    {
      type: SUPPORTED_TOKEN_TYPE.ETH,
      amount: "0.0000001",
    },
  ],
  transactions: [
    {
      to: contractAddress,
      data: interf.encodeFunctionData("checkIn"),
      value: toBeHex(parseEther("0.0000001")),
    },
  ],
});

const signature = await provider.signMessage(transaction.rootHash);
const result = await ua.sendTransaction(transaction, signature);

console.log("Explorer URL:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

The returned `TransactionResult` will include the transaction's ID and metadata like token movements and fee breakdowns.

## Sending a Buy Transaction

The Universal Accounts SDK supports **buy/swap transactions** directly through the `createBuyTransaction()` method. This allows you to programmatically route an amount in **USD** into a target token (e.g., USDT on Arbitrum), without requiring the user to hold funds on the destination chain.

Once the transaction is created, it returns a `rootHash` value representing the payload to be signed. You then use your **signer** (in this case, Particle Auth) to sign the message, and pass the result into sendTransaction() to broadcast it:

<Note>
  You can specify the specific tokens you want to use as source for the swap by setting the `usePrimaryTokens` property in the `tradeConfig` object when initializing the Universal Account.

  ```ts theme={null}
  tradeConfig: {
      usePrimaryTokens: [SUPPORTED_TOKEN_TYPE.SOL],
    },
  ```
</Note>

```ts theme={null}
import { CHAIN_ID, UniversalAccount } from "@particle-network/universal-account-sdk";
import { useEthereum } from "@particle-network/authkit";

// extract the provider from Particle Auth
const { provider } = useEthereum();

// In your app
const transaction = await ua.createBuyTransaction({
  token: {
    chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
    address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT on Arbitrum
  },
  amountInUSD: "10", // Target amount in USD sourced from primary assets held
});

const signature = await provider.signMessage(transaction.rootHash);
const result = await ua.sendTransaction(transaction, signature);

console.log("Explorer URL:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

The `sendTransaction` method will then return a `TransactionResult` object, which includes the transaction ID and other metadata.

<Card title="Sample Swap Transaction" icon="coin-front" href="https://github.com/soos3d/auth-universal-accounts/blob/main/auth-universal-demo/app/components/SendTransactionCard.tsx">
  See how to initiate a swap transaction in a demo Next.js app using both Particle Auth and Universal Accounts.
</Card>

## Sending a Sell Transaction

The **Universal Accounts SDK** supports **sell/swap transactions** through the `createSellTransaction()` method. This allows you to programmatically sell a token (e.g., ARB on Arbitrum) into a target primary asset, with the transaction routed through Particle’s Chain Abstraction layer.

Once the transaction is created, it returns a `rootHash` payload that needs to be signed. You then use your signer (e.g., Particle Auth) to sign and pass it into `sendTransaction()` to broadcast.

<Note>
  The amount you specify is the raw token amount to be sold (no decimals adjustment needed). Ensure the **Universal Account** has a sufficient balance of the token before calling `createSellTransaction()`.
</Note>

```ts theme={null}
import { CHAIN_ID, UniversalAccount } from "@particle-network/universal-account-sdk";
import { useEthereum } from "@particle-network/authkit";

// extract the provider from Particle Auth
const { provider } = useEthereum();

// In your app
const transaction = await ua.createSellTransaction({
  token: {
    chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
    address: "0x912CE59144191C1204E64559FE8253a0e49E6548", // ARB on Arbitrum
  },
  amount: "0.1", // Amount of token to sell
});

const signature = await provider.signMessage(transaction.rootHash);
const result = await ua.sendTransaction(transaction, signature);

console.log("Explorer URL:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

The `sendTransaction(`) method returns a `TransactionResult` object with the transaction ID and other metadata.

### Specifying the Output Token Type

By default, `createSellTransaction()` accepts an optional second argument, `tradeConfig`, which lets you control what token you receive as proceeds from the sell.

The `preferTokenType` field inside `tradeConfig` accepts either a numeric literal or the named `PREFER_TOKEN_TYPE` enum member — both are equivalent since `PREFER_TOKEN_TYPE` is a numeric TypeScript enum.

| Value | Enum                       | Meaning                                          |
| ----- | -------------------------- | ------------------------------------------------ |
| `0`   | `PREFER_TOKEN_TYPE.USD`    | Receive a USD-pegged stablecoin (USDC)           |
| `1`   | `PREFER_TOKEN_TYPE.NATIVE` | Receive the chain's native token (e.g. ETH, BNB) |

<Note>
  If `tradeConfig` is omitted entirely, the SDK uses its own default output token.
</Note>

```ts theme={null}
import { CHAIN_ID, PREFER_TOKEN_TYPE, UniversalAccount } from "@particle-network/universal-account-sdk";
import { useEthereum } from "@particle-network/authkit";

const { provider } = useEthereum();

const transaction = await universalAccount.createSellTransaction(
  {
    token: {
      chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
      address: "0x912CE59144191C1204E64559FE8253a0e49E6548", // ARB on Arbitrum
    },
    amount: "1",
  },
  {
    preferTokenType: PREFER_TOKEN_TYPE.USD, // 0 = USDC, 1 = native token
  },
);

const signature = await provider.signMessage(transaction.rootHash);
const result = await universalAccount.sendTransaction(transaction, signature);

console.log("Explorer URL:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

## Sending a Conversion Transaction

You can convert [Primary Assets](/universal-accounts/cha/chains#primary-assets) with the `createConvertTransaction` method.

The example below demonstrates how to convert any primary asset into another—USDC on Arbitrum, in this case:

```ts theme={null}
import { CHAIN_ID, SUPPORTED_TOKEN_TYPE, UniversalAccount } from "@particle-network/universal-account-sdk";
import { useEthereum } from "@particle-network/authkit";

// extract the provider from Particle Auth
const { provider } = useEthereum();

// In your app
const transaction = await ua.createConvertTransaction({
    expectToken: { type: SUPPORTED_TOKEN_TYPE.USDC, amount: '1' },
    chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
});

const signature = await provider.signMessage(transaction.rootHash);
const result = await ua.sendTransaction(transaction, signature);

console.log("Explorer URL:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

This method is useful to convert assets directly to your target chain (for example, upon deposit).

## Solana Transactions

Universal Accounts support **Solana** as well.

You can swap any token to and from SOL using the `createTransferTransaction()` method, even without assets on Solana.

Here is an example:

```ts theme={null}
import { CHAIN_ID } from "@particle-network/universal-account-sdk";
import { useEthereum } from "@particle-network/authkit";

// extract the provider from Particle Auth
const { provider } = useEthereum();

// In your app
const transaction = await ua.createBuyTransaction({
    // Buy sol
    // token: { chainId: CHAIN_ID.SOLANA_MAINNET, address: "0x0000000000000000000000000000000000000000" },
    
    // Buy a token
    token: { chainId: CHAIN_ID.SOLANA_MAINNET, address: "6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN" },
    // buy $0.001 of trump
    amountInUSD: "0.001",
});

const signature = await provider.signMessage(transaction.rootHash);
const result = await universalAccount.sendTransaction(transaction, signature);

console.log("Explorer URL:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

<Note>
  Even if your Universal Account doesn’t hold SOL for gas, you can still purchase SOL or any other Solana token. Universal Accounts will automatically handle routing and liquidity across chains to cover gas fees.
</Note>

Solana transactions can be signed with any provider compatible with the Universal Accounts SDK. In this case, Particle Auth is used to sign the `rootHash`.

You can combine this swap transaction with a SOL transfer to automatically convert EVM-based assets to SOL and send them to another account in a single flow.

## Transaction Preview

The transaction object returned by methods like `createTransferTransaction()` provides a full preview of the transaction before it’s executed. This includes key details such as estimated fees, token transfers, and other relevant metadata—allowing you to display clear, actionable information to users before confirmation.

For example:

```tsx page.tsx theme={null}
const transaction = await universalAccount.createBuyTransaction({
  token: {
    chainId: CHAIN_ID.BSC_MAINNET,
    address: "0x59264f02D301281f3393e1385c0aEFd446Eb0F00", // PARTI token on BNB Chain
  },
  amountInUSD: "1",
});
```

The returned transaction object includes **metadata** such as **sender** and **recipient** addresses, **tokens** used, and **estimated fees**.

Here’s how to extract and display the estimated fees:

```ts page.tsx theme={null}
import { formatUnits } from "ethers";

const feeQuote = transaction.feeQuotes[0];
const fee = feeQuote.fees.totals;

console.log("Total fee (USD):", `$${formatUnits(fee.feeTokenAmountInUSD, 18)}`);
console.log("Gas fee (USD):", `$${formatUnits(fee.gasFeeTokenAmountInUSD, 18)}`);
console.log("Service fee (USD):", `$${formatUnits(fee.transactionServiceFeeTokenAmountInUSD, 18)}`);
console.log("LP fee (USD):", `$${formatUnits(fee.transactionLPFeeTokenAmountInUSD, 18)}`);
```

<Info>
  For a full breakdown of the preview structure and practical usage examples, see the [Preview Transaction Details with Universal Accounts](/universal-accounts/cha/how-to/tx-preview) guide.
</Info>

## `sendTransaction()` Response Structure

After broadcasting a transaction with `sendTransaction()`, the SDK will return a detailed object containing its execution status, fee breakdowns, token flows, and analytics.

Below is a breakdown of the key fields within this object:

<Accordion title="Top-Level Fields">
  | Field                       | Description                                                             |
  | --------------------------- | ----------------------------------------------------------------------- |
  | `transactionId`             | Unique ID of the transaction (used to query status or activity details) |
  | `mode`                      | Network mode, typically `"mainnet"` or `"testnet"`                      |
  | `sender` / `receiver`       | Address that initiated and received the transaction (usually same)      |
  | `type`                      | Transaction type (e.g. `"universal"`)                                   |
  | `status`                    | Execution status code (internal enum)                                   |
  | `tag`                       | Transaction tag (e.g., `"buy"` or `"swap"`)                             |
  | `created_at` / `updated_at` | ISO timestamps for lifecycle tracking                                   |
</Accordion>

<Accordion title="`fees` (Cost Breakdown)">
  | Field                           | Description                                                  |
  | ------------------------------- | ------------------------------------------------------------ |
  | `totals.feeTokenAmountInUSD`    | Total fee in USD                                             |
  | `feeTokens[]`                   | List of tokens used to pay fees, with symbols and USD values |
  | `freeGasFee` / `freeServiceFee` | Whether any component was waived                             |
</Accordion>

<Accordion title="`depositTokens` / `lendingTokens`">
  Tokens deducted from the user's account to fund the transaction. Each entry includes:

  * `token.symbol`
  * `token.chainId`
  * `amount` and `amountInUSD`
  * Full metadata (decimals, icon, etc.)
</Accordion>

<Accordion title="tokenChanges (Before/After Effects)">
  Provides the most useful high-level insight into what changed:

  | Field             | Description                                          |
  | ----------------- | ---------------------------------------------------- |
  | `decr[]`          | Tokens deducted from the user (chain, token, amount) |
  | `incr[]`          | Tokens received by the user                          |
  | `swaps[]`         | Swap routes (e.g. from USDC to USDT via 1inch)       |
  | `tokenBalances[]` | Final post-transaction token balances                |
</Accordion>

<Accordion title="Analytics & Valuation">
  | Field                   | Description                                                    |
  | ----------------------- | -------------------------------------------------------------- |
  | `slippage`              | Slippage used for the route (in basis points)                  |
  | `totalFeeInUSD`         | Final USD fee value                                            |
  | `totalDecrAmountInUSD`  | Total USD value deducted                                       |
  | `totalIncrAmountInUSD`  | Total USD value received                                       |
  | `priceImpact`           | Estimated price impact (0 if none)                             |
  | `minReceiveAmountInUSD` | Minimum expected amount (to be received, post-slippage) in USD |
  | `minReceiveToken`       | Token  targeted by the buy/swap action                         |
</Accordion>

<Accordion title="`Explorer / Activity Link`">
  You can construct a link to see the activity on UniversalX:

  ```ts theme={null}
  const url = `https://universalx.app/activity/details?id=${transactionId}`;
  ```
</Accordion>

## Fetching Transaction History

The **Universal Accounts SDK** allows you to retrieve a user's transaction history directly from their **Universal Account**.\
You can query both **general transactions** (all activity) and **token-specific transactions** (activity related to a specific token).

This is useful for displaying activity feeds, wallet histories, or user analytics dashboards.

### Fetch All Transactions

To retrieve the complete transaction history of a Universal Account, use the `getTransactions(page, pageSize)` method:

```ts theme={null}
const transactions = await universalAccount.getTransactions(1, 20);
console.log("transactions", transactions);
```

This call will return a paginated list of transactions ordered by most recent. Each page includes up to the number of records you specify in the `pageSize` argument.

### Fetch Token-Specific Transactions

To filter the transaction history by a specific token and chain, use the `getTokenTransactions()` method:

```tsx theme={null}
const tokenTransactions = await universalAccount.getTokenTransactions({
  chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
  address: "0x912CE59144191C1204E64559FE8253a0e49E6548", // ARB token on Arbitrum
});

console.log("tokenTransactions", tokenTransactions);
```

This will return a list of transactions where the specified token was involved.

Each response includes a `nextPageToken` you can use to fetch additional results.

#### Pagination Example

To retrieve the next page of token-specific transactions, simply pass the returned `nextPageToken`:

```tsx theme={null}
const tokenTransactions2 = await universalAccount.getTokenTransactions(
  {
    chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
    address: "0x912CE59144191C1204E64559FE8253a0e49E6548",
  },
  tokenTransactions.nextPageToken
);

console.log("tokenTransactions2", tokenTransactions2);
```

<Note>
  Both `getTransactions()` and `getTokenTransactions()` support pagination.\
  Always check for the `nextPageToken` field in the response to determine if more results are available.
</Note>

## Fetching Transaction Details

To retrieve the details of a specific transaction, use the `getTransaction(transactionId)` method:

<Note>
  The `transactionId` is the transaction's unique identifier; you can retrieve it from the transaction history.
</Note>

```ts theme={null}
const txDetails = await universalAccountInstance.getTransaction(transactionId);
console.log("txDetails", txDetails);
```

This will return a `TransactionDetails` object containing detailed information about the transaction. You can then use this object to display transaction details in your application.

## Using Particle Connect with Universal Accounts

<Note>
  The following example uses **Particle Connect** instead of Particle Auth. It shows how to sign and send a Universal Account transaction using a connected wallet.
</Note>

When using **Particle Connect**, you can access the connected wallet through the `useWallets()` hook. The `primaryWallet` object exposes a `walletClient`, which acts as the signer.

This lets you sign the **Universal Account transaction** payload (rootHash) using any **wallet** connected via **Particle Connect**.

The following code snippet shows how to use the **Universal Accounts SDK** to sign a transaction with Particle Connect:

```tsx theme={null}
import { useWallets, useAccount } from "@particle-network/connectkit";
import { CHAIN_ID } from "@particle-network/universal-account-sdk";

// Get wallet from Particle Connect
const [primaryWallet] = useWallets();
const walletClient = primaryWallet?.getWalletClient();
const { address } = useAccount();

// Create a cross-chain transfer transaction via Universal Accounts
const transaction = await ua.createTransferTransaction({
  token: {
    chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
    address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT on Arbitrum
  },
  amount: "0.1", // Amount to send (human-readable string)
  receiver: receiverAddress, // Target address
});

// Sign the transaction's root hash using connected wallet
const signature = await walletClient?.signMessage({
  account: address as `0x${string}`,
  message: { raw: transaction.rootHash },
});

// Send the signed transaction via Universal Account SDK
const sendResult = await universalAccount.sendTransaction(
  transaction,
  signature
);

// Log UniversalX explorer link
console.log(
  "Explorer URL:",
  `https://universalx.app/activity/details?id=${sendResult.transactionId}`
);
```

## Using Universal Accounts in the backend

The **Universal Accounts SDK** can also be used in backend environments to construct and sign transactions programmatically.

The example below demonstrates usage with ethers.js and a private key in Node.js:

```ts theme={null}
import { UniversalAccount, CHAIN_ID } from "@particle-network/universal-account-sdk";
import { Wallet, getBytes } from "ethers";

// Initialize wallet
const wallet = new Wallet("PRIVATE_KEY_OR_MNEMONIC");

// Create a Universal Account instance
const ua = new UniversalAccount({
  projectId: "UA_PROJECT_ID",
  ownerAddress: wallet.address,
  tradeConfig: {
    slippageBps: 100,      // Set slippage to 1% (100 basis points)
    universalGas: true     // Use PARTI tokens to cover gas fees
  }
});

// Create a transaction to buy $0.1 worth of ARB on Arbitrum
const tx = await ua.createBuyTransaction({
  token: {
    chainId: CHAIN_ID.ARBITRUM_MAINNET_ONE,
    address: "0x912CE59144191C1204E64559FE8253a0e49E6548" // ARB token contract
  },
  amountInUSD: "0.1"
});

// Sign and send the transaction
const result = await ua.sendTransaction(tx, wallet.signMessageSync(getBytes(tx.rootHash)));

// Log the transaction result and link to explorer
console.log("Transaction ID:", result.transactionId);
console.log("View on Explorer:", `https://universalx.app/activity/details?id=${result.transactionId}`);
```

## Registering a UniversalX Account

The **Universal Accounts SDK** also allows you to register an account on [UniversalX](https://universalx.app), a chain-abstracted trading platform built upon **Universal Accounts**.

This can automatically onboard your users into **UniversalX** when they create or initialize a Universal Account. UniversalX registration is optional and only needs to be done **once per UA**.

```ts theme={null}
import { UniversalAccount, createUnsignedMessage } from "@particle-network/universal-account-sdk";
import { randomUUID } from "crypto";

const ua = new UniversalAccount({
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  projectClientKey: process.env.NEXT_PUBLIC_CLIENT_KEY!,
  projectAppUuid: process.env.NEXT_PUBLIC_APP_ID!,
  ownerAddress: 'USER_ADDRESS',
});

// Fetch smart account info to get the UA address
const smartAccountOptions = await ua.getSmartAccountOptions();

// Optional invite code
const inviteCode = "000000";

// Prepare registration payload
const deviceId = randomUUID();
const timestamp = Date.now();

const unsignedMessage = createUnsignedMessage(
  smartAccountOptions.smartAccountAddress!,
  deviceId,
  timestamp
);

// Sign message using EOA (can be a backend wallet)
const signature = provider.signMessage(unsignedMessage);

// Register the UniversalX account
const result = await ua.register(inviteCode, deviceId, timestamp, signature);

if (!!result.token) {
  console.log("Registration successful.");
} else {
  console.error("Registration failed:", result);
}
```

<Info>
  You can also pass an invite code (`"000000"` by default) if you have one.
</Info>
