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

# Offer Yield Accounts to Your Users

> Add an interest-bearing savings feature to your fintech app. Users deposit funds, earn yield automatically, and withdraw at any time, with no crypto knowledge required.

This guide shows how to build a yield account feature for a neobank or fintech app. Your users deposit funds, which are allocated to a [Morpho lending vault](https://app.morpho.org/polygon/vault/0x781FB7F6d845E3bE129289833b04d43Aa8558c42/compound-usdc) via [Trails](https://docs.trails.build). The blockchain layer is fully abstracted: your users see a savings balance and an interest rate, nothing else.

## How it works

```
User taps "Move to Savings"
    → Your backend quotes the deposit route via Trails
        → User authorizes with a single tap (gasless, no fees to manage)
            → Trails routes funds into the yield vault
                → Yield accrues; user withdraws any time
```

<Note>
  Users never interact with a blockchain directly. Wallet creation, gas, and transaction signing all happen in the background via an embedded wallet tied to their account.
</Note>

## Prerequisites

* A [Trails API key](https://dashboard.trails.build/landing)
* A wagmi-compatible embedded wallet provider ([Polygon Wallet](/wallets/embedded-wallets), [Privy](https://privy.io), Dynamic, or similar), which powers the invisible signing layer your users never see

## Install

```bash theme={null}
pnpm install 0xtrails viem wagmi @tanstack/react-query @privy-io/react-auth
```

## Addresses

|                            | Address                                      |
| -------------------------- | -------------------------------------------- |
| USDC (Polygon)             | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |
| Morpho Compound USDC vault | `0x781FB7F6d845E3bE129289833b04d43Aa8558c42` |

## Step 1: Set up the embedded wallet

Each user gets an embedded wallet created automatically at sign-up, with no crypto onboarding and no seed phrases. This wallet is the signing key for their yield account; users never see or interact with it directly.

```tsx theme={null}
import { PrivyProvider } from "@privy-io/react-auth";
import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { polygon } from "viem/chains";

const queryClient = new QueryClient();

export const App = () => (
  <PrivyProvider
    appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
    config={{
      defaultChain: polygon,
      supportedChains: [polygon],
      embeddedWallets: {
        createOnLogin: "users-without-wallets", // automatic, no user action required
      },
    }}
  >
    <QueryClientProvider client={queryClient}>
      <WagmiProvider config={wagmiConfig}>
        <YourApp />
      </WagmiProvider>
    </QueryClientProvider>
  </PrivyProvider>
);
```

Users log in with email, phone, or SSO via your standard auth flow. Privy silently provisions the wallet in the background:

```tsx theme={null}
import { usePrivy } from "@privy-io/react-auth";

const LoginButton = () => {
  const { login, logout, authenticated } = usePrivy();

  return authenticated ? (
    <button onClick={logout}>Sign out</button>
  ) : (
    <button onClick={login}>Sign in</button>
  );
};
```

## Step 2: Discover the vault market

Composable actions use market IDs instead of hand-encoded vault calldata. Fetch the available vaults, then pass the selected market's `id` to the `deposit` action:

```tsx theme={null}
import { useEarnMarkets } from "0xtrails";

function useMorphoUsdcVault() {
  const { data: markets, isLoading, error } = useEarnMarkets({
    chain: "polygon",
    type: "vault",
    provider: "morpho",
    search: "USDC",
  });

  return {
    market: markets?.[0],
    isLoading,
    error,
  };
}
```

## Step 3: Quote and execute the deposit

Use `useQuote` with the `deposit` action. Trails quotes the route, handles any bridge or swap, and executes the vault deposit from the destination intent wallet:

```tsx theme={null}
import { useQuote, deposit } from "0xtrails";
import { useAccount } from "wagmi";

const MORPHO_USDC_MARKET_ID = "MARKET_ID_FROM_USE_EARN_MARKETS";

export function MoveToSavingsButton() {
  const { address } = useAccount();

  if (!address) return <button disabled>Sign in first</button>;

  return <MoveToSavingsForm walletAddress={address} />;
}

function MoveToSavingsForm({
  walletAddress,
}: {
  walletAddress: `0x${string}`;
}) {
  const { send, isLoadingQuote, quoteError } = useQuote({
    walletAddress,
    from: { chain: "polygon", token: "USDC", amount: "100" },
    to: { chain: "polygon", token: "USDC" },
    actions: [
      deposit({
        marketId: MORPHO_USDC_MARKET_ID,
        amount: "100",
        receiverAddress: walletAddress,
      }),
    ],
    onStatusUpdate: (states) => {
      console.log("Savings deposit status:", states);
    },
  });

  if (isLoadingQuote) return <button disabled>Preparing...</button>;
  if (quoteError) return <p>{quoteError.message}</p>;

  return <button disabled={!send} onClick={() => send?.()}>Move $100 to Savings</button>;
}
```

From the user's perspective this is still one confirmation. From your app's perspective, `onStatusUpdate` gives you progress events for the route and destination action so you can update the savings balance after settlement.

## Withdraw

Users can withdraw their full balance (principal + accrued yield) at any time. Use `useEarnBalances` to read the user's position, then build a plain ERC-4626 `redeem` transaction with `viem`:

```tsx theme={null}
import { encodeFunctionData } from "viem";
import { useEarnBalances } from "0xtrails";
import { useAccount, useWalletClient } from "wagmi";

const MORPHO_USDC_MARKET_ID = "MARKET_ID_FROM_USE_EARN_MARKETS";
const MORPHO_VAULT = "0x...";

const VAULT_ABI = [
  {
    type: "function",
    name: "redeem",
    stateMutability: "nonpayable",
    inputs: [
      { name: "shares", type: "uint256" },
      { name: "receiver", type: "address" },
      { name: "owner", type: "address" },
    ],
    outputs: [{ name: "assets", type: "uint256" }],
  },
] as const;

function hasBalance(balance: { amount?: string; amountRaw?: string }) {
  if (balance.amountRaw) return BigInt(balance.amountRaw) > 0n;
  return Number(balance.amount ?? "0") > 0;
}

export function WithdrawButton() {
  const { address } = useAccount();
  const { data: walletClient } = useWalletClient();

  const { data: positions, isLoading, error } = useEarnBalances({
    walletAddress: address ?? "",
    chain: "polygon",
    marketId: MORPHO_USDC_MARKET_ID,
    enabled: Boolean(address),
  });

  const position = positions?.[0];
  const displayBalance =
    position?.outputTokenBalance ??
    position?.balances.find((item) => hasBalance(item));
  const shareBalance = position?.balances.find((item) =>
    item.shareToken?.address?.toLowerCase() === MORPHO_VAULT.toLowerCase() ||
    item.token.address?.toLowerCase() === MORPHO_VAULT.toLowerCase()
  );

  async function withdrawAllShares() {
    if (!address || !walletClient || !shareBalance?.amountRaw) return;

    const data = encodeFunctionData({
      abi: VAULT_ABI,
      functionName: "redeem",
      args: [BigInt(shareBalance.amountRaw), address, address],
    });

    await walletClient.sendTransaction({
      account: address,
      to: MORPHO_VAULT,
      data,
    });
  }

  if (!address) return <button disabled>Sign in first</button>;
  if (isLoading) return <button disabled>Loading balance...</button>;
  if (error) return <p>{error.message}</p>;
  if (!displayBalance || !hasBalance(displayBalance)) {
    return <button disabled>No balance</button>;
  }

  return (
    <div>
      <p>
        Balance: {displayBalance.amount} {displayBalance.token.symbol}
      </p>
      <button disabled={!walletClient || !shareBalance} onClick={withdrawAllShares}>
        Withdraw all
      </button>
    </div>
  );
}
```

`useEarnBalances` returns the normalized earn position for the wallet, including the market `yieldId`, the display balance, and protocol-specific share/output token balances. Until the SDK exposes a higher-level withdraw helper, you can encode the vault's ERC-4626 `redeem` call directly and submit it with the user's wallet.

## Next steps

<CardGroup cols={2}>
  <Card title="Trails API Reference" icon="book" href="https://docs.trails.build/api-reference/introduction">
    Full reference for QuoteIntent, CommitIntent, ExecuteIntent, and monitoring.
  </Card>

  <Card title="Wallet Compatibility" icon="wallet" href="https://docs.trails.build/examples/wallet-compatibility">
    Full list of supported embedded wallet providers including Privy, Dynamic, and WalletConnect.
  </Card>

  <Card title="Smart Sessions" icon="bolt" href="/wallets/smart-sessions">
    Automate recurring deposits: schedule weekly transfers to savings without per-transaction prompts.
  </Card>

  <Card title="Treasury Wallet" icon="building-columns" href="/wallets/treasury-wallet">
    Sweep funds from user accounts into a central treasury wallet on a schedule.
  </Card>
</CardGroup>
