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

# Prepare and sign transactions

> Build an unsigned plan, verify it against your own intent and execute a supported Base swap with your signer.

Otto's free `otto_prepare_*` tools return unsigned action plans. They never sign or submit. Each plan binds an account to a payload, checks and expiry. Your signer decides whether to execute it.

The published [otto-execute@0.1.3](https://www.npmjs.com/package/otto-execute) client verifies and executes the **LI.FI Base EOA swap** shown below. Other constructors need their corresponding signer integration; this version has no bridge, Hyperliquid or Polymarket submission adapter. Coinbase delegation is a separate authenticated path, described at the end of this page.

## Supported constructors

| Tool                            | Builds                                                                                |
| ------------------------------- | ------------------------------------------------------------------------------------- |
| `otto_prepare_swap`             | Prepare an Otto-attributed LI.FI swap                                                 |
| `otto_prepare_bridge`           | Prepare an Otto-attributed LI.FI cross-chain USDC bridge                              |
| `otto_prepare_polymarket_order` | Prepare an Otto-attributed Polymarket CLOB limit order                                |
| `otto_prepare_perp_order`       | Prepare an unsigned Hyperliquid perp order L1 action                                  |
| `otto_prepare_stock_buy`        | Prepare a tokenized-stock buy (Coinbase B20 on Base) as a delegable swap pair         |
| `otto_prepare_yield_deposit`    | Prepare a Yield Copilot vault deposit (Morpho USDC on Base) as a delegable swap pair  |
| `otto_prepare_yield_withdraw`   | Prepare a Yield Copilot vault withdraw (Morpho USDC on Base) as a delegable swap pair |

Use `tools/list` on the [hosted MCP](/acp-swarm/otto-intel-mcp) for complete JSON schemas. Swap and bridge amounts are strings in atomic token units; Hyperliquid size/price and Polymarket price/shares use decimal strings. Stock and yield tools require the executable registry ID and commitment returned by the relevant registry. Listing a constructor does not establish that every app action is enabled.

## Prepare a Base swap

This example prepares **1 USDC to WETH** for an EOA you control. Preparation and verification move no funds. It needs Bash, Node 22+, and a Base address. USDC has 6 decimals; WETH has 18.

```bash theme={null}
mkdir otto-swap
cd otto-swap
umask 077
npm init -y
npm install --save-exact otto-execute@0.1.3 @modelcontextprotocol/sdk@1.29.0
read -r -p 'Your Base EOA address: ' OTTO_ACCOUNT
read -r -p 'Minimum WETH output in atomic units: ' OTTO_MIN_WETH_ATOMIC
export OTTO_ACCOUNT OTTO_MIN_WETH_ATOMIC
```

Choose your minimum output independently, using a price source you trust. For units only, `200000000000000` means 0.0002 WETH; this is not a current quote or a recommended floor.

Save this as `prepare.mjs`. It records **your intent before requesting a plan**, then saves the complete MCP response and parsed envelope:

```javascript theme={null}
import { writeFileSync } from 'node:fs';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const address = process.env.OTTO_ACCOUNT;
const minimum = process.env.OTTO_MIN_WETH_ATOMIC;
if (!/^0x[0-9a-fA-F]{40}$/.test(address ?? '')) throw new Error('Invalid address');
if (!/^[1-9][0-9]*$/.test(minimum ?? '')) throw new Error('Minimum must be positive atomic units');
const intent = {
  fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  toToken: '0x4200000000000000000000000000000000000006',
  fromAmount: '1000000',
  minAmountOut: minimum,
};
const input = {
  accountProfile: { kind: 'eoa', address, chainId: 8453 },
  chainId: 8453,
  fromToken: intent.fromToken,
  toToken: intent.toToken,
  fromAmount: intent.fromAmount,
  slippage: { maxBps: 50, minAmountOut: intent.minAmountOut },
  client_ref: 'my-first-otto-swap',
};
const save = (path, value) => writeFileSync(path, JSON.stringify(value, null, 2), { mode: 0o600 });
save('intent.json', intent);
save('prepare.json', input);
const client = new Client({ name: 'otto-swap-example', version: '1.0.0' });
try {
  await client.connect(new StreamableHTTPClientTransport(
    new URL('https://mcp.ottoai.services/mcp'),
  ));
  const result = await client.callTool({ name: 'otto_prepare_swap', arguments: input });
  save('envelope.mcp.json', result);
  const text = result.content.find(part => part.type === 'text')?.text;
  if (result.isError || !text) throw new Error('Constructor refused; inspect envelope.mcp.json');
  save('envelope.json', JSON.parse(text));
} finally {
  await client.close();
}
```

Run it, then verify the result against the saved intent and request:

```bash theme={null}
node prepare.mjs
npx --no-install otto-execute verify \
  --envelope envelope.json --intent intent.json \
  --prepare-input prepare.json --account "$OTTO_ACCOUNT"
```

A successful verification prints the account, exact input, minimum output, decoded fee distributions and remaining validity. Check those values. Never derive `intent.json` from Otto's returned plan: that would compare the plan with itself.

The hosted envelope expires after five minutes. A refusal has exit code 2 and a named code: `STALE_ARTIFACT` means prepare afresh; account, intent or payload mismatches need investigation. Do not remove a check to make the plan pass.

## Sign and submit

The signing EOA needs Base USDC for the swap and Base ETH for gas. It can differ from the wallet paying for intelligence. Load its key locally using a secret manager or a hidden terminal prompt:

```bash theme={null}
read -r -s -p 'Swap wallet private key (hidden): ' OTTO_EXECUTE_PRIVATE_KEY
printf '\n'
export OTTO_EXECUTE_PRIVATE_KEY

npx --no-install otto-execute sign \
  --envelope envelope.json --intent intent.json \
  --prepare-input prepare.json > signed-dry-run.json
```

`sign` signs and decodes the ordered steps without broadcasting. Keep the output private: it contains signed transaction material.

The following command **broadcasts on Base mainnet**. Use a current plan and run it only after deciding to execute this trade:

```bash theme={null}
npx --no-install otto-execute submit \
  --envelope envelope.json --intent intent.json \
  --prepare-input prepare.json --i-understand-mainnet > execution-result.json
unset OTTO_EXECUTE_PRIVATE_KEY
```

The CLI checks freshness before each signature. Preserve transaction hashes and inspect chain status after an interruption before preparing a replacement trade. The constructor does not guarantee single execution: account nonces or venue order identifiers enforce the applicable replay boundary. An interrupted process does not prove that nothing was submitted.

## What the envelope proves

| Field                             | Meaning                                                                       |
| --------------------------------- | ----------------------------------------------------------------------------- |
| `account_binding`                 | The supplied account and its sender/receiver roles                            |
| `payload`                         | Ordered EVM steps, or the venue-specific unsigned action                      |
| `assertions`                      | Constructor checks and their results                                          |
| `valid_until`                     | Expiry committed into the artifact digest                                     |
| `submission.payload_digest`       | Commitment used to detect an altered plan                                     |
| `submission.adapter_owned_fields` | Values the signer supplies, such as nonce, gas and serialization              |
| `required_capabilities`           | Signing capabilities needed to execute                                        |
| `fee_attribution`                 | Fee mechanism and configuration; LI.FI fee legs can be decoded before signing |
| `replay_disclosure`               | Construction aid, not a single-execution guarantee                            |

<Accordion title="Base swap verification checks">
  An `otto_prepare_swap` envelope carries twelve assertions, each reported with
  its result in the response. A check that does not hold **refuses the call by
  name** rather than returning a plan.

  | Assertion                          | What it establishes                                                                                                                                              |
  | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `lifi_integrator_exact`            | The decoded calldata carries Otto's exact integrator string and no other.                                                                                        |
  | `lifi_fee_recipient_exact`         | Every distribution recipient in the decoded body is one of the reviewed, versioned set. An unrecognized recipient is refused.                                    |
  | `lifi_fee_amount_exact`            | Each distribution carries exactly the configured amount for this gross input.                                                                                    |
  | `lifi_fee_recipient_set_versioned` | The distribution body names the complete versioned recipient set, once each — no duplicates, none missing.                                                       |
  | `lifi_fee_aggregate_cap`           | The sum of all distribution legs is at or below the configured cap for this gross input.                                                                         |
  | `lifi_receiver_account_bound`      | The receiver decoded out of the calldata, and the quote's sender and receiver, are the address you supplied.                                                     |
  | `lifi_min_out_slippage_bound`      | The minimum output in the calldata equals the quoted minimum and sits at or above your slippage floor, and at or above your own `minAmountOut` when you set one. |
  | `lifi_token_amount_bound`          | Every deposit leg spends only your input token and they total exactly your input amount; the final leg's output token is the one you asked for.                  |
  | `lifi_targets_value_bound`         | The approval target and the call target are both the reviewed LI.FI Diamond, and the call moves zero native value.                                               |
  | `payload_digest_committed`         | The returned plan is hashed and the digest travels with it in `submission.payload_digest`.                                                                       |
  | `erc20_allowance_reset_ordered`    | The plan clears the allowance to zero before setting the new one, so it is executable from any prior allowance state, including on approval-race-guarded tokens. |
  | `artifact_expiry_committed`        | `valid_until` is committed into the artifact digest, so a re-dated plan no longer matches.                                                                       |
</Accordion>

## Optional Coinbase delegation

[Delegate](https://useotto.xyz/app/delegate) creates permission for a **separate Coinbase end-user account**. Fund that exact account for actions using it; connected-wallet and legacy Safe balances do not fund it. Public MCP access alone does not authorize protected submission.

The app lets you choose a personal per-swap cap and expiry. The shared ceiling is \$5,000 USDC per swap; this is neither a daily budget nor a total-loss limit. Expiry defaults to 90 days and is limited to 180 days. Inspect `otto_delegation_fence_status` before minting: `present`, `policy_name`, `rules_digest`, `ruleset_version`, `mint_enabled` and the current cap.

Permission covers only supported Base swap shapes where enabled, including eligible stock buys and Morpho vault operations. It does not automatically cover stock sells, bridges, Hyperliquid or Polymarket. Retail Stocks uses connected-wallet signatures. Withdrawals redeem vault shares to USDC **inside the Coinbase account**; their share-quantity and recipient checks are separate from the per-swap spend cap.

Revocation removes authority; it does not redeem positions or move assets. A permission predating withdrawal consent must be revoked and minted again before it can authorize that withdrawal. Use [Moving funds out](/account-and-settings/removing-funds) for the separate exit steps.

<Accordion title="Manage permission from the published CLI">
  Version 0.1.3 supports email OTP permission management with these optional dependencies:

  ```bash theme={null}
  npm install --save-exact @coinbase/cdp-core@0.0.120 react@19.2.0
  unset OTTO_EXECUTE_PRIVATE_KEY
  npx --no-install otto-execute delegate --email you@example.com --expires-days 1
  npx --no-install otto-execute delegation status --email you@example.com
  npx --no-install otto-execute delegation revoke --email you@example.com
  ```

  Use your own email. Enter the OTP interactively. This flow runs without a wallet private key or Otto service credentials in its environment. The CLI has no personal-cap flag and mints with the shared \$5,000 per-swap ceiling; use the app for a smaller cap. Its Node OTP bridge uses Coinbase's web SDK and is not a Coinbase-supported Node integration.

  Require read-back and durable confirmation after revocation. `REVOKE_NOT_DURABLE` means confirmation is incomplete and revocation should be retried. These commands manage permission; `submit` remains the EOA path above. Do not request or distribute Otto's private delegation header.
</Accordion>
