diff --git a/.claude/rules/decisions.md b/.claude/rules/decisions.md
index 3a11031a..9bfab87b 100644
--- a/.claude/rules/decisions.md
+++ b/.claude/rules/decisions.md
@@ -24,6 +24,18 @@ BEFORE making the change.
## Active Decisions
+### [2026-07-02] Lend AMM integration guide as lend/dex/ ("Lend AMM" nav group)
+**Status:** implemented
+**Scope:** new-section | navigation
+**Files affected:** `lend/dex/index.mdx`, `lend/dex/typescript.mdx`, `lend/dex/api.mdx`, `lend/dex/cpi.mdx`, `lend/dex/errors.mdx`, `docs.json`, `lend/program-addresses.mdx`
+**Linear issue:** DEV-690
+
+**Context:** Fluid (who built Jupiter Lend) published a swap integration guide for their Solana DEX so that routers/aggregators other than Jupiter can route through Jupiter Lend as a DEX. Source is the DEX docs on the `dex-deployments` branch of the private `Instadapp/fluid-contracts-solana` repo.
+**Decision:** Port as a public "Lend AMM" group in the Lend nav, ordered Earn → Borrow → Lend AMM (directly after Borrow, per YY). Page titles: "AMM Overview" (`index`, includes condensed architecture), "AMM SDK" (`typescript`), "AMM APIs" (`api`, coming-soon stub with `sidebarTitle: "AMM APIs (coming soon)"`), "AMM CPI Integration" (`cpi`), "AMM Errors" (`errors`). Product branded **Lend AMM** in prose (was briefly "Jupiter Lend DEX"; renamed per YY). "DEX" is reserved for the on-chain program (the DEX program, `jupZ4m2…`) and code identifiers (`Dex`, `dex_id`, `DexOperation`, `Dex*` error names). File paths stay at `lend/dex/` (match the program name; preview links already shared). No links to the private source repo; pages are self-contained.
+**Rationale:** YY initially asked for preview-link-only, then decided pages go in nav un-hidden, then set the AMM naming convention. 5 source pages collapsed: the standalone architecture page was background the overview half-duplicated, so it was folded into the index. TypeScript and CPI stay separate (distinct audiences, each near the ~200-line split threshold); errors is a shared lookup reference linked from both.
+**Alternatives considered:** (1) Hidden pages (original ask), superseded by YY's call to publish in nav. (2) Merging errors into CPI, rejected because the TypeScript path needs the same table. (3) Porting the full upstream doc set (pricing, swaps internals, liquidity actions), out of scope, swaps only. (4) Renaming paths to `lend/amm/`, rejected to keep preview links stable.
+**Migration notes:** New paths only, no redirects needed.
+
### [2026-06-17] Portal nav ordered by developer journey, not feature-vs-reference
**Status:** implemented
**Scope:** navigation
diff --git a/.claude/rules/product-learning.md b/.claude/rules/product-learning.md
index 4e836203..5c28d553 100644
--- a/.claude/rules/product-learning.md
+++ b/.claude/rules/product-learning.md
@@ -302,6 +302,26 @@ source code > SDK/FE > docs). Keep it current as a side effect of documenting th
---
+# Jupiter Lend AMM
+
+## Sources
+
+- **Upstream docs:** `Instadapp/fluid-contracts-solana` (PRIVATE repo), `dex-deployments` branch — its DEX docs (integration guide + architecture) are the source for `lend/dex/*` (DEV-690). Because the repo is private, the docs pages must stay self-contained: never link to source files there, and don't cite its file paths here (this repo is public).
+- **SDKs:** `@jup-ag/lend-read` (quoting: `Dex.estimateSwapIn`/`estimateSwapOut`) and `@jup-ag/lend/dex` subpath (execution: `getSwapInIx`/`getSwapOutIx`).
+
+## Facts
+
+- [2026-07-02] The product is Fluid's concentrated-liquidity AMM running on the Jupiter Lend Liquidity Layer. Upstream calls it "Fluid DEX"; our docs brand it **Lend AMM** (YY decision, DEV-690; briefly "Jupiter Lend DEX" before YY set the AMM naming). In prose the on-chain program is called the "AMM program"; "dex"/"Dex" appears only in literal code identifiers (accounts, seeds, error names, SDK subpath, file paths). Program IDL: https://solscan.io/account/jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd#programIdl Audience framing: routers/aggregators routing Jupiter Lend as a DEX directly (Jupiter's own routing engine already integrates it). REST endpoints are planned: nav has an "AMM APIs (coming soon)" stub at `lend/dex/api.mdx`; replace the stub when they launch.
+- [2026-07-02] Program addresses: DEX `jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd`, Liquidity Layer `jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC`, Oracle `jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc`.
+- [2026-07-02] Swap-path essentials: `swap_in` (exact-in) / `swap_out` (exact-out) return the counter-amount as Anchor `u64` return data; `token0`/`token1` ordered by pubkey sort; all four supply/borrow position accounts are mandatory for swaps (`None` → 6077) even though deposit/withdraw gate them; external-center-price pools need `[center_price_address, ...sources]` as leading remaining accounts; `ADDRESS_DEAD` (all-zero) recipient is a deliberate simulate-only quoting sentinel (errors 6081/6082).
+- [2026-07-02] SDK availability at time of writing: `@jup-ag/lend-read@0.0.13` (latest) already ships the `Dex` quoting class, but the `./dex` execution subpath only exists in `@jup-ag/lend@0.2.0-beta.2` (`beta` dist-tag), NOT in latest 0.1.10. Upstream doc claims the IDL ships at `@jup-ag/lend/dist/idl/dex.json` — false in both published versions (no idl files in the tarballs), and the public `jup-ag/jupiter-lend` repo `target/idl/` has no `dex.json` either. The IDL IS published on-chain (verified: `anchor idl fetch jupZ4m2…` finds the account; needs Anchor 0.30+ to parse). Docs point CPI integrators at `anchor idl fetch` and note the beta tag on the TypeScript page. Re-check at DEX launch: when 0.2.x hits `latest`, drop the beta-tag Note.
+
+## Content Gaps
+
+- [2026-07-02] Upstream `docs/dex/` pages NOT ported (out of DEV-690 scope): pricing.md, swaps.md (internals), liquidity.md, arbitrage.md, parameters.md, deployments.md, and the top-level integration.md (full instruction surface incl. liquidity actions and `preview_dex_shares`). Port candidates if LP/liquidity-action docs are ever requested.
+
+---
+
# Swap API V2 — Squads Multisig
## Architecture
diff --git a/.claude/rules/style-guide.md b/.claude/rules/style-guide.md
index 840b7e59..c3b372c0 100644
--- a/.claude/rules/style-guide.md
+++ b/.claude/rules/style-guide.md
@@ -208,3 +208,5 @@ Format: `- [YYYY-MM-DD] Decision: rationale`
- [2026-04-16] British English spelling for docs prose (e.g. "randomise", "optimise", "colour"). Do not Americanise existing British spellings.
- [2026-06-17] Write in plain engineer English. Before showing any page, scan the prose for (1) em dashes, (2) the AI-slop phrases in the Words to Avoid table ("ship", "blast radius", "before it ever counts against your plan", "at a glance", rhetorical triplets). Prefer the concrete action and the literal effect over flourish. "An API key used in a client the user controls is not private" beats "When you ship a key, that key is no longer a secret".
- [2026-06-21] Fee settlement / rev-share docs use abstract placeholders for the integrator/Jupiter split (`y%` integrator, `x%` Jupiter, `y = 100 − x`; `F bps` for the total), never concrete bps or split ratios (no "85/15", no "100 bps" example). Splits are negotiated per-integrator and Jupiter aims to maximise its share, so a concrete example would anchor integrators to a number that may be lower than what is actually charged. Keep the split illustration general (e.g. `y/100 × F`).
+- [2026-07-02] Diagrams use Mermaid (```mermaid code blocks, rendered natively by Mintlify), not ASCII art. First used on `lend/dex/index.mdx` (YY request). Port ASCII diagrams from upstream sources to Mermaid.
+- [2026-07-02] Lend AMM naming: the product is the **Lend AMM** ("Jupiter Lend AMM" only where the Jupiter context isn't already established). Nav group "Lend AMM"; page titles "AMM Overview", "AMM SDK", "AMM APIs", "AMM CPI Integration", "AMM Errors". In prose, the on-chain program is the "AMM program" and program-address tables say "Lend AMM" / "Jupiter Lend (AMM)". Never say "DEX program" or explain AMM-vs-DEX naming (YY: too confusing). "dex"/"Dex" survives ONLY in literal code identifiers: `Dex` account, `DexMetadata`, `dex_id`, `DexOperation`, `Dex*` error names, `["dex", ...]` seeds, the `@jup-ag/lend/dex` subpath, and file paths (`lend/dex/`). IDL link: https://solscan.io/account/jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd#programIdl
diff --git a/changelog/index.mdx b/changelog/index.mdx
index 266c7b39..48369228 100644
--- a/changelog/index.mdx
+++ b/changelog/index.mdx
@@ -19,6 +19,20 @@ llmsDescription: "Changelog for Jupiter developer APIs, SDKs, and docs. Tracks b
---
+
+
+## Jupiter Lend: AMM Integration Guide
+
+The Lend AMM, a concentrated-liquidity AMM on the Liquidity Layer, now has a swap integration guide for routers, aggregators, and on-chain programs.
+
+- Quote and execute swaps from TypeScript with `@jup-ag/lend-read` and `@jup-ag/lend/dex`
+- Call `swap_in` / `swap_out` via CPI from your own program, with a raw instruction recipe for non-Anchor integrators
+- [AMM overview](/lend/dex)
+
+
+
+---
+
## Swap API: JupiterZ Integrator Fees
diff --git a/docs.json b/docs.json
index c8e508fa..5b1adbb7 100644
--- a/docs.json
+++ b/docs.json
@@ -254,6 +254,16 @@
"lend/borrow/cpi"
]
},
+ {
+ "group": "Lend AMM",
+ "pages": [
+ "lend/dex/index",
+ "lend/dex/typescript",
+ "lend/dex/api",
+ "lend/dex/cpi",
+ "lend/dex/errors"
+ ]
+ },
{
"group": "Flashloan",
"pages": ["lend/flashloan/index", "lend/flashloan/execute"]
diff --git a/lend/dex/api.mdx b/lend/dex/api.mdx
new file mode 100644
index 00000000..be817558
--- /dev/null
+++ b/lend/dex/api.mdx
@@ -0,0 +1,19 @@
+---
+title: "AMM APIs"
+sidebarTitle: "AMM APIs (Coming soon)"
+description: "REST endpoints for quoting and building Lend AMM swaps. Coming soon."
+llmsDescription: "Placeholder for the upcoming Jupiter Lend AMM REST API. REST endpoints for quoting and building swaps against the Lend AMM over HTTP are not yet available. Until they launch, integrate with the TypeScript SDKs (@jup-ag/lend-read for quoting, @jup-ag/lend/dex for building swap instructions) or via Rust CPI from an on-chain program."
+---
+
+REST endpoints for quoting and building Lend AMM swaps over HTTP are coming soon.
+
+Until they launch, two integration paths are available:
+
+
+
+ Quote with `@jup-ag/lend-read`, build and send the swap with `@jup-ag/lend/dex`.
+
+
+ Call `swap_in` / `swap_out` from your own program via CPI.
+
+
diff --git a/lend/dex/cpi.mdx b/lend/dex/cpi.mdx
new file mode 100644
index 00000000..235c3cc6
--- /dev/null
+++ b/lend/dex/cpi.mdx
@@ -0,0 +1,173 @@
+---
+title: "AMM CPI Integration"
+description: "Call swap_in and swap_out on the Lend AMM from your own Solana program via CPI."
+llmsDescription: "How to swap against the Jupiter Lend AMM from an on-chain Solana program via cross-program invocation (CPI). Covers generating the CPI client from the published IDL with Anchor's declare_program! macro, the full 25-account DexOperation context in exact order, why all four supply/borrow position accounts are mandatory for swaps, reading the u64 return value with .get(), signing as a PDA with new_with_signer, passing external center-price oracle accounts as remaining accounts, Token-2022 handling, compute budget sizing, and a raw instruction recipe (discriminators and Borsh argument layout) for non-Anchor programs. AMM program id jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd."
+---
+
+This page is for **on-chain** integrators: programs that swap against the Lend AMM as part of their own instruction (a vault, router, strategy, or aggregator hop). You invoke `swap_in` / `swap_out` on the AMM program and read the returned amount.
+
+You do not need the AMM program's source crate. Anchor's `declare_program!` macro generates a full CPI client from the program's published [IDL](https://solscan.io/account/jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd#programIdl). If you're not on Anchor, a raw instruction recipe is at the [bottom](#raw-instruction-without-anchor).
+
+AMM program id: `jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd`.
+
+## Generate the CPI client from the IDL
+
+Fetch the on-chain IDL into your program's `idls/` directory (requires Anchor 0.30+):
+
+```bash
+anchor idl fetch jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd \
+ --provider.cluster mainnet -o idls/dex.json
+```
+
+Then generate the client:
+
+```rust
+use anchor_lang::prelude::*;
+
+// Reads ./idls/dex.json and generates `dex::cpi`, `dex::accounts`, `dex::client`.
+declare_program!(dex);
+```
+
+This gives you `dex::cpi::swap_in` / `swap_out`, the `dex::cpi::accounts::DexOperation` account struct, and the program id, all matching the deployed program.
+
+## The account context (DexOperation)
+
+A swap uses **25 accounts**, in this exact order. Your instruction declares them in its own `#[derive(Accounts)]`, then maps them into `DexOperation`:
+
+| # | Account | Flags | What it is |
+| --- | ----------------------------- | ------------- | ---------------------------------------- |
+| 1 | `signer` | mut, signer | funds leave its token account |
+| 2 | `dex` | mut | the `Dex` pool account |
+| 3 | `user_token_0_account` | mut | signer's token0 ATA (authority = signer) |
+| 4 | `user_token_1_account` | mut | signer's token1 ATA (authority = signer) |
+| 5 | `recipient` | optional | output owner; omit for signer |
+| 6 | `recipient_token_0_account` | mut, optional | recipient's token0 ATA |
+| 7 | `recipient_token_1_account` | mut, optional | recipient's token1 ATA |
+| 8 | `token_0` | readonly | token0 mint |
+| 9 | `token_1` | readonly | token1 mint |
+| 10 | `token_0_reserve` | mut | Liquidity Layer reserve for token0 |
+| 11 | `token_1_reserve` | mut | Liquidity Layer reserve for token1 |
+| 12 | `token_0_rate_model` | readonly | Liquidity Layer rate model for token0 |
+| 13 | `token_1_rate_model` | readonly | Liquidity Layer rate model for token1 |
+| 14 | `token_0_vault` | mut | Liquidity Layer vault ATA for token0 |
+| 15 | `token_1_vault` | mut | Liquidity Layer vault ATA for token1 |
+| 16 | `dex_supply_position_token_0` | mut, optional | pool's token0 supply position |
+| 17 | `dex_supply_position_token_1` | mut, optional | pool's token1 supply position |
+| 18 | `dex_borrow_position_token_0` | mut, optional | pool's token0 borrow position |
+| 19 | `dex_borrow_position_token_1` | mut, optional | pool's token1 borrow position |
+| 20 | `liquidity` | readonly | Liquidity Layer root account |
+| 21 | `liquidity_program` | readonly | Liquidity Layer program |
+| 22 | `token_0_program` | readonly | Token / Token-2022 for token0 |
+| 23 | `token_1_program` | readonly | Token / Token-2022 for token1 |
+| 24 | `oracle_program` | readonly | Oracle program |
+| 25 | `associated_token_program` | readonly | ATA program |
+
+All addresses are deterministic: derive them from the seeds in the [overview](/lend/dex#pdas), or read them from the pool's lookup table. The Liquidity Layer accounts are validated inside the Liquidity Layer CPI; you don't verify them yourself.
+
+
+**All four positions (16 to 19) are mandatory for swaps.** Unlike deposit/withdraw (which gate positions on which side is enabled), the swap path unconditionally loads both supply and borrow positions for the legs it touches. Passing `None` for any of the four fails with [`DexValidateInvalidLiquidityPosition` (6077)](/lend/dex/errors). They exist from pool setup, so always pass all four `Some(...)`.
+
+`recipient` (5 to 7) is genuinely optional: omit it (`None`) to send output to the signer.
+
+
+## Calling swap_in / swap_out
+
+```rust
+pub fn my_swap(ctx: Context, amount_in: u64, amount_out_min: u64) -> Result<()> {
+ let cpi_accounts = dex::cpi::accounts::DexOperation {
+ signer: ctx.accounts.signer.to_account_info(),
+ dex: ctx.accounts.dex.to_account_info(),
+ user_token_0_account: ctx.accounts.user_token_0.to_account_info(),
+ user_token_1_account: ctx.accounts.user_token_1.to_account_info(),
+ recipient: None, // output -> signer
+ recipient_token_0_account: None,
+ recipient_token_1_account: None,
+ token_0: ctx.accounts.token_0.to_account_info(),
+ token_1: ctx.accounts.token_1.to_account_info(),
+ token_0_reserve: ctx.accounts.token_0_reserve.to_account_info(),
+ token_1_reserve: ctx.accounts.token_1_reserve.to_account_info(),
+ token_0_rate_model: ctx.accounts.token_0_rate_model.to_account_info(),
+ token_1_rate_model: ctx.accounts.token_1_rate_model.to_account_info(),
+ token_0_vault: ctx.accounts.token_0_vault.to_account_info(),
+ token_1_vault: ctx.accounts.token_1_vault.to_account_info(),
+ dex_supply_position_token_0: Some(ctx.accounts.supply_pos_0.to_account_info()),
+ dex_supply_position_token_1: Some(ctx.accounts.supply_pos_1.to_account_info()),
+ dex_borrow_position_token_0: Some(ctx.accounts.borrow_pos_0.to_account_info()),
+ dex_borrow_position_token_1: Some(ctx.accounts.borrow_pos_1.to_account_info()),
+ liquidity: ctx.accounts.liquidity.to_account_info(),
+ liquidity_program: ctx.accounts.liquidity_program.to_account_info(),
+ token_0_program: ctx.accounts.token_0_program.to_account_info(),
+ token_1_program: ctx.accounts.token_1_program.to_account_info(),
+ oracle_program: ctx.accounts.oracle_program.to_account_info(),
+ associated_token_program: ctx.accounts.associated_token_program.to_account_info(),
+ };
+
+ let cpi_ctx = CpiContext::new(ctx.accounts.dex_program.to_account_info(), cpi_accounts);
+
+ let swap0to1 = true;
+ let amount_out: u64 = dex::cpi::swap_in(cpi_ctx, swap0to1, amount_in, amount_out_min)?.get();
+
+ msg!("received {} out", amount_out);
+ Ok(())
+}
+```
+
+Exact-out is identical with `dex::cpi::swap_out(cpi_ctx, swap0to1, amount_out, amount_in_max)?.get()`, which returns the `amount_in` spent.
+
+### Reading the return value
+
+Both instructions return `Result`. Through CPI that arrives as an Anchor `Return`; call `.get()`. This is the authoritative amount (the same value the `LogSwap` event records). No `get_return_data` boilerplate or log parsing needed.
+
+## Signing as a PDA
+
+If the swapper is a **PDA your program owns** (the usual case: a vault paying from its own token account), build the context with `new_with_signer` and your seeds:
+
+```rust
+let seeds: &[&[&[u8]]] = &[&[b"my_vault", &[bump]]];
+let cpi_ctx = CpiContext::new_with_signer(
+ ctx.accounts.dex_program.to_account_info(),
+ cpi_accounts,
+ seeds,
+);
+let out = dex::cpi::swap_in(cpi_ctx, swap0to1, amount_in, amount_out_min)?.get();
+```
+
+The `user_token_*_account`s must have `authority = signer`: the PDA must own the token accounts funds leave from.
+
+## External center price and remaining accounts
+
+Pools priced by an external oracle require the oracle plus its price sources as **leading remaining accounts**, in order `[center_price_address, ...sources]`. `remaining_accounts[0]` **must** equal the pool's `center_price_address`, or the swap fails with [`DexInvalidExternalCenterPrice` (6068)](/lend/dex/errors) / [`DexMissingExternalCenterPrice` (6067)](/lend/dex/errors).
+
+```rust
+let cpi_ctx = CpiContext::new(dex_program, cpi_accounts)
+ .with_remaining_accounts(center_price_accounts); // [oracle, sources...]
+```
+
+Internal-price pools take no remaining accounts. Read `Dex.center_price_address` (all-zero means internal) to tell which a pool uses.
+
+## Edge cases and gotchas
+
+- **All four positions required.** The most common CPI mistake. `None` fails with error `6077`.
+- **`token0`/`token1` ordering is fixed by pubkey sort**, not by your call. `swap0to1 = true` always means "lower-pubkey mint to higher-pubkey mint". Derive the direction from the mints you hold.
+- **Token-2022.** `token_0_program` / `token_1_program` must each match the mint's owning program; a pool can mix classic SPL and Token-2022. Don't hardcode `TOKEN_PROGRAM_ID`.
+- **All-or-nothing.** A routed swap performs up to two Liquidity Layer CPIs plus the oracle update; if any leg reverts, the whole swap reverts (no partial fill).
+- **Re-entrancy lock.** The pool locks for the duration; a second swap on the same pool within one instruction fails with [`DexAlreadyEntered` (6017)](/lend/dex/errors).
+- **Compute budget.** A two-pool routed swap plus oracle update is CU-heavy. Add a `ComputeBudgetProgram::set_compute_unit_limit` to the outer transaction; the default 200k is often not enough with your own logic included.
+- **`ADDRESS_DEAD` recipient reverts.** If `recipient` resolves to the all-zero pubkey, the instruction intentionally errors ([`DexSwapInResult` 6081 / `DexSwapOutResult` 6082](/lend/dex/errors)). That's the off-chain simulation sentinel; never pass it from a real CPI.
+- **Slippage still applies.** `amount_out_min` / `amount_in_max` are enforced inside the CPI. Pass a real bound; `0` / `u64::MAX` disables the guard.
+- **Dust and size floors** apply as they do off-chain. See [`DexLimitingAmountsSwapAndNonPerfect` (6060)](/lend/dex/errors) and [`DexSwapInLimitingAmounts` (6052)](/lend/dex/errors).
+
+## Raw instruction (without Anchor)
+
+If you can't use `declare_program!`, build the instruction by hand. Discriminator (8 bytes) plus Borsh args, in order:
+
+| Instruction | Discriminator (bytes) | Args (Borsh) |
+| ----------- | ------------------------------- | --------------------------------------------------------- |
+| `swap_in` | `[141,172,10,208,69,9,56,154]` | `swap0to1: bool`, `amount_in: u64`, `amount_out_min: u64` |
+| `swap_out` | `[206,36,149,14,163,132,148,1]` | `swap0to1: bool`, `amount_out: u64`, `amount_in_max: u64` |
+
+`bool` is 1 byte (`0`/`1`); each `u64` is 8 bytes little-endian. So the data is `discriminator ++ [swap0to1] ++ amount_a.to_le_bytes() ++ amount_b.to_le_bytes()` (17 bytes of args).
+
+Account metas follow the [25-account order above](#the-account-context-dexoperation) with the listed `signer` / `writable` flags, then any center-price remaining accounts (all readonly). For an **omitted optional** account (`recipient` when you want output to go to the signer), pass the **AMM program id** as the placeholder pubkey (readonly, non-signer) in that slot. This is the Anchor convention for `None`. Then `invoke` / `invoke_signed` the `Instruction`.
+
+See [Errors](/lend/dex/errors) for the full swap-path error table.
diff --git a/lend/dex/errors.mdx b/lend/dex/errors.mdx
new file mode 100644
index 00000000..e0ad16eb
--- /dev/null
+++ b/lend/dex/errors.mdx
@@ -0,0 +1,77 @@
+---
+title: "AMM Errors"
+description: "Error codes on the Lend AMM swap path, what triggers them, and how to handle them."
+llmsDescription: "Reference table of Jupiter Lend AMM program errors on the swap path. Covers slippage errors (DexNotEnoughAmountOut 6024, DexExceedsAmountInMax 6051), size and dust limits (DexSwapInLimitingAmounts 6052, DexSwapOutLimitingAmounts 6053, DexLimitingAmountsSwapAndNonPerfect 6060, DexOracleUpdateHugeSwapDiff 6048), reserve and utilization errors (6022, 6057, 6058, 6059, 6025, 6023), pool state errors (DexSwapAndArbitragePaused 6050, DexAlreadyEntered 6017, 6021, 6019, 6020), external center-price errors (6067, 6068, 6056), CPI account errors (DexValidateInvalidLiquidityPosition 6077, 6078, 6066, 6079), the intentional simulation sentinels (DexSwapInResult 6081, DexSwapOutResult 6082), and how to read the custom error code from a failed transaction in TypeScript or CPI."
+---
+
+These are the AMM program errors an integrator can hit on the **swap path** of the Lend AMM. Codes are the Anchor custom-error numbers (the program's error enum starts at `6000`); the name is the `ErrorCodes` variant. The on-chain message string is the SCREAMING_SNAKE form of the name (e.g. `DEX_NOT_ENOUGH_AMOUNT_OUT`).
+
+## Slippage
+
+| Code | Name | When | Fix |
+| ------ | ----------------------- | --------------------------------------------- | ------------------------------- |
+| `6024` | `DexNotEnoughAmountOut` | `swap_in` output would be `< amount_out_min`. | Re-quote and/or widen slippage. |
+| `6051` | `DexExceedsAmountInMax` | `swap_out` input would be `> amount_in_max`. | Re-quote and/or widen slippage. |
+
+These are the expected failures when the market moves between quote and execution. No funds move. Re-quote with a fresh snapshot and retry.
+
+## Size and dust limits
+
+| Code | Name | When | Fix |
+| ------ | ------------------------------------- | ----------------------------------------------------------------------------- | ------------------------- |
+| `6052` | `DexSwapInLimitingAmounts` | Adjusted input `> 50%` of combined input-side imaginary reserves. | Split into smaller swaps. |
+| `6053` | `DexSwapOutLimitingAmounts` | Adjusted output `> 50%` of combined output-side imaginary reserves. | Split into smaller swaps. |
+| `6060` | `DexLimitingAmountsSwapAndNonPerfect` | Input/output below the dust floor (`~1e6` in 9-dec terms, `1e2` native). | Increase the amount. |
+| `6048` | `DexOracleUpdateHugeSwapDiff` | The swap would move the pool price more than the oracle's 5%-per-swap bound. | Split into smaller swaps. |
+
+## Reserves and utilization
+
+| Code | Name | When | Fix |
+| ------ | ------------------------------ | ------------------------------------------------------------------------------ | -------------------------------------------- |
+| `6022` | `DexTokenReservesTooLow` | A post-swap real reserve would fall below the minimum-liquidity floor. | Smaller swap / other direction. |
+| `6057` | `DexDebtReservesTooLow` | Debt-pool reserves insufficient for the routed leg. | Smaller swap. |
+| `6058` | `DexInvalidCollateralReserves` | Collateral reserves in an invalid state for the trade. | Smaller swap / re-quote. |
+| `6059` | `DexInvalidDebtReserves` | Debt reserves in an invalid state for the trade. | Smaller swap / re-quote. |
+| `6025` | `DexUtilizationCapReached` | Post-swap Liquidity Layer utilization exceeds the token's `max_utilization`. | Wait for utilization to ease / smaller swap. |
+| `6023` | `DexNoSwapRoute` | Neither side can service the requested direction. | Check the pool has the needed side enabled. |
+
+## Pool state
+
+| Code | Name | When | Fix |
+| ------ | --------------------------- | ---------------------------------------------------------------------- | -------------------------------- |
+| `6050` | `DexSwapAndArbitragePaused` | Swaps and arbitrage are paused on this pool. | None, pool is paused by admin. |
+| `6017` | `DexAlreadyEntered` | Re-entrancy lock held (e.g. two swaps on one pool in one instruction). | Don't re-enter the same pool. |
+| `6021` | `DexPoolNotInitialized` | Pool not set up / no side enabled. | Use a live `dex_id`. |
+| `6019` | `DexSmartColNotEnabled` | Collateral (supply) side not enabled. | Use a pool with the side enabled.|
+| `6020` | `DexSmartDebtNotEnabled` | Debt (borrow) side not enabled. | Use a pool with the side enabled.|
+
+## Center price (external-oracle pools)
+
+| Code | Name | When | Fix |
+| ------ | ------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- |
+| `6067` | `DexMissingExternalCenterPrice` | Pool needs an external center price but no oracle accounts were passed. | Pass `[center_price_address, ...sources]` as remaining accounts. |
+| `6068` | `DexInvalidExternalCenterPrice` | `remaining_accounts[0]` is not the pool's `center_price_address`. | Pass the correct oracle first. |
+| `6056` | `DexCenterPriceOutOfRange` | Resolved center price outside the pool's configured clamps. | Transient: retry. Persistent: pool misconfigured. |
+
+## Accounts (CPI integrators)
+
+| Code | Name | When | Fix |
+| ------ | ------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------- |
+| `6077` | `DexValidateInvalidLiquidityPosition` | A required supply/borrow position account was `None` or wrong. | Pass **all four** positions (see [CPI](/lend/dex/cpi)). |
+| `6078` | `DexValidateMissingLiquidityPosition` | A liquidity position required for an enabled leg is missing. | Include the leg's position. |
+| `6066` | `DexInvalidTokenAccount` | A token account's mint/authority/program doesn't match. | Fix ATA owner / mint / token program. |
+| `6079` | `DexInvalidRecipientPositionWithdraw` | Output routed to a recipient but its token account is missing/invalid. | Provide a valid `recipient_token_*_account`. |
+
+## Simulation sentinel (not a real failure)
+
+| Code | Name | When |
+| ------ | ------------------ | ----------------------------------------------------------------------------- |
+| `6081` | `DexSwapInResult` | Recipient is `ADDRESS_DEAD`; `swap_in` reverts with the result in the log. |
+| `6082` | `DexSwapOutResult` | Recipient is `ADDRESS_DEAD`; `swap_out` reverts with the result in the log. |
+
+These are **intentional**: the off-chain quote-by-simulation path (see [TypeScript: exact on-chain quoting via simulation](/lend/dex/typescript#exact-on-chain-quoting-via-simulation-optional)). A real swap must never use the `ADDRESS_DEAD` recipient.
+
+## Reading the code from a failed transaction
+
+- **TypeScript:** Anchor throws an `AnchorError`; read `err.error.errorCode.number` (e.g. `6024`) and `err.error.errorMessage`. Raw logs contain `Program ... failed: custom program error: 0x1798`, which is the code in hex: `0x1798` = `6040`, `6024` = `0x1788`.
+- **CPI:** the inner error propagates to your program. Wrap the `dex::cpi::*` call in a `match` and map the failure to your own error if you want a cleaner message; otherwise let it bubble up and the transaction fails with the error code above.
diff --git a/lend/dex/index.mdx b/lend/dex/index.mdx
new file mode 100644
index 00000000..ec6e6933
--- /dev/null
+++ b/lend/dex/index.mdx
@@ -0,0 +1,123 @@
+---
+title: "AMM Overview"
+description: "Route swaps directly through the Lend AMM, Jupiter Lend's concentrated-liquidity AMM on the Liquidity Layer."
+llmsDescription: "Overview of integrating swaps against the Jupiter Lend AMM on Solana, a concentrated-liquidity AMM built on the Jupiter Lend Liquidity Layer. Covers pool shapes (collateral pool, debt pool, collateral + debt), the swap_in (exact-in) and swap_out (exact-out) instructions, slippage guards enforced on-chain, token0/token1 pubkey ordering, native-unit amounts, pool-favouring rounding, dex_id pool addressing, program addresses (Lend AMM jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd, Liquidity Layer jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC, Oracle jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc), PDA seeds, and how pools sit on the Liquidity Layer (shares, interest accrual, re-entrancy, pausing). Entry point for routers, aggregators, and on-chain programs that want to route swaps through Jupiter Lend as a DEX via the TypeScript SDKs or Rust CPI."
+---
+
+The Lend AMM is a concentrated-liquidity AMM that runs on top of the Jupiter Lend Liquidity Layer. This guide is for routers, aggregators, and on-chain programs that want to swap against the AMM directly.
+
+The guide is self-contained: the on-chain behaviour relevant to swaps is described here, and the SDKs mirror it. You do not need access to the program source. It covers the two paths integrators build on:
+
+- **[AMM SDK](/lend/dex/typescript)**: request a quote, then execute the swap from a wallet or backend using the published TypeScript SDKs.
+- **[AMM CPI Integration](/lend/dex/cpi)**: call `swap_in` / `swap_out` from your own on-chain program via cross-program invocation.
+- **[AMM Errors](/lend/dex/errors)**: the swap-path error codes, what triggers them, and how to avoid them.
+
+REST endpoints for quoting and building AMM swaps are [coming soon](/lend/dex/api).
+
+
+**Scope: swaps only.** Liquidity actions (deposit, withdraw, borrow, payback) and admin instructions are out of scope for this guide.
+
+
+## How the AMM works
+
+A Lend AMM **pool** trades exactly one pair of tokens, `token0` and `token1`. Liquidity is packed into a price range around a center price, so trades near the current price get much deeper liquidity than a constant-product pool of the same size.
+
+The tokens backing a pool are **Liquidity Layer** positions, not tokens the pool custodies. A pool can therefore exist in three shapes:
+
+| Pool shape | Smart collateral | Smart debt | Behaviour |
+| --------------------- | :--------------: | :--------: | -------------------------------------------------- |
+| **Collateral pool** | on | off | swaps trade against LP supply |
+| **Debt pool** | off | on | swaps trade against borrow debt |
+| **Collateral + debt** | on | on | swaps route across both sides, kept in price-sync |
+
+For swapping you don't need to care which shape a pool is. The program routes across whatever sides are enabled and returns a single input/output amount. The SDK quote does the same math off-chain.
+
+## The two swap instructions
+
+| Instruction | Form | You specify | Returns |
+| ----------- | --------- | ----------------------------------------- | ---------------------- |
+| `swap_in` | exact-in | `swap0to1`, `amount_in`, `amount_out_min` | `amount_out` received |
+| `swap_out` | exact-out | `swap0to1`, `amount_out`, `amount_in_max` | `amount_in` it cost |
+
+- **`swap0to1`** picks direction: `true` trades `token0 → token1`, `false` trades `token1 → token0`.
+- **`amount_out_min` / `amount_in_max`** are your slippage guards, enforced on-chain. `swap_in` reverts if the output is below `amount_out_min`; `swap_out` reverts if the input exceeds `amount_in_max`. No funds move on a revert.
+- Both instructions **return** the computed counter-amount as a `u64` (Anchor return data), so a CPI caller reads the result directly.
+
+## Conventions that affect integrators
+
+- **`token0` / `token1` are ordered by public key.** `token0` is the mint whose address sorts lower. This is fixed per pool; compute `swap0to1` from the mints you hold, not from a fixed slot.
+- **All amounts are native token units** (base units of that mint's decimals) at the API edge. Internally the pool works in a common 9-decimal representation and scales back to native decimals at the boundary. Token decimals above 9 are not supported.
+- **Rounding always favours the pool.** Exact-in floors the output; exact-out ceils the input. Quote, then guard with slippage.
+- **Pools are addressed by a small integer `dex_id`** (`1..=totalDexes`).
+
+## Program addresses
+
+| Program | Address |
+| --------------- | --------------------------------------------- |
+| Lend AMM | `jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd` |
+| Liquidity Layer | `jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC` |
+| Oracle | `jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc` |
+
+## PDAs
+
+The AMM's own PDAs are derived against the **Lend AMM** program id; the Liquidity Layer accounts a swap touches are derived against the **Liquidity Layer** program id. Seeds (all strings are ASCII bytes; `dex_id` is a little-endian `u16`):
+
+| Account | Program | Seeds |
+| -------------------- | --------- | ------------------------------------- |
+| Dex (pool) | Lend AMM | `["dex", dex_id]` |
+| DexMetadata | Lend AMM | `["dex_metadata", dex_id]` |
+| Reserve | Liquidity | `["reserve", mint]` |
+| Rate model | Liquidity | `["rate_model", mint]` |
+| User supply position | Liquidity | `["user_supply_position", mint, dex]` |
+| User borrow position | Liquidity | `["user_borrow_position", mint, dex]` |
+| Liquidity (root) | Liquidity | `["liquidity"]` |
+
+Token **vaults** are the associated token account of the Liquidity root PDA for each mint (allow-owner-off-curve). The **`DexMetadata`** account stores the pool's **address lookup table** (`lookup_table`), which you use to keep swap transactions under the size limit. The SDK derives all of these for you.
+
+## How a pool sits on the Liquidity Layer
+
+An AMM pool holds no token balances of its own. It is registered as a protocol on the Liquidity Layer, and the tokens that back it are Liquidity Layer positions:
+
+- The **collateral pool** is a pair of Liquidity Layer **supply** positions (one for `token0`, one for `token1`). Liquidity providers deposit, and the supplied tokens earn the lending supply rate while also serving swaps.
+- The **debt pool** is a pair of Liquidity Layer **borrow** positions. Borrowers draw debt against the pool, and that outstanding debt is itself the liquidity a swap trades against.
+
+```mermaid
+flowchart TB
+ AMM["AMM pool
token0 / token1, center price, ranges,
fee, shares, oracle state"]
+ subgraph LL["Liquidity Layer"]
+ direction LR
+ subgraph COL["Collateral pool"]
+ S0["token0 supply position"]
+ S1["token1 supply position"]
+ end
+ subgraph DEBT["Debt pool"]
+ B0["token0 borrow position"]
+ B1["token1 borrow position"]
+ end
+ end
+ AMM -- "CPI (supply legs)" --> COL
+ AMM -- "CPI (borrow legs)" --> DEBT
+```
+
+Every swap drives this CPI flow: the AMM computes amounts, then calls the Liquidity Layer to move the actual tokens and update the positions. Because the reserves are Liquidity Layer positions, they carry exchange prices (interest accrual): the AMM converts raw position amounts into current-value amounts before doing any pricing math, so a pool reflects accrued interest automatically.
+
+Details that matter when you integrate:
+
+- **Pool state** lives in one `Dex` account per pair (seeds `["dex", dex_id]`), holding the pair identity, live pricing (`center_price` and the oracle's last stored prices), mode switches, range configuration, fees, and share accounting. The optional `center_price_address` field points at an external price source; an all-zero value means the pool prices internally. This distinction matters for the accounts a swap must pass, see [external center price](/lend/dex/cpi#external-center-price-and-remaining-accounts).
+- **Liquidity provider stakes are shares, not token balances.** Supply shares are a proportional claim on the collateral pool's two reserves; borrow shares are a proportional obligation against the debt pool's two reserves. Swappers never touch shares.
+- **Re-entrancy lock.** Every user-facing entry point takes a per-pool `re_entrancy` lock and releases it at the end. A second entry while locked reverts with `DexAlreadyEntered` (6017), so you cannot swap the same pool twice inside one instruction.
+- **Pausing.** Admins can pause a whole pool (`pause_dex`) or only swaps and the rebalance step (`pause_swap_and_arbitrage`). Paused swaps revert with `DexSwapAndArbitragePaused` (6050).
+- **Automatic arbitrage.** Every operation on a two-sided pool ends with an automatic arbitrage step that re-syncs the collateral and debt prices. It runs inside the swap instruction; you don't invoke it.
+
+## Integration paths
+
+Both paths are the same three steps: **quote → guard → execute**. The only difference is where the code runs.
+
+
+
+ Quote with `@jup-ag/lend-read`, build and send the swap with `@jup-ag/lend/dex`. For off-chain integrations.
+
+
+ Call `swap_in` / `swap_out` from your own program via CPI, with the full 25-account context and a raw instruction recipe.
+
+
diff --git a/lend/dex/typescript.mdx b/lend/dex/typescript.mdx
new file mode 100644
index 00000000..53d62452
--- /dev/null
+++ b/lend/dex/typescript.mdx
@@ -0,0 +1,278 @@
+---
+title: "AMM SDK"
+description: "Quote and execute Lend AMM swaps with the @jup-ag/lend-read and @jup-ag/lend TypeScript SDKs."
+llmsDescription: "How to swap against the Jupiter Lend AMM using the TypeScript SDKs: quote off-chain with Dex.estimateSwapIn / estimateSwapOut from @jup-ag/lend-read, derive on-chain slippage guards (amountOutMin / amountInMax), then build and send the swap instruction with getSwapInIx / getSwapOutIx from @jup-ag/lend/dex. Covers the SwapResult quote fields, the builder return shape (ixs, accounts, remainingAccounts, addressLookupTableAccounts), versioned transactions with the pool's address lookup table, compute budget sizing, sending output to a different recipient, exact on-chain quoting via simulation with the ADDRESS_DEAD sentinel, and mapping AnchorError codes like DexNotEnoughAmountOut (6024) and DexExceedsAmountInMax (6051)."
+---
+
+Swapping against the Lend AMM from TypeScript is always three steps:
+
+1. **Quote** the swap off-chain with `@jup-ag/lend-read`.
+2. **Derive a slippage guard** from the quote (`amountOutMin` / `amountInMax`).
+3. **Build and send** the swap instruction with `@jup-ag/lend/dex`.
+
+Two packages, by design:
+
+| Package | Role | Swap API |
+| ------------------- | ------------------- | -------------------------------------------- |
+| `@jup-ag/lend-read` | quoting (read-only) | `Dex.estimateSwapIn` / `estimateSwapOut` |
+| `@jup-ag/lend` | execution | `getSwapInIx` / `getSwapOutIx` (from `/dex`) |
+
+## Install
+
+```bash
+npm install @jup-ag/lend @jup-ag/lend-read @solana/web3.js @coral-xyz/anchor bn.js
+```
+
+
+The `/dex` entry point ships from `@jup-ag/lend` v0.2.0. If the `latest` tag is still older, install the beta tag: `npm install @jup-ag/lend@beta`.
+
+
+## 1. Request a quote
+
+```typescript
+import { Connection } from "@solana/web3.js";
+import BN from "bn.js";
+import { Dex } from "@jup-ag/lend-read";
+
+const connection = new Connection("https://api.mainnet-beta.solana.com");
+const reader = new Dex(connection); // read-only; accepts a URL string or Connection
+
+const dexId = 1;
+const swap0to1 = true; // token0 -> token1
+
+// Exact-in: paying exactly this many base units of the INPUT token.
+const amountIn = new BN(1_000_000);
+
+const quote = await reader.estimateSwapIn(dexId, swap0to1, amountIn);
+console.log("amountIn ", quote.amountIn.toString());
+console.log("amountOut", quote.amountOut.toString()); // in the OUTPUT token's native units
+```
+
+`estimateSwapIn` / `estimateSwapOut` return a `SwapResult`:
+
+| Field | Meaning |
+| --------------------------- | -------------------------------------------------- |
+| `amountIn` | input, in the **input token's** native units |
+| `amountOut` | output, in the **output token's** native units |
+| `colDeposit`, `colWithdraw` | amount routed through the collateral (supply) side |
+| `debtPayback`, `debtBorrow` | amount routed through the debt (borrow) side |
+| `newPrice` | pool price after the swap |
+| `centerPrice` | center price used for the quote |
+
+You normally only need `amountIn` / `amountOut`. The `col*` / `debt*` legs are the per-side routing split (they sum to the total) and are exposed for analytics.
+
+The quote is pure math on a fresh snapshot and mirrors the on-chain formulas, so it can `throw` before you build a transaction if the swap would be rejected on-chain, for example when the trade exceeds 50% of the input reserves, falls below the dust floor, or the pool's swaps are paused. Treat a thrown quote as "not executable right now".
+
+### Exact-out quoting
+
+```typescript
+const amountOut = new BN(500_000); // want exactly this many OUTPUT base units
+const quote = await reader.estimateSwapOut(dexId, swap0to1, amountOut);
+// quote.amountIn is what it will cost.
+```
+
+## 2. Derive the slippage guard
+
+The quote is a point-in-time estimate; the pool can move before your transaction lands. Turn the quote into a hard bound the program enforces.
+
+```typescript
+const slippageBps = 50; // 0.50%
+
+// swap_in: floor the output you'll accept.
+const amountOutMin = quote.amountOut
+ .mul(new BN(10_000 - slippageBps))
+ .div(new BN(10_000));
+
+// swap_out: cap the input you'll spend.
+const amountInMax = quote.amountIn
+ .mul(new BN(10_000 + slippageBps))
+ .div(new BN(10_000));
+```
+
+If the market moves past your bound, the swap reverts with [`DexNotEnoughAmountOut` (6024)](/lend/dex/errors) for `swap_in` or [`DexExceedsAmountInMax` (6051)](/lend/dex/errors) for `swap_out`. No funds move.
+
+## 3. Build and send the swap
+
+`getSwapInIx` / `getSwapOutIx` resolve every account for the pool (mints, token programs, reserves, vaults, the four liquidity positions), auto-attach the external center-price accounts when the pool needs them, and load the pool's address lookup table. They return the instruction(s) plus the loaded ALT. You sign and send.
+
+```typescript
+import {
+ PublicKey,
+ VersionedTransaction,
+ TransactionMessage,
+ ComputeBudgetProgram,
+ Keypair,
+} from "@solana/web3.js";
+import { getSwapInIx } from "@jup-ag/lend/dex";
+
+const wallet: Keypair = /* your signer */;
+
+const { ixs, addressLookupTableAccounts } = await getSwapInIx({
+ connection,
+ signer: wallet.publicKey, // a PublicKey; you build and sign the tx yourself
+ dexId,
+ swap0to1,
+ amountIn,
+ amountOutMin,
+ // recipient?: PublicKey (defaults to signer)
+});
+
+// A routed two-pool swap runs two Liquidity-Layer CPIs + the oracle update,
+// so raise the compute budget above the 200k default.
+const instructions = [
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
+ ...ixs,
+];
+
+const { blockhash } = await connection.getLatestBlockhash();
+const message = new TransactionMessage({
+ payerKey: wallet.publicKey,
+ recentBlockhash: blockhash,
+ instructions,
+}).compileToV0Message(addressLookupTableAccounts); // the ALT keeps the tx under size
+
+const tx = new VersionedTransaction(message);
+tx.sign([wallet]);
+const sig = await connection.sendTransaction(tx);
+console.log("swap tx:", sig);
+```
+
+Exact-out is symmetric: `getSwapOutIx` takes `amountOut` + `amountInMax`.
+
+```typescript
+import { getSwapOutIx } from "@jup-ag/lend/dex";
+
+const { ixs, addressLookupTableAccounts } = await getSwapOutIx({
+ connection,
+ signer: wallet.publicKey,
+ dexId,
+ swap0to1,
+ amountOut,
+ amountInMax,
+});
+```
+
+### Return shape
+
+Both builders return:
+
+| Field | Use |
+| ----------------------------- | ------------------------------------------------------------ |
+| `ixs` | the swap instruction(s) to include in your transaction |
+| `accounts` | the resolved named accounts (for inspection / composition) |
+| `remainingAccounts` | center-price accounts (empty for internal-price pools) |
+| `addressLookupTableAddresses` | the pool's ALT address(es) |
+| `addressLookupTableAccounts` | the **loaded** ALT account(s), pass to `compileToV0Message` |
+
+Because the builders return raw instructions, you can compose a swap with your own instructions (wrap/unwrap SOL, create an ATA, etc.) in the same transaction. Include all of them and pass `addressLookupTableAccounts` when compiling.
+
+### Sending output to a different recipient
+
+Pass `recipient` (a `PublicKey`) to deliver the output elsewhere; the builder derives that owner's ATAs for both mints. The recipient's associated token account for the output mint must already exist (or be created in the same transaction).
+
+```typescript
+await getSwapInIx({
+ connection,
+ signer,
+ dexId,
+ swap0to1,
+ amountIn,
+ amountOutMin,
+ recipient,
+});
+```
+
+## Full example: quote to swap (exact-in)
+
+```typescript
+import {
+ Connection,
+ Keypair,
+ VersionedTransaction,
+ TransactionMessage,
+ ComputeBudgetProgram,
+} from "@solana/web3.js";
+import BN from "bn.js";
+import { Dex } from "@jup-ag/lend-read";
+import { getSwapInIx } from "@jup-ag/lend/dex";
+
+export async function swapExactIn(
+ connection: Connection,
+ wallet: Keypair,
+ dexId: number,
+ swap0to1: boolean,
+ amountIn: BN,
+ slippageBps = 50,
+) {
+ // 1. quote
+ const quote = await new Dex(connection).estimateSwapIn(
+ dexId,
+ swap0to1,
+ amountIn,
+ );
+
+ // 2. slippage guard
+ const amountOutMin = quote.amountOut
+ .mul(new BN(10_000 - slippageBps))
+ .div(new BN(10_000));
+
+ // 3. build & send
+ const { ixs, addressLookupTableAccounts } = await getSwapInIx({
+ connection,
+ signer: wallet.publicKey,
+ dexId,
+ swap0to1,
+ amountIn,
+ amountOutMin,
+ });
+
+ const { blockhash } = await connection.getLatestBlockhash();
+ const msg = new TransactionMessage({
+ payerKey: wallet.publicKey,
+ recentBlockhash: blockhash,
+ instructions: [
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
+ ...ixs,
+ ],
+ }).compileToV0Message(addressLookupTableAccounts);
+
+ const tx = new VersionedTransaction(msg);
+ tx.sign([wallet]);
+ return {
+ sig: await connection.sendTransaction(tx),
+ expectedOut: quote.amountOut,
+ minOut: amountOutMin,
+ };
+}
+```
+
+## Why a versioned transaction and lookup table
+
+A swap touches ~25 fixed accounts, plus the oracle and its price sources for external-center-price pools. A legacy transaction can exceed the 1232-byte limit, so always send a versioned (v0) transaction and pass the pool's ALT (returned by the builders as `addressLookupTableAccounts`). This is not optional for external-center-price pools and is good practice for all of them.
+
+## Exact on-chain quoting via simulation (optional)
+
+`estimateSwapIn` / `estimateSwapOut` are kept in lockstep with the program and are enough for production. If you want the pool's exact on-chain result (for example, to cross-check), build a swap whose recipient is the all-zero pubkey (`PublicKey.default`, the `ADDRESS_DEAD` sentinel) and **simulate** it. The instruction runs the full pricing path and deliberately reverts, logging:
+
+```
+DexSwapInResult: [, , ]
+```
+
+Parse the log from `simulateTransaction`. This never moves funds.
+
+
+Never use the `ADDRESS_DEAD` recipient in a real (non-simulated) swap. The instruction always reverts with it.
+
+
+## Error handling
+
+Anchor throws an `AnchorError`; read `err.error.errorCode.number` and map it. The full table is in [Errors](/lend/dex/errors); the ones you'll hit most:
+
+| Code | Name | Cause | Fix |
+| ---- | ------------------------------------- | ------------------------------- | ------------------------- |
+| 6024 | `DexNotEnoughAmountOut` | price moved past `amountOutMin` | re-quote / widen slippage |
+| 6051 | `DexExceedsAmountInMax` | price moved past `amountInMax` | re-quote / widen slippage |
+| 6052 | `DexSwapInLimitingAmounts` | trade > 50% of input reserves | split into smaller swaps |
+| 6060 | `DexLimitingAmountsSwapAndNonPerfect` | amount below the dust floor | increase size |
+| 6048 | `DexOracleUpdateHugeSwapDiff` | swap would move price > 5% | split into smaller swaps |
+| 6050 | `DexSwapAndArbitragePaused` | swaps paused on this pool | none, pool is paused |
diff --git a/lend/program-addresses.mdx b/lend/program-addresses.mdx
index b8337a84..3d8f1e91 100644
--- a/lend/program-addresses.mdx
+++ b/lend/program-addresses.mdx
@@ -1,7 +1,7 @@
---
title: "Program addresses"
description: "Official program addresses for Jupiter Lend protocols."
-llmsDescription: "Official on-chain program addresses for all Jupiter Lend programs: Earn (jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9), Liquidity (jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC), Lending Rewards Rate Model (jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKar), Oracle (jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc), Vaults/Borrow (jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi), and Flashloan (jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS)."
+llmsDescription: "Official on-chain program addresses for all Jupiter Lend programs: Earn (jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9), Liquidity (jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC), Lending Rewards Rate Model (jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKar), Oracle (jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc), Vaults/Borrow (jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi), Flashloan (jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS), and AMM (jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd)."
---
The official program addresses for Jupiter Lend across different protocols:
@@ -14,6 +14,7 @@ The official program addresses for Jupiter Lend across different protocols:
| **Jupiter Oracle** | Mainnet | `jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc` |
| **Jupiter Vaults (Borrow)** | Mainnet | `jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi` |
| **Jupiter Flashloan** | Mainnet | `jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS` |
+| **Jupiter Lend (AMM)** | Mainnet | `jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd` |
## IDL and types
diff --git a/llms.txt b/llms.txt
index 4bbae5fd..4c80aec0 100644
--- a/llms.txt
+++ b/llms.txt
@@ -200,6 +200,14 @@ Lending protocol with Earn (deposit yield), Borrow (collateralised loans), and F
- [Borrow CPI Integration](https://developers.jup.ag/docs/lend/borrow/cpi.md): How to integrate Jupiter Lend Borrow vaults via Cross-Program Invocation (CPI) from an on-chain Solana program. Covers init_position and operate function discriminators (sha256 global:init_position and global:operate), complete Rust InitPositionParams and OperateParams structs with all account fields and mutability, account explanation tables for core vault, position management, and liquidity integration accounts, remaining_accounts structure (oracle sources, branches, tick debt arrays), signed-amount convention (positive = deposit/borrow, negative = withdraw/payback, i128::MIN = max), transfer types (Normal vs Claim), operation patterns (deposit, borrow, payback, withdraw, max variants), error codes, return values (nft_id, new_col_final, new_debt_final), and TypeScript CPI integration using getOperateIx from @jup-ag/lend/borrow with setup instructions pattern. Programs: Vaults jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi (mainnet), Ho32sUQ4NzuAQgkPkHuNDG3G18rgHmYtXFA8EBmqQrAu (devnet).
+### Lend AMM
+
+- [AMM Overview](https://developers.jup.ag/docs/lend/dex/index.md): Overview of integrating swaps against the Jupiter Lend AMM on Solana, a concentrated-liquidity AMM built on the Jupiter Lend Liquidity Layer. Covers pool shapes (collateral pool, debt pool, collateral + debt), the swap_in (exact-in) and swap_out (exact-out) instructions, slippage guards enforced on-chain, token0/token1 pubkey ordering, native-unit amounts, pool-favouring rounding, dex_id pool addressing, program addresses (Lend AMM jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd, Liquidity Layer jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC, Oracle jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc), PDA seeds, and how pools sit on the Liquidity Layer (shares, interest accrual, re-entrancy, pausing). Entry point for routers, aggregators, and on-chain programs that want to route swaps through Jupiter Lend as a DEX via the TypeScript SDKs or Rust CPI.
+- [AMM SDK](https://developers.jup.ag/docs/lend/dex/typescript.md): How to swap against the Jupiter Lend AMM using the TypeScript SDKs: quote off-chain with Dex.estimateSwapIn / estimateSwapOut from @jup-ag/lend-read, derive on-chain slippage guards (amountOutMin / amountInMax), then build and send the swap instruction with getSwapInIx / getSwapOutIx from @jup-ag/lend/dex. Covers the SwapResult quote fields, the builder return shape (ixs, accounts, remainingAccounts, addressLookupTableAccounts), versioned transactions with the pool's address lookup table, compute budget sizing, sending output to a different recipient, exact on-chain quoting via simulation with the ADDRESS_DEAD sentinel, and mapping AnchorError codes like DexNotEnoughAmountOut (6024) and DexExceedsAmountInMax (6051).
+- [AMM APIs](https://developers.jup.ag/docs/lend/dex/api.md): Placeholder for the upcoming Jupiter Lend AMM REST API. REST endpoints for quoting and building swaps against the Lend AMM over HTTP are not yet available. Until they launch, integrate with the TypeScript SDKs (@jup-ag/lend-read for quoting, @jup-ag/lend/dex for building swap instructions) or via Rust CPI from an on-chain program.
+- [AMM CPI Integration](https://developers.jup.ag/docs/lend/dex/cpi.md): How to swap against the Jupiter Lend AMM from an on-chain Solana program via cross-program invocation (CPI). Covers generating the CPI client from the published IDL with Anchor's declare_program! macro, the full 25-account DexOperation context in exact order, why all four supply/borrow position accounts are mandatory for swaps, reading the u64 return value with .get(), signing as a PDA with new_with_signer, passing external center-price oracle accounts as remaining accounts, Token-2022 handling, compute budget sizing, and a raw instruction recipe (discriminators and Borsh argument layout) for non-Anchor programs. AMM program id jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd.
+- [AMM Errors](https://developers.jup.ag/docs/lend/dex/errors.md): Reference table of Jupiter Lend AMM program errors on the swap path. Covers slippage errors (DexNotEnoughAmountOut 6024, DexExceedsAmountInMax 6051), size and dust limits (DexSwapInLimitingAmounts 6052, DexSwapOutLimitingAmounts 6053, DexLimitingAmountsSwapAndNonPerfect 6060, DexOracleUpdateHugeSwapDiff 6048), reserve and utilization errors (6022, 6057, 6058, 6059, 6025, 6023), pool state errors (DexSwapAndArbitragePaused 6050, DexAlreadyEntered 6017, 6021, 6019, 6020), external center-price errors (6067, 6068, 6056), CPI account errors (DexValidateInvalidLiquidityPosition 6077, 6078, 6066, 6079), the intentional simulation sentinels (DexSwapInResult 6081, DexSwapOutResult 6082), and how to read the custom error code from a failed transaction in TypeScript or CPI.
+
### Flashloan
- [Flashloans](https://developers.jup.ag/docs/lend/flashloan/index.md): Overview of Jupiter Lend Flashloans: borrow any supported asset with no collateral and no fees, as long as the borrowed amount is returned within the same transaction. Covers use cases (arbitrage, liquidations, collateral swapping), how the atomic borrow-execute-payback flow works on Solana, and links to the execute guide for SDK integration with getFlashloanIx, getFlashBorrowIx, and getFlashPaybackIx from @jup-ag/lend/flashloan.
@@ -232,7 +240,7 @@ Lending protocol with Earn (deposit yield), Borrow (collateralised loans), and F
### Resources
-- [Program addresses](https://developers.jup.ag/docs/lend/program-addresses.md): Official on-chain program addresses for all Jupiter Lend programs: Earn (jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9), Liquidity (jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC), Lending Rewards Rate Model (jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKar), Oracle (jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc), Vaults/Borrow (jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi), and Flashloan (jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS).
+- [Program addresses](https://developers.jup.ag/docs/lend/program-addresses.md): Official on-chain program addresses for all Jupiter Lend programs: Earn (jup3YeL8QhtSx1e253b2FDvsMNC87fDrgQZivbrndc9), Liquidity (jupeiUmn818Jg1ekPURTpr4mFo29p46vygyykFJ3wZC), Lending Rewards Rate Model (jup7TthsMgcR9Y3L277b8Eo9uboVSmu1utkuXHNUKar), Oracle (jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc), Vaults/Borrow (jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi), Flashloan (jupgfSgfuAXv4B6R2Uxu85Z1qdzgju79s6MfZekN6XS), and AMM (jupZ4m2GqUCJ5iueMfzQf8khFfH31d4XAQt3RzCT9Vd).
- [IDL and types](https://developers.jup.ag/docs/lend/idl-and-types.md): Anchor IDL JSON files and TypeScript type definitions for Jupiter Lend on-chain programs. Located in the jup-ag/jupiter-lend GitHub repository under the target directory. Use for CPI integrations, custom clients, and low-level program interactions.
- [Dune Analytics](https://developers.jup.ag/docs/lend/dune.md): Links to Dune Analytics dashboards for Jupiter Lend protocol data, including TVL, utilisation, supply/borrow metrics, and community-built analytics.