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

# Crosschain vault deposit

> Deposit into an ERC-4626 vault on any chain using tokens from any supported chain.

With Rhinestone intents, you can deposit into any ERC-4626 vault on a destination chain using tokens from any supported chain. This is powered by `destinationExecutions` — arbitrary calls that run on the destination chain as part of the intent settlement.

The flow works as follows:

1. Encode the vault deposit call
2. Build the meta intent with the deposit execution
3. Get a quote from Rhinestone
4. Fulfill any token requirements
5. Sign the intent
6. Submit and poll for completion

## Encoding the Vault Deposit

<Tabs>
  <Tab title="EOA">
    For EOAs, destination executions run in an intermediary contract — not in the user's account context — so any tokens the execution produces (like vault shares) would otherwise be stranded there.

    ERC-4626's `deposit(assets, receiver)` sidesteps this: set `receiver` to the user's address and shares mint directly to them. First approve the vault to pull the deposit token from the intermediary, then deposit.

    ```ts theme={null}
    import { encodeFunctionData, erc20Abi, parseUnits } from "viem";

    const VAULT_ADDRESS = "0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9"; // Steakhouse Prime USDC vault (ERC-4626) on Base
    const DEPOSIT_TOKEN = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base (the vault's underlying asset)
    const USER_ADDRESS = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // the EOA receiving the vault shares
    const depositAmount = parseUnits("100", 6); // 100 USDC (6 decimals)

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

    const destinationExecutions = [
      // 1. Approve the vault to pull USDC from the intermediary
      {
        to: DEPOSIT_TOKEN,
        value: "0",
        data: encodeFunctionData({
          abi: erc20Abi,
          functionName: "approve",
          args: [VAULT_ADDRESS, depositAmount],
        }),
      },
      // 2. Deposit, minting shares straight to the user via the receiver arg
      {
        to: VAULT_ADDRESS,
        value: "0",
        data: encodeFunctionData({
          abi: vaultAbi,
          functionName: "deposit",
          args: [depositAmount, USER_ADDRESS],
        }),
      },
    ];
    ```

    <Note>
      Because executions run in the intermediary — not the user's account — tokens produced onchain aren't swept back automatically. ERC-4626's `receiver` argument handles this: shares mint straight to the user. For outputs from calls that don't expose a recipient (e.g. a swap), add an explicit `transfer` back to the user.
    </Note>
  </Tab>

  <Tab title="Smart Account">
    For Smart Accounts, the IntentExecutor runs destination executions within the account's own context, so shares mint directly to the account with the account as `receiver` — no post-hoc transfer needed. You still approve the vault to pull the deposit token first.

    ```ts theme={null}
    import { encodeFunctionData, erc20Abi, parseUnits } from "viem";

    const VAULT_ADDRESS = "0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9"; // Steakhouse Prime USDC vault (ERC-4626) on Base
    const DEPOSIT_TOKEN = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base (the vault's underlying asset)
    const ACCOUNT_ADDRESS = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // your smart account address
    const depositAmount = parseUnits("100", 6); // 100 USDC (6 decimals)

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

    const destinationExecutions = [
      // 1. Approve the vault to pull USDC from the account
      {
        to: DEPOSIT_TOKEN,
        value: "0",
        data: encodeFunctionData({
          abi: erc20Abi,
          functionName: "approve",
          args: [VAULT_ADDRESS, depositAmount],
        }),
      },
      // 2. Deposit — shares mint directly to the account
      {
        to: VAULT_ADDRESS,
        value: "0",
        data: encodeFunctionData({
          abi: vaultAbi,
          functionName: "deposit",
          args: [depositAmount, ACCOUNT_ADDRESS],
        }),
      },
    ];
    ```
  </Tab>
</Tabs>

## Constructing the Meta Intent

<Tabs>
  <Tab title="EOA">
    ```ts theme={null}
    const metaIntent = {
      destinationChainId: "eip155:8453", // Base (where the vault lives)
      tokenRequests: [
        {
          tokenAddress: DEPOSIT_TOKEN,
          amount: depositAmount.toString(),
        },
      ],
      account: {
        address: USER_ADDRESS,
        accountType: "EOA",
      },
      destinationExecutions,
    };
    ```
  </Tab>

  <Tab title="Smart Account">
    ```ts theme={null}
    const metaIntent = {
      destinationChainId: "eip155:8453", // Base (where the vault lives)
      tokenRequests: [
        {
          tokenAddress: DEPOSIT_TOKEN,
          amount: depositAmount.toString(),
        },
      ],
      account: {
        address: ACCOUNT_ADDRESS,
        accountType: "ERC7579",
        setupOps: [], // include factory args if the account is not yet deployed
      },
      destinationExecutions,
    };
    ```
  </Tab>
