# Simulate a base lending operation on-chain

Endpoint reference for the 1delta API. Index: https://docs.1delta.io/llms.txt · every endpoint: https://docs.1delta.io/llms-full.txt

---

### GET /v1/actions/lending/simulate

- operationId: `lending-simulate`
- docs: https://docs.1delta.io/1delta-api/lending-simulate/
- markdown: https://docs.1delta.io/1delta-api/lending-simulate.md
- tags: Lending (Actions)

Simulate a base lending operation on-chain

Build a base lending operation and **execute it against live chain state** before the user signs it.

This is an EVM check, not the arithmetic projection that `simulate=true` adds to the other lending endpoints. The transaction is built by the very endpoint the client would call (`/v1/actions/lending/{action}`) and then replayed through an `eth_call` with storage overrides — so what is verified is the exact calldata that would be submitted, including the lender quirks that only surface on-chain.

**Scope is the four single-grant operations.** `deposit` and `repay` override the pay token (funding balance and approval in one write); `withdraw` and `borrow` override the lender's own grant mapping (Aave credit delegation, Morpho `isAuthorized`, Comet `isAllowed`) so the action can be previewed *before* the user has signed that permission. Anything needing two grants at once — notably a leverage open that pulls fresh margin **and** borrows on behalf — cannot be previewed cold and is out of scope.

**The override is optimistic.** `willSucceed: true` with `grantOverridden: true` means "this succeeds once the permission exists"; it is not evidence the permission exists. Keep gating the real grant on the permissions array.

The `simulation` object is either:
- `{ simulated: true, willSucceed, gasUsed, amountOut, returnData, revertReason?, block, grantOverridden }`
- `{ simulated: false, reason, detail }` — where `reason` is one of `NO_TRANSACTION`, `MULTI_STEP`, `NATIVE_VALUE`, `MULTIPLE_GRANTS`, `GRANT_SLOT_UNKNOWN`, `SIMULATOR_UNAVAILABLE`.

