# navi-defi DeFi MCP skill (v0.1.0)

MCP endpoint (Streamable HTTP, stateless, OAuth: clients authorize automatically on first connect): `https://defi-mcp.naviprotocol.io/mcp`

- Default is Code Mode: three tools, `defi_search`, `defi_execute`, `defi_docs`. Write an async arrow function that calls `defi.*` and return what you need.
- Append `?codemode=false` for one MCP tool per function (same catalog, same handlers).
- Append `?truncateToolResult=false` to disable the ~6k-token result truncation.
- Builders never sign or send. They return unsigned bytes, a dry-run, deterministic risk rules and a `previewUrl` where the user connects a wallet and signs; poll `defi_get_tx_status` afterwards.

# Workflows

The DeFi MCP never signs or broadcasts. Every `build*` function returns unsigned transaction bytes, a dry-run, a risk assessment and a `previewUrl`. Give the `previewUrl` to the user: they open it, connect their wallet, review and sign in the browser. Then poll `getTxStatus` to learn whether it executed.

## How do I swap tokens: quote → build → preview → status

1. Quote: `defi.getSwapQuote({ from, to, amount })` returns amountIn, amountOut, minimum out and price impact. `from`/`to` are symbols or coin types; `amount` is a human-readable decimal string.
2. Build: `defi.buildSwap({ address, from, to, amount, slippageBps })` builds the swap for `address` and stores a preview. Returns `previewId`, `previewUrl`, `dryRun`, `risk`, `warnings`.
3. Preview: send the user the `previewUrl` (or call `defi.getTxPreview({ previewId })` to show the dry-run and AI summary yourself).
4. Status: `defi.getTxStatus({ previewId })` returns `created | signed | executed | failed | expired` and the on-chain digest once executed. Previews expire after 30 minutes.

In one `defi_execute` program:

```js
async () => {
  const quote = await defi.getSwapQuote({ from: "SUI", to: "USDC", amount: "10" });
  const built = await defi.buildSwap({ address: "0x...", from: "SUI", to: "USDC", amount: "10" });
  return { expectedOut: quote.amountOut, previewUrl: built.previewUrl, risk: built.risk.level };
}
```

## Positions → claim rewards

1. `defi.getPositions({ address })` lists every protocol position with claimable rewards. Read `errors[]` for protocols that could not be fetched.
2. `defi.buildClaimRewards({ address, protocols })` builds one transaction claiming across the given protocols and returns a `previewUrl`.
3. Hand the `previewUrl` to the user, then `defi.getTxStatus({ previewId })`.

## Balances → swap

1. `defi.getWalletBalances({ address })` returns every coin with amount, USD value and coin type.
2. Pick the coin and amount, quote it with `defi.getSwapQuote`, then build with `defi.buildSwap` as above.

## Lending (NAVI): supply, withdraw, borrow, repay

`defi.buildLendingAction({ address, action, asset, amount })` builds the NAVI lending transaction and returns a `previewUrl`; borrow and withdraw report health factor before and after. Vaults and liquid staking follow the same pattern with `buildVaultAction` and `buildLstAction`.

## Checking a transaction the user already sent

`defi.getWalletTransactions({ address })` lists recent transactions; `defi.getTransaction({ digest })` explains one (balance changes, Move calls, status).

# Units, amounts and addresses

## Addresses

- Sui addresses are `0x` followed by 64 hex characters. Short forms are normalized, but always pass the full address when you have it.
- The `address` input of every read tool is the wallet to inspect; for `build*` tools it is the sender who will sign in the preview page.

## Coins and tokens

- Coin types are fully-qualified Move types, e.g. `0x2::sui::SUI` or `0xdba3...::usdc::USDC`. Token inputs accept a symbol (`SUI`, `USDC`) or a coin type; outputs always include `coinType`, `symbol` and `decimals`.
- Use `defi.searchTokens({ keyword })` when a symbol is ambiguous and copy the exact `coinType` into later calls.

## Amounts

- Every amount you pass is a human-readable decimal string: `"1.5"` means 1.5 SUI, `"100"` means 100 USDC. Never pass raw base units (MIST) as input.
- Outputs give both `amount` (decimal string) and `amountRaw` (base units as a string, 9 decimals for SUI, 6 for USDC). Prices are `priceUsd` numbers; USD values are `usdValue`.
- Slippage for swaps is `slippageBps` in basis points (50 = 0.5%).

