---
title: Deposit on EVM
description: The four router entry points, when each applies, and how to build the call from a quote.
---
The deposit is one call on the source chain, carrying the quote hash. The quote's parameters, not preference, determine which entry point you use and which contract it goes to. Plain deposits go to the escrow, which is the `router_address` on the quote; deposits that swap go to the router returned by `GetSwapInstructions`.
## Choosing the call
| Condition on `order_params` | Call | Target |
| --- | --- | --- |
| `src_token` is the zero address | `depositNative` | `order_params.router_address` |
| `src_token` equals `bridge_token_src` | `depositToken` | `order_params.router_address` |
| `src_token` differs from `bridge_token_src` | `depositWithSwap` | `GetSwapInstructions` `router_address` |
| `src_chain` equals `dst_chain` | `swapAndForward`, see [Same-chain swaps](/integration/same-chain-swaps) | `order_params.router_address` |
```ts
const params = quote.orderParams;
const native = /^0x0*$/i.test(params.srcToken);
const swap = params.srcToken !== params.bridgeTokenSrc;
```
When the quote has `direct_transfer: true`, the `depositToken` row has a cheaper alternative: a plain ERC-20 transfer to the escrow with the quote hash appended to the calldata. See [Direct transfer](#direct-transfer).
A quote requested with `gasless: true` takes none of these paths: the user signs a permit2 authorization instead of sending a transaction, and the operator relays the deposit. See [SubmitGaslessOrder](/api-reference/gasless).
## Native deposits
No approval, no allowance check. The amount rides as transaction value.
```solidity
function depositNative(bytes32 quoteHash) external payable;
```
```ts
await wallet.sendTransaction({
to: params.routerAddress,
data: encodeFunctionData({
abi: routerAbi,
functionName: "depositNative",
args: [quote.quoteHash],
}),
value: BigInt(params.amountIn),
});
```
## Token deposits
The router pulls the tokens, so it needs an allowance first. Check before prompting: a user who already approved should not be asked twice.
```solidity
function depositToken(bytes32 quoteHash, address token, uint256 amount) external;
```
```ts
const allowance = await erc20.read.allowance([owner, params.routerAddress]);
```
Approve at least `amount_in`. Wait for the approval to confirm before the deposit; sending both in one block fails on most RPCs.
```ts
await wallet.sendTransaction({
to: params.routerAddress,
data: encodeFunctionData({
abi: routerAbi,
functionName: "depositToken",
args: [quote.quoteHash, params.srcToken, BigInt(params.amountIn)],
}),
});
```
### Permit
Tokens supporting EIP-2612 can skip the approval transaction entirely by signing instead. The catalog marks them with `permit: true`. The permit variants live on the router contract (`router` from `GetChains`), which forwards the funds into the escrow, so the permit's spender and the transaction target are the router.
```solidity
function depositTokenWithPermit(
bytes32 quoteHash,
address token,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
```
The permit is attempted and its failure swallowed, so a permit that was already consumed does not break the deposit, but the transfer still needs the allowance to exist by then, from either the permit or a prior approval.
`depositTokenWithPermit2` is the equivalent for wallets with a standing [permit2](https://github.com/Uniswap/permit2) allowance: a `SignatureTransfer` signature replaces the approval, and any ERC-20 works, not just EIP-2612 tokens.
### Direct transfer
When the quote carries `direct_transfer: true`, the whole deposit collapses into a single ERC-20 `transfer` to the vault, with the 32-byte quote hash appended after the calldata. No approval, no router call, and the cheapest gas of any path. The flag is only set on cross-chain quotes where `src_token` equals `bridge_token_src` on a chain that supports it.
```ts
await wallet.sendTransaction({
to: params.srcToken,
data: encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: [params.routerAddress, BigInt(params.amountIn)],
}) + quote.quoteHash.replace(/^0x/, ""),
});
```
The transfer must be the transaction's own calldata: sent from the wallet directly to the token contract, for exactly `amount_in`, with the quote hash as the trailing 32 bytes. A transfer routed through a contract wallet or batching layer hides the calldata and the deposit will not be attributed. The SDK uses this path automatically when the quote allows it.
## Deposits that swap first
When the source token is not the asset that bridges, the router swaps through an allowlisted aggregator inside the deposit transaction. The calldata comes from `GetSwapInstructions`, keyed by quote hash.
```ts
const instructions = await hular.getSwapInstructions({ quoteHash: quote.quoteHash });
```
| Field | Use |
| --- | --- |
| `router_address` | The router contract to send the deposit to. |
| `payload.evm.aggregator` | Target of the swap, allowlisted on the router. |
| `payload.evm.swap_calldata` | Calldata to execute. |
| `src_token`, `amount_in` | Input leg. |
| `bridge_token`, `min_bridge_out` | Output leg and its floor. |
| `payload.evm.value` | Transaction value, non-zero when the source token is native. |
```solidity
function depositWithSwap(
bytes32 quoteHash,
address srcToken,
uint256 amountIn,
address aggregator,
bytes calldata swapData,
address bridgeToken,
uint256 minBridgeOut
) external payable;
```
Fetch the instructions immediately before sending. Routing calldata reflects pool state at the time it was built, and a stale route is the most common cause of a reverted deposit. `depositWithSwapAndPermit` takes the same arguments plus permit parameters.
If the swap returns less than `min_bridge_out` the transaction reverts with `InsufficientOutput`. The user pays gas and nothing else; their funds never leave their wallet.
## With the SDK
`ChainAdapter` resolves all of this (entry point, allowance, swap instructions, chain switching) into an ordered list of steps.
```ts
import { createChainAdapter } from "@mayanfinance/hular-sdk";
const adapter = createChainAdapter(client, chainStatus, 8453n);
const steps = await adapter.steps(wallet, quote.quoteHash, quote.orderParams);
for (const step of steps) {
const hash = await step.run(); // step.kind is "approve" or "deposit"
}
```
Render `step.kind` in your UI so the user knows which prompt they are signing.
## After it lands
The deposit emits `Deposit` and the indexer picks it up. There is nothing else to submit: no attestation, no claim, no destination transaction from the user. Move to [Track an order](/integration/track).
Deposit exactly `amount_in`. A different amount does not get a proportional payout; it is a mismatched deposit and is refunded.