Note `amountOut` is a measured balance delta on `operator`, so it reads `0` for lenders with no receipt token (Morpho Blue and every CDP-shaped lender) — those return their own values in `returnData` instead. A simulator outage never fails the request: the calldata is still returned and remains valid.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `action` | query | string | yes | Which base operation to build and replay: `deposit`, `withdraw`, `borrow` or `repay`. |
| `marketUid` | query | string | yes | Market identifier (`lender:chainId:address`). The address encodes the underlying token (Aave/Morpho/CompoundV3), cToken (CompoundV2), or pool (Init Capital). |
| `amount` | query | string | yes | Amount in wei |
| `mode` | query | "proxy" \| "direct" | no | Execution mode. proxy = 1delta composer, direct = raw protocol |
| `operator` | query | string | no | Wallet address of the user executing the action |
| `receiver` | query | string | no | Recipient of the lender position (deposit) or underlying tokens (borrow/withdraw). Defaults to `operator` when omitted. Custom-receiver support is per-lender. The API auto-selects the execution path: **Direct path supported** - Aave V2/V3 — `supply` / `withdraw` honor `to`. - Aave V4 — deposit via GiverPM `supplyOnBehalfOf`. Requires the GiverPM to be curated in `aave-v4-peripherals.json` with a name containing "giver". - Compound V3 — `supplyTo` / `withdrawTo`. - Morpho Blue & Lista DAO ERC-20 markets — Morpho `onBehalf`. - Euler V2 ERC-20 vaults — ERC-4626 `deposit(receiver)`. Collateral auto-enable is dropped for non-sub-account receivers. - Silo V2/V3 ERC-20 — `deposit(_, receiver, _)`. - Gearbox V3 passive-pool deposits — ERC-4626 `deposit(_, receiver)`. - Fluid borrow & withdraw — `operate(..., to_)` outflow slot. - Fluid **deposit to an existing NFT** when `accountId` is supplied — pure-deposit `operate()` calls bypass Fluid's owner auth gate, and the API folds a pre-flight `VaultFactory.ownerOf(nftId) == receiver` check into the merged multicall (rejects with 400 `INVALID_PARAM` on mismatch, `VALIDATION_FAILED` if the RPC can't confirm ownership). **Routes to composer (proxy) path** - Compound V2 family — direct `mint` is `msg.sender`-only. - Fluid **fresh-open deposits** — no `accountId` or `accountId=0`. The composer mints the new position NFT to itself, then transfers it to `receiver` via the encoder's `nftReceiver` slot. - Native-ETH vaults on Fluid (deposit / withdraw / repay) and the CompoundV2 family — the composer forwards `msg.value` to the vault's payable entrypoint. Other lenders' composer paths reject native asset and require wrapped-native as `payAsset`. **Direct-only branches that throw** - Gearbox V3 credit-side `addCollateral` — CA bound to operator. - Aave V4 native-gateway path — `supplyAsCollateralNative` has no recipient slot. **Unsupported in any single tx** - Init Capital — position NFT swept to operator. - Euler V2 / Silo V2/V3 native deposit paths — router/orchestrator credits msg.sender. - Fluid repay — on-chain primitive permits it, direct handler doesn't expose a receiver slot yet (routes to proxy as a conservative default). When the lender cannot honor a custom receiver on the direct path, `getTarget` falls back to `proxy` automatically. Forcing `mode=direct` on an unsupported path either throws (Gearbox credit-side, Aave V4 spoke, the explicit Silo router check) or silently credits the operator instead (Compound V2, Init, and Fluid fresh-open). |
| `payAsset` | query | string | no | Asset to pay with. Use the zero address to pay with native ETH on lenders whose vault accepts native (e.g. Init Capital, Fluid native-ETH vaults, CompoundV2 `cETH` markets). Proxy mode: defaults to market asset. Native is forwarded as `msg.value` only when the lender supports it (e.g. Fluid, CompoundV2); otherwise the composer rejects. |
| `isShares` | query | boolean | no | Amount is in shares (direct mode) |
| `accountId` | query | string | no | Per-position identifier. Lender-specific: - **Init Capital** — account ID, required for borrow/withdraw/repay. - **Euler V2** — sub-account index (0..255), defaults to 0. - **Gearbox V3** — credit-account address (alias: `creditAccount`). - **Fluid** — position NFT id. Omit or pass `0` to mint a new position; supply an existing nftId to act on an existing position. Required for borrow/withdraw/repay; for deposit it unlocks the direct path with a custom `receiver` (deposit lands on the receiver-owned NFT after pre-flight `ownerOf` validation). |
| `slippage` | query | number | no | Slippage tolerance in **basis points** (`50` = 0.5%, `100` = 1%). Required whenever the action has to price something: - **Aggregator swap** — when `payAsset` / `receiveAsset` differs from the market's underlying, the currency conversion is routed through a DEX aggregator and this bounds that swap. - **Order-book take (Morpho Midnight)** — bounds the worst execution price accepted while filling offers. Ignored when neither applies (same-asset action on a pool lender). |
| `simulate` | query | boolean | no | When true, the API fetches the user's on-chain balances and returns projected post-trade metrics in the `simulation` field. Default: false. |
| `block` | query | string | no | Block number or tag to simulate against. Defaults to `latest`. |

**Response `200`** — Transaction calldata plus the on-chain execution result

**Response `400`** — Validation error

**Response `429`** — Rate limited. Unauthenticated callers share a per-IP budget; send an `x-api-key` header to lift it. Retry with exponential backoff.

**Response `500`** — Unexpected server error. Safe to retry with backoff.

**Response `502`** — An upstream data source or protocol origin failed (`error.code` is `ORIGIN_FAILED`). `error.details` carries the per-origin status. This is also what a missing or malformed required parameter currently returns, rather than a 400.