## Gas

Dry-run gas figures are in MIST (`budgetMist`, `computationMist`, `storageMist`, `rebateMist`, `netMist`) with `netSui` as the decimal SUI equivalent. 1 SUI = 1,000,000,000 MIST.

## Time

Timestamps are ISO-8601 strings in UTC. Previews carry `createdAt` and `expiresAt`; a preview expires 30 minutes after it is built and `getTxStatus` then reports `expired`.

# DefiHub positions

`defi.getPositions({ address })` aggregates a wallet's DeFi positions across Sui protocols through DefiHub. `defi.getProtocolPositions({ address, protocol })` is the single-protocol, faster variant; `defi.listProtocols()` and `defi.getProtocol({ slug })` describe what is supported.

## Shape

```
{
  address,
  summary: { totalUsd, byType: { lending, lp, vault, lst, staking, ... } },
  positions: [
    {
      protocol,            // slug, e.g. "navi", "cetus"
      type,                // lending | lp | vault | lst | staking | farming | ...
      usdValue,
      assets: [{ coinType, symbol, decimals, amount, amountRaw, usdValue, role }],   // role: supplied | borrowed | lp | reward ...
      rewards: [{ coinType, symbol, amount, amountRaw, usdValue, claimable }],
      details: { ... }     // protocol-specific: health factor, APY, pool id, tick range ...
    }
  ],
  errors: [ { protocol, message } ],
  meta: { sources: [{ protocol, cache, fetchedAt }] }
}
```

Use `protocols` to restrict the query, `types` to filter by position type, `minUsd` to hide dust and `noDetails: true` to drop the protocol-specific `details` when you only need totals (it also keeps results well under the truncation budget).

## errors[] semantics

`errors` lists protocols DefiHub could not fetch for this request (upstream outage, RPC timeout, unsupported protocol). The rest of the response is still valid: a non-empty `errors` means "totals exclude these protocols", not "the call failed". Report them to the user as "positions on X could not be loaded" rather than retrying blindly. Protocol slugs that DefiHub does not know are listed there with an "unknown protocol" message.

## Claimable rewards

`rewards[].claimable` is true when the protocol lets the wallet claim now. Pass the matching `protocol` slugs to `defi.buildClaimRewards({ address, protocols })`.

# Code Mode: writing defi_execute programs

`defi_execute(code)` runs a JavaScript program in an isolated sandbox. Inside it, `defi.*` functions call the same DeFi tools the native mode exposes, one HTTP round-trip each. The program is an async arrow function; whatever it returns is sent back to you as JSON.

## Shape of the code

```js
async () => {
  const status = await defi.getTxStatus({ previewId: "..." });
  return { status: status.status, digest: status.digest };
}
```

- Must be an `async () => { ... }` expression. Top-level statements are not accepted.
- Every `defi.<fn>(input)` takes one plain object and returns a Promise of the tool's data.
- Return only what you need: results are truncated to about 6,000 tokens. Pick fields, slice arrays, sum numbers in the sandbox instead of returning raw payloads.
- `console.log` output comes back as `logs` after the result, capped at 4,000 characters.

## Limits

- At most 50 `defi.*` calls per execution. The 51st throws `defi.<fn> failed (rate_limited)`.
- 55 seconds of run time; an infinite loop is cut and reported as a timeout.
- Results larger than 1 MB are refused inside the sandbox; return less data.
- No network except the DeFi MCP host, no filesystem persistence, no secrets: the sandbox only holds a short-lived execution token.

## Errors

A failed call throws an `Error` whose message starts with `defi.<fn> failed (<code>): ...`. Codes: `invalid_input`, `invalid_address`, `not_found`, `unsupported`, `upstream_failed`, `upstream_timeout`, `rate_limited`. The error also carries `code`, `status`, `retryable` and `details`. Catch it when one failing protocol should not abort the whole program:

```js
async () => {
  const out = {};
  for (const address of addresses) {
    try { out[address] = await defi.getPositions({ address }); }
    catch (err) { out[address] = { error: err.message }; }
  }
  return out;
}
```

An uncaught error ends the program and comes back as `isError: true` with the message.

## Discovering functions

Use `defi_search(query)` for the signatures of matching functions and `defi_docs(query)` for conventions and workflows. The full `declare const defi` block is in the `defi_execute` tool description.

## Tool reference