</Tabs>

## Getting a Quote

Submit the meta intent to the `/quotes` endpoint:

```ts theme={null}
const baseUrl = "https://v1.orchestrator.rhinestone.dev";
const apiKey = process.env.RHINESTONE_API_KEY;

const headers = {
  "Content-Type": "application/json",
  "x-api-key": apiKey,
  "x-api-version": "2026-04.blanc",
};

const res = await fetch(`${baseUrl}/quotes`, {
  method: "POST",
  headers,
  body: JSON.stringify(metaIntent),
});

const { routes } = await res.json();
const route = routes[0];
const { intentId, cost, signData, tokenRequirements } = route;
```

The response is a server-ranked `routes` array. Each route carries:

* `intentId`: server-stored handle, used to submit
* `cost`: input/output amounts and fee breakdown
* `signData`: EIP-712 typed data to sign
* `tokenRequirements`: prerequisite token operations (EOA only)

<Card title="Getting a Quote" icon="route" href="../guides/getting-a-quote">
  See the full guide for advanced options like sponsorship and source chain filtering
</Card>

## Fulfilling Token Requirements

<Tabs>
  <Tab title="EOA">
    Before submitting, EOAs must fulfill the token requirements returned in the quote. There are two types:

    **ERC-20 Approvals** — approve tokens to the Permit2 contract:

    ```ts theme={null}
    import { maxUint256 } from "viem";

    const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";

    const hash = await walletClient.writeContract({
      address: tokenAddress,
      abi: erc20Abi,
      functionName: "approve",
      args: [PERMIT2, maxUint256],
    });
    ```

    **ETH Wrapping** — wrap native ETH to WETH:

    ```ts theme={null}
    const hash = await walletClient.writeContract({
      address: WETH_ADDRESS,
      abi: [
        {
          name: "deposit",
          type: "function",
          stateMutability: "payable",
          inputs: [],
          outputs: [],
        },
      ],
      functionName: "deposit",
      value: wrapAmount,
    });
    ```

    <Info>
      Approvals are only ever to the [Permit2](https://github.com/Uniswap/permit2) contract. We recommend using max approvals for the best UX. Alternatively, inspect `cost.input` for the exact amount needed.
    </Info>
  </Tab>

  <Tab title="Smart Account">
    Smart Accounts handle token approvals and wrapping automatically via pre-claim ops — no manual steps are needed. You can proceed directly to signing.
  </Tab>
</Tabs>

<Card title="Token Requirements" icon="list-check" href="../guides/token-requirements">
  Full details on fulfilling token requirements
</Card>

## Signing the Intent

<Tabs>
  <Tab title="EOA">
    Forward `signData.origin[]` and `signData.destination` directly to your wallet. One signature per source chain, plus the destination signature.

    ```ts theme={null}
    const originSignatures = await Promise.all(
      signData.origin.map((typedData) =>
        walletClient.signTypedData(typedData),
      ),
    );
    const destinationSignature = await walletClient.signTypedData(
      signData.destination,
    );
    ```
  </Tab>

  <Tab title="Smart Account">
    Smart account signing wraps the signature with the installed validator's encoding. The Rhinestone SDK handles this automatically — use it for smart account flows rather than driving the raw API by hand.

    <Card title="Smart Wallet quickstart" icon="bolt" href="/smart-wallet/quickstart">
      Send a crosschain intent from a smart account using the SDK
    </Card>
  </Tab>
</Tabs>

## Submitting and Polling

Submit the signed intent to `/intents` using the `intentId` from the quote:

```ts theme={null}
const res = await fetch(`${baseUrl}/intents`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    intentId,
    signatures: {
      origin: originSignatures,
      destination: destinationSignature,
    },
  }),
});

const { intentId: submittedId } = await res.json();
```

Poll for status using the `intentId`:

```ts theme={null}
const poll = async (intentId: string) => {
  const res = await fetch(`${baseUrl}/intents/${intentId}`, { headers });
  return res.json();
};
```

See the [tracking intents guide](../guides/tracking-intents) for the full list of intent statuses.