26 functions. Names below are the native-mode tool names; in Code Mode call the camelCase `defi.<jsName>` shown in each signature.

## Read

### defi_get_positions

List all DeFi positions of a Sui address across every supported protocol (lending, LP, vaults, staking, LST, farming, CDP, perps...). Returns `positions[]` sorted by absolute net USD (each with `netUsd`, role-tagged `assets[]`: supplied, borrowed, liquidity, staked, reward..., and `yields[]`), a USD `summary` (netUsd, suppliedUsd, borrowedUsd, rewardsUsd, pnlUsd), `positionCount`, `errors[]` and `meta.sources[]` ("protocol:type=hit|miss|error|skipped" per adapter). Large portfolios are truncated after the biggest positions (a "--- TRUNCATED ---" marker says how many were cut; `positionCount` is the real total). Every amount is a decimal string in token units (`amount`) with the base-unit integer in `amountRaw`; USD values and the `summary` totals are decimal strings. `yields[].apr/apy` are fractions (0.05 = 5%). Entries in `errors[]` are protocols whose adapter failed this request: their positions are UNKNOWN, not empty. Never tell the user they hold nothing in a protocol listed under `errors`. Use `protocols`/`types` to narrow the fan-out and `minUsd` to hide dust; set `noDetails:false` only when you need health factors, tick ranges or other protocol-specific details (large output). Takes a few seconds on a cache miss.

```ts
getPositions(input: { address: string; protocols?: string[]; types?: Array<"lending" | "cdp" | "lp" | "farming" | "vault" | "staking" | "lst" | "perp" | "options" | "vesting" | "rewards" | "other">; minUsd?: string; noDetails?: boolean }): Promise<unknown>
// await defi.getPositions({ address: "0x..." })
```

- `address`: string — Sui address (0x...) of the wallet to inspect
- `protocols`?: string[] — Protocol slugs to include, e.g. ["navi","cetus"]
- `types`?: Array<"lending" | "cdp" | "lp" | "farming" | "vault" | "staking" | "lst" | "perp" | "options" | "vesting" | "rewards" | "other"> — Position types to include, e.g. ["lending","lp"]
- `minUsd`?: string — Drop positions below this absolute USD value (unpriced positions are kept)
- `noDetails`?: boolean — Strip the per-position `details` object (default true, keeps output small) (default: true)

### defi_get_protocol_positions

Positions of a Sui address in ONE protocol (optionally one position type). Same response shape as defi_get_positions but faster: use it when the user asks about a specific protocol such as "my NAVI loans". `protocol` is a slug from defi_list_protocols (e.g. "navi", "suilend", "cetus"). Every amount is a decimal string in token units (`amount`) with the base-unit integer in `amountRaw`; USD values and the `summary` totals are decimal strings. `yields[].apr/apy` are fractions (0.05 = 5%). Entries in `errors[]` are protocols whose adapter failed this request: their positions are UNKNOWN, not empty. Never tell the user they hold nothing in a protocol listed under `errors`.

```ts
getProtocolPositions(input: { address: string; protocol: string; type?: "lending" | "cdp" | "lp" | "farming" | "vault" | "staking" | "lst" | "perp" | "options" | "vesting" | "rewards" | "other"; minUsd?: string; noDetails?: boolean }): Promise<unknown>
// await defi.getProtocolPositions({ address: "0x...", protocol: "<protocol>" })
```

- `address`: string — Sui address (0x...) of the wallet to inspect
- `protocol`: string — Protocol slug, e.g. "navi"
- `type`?: "lending" | "cdp" | "lp" | "farming" | "vault" | "staking" | "lst" | "perp" | "options" | "vesting" | "rewards" | "other" — Restrict to one position type, e.g. "lending"
- `minUsd`?: string — Drop positions below this absolute USD value
- `noDetails`?: boolean — Strip the per-position `details` object (default true) (default: true)

### defi_get_account_activity

Latest on-chain transaction of a Sui address (digest, timestamp, checkpoint, direction "from" = sent, "to" = received). `latestTx` is null for an address with no history. Use it as a freshness anchor: no new transaction since a previous snapshot means the position structure is unchanged (accrued interest/rewards still drift, and liquidations by third parties do not show up here).

```ts
getAccountActivity(input: { address: string }): Promise<unknown>
// await defi.getAccountActivity({ address: "0x..." })
```

- `address`: string — Sui address (0x...) of the wallet to inspect

### defi_list_protocols

List the Sui DeFi protocols supported by the position tools: slug, name, website, categories (position types) and adapters with `staleness` ("static" amounts only change through the user's own transactions; "accruing" drift over time) and `lifecycle` (active | withdraw-only | discontinued). Use the `slug` as the `protocol` argument of defi_get_protocol_positions. Filter with `category` (e.g. "lending", "lp", "vault", "lst"). Cached for 1 hour.

```ts
listProtocols(input?: { category?: "lending" | "cdp" | "lp" | "farming" | "vault" | "staking" | "lst" | "perp" | "options" | "vesting" | "rewards" | "other" }): Promise<unknown>
// await defi.listProtocols()
```

- `category`?: "lending" | "cdp" | "lp" | "farming" | "vault" | "staking" | "lst" | "perp" | "options" | "vesting" | "rewards" | "other" — Only protocols with this position type in their categories

### defi_get_protocol

Metadata of one supported protocol by slug: name, website, description, categories and adapters (type, variant, staleness, lifecycle). Returns not_found for an unknown slug; use defi_list_protocols to discover slugs.

```ts
getProtocol(input: { slug: string }): Promise<unknown>
// await defi.getProtocol({ slug: "<slug>" })
```

- `slug`: string — Protocol slug, e.g. "navi"

### defi_get_token_prices

Current USD prices for up to 40 Sui tokens. Each entry of `tokens` is a symbol ("SUI", "USDC") or a full coin type ("0x2::sui::SUI"). Returns `prices[]` with {input, coinType, symbol, decimals, priceUsd, logoURI}; `priceUsd` is a decimal string or null when no price source covers the token. Inputs that cannot be resolved (unknown symbol, coin type without on-chain metadata) are listed in `errors[]` instead of failing the call. Prefer coin types over symbols when the symbol is ambiguous (several "USDC" bridges exist).

```ts
getTokenPrices(input: { tokens: string[] }): Promise<unknown>
// await defi.getTokenPrices({ tokens: [] })
```

- `tokens`: string[] — Symbols or coin types, max 40

### defi_search_tokens

Search the Sui token list by symbol or coin-type fragment (case-insensitive). Returns `tokens[]` with {coinType, symbol, decimals, priceUsd, category, logoURI, isVerify, isSuggest}, exact matches first, then suggested and verified tokens. Use it to turn a user's token name into a coin type before quoting or building a swap; `isVerify:false` tokens may be impostors. The underlying index returns at most 6 matches per query.

```ts
searchTokens(input: { keyword: string; limit?: number }): Promise<unknown>
// await defi.searchTokens({ keyword: "<keyword>" })
```

- `keyword`: string — Symbol or coin type fragment, e.g. "USDC" or "::deep::"
- `limit`?: number — Max results (default 6) (default: 6)

### defi_get_wallet_balances

Coin balances held directly in a Sui wallet (not DeFi positions; use defi_get_positions for those). Returns `balances[]` with {coinType, symbol, decimals, amount, amountRaw, priceUsd, usdValue} sorted by usdValue descending (unpriced coins last, usdValue null), plus `totalUsd` over priced coins and `unpriced` count. `amount` is a decimal string in token units; `amountRaw` the base-unit integer. `minUsd` hides dust: priced coins below that value and all unpriced coins (their count is returned as `unpricedHidden`; unpriced means unknown value, not zero). Wallets often hold dozens of unpriced airdrop/spam coins, so pass `minUsd` for a summary.

```ts
getWalletBalances(input: { address: string; minUsd?: string }): Promise<unknown>
// await defi.getWalletBalances({ address: "0x..." })
```

- `address`: string — Sui address (0x...)
- `minUsd`?: string — Hide priced balances below this USD value

### defi_get_wallet_transactions

Recent Sui mainnet transactions of an address, newest first. `scope:"affected"` (default) includes every transaction that touched the address (sent or received); `scope:"sent"` only those it signed. Each entry has digest, sender, status (success|failure), timestamp, checkpoint, `moveCalls` as "package::module::function" (identifies the protocol/action) and the address's own `balanceChanges` {coinType, symbol, amount, amountRaw} (signed decimal strings; gas shows as a small negative SUI change). Page with `cursor` = previous `nextCursor` while `hasMore` is true. Use defi_get_transaction for the full detail of one digest.

```ts
getWalletTransactions(input: { address: string; scope?: "sent" | "affected"; limit?: number; cursor?: string }): Promise<unknown>
// await defi.getWalletTransactions({ address: "0x..." })
```

- `address`: string — Sui address (0x...)
- `scope`?: "sent" | "affected" — (default: "affected")
- `limit`?: number — (default: 20)
- `cursor`?: string — `nextCursor` from the previous page

### defi_get_transaction

Details of one Sui mainnet transaction by digest. `format:"explained"` (default) returns a compact human-oriented summary: status, operationType, sender, gas, moveCalls (package::module::function), balanceChanges per owner with symbol and decimal `amount`, objectChanges counts and decoded events. `format:"raw"` returns the full GraphQL record (transactionJson, effectsJson, balanceChangesJson, events, objectChanges) for debugging; it is large. Returns not_found for an unknown digest.

```ts
getTransaction(input: { digest: string; format?: "explained" | "raw" }): Promise<unknown>
// await defi.getTransaction({ digest: "<digest>" })
```

- `digest`: string — Base58 transaction digest
- `format`?: "explained" | "raw" — (default: "explained")

### defi_simulate_transaction

Dry-run unsigned Sui transaction bytes (base64 BCS TransactionData, e.g. `transactionBytes` from a defi_build_* tool or a third-party dapp) on mainnet without executing them. Returns status (success|failure) with the execution error, gas estimate in MIST and SUI, balanceChanges per owner (decimal `amount`, `amountRaw`, usdValue), objectChanges counts and moveCalls with known protocol labels. Use it before asking the user to sign anything you did not build yourself.

```ts
simulateTransaction(input: { transactionBytes: string }): Promise<unknown>
// await defi.simulateTransaction({ transactionBytes: "<transactionBytes>" })
```

- `transactionBytes`: string — base64-encoded BCS TransactionData

### defi_get_swap_quote

Quote a token swap on Sui through the Astros aggregator without building a transaction. `from`/`to` are symbols or coin types; `amount` is the input amount in token units as a decimal string ("1.5"). Returns amountIn/amountOut (decimal strings) with their base-unit `amountInRaw`/`amountOutRaw`, `minAmountOut` after applying `slippageBps` (default 50 = 0.5%), `priceImpactHigh` (warn the user when true), `isAccurate` (false means the aggregator's dry-run failed and the quote should not be trusted), the DEXes used and a `routeSummary` of hops as "provider:FROM→TO". Quotes expire within seconds; re-quote before building.

```ts
getSwapQuote(input: { from: string; to: string; amount: string; slippageBps?: number }): Promise<unknown>
// await defi.getSwapQuote({ from: "<from>", to: "<to>", amount: "1.5" })
```

- `from`: string — Input token symbol or coin type
- `to`: string — Output token symbol or coin type
- `amount`: string — Input amount in token units, e.g. "10" or "0.5"
- `slippageBps`?: number — Slippage tolerance in basis points (default: 50)

## Build (never signs)

### defi_build_swap

Build an unsigned swap transaction through the Astros aggregator (best route across Sui DEXes). Returns transaction bytes, a dry-run, deterministic risk rules and a previewUrl. This never signs or executes: the user must open previewUrl to review and sign in their wallet.

```ts
buildSwap(input: { address: string; from: string; to: string; amount: string; slippageBps?: number }): Promise<unknown>
// await defi.buildSwap({ address: "0x...", from: "<from>", to: "<to>", amount: "1.5" })
```

- `address`: string — Sui address that will sign the swap
- `from`: string — Coin to sell: symbol ("SUI") or full coin type
- `to`: string — Coin to buy: symbol ("USDC") or full coin type
- `amount`: string — Amount of `from` to sell, human units (e.g. "0.1")
- `slippageBps`?: number — Max slippage in basis points (50 = 0.5%) (default: 50)

### defi_build_lending_action

Build an unsigned NAVI lending transaction: supply, withdraw, borrow or repay an asset in a market. Includes the health factor before and after (null when the account has no debt). Returns transaction bytes, a dry-run, deterministic risk rules and a previewUrl. This never signs or executes: the user must open previewUrl to review and sign in their wallet.

```ts
buildLendingAction(input: { address: string; protocol?: "navi"; action: "supply" | "withdraw" | "borrow" | "repay"; asset: string; amount: string; market?: string; env?: "prod" | "dev" | "test" }): Promise<unknown>
// await defi.buildLendingAction({ address: "0x...", action: "supply", asset: "<asset>", amount: "1.5" })
```

- `address`: string — Sui address that will sign
- `protocol`?: "navi" — Lending protocol; only "navi" today (default: "navi")
- `action`: "supply" | "withdraw" | "borrow" | "repay"
- `asset`: string — Pool asset: symbol ("SUI", "USDC") or full coin type
- `amount`: string — Amount in human units (e.g. "0.1")
- `market`?: string — NAVI market key, e.g. "main", "sui-usdc", "vsui-sui" (default: "main")
- `env`?: "prod" | "dev" | "test" — Contract environment; defaults to the deployment setting (prod)

### defi_build_claim_rewards

Build one unsigned transaction that claims every claimable reward across the requested protocols (NAVI, Suilend, Momentum, Cetus, AlphaFi, Bluefin, Magma, Scallop, Current, Zo). Returns transaction bytes, a dry-run, deterministic risk rules and a previewUrl. This never signs or executes: the user must open previewUrl to review and sign in their wallet.

```ts
buildClaimRewards(input: { address: string; protocols?: Array<"navi" | "suilend" | "momentum" | "cetus" | "alphafi" | "bluefin" | "magma" | "scallop" | "current" | "zo"> }): Promise<unknown>
// await defi.buildClaimRewards({ address: "0x..." })
```

- `address`: string — Sui address whose rewards are claimed
- `protocols`?: Array<"navi" | "suilend" | "momentum" | "cetus" | "alphafi" | "bluefin" | "magma" | "scallop" | "current" | "zo"> — Protocols to claim from; default all of: navi, suilend, momentum, cetus, alphafi, bluefin, magma, scallop, current, zo

### defi_build_vault_action

Build an unsigned vault deposit or withdrawal for a NAVI, Astros or Volo vault. The bytes come from the vault operator API and are verified here (sender check + dry-run) before being stored. Returns transaction bytes, a dry-run, deterministic risk rules and a previewUrl. This never signs or executes: the user must open previewUrl to review and sign in their wallet.

```ts
buildVaultAction(input: { address: string; protocol: "navi" | "astros" | "volo"; vaultId: string; action: "deposit" | "withdraw"; amount: string }): Promise<unknown>
// await defi.buildVaultAction({ address: "0x...", protocol: "navi", vaultId: "<vaultId>", action: "deposit", amount: "1.5" })
```

- `address`: string — Sui address that will sign
- `protocol`: "navi" | "astros" | "volo" — Vault operator; vaults are listed per protocol
- `vaultId`: string — Vault object id (from the vault list)
- `action`: "deposit" | "withdraw"
- `amount`: string — deposit: amount of the vault stake coin, human units; withdraw: number of vault shares

### defi_build_lst_action

Build an unsigned Volo liquid-staking transaction: stake SUI for vSUI or unstake vSUI back to SUI. Returns transaction bytes, a dry-run, deterministic risk rules and a previewUrl. This never signs or executes: the user must open previewUrl to review and sign in their wallet.

```ts
buildLstAction(input: { address: string; protocol?: "volo"; action: "stake" | "unstake"; amount: string }): Promise<unknown>
// await defi.buildLstAction({ address: "0x...", action: "stake", amount: "1.5" })
```

- `address`: string — Sui address that will sign
- `protocol`?: "volo" — Liquid staking protocol; only "volo" (vSUI) today (default: "volo")
- `action`: "stake" | "unstake" — stake: SUI → vSUI; unstake: vSUI → SUI
- `amount`: string — Amount in human units: SUI to stake, or vSUI to unstake

## Preview

### defi_get_tx_preview

Get a stored transaction preview by id: intent, dry-run, deterministic risk rules and the AI summary (ai.status is "pending" until generated). Use it to show the user what they are about to sign.

```ts
getTxPreview(input: { previewId: string }): Promise<unknown>
// await defi.getTxPreview({ previewId: "<previewId>" })
```

- `previewId`: string — Preview id returned by a defi_build_* tool

### defi_get_tx_status

Check whether the user has signed and executed a previewed transaction. Returns status (created | signed | executed | failed | expired) and the on-chain digest once executed.

```ts
getTxStatus(input: { previewId: string }): Promise<unknown>
// await defi.getTxStatus({ previewId: "<previewId>" })
```

- `previewId`: string — Preview id returned by a defi_build_* tool
