> For the complete documentation index, see [llms.txt](https://docs.veda.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.veda.tech/api/transaction-builder.md).

# Transaction Builder

The Transaction Builder API turns a high-level intent — "supply 1,000 USDC to Aave V3" or "bridge USDC from Ethereum to Base via CCTP" — into ready-to-submit calldata for a vault's Manager contract. It's the same engine that powers the rebalance feature in the Veda Console, exposed for programmatic use.

{% hint style="danger" %}
**Always verify calldata independently before signing.** The Transaction Builder is a convenience — it is not a substitute for review. Decode the returned `encoded_tx_data` against the Manager ABI and run it through [Tenderly](https://dashboard.tenderly.co/) or another simulator before broadcasting. Confirm the target address and every encoded argument match what you intended. **Never rely solely on the API response for correctness.** Independent simulation is the last line of defense.
{% endhint %}

## Concepts

Veda vaults route depositor funds to trusted DeFi protocols according to the curator's predefined yield strategies. A **rebalance** — moving funds between positions, swapping, supplying, withdrawing — is one or more of those onchain actions, executed through the vault's Manager contract.

The Manager only accepts actions that are present as leaves in the vault's active merkle tree; every call must carry a merkle proof against the root pinned on the Manager (see [Policy Manager & Merkle Trees](/api/policy-manager-and-merkle-trees.md)). The Transaction Builder embeds the right proof for the action you ask it to build, so the calldata it returns is one the Manager will accept.

The API is **build-only**: it returns encoded calldata and a target address; you sign and submit the transaction from your strategist address.

The workflow:

1. **Discover** what actions are available for a vault — `GET /actions`
2. **Inspect constraints** for actions with correlated parameters — `GET /constraints`
3. **Build** a single action — `POST /execute` — or several atomically — `POST /execute-multiple`
4. **Submit** the returned `encoded_tx_data` to the Manager from the strategist address

Every action endpoint is scoped by three path parameters:

| Param                | Meaning                                                                                                                                               |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chain`              | Lowercase chain slug (e.g. `ethereum`, `base`, `arbitrum`). See [Chain Support](/resources/chain-support.md) for the full list.                       |
| `vault_address`      | The BoringVault contract address                                                                                                                      |
| `strategist_address` | The strategist address whose merkle tree / permissions are being used. A single vault can have multiple strategists with different permitted actions. |

***

## 1. List available actions

```
GET /v1/tx-builder/api/actions/{chain}/{vault_address}/{strategist_address}
```

Returns every action the vault's merkle tree authorizes for the given strategist, along with the parameter metadata you need to build a transaction. No request body, no query parameters.

### Example request

```bash
curl https://api.veda.tech/v1/tx-builder/api/actions/ethereum/0xA20f97813014129E7609171d2D3AA3da5206259e/0x2322ba43eFF1542b6A7bAeD35e66099Ea0d12Bd1 \
  -H "Authorization: Bearer $VEDA_API_KEY"
```

### Response structure

A JSON object keyed by **route** (e.g. `aavev3/supply`). Each entry describes one action:

| Field              | Type    | Meaning                                                                               |
| ------------------ | ------- | ------------------------------------------------------------------------------------- |
| `name`             | string  | Human-readable action name                                                            |
| `protocol`         | string  | Protocol identifier (e.g. `AaveV3`, `1Inch`)                                          |
| `protocol_app_url` | string  | Link to the protocol's app, when applicable                                           |
| `description`      | string  | What the action does                                                                  |
| `has_constraints`  | boolean | If `true`, params are correlated — call `/constraints/{route}` for valid combinations |
| `params`           | array   | Parameter definitions (see below)                                                     |

Each `params[i]` is:

| Field             | Type            | Meaning                                                                                                                      |
| ----------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `name`            | string          | Parameter name to pass in the execute request                                                                                |
| `type`            | string          | Python type name (`str`, `int`, `float`, `Decimal`)                                                                          |
| `description`     | string          | Human-readable description                                                                                                   |
| `required`        | boolean         | Whether the parameter must be provided                                                                                       |
| `default_value`   | any \| null     | Default if not provided (only meaningful for non-required params)                                                            |
| `choices`         | array \| `null` | Allowed values. `[]` means freeform; `null` means optional and may be omitted; a populated list means you must pick from it. |
| `min_value`       | number \| null  | Minimum valid value, if applicable                                                                                           |
| `max_value`       | number \| null  | Maximum valid value, if applicable                                                                                           |
| `omitToUseAll`    | boolean         | If `true`, omitting this parameter uses the vault's full balance of the token                                                |
| `acceptRawString` | boolean         | If `true`, the value is forwarded as-is (no numeric conversion)                                                              |

{% hint style="info" %}
**Grouped choices.** Most params have `choices` as a flat array of strings (`["USDC", "USDT"]`). Some params use **nested arrays** (`[["USDC", "0xa0b8..."], ["USDT", "0xdac1..."]]`) where each inner array is `[display_label, value]`. When building a UI, check whether the first element of `choices` is itself an array to decide which format you're dealing with.
{% endhint %}

### Example response (abbreviated)

```json
{
  "aavev3/supply": {
    "name": "Supply",
    "protocol": "AaveV3",
    "protocol_app_url": "https://app.aave.com",
    "description": "Supply tokens to AaveV3",
    "has_constraints": false,
    "params": [
      { "name": "token",  "type": "str",   "required": true, "choices": ["USDC", "USDT"] },
      { "name": "amount", "type": "float", "required": true, "choices": [] },
      { "name": "market", "type": "str",   "required": true, "choices": ["core"] }
    ]
  },
  "one_inch/swap": {
    "name": "Swap",
    "protocol": "1Inch",
    "protocol_app_url": "https://app.1inch.io",
    "description": "Swap tokens using 1Inch",
    "has_constraints": true,
    "params": [
      { "name": "token_in",  "type": "str",   "required": true, "choices": ["MORPHO", "USDC", "USDT"] },
      { "name": "token_out", "type": "str",   "required": true, "choices": ["USDC", "USDT"] },
      { "name": "amount",    "type": "float", "required": true, "choices": [] },
      {
        "name": "slippage_tolerance",
        "type": "float",
        "required": true,
        "default_value": 0.0001,
        "choices": [],
        "min_value": 0,
        "max_value": 0.1
      }
    ]
  }
}
```

{% hint style="info" %}
When `has_constraints` is `true`, the values in each param's `choices` are the **projection** of the valid combinations onto that one param. You still need to call the constraints endpoint to know which combinations are actually valid (e.g. `USDC → USDT` may be allowed but `USDC → USDC` is not).
{% endhint %}

***

## 2. Inspect parameter constraints

When `has_constraints` is `true`, not every combination of `choices` is valid. The constraints endpoint returns the valid combinations.

```
GET /v1/tx-builder/api/actions/{chain}/{vault_address}/{strategist_address}/constraints/{route}
```

### Example request

```bash
curl https://api.veda.tech/v1/tx-builder/api/actions/ethereum/0xA20f97813014129E7609171d2D3AA3da5206259e/0x2322ba43eFF1542b6A7bAeD35e66099Ea0d12Bd1/constraints/one_inch/swap \
  -H "Authorization: Bearer $VEDA_API_KEY"
```

### Example response

```json
{
  "route": "one_inch/swap",
  "constraints": [
    { "token_in": "MORPHO", "token_out": "USDC" },
    { "token_in": "MORPHO", "token_out": "USDT" },
    { "token_in": "USDC",   "token_out": "USDT" },
    { "token_in": "USDT",   "token_out": "USDC" }
  ]
}
```

Each entry in `constraints` is one valid combination. Use this to drive dependent dropdowns in a UI, or to filter to a single combination in a script. Routes where `has_constraints` is `false` have fully independent parameters — any combination of values from `choices` is valid.

A `404` from this endpoint means the route has no parameter constraints (i.e. you didn't need to call it).

***

## 3. Build a single action

```
POST /v1/tx-builder/api/execute/{chain}/{vault_address}/{tail:path}
```

The `tail` matcher captures everything after the vault. It accepts either of:

* `{strategist_address}/{route}` — recommended; explicit
* `{route}` — the API resolves the strategist for the vault

When `tail` begins with a `0x…`-prefixed 42-character string, that prefix is treated as the strategist address and the rest is the route. The route itself contains a slash (e.g. `aavev3/supply`), which is why `tail:path` is used.

### Request body

| Field      | Type                  | Default | Description                                                                                                                                                                       |
| ---------- | --------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `params`   | object (string → any) | `{}`    | Key-value map of action parameters, matching the `/actions` metadata for this route                                                                                               |
| `verbose`  | boolean               | `false` | Return detailed simulation output — token transfers, gas cost in ETH, and protocol-level metadata in `action_api_response` (including warnings). Recommended for any review step. |
| `skip_sim` | boolean               | `false` | Skip the Tenderly simulation. Set `true` for faster responses or when you've already simulated.                                                                                   |

### Example request

```bash
curl -X POST \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  https://api.veda.tech/v1/tx-builder/api/execute/ethereum/0xA20f97813014129E7609171d2D3AA3da5206259e/0x2322ba43eFF1542b6A7bAeD35e66099Ea0d12Bd1/aavev3/supply \
  -d '{
    "params": { "token": "USDC", "amount": 1000, "market": "core" },
    "skip_sim": false
  }'
```

### Response structure

| Field                     | Type              | Always present? | Meaning                                                                                          |
| ------------------------- | ----------------- | --------------- | ------------------------------------------------------------------------------------------------ |
| `encoded_tx_data`         | string            | Yes             | Calldata (hex, `0x`-prefixed) to submit to the vault's Manager contract                          |
| `target`                  | string            | Yes             | Manager contract address — the `to` of your transaction                                          |
| `description`             | string            | Yes             | Human-readable description of the action being built                                             |
| `action_api_response`     | object \| null    | Yes             | Protocol-specific metadata. See [Simulation warnings](#simulation-warnings) for the warning keys |
| `simulation_status`       | boolean \| string | When simulated  | `true` if the Tenderly simulation succeeded; may also be an error string on failure              |
| `tenderly_simulation_url` | string \| null    | When simulated  | Link to the Tenderly simulation                                                                  |
| `simulation_logs`         | array             | When simulated  | Logs emitted during the simulation (when available)                                              |
| `simulation_gas_used`     | number \| null    | When simulated  | Gas used during the simulation (when available)                                                  |
| `simulation_gas_cost_eth` | string \| null    | When verbose    | Estimated gas cost denominated in ETH                                                            |
| `token_transfers`         | array             | When verbose    | Structured list of tokens moved during the simulation (see below)                                |
| `simulation_skipped`      | boolean           | When skipped    | Present and `true` when `skip_sim: true` was passed                                              |

#### Token transfers

When `verbose: true`, the response includes a `token_transfers` array with one entry per token movement observed during the simulation:

| Field           | Type                     | Meaning                                               |
| --------------- | ------------------------ | ----------------------------------------------------- |
| `token`         | string                   | Token symbol (e.g. `USDC`)                            |
| `token_address` | string                   | Token contract address                                |
| `amount`        | string                   | Human-readable amount in display units                |
| `raw_amount`    | string                   | Amount in the token's smallest unit (wei / base unit) |
| `direction`     | `"in"` \| `"out"`        | Whether the vault receives or sends the token         |
| `dollar_value`  | string \| number \| null | Estimated USD value, when a price is available        |

### Example response

```json
{
  "encoded_tx_data": "0x244b0f6a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001...",
  "target": "0x20dCC05A7fE66d6442bE7d50C9EC41c4Bfda7f68",
  "description": "Supply tokens to AaveV3",
  "action_api_response": {
    "Input Amount": "1,000.00 USDC",
    "Output Amount": "999.87 aUSDC"
  },
  "simulation_status": true,
  "tenderly_simulation_url": "https://www.tdly.co/shared/simulation/abc123",
  "simulation_gas_used": 312418,
  "simulation_gas_cost_eth": "0.004218",
  "token_transfers": [
    {
      "token": "USDC",
      "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "amount": "1,000.00",
      "raw_amount": "1000000000",
      "direction": "out",
      "dollar_value": "1000.00"
    },
    {
      "token": "aUSDC",
      "token_address": "0xBcca60bB61934080951369a648Fb03DF4F96263C",
      "amount": "999.87",
      "raw_amount": "999870000",
      "direction": "in",
      "dollar_value": "999.87"
    }
  ]
}
```

{% hint style="info" %}
The `token_transfers` and `simulation_gas_cost_eth` fields are only present when the request includes `verbose: true`. Without it, you still get `simulation_status`, `simulation_gas_used`, and `tenderly_simulation_url`.
{% endhint %}

***

## 4. Build multiple actions (atomic batch)

```
POST /v1/tx-builder/api/execute-multiple/{chain}/{vault_address}/{strategist_address}
```

Build a single atomic transaction that runs several actions in order. The response is one piece of calldata that calls the Manager with all actions packed together — recommended for any rebalance that involves more than one step (e.g. swap then supply, withdraw from one position and deposit into another).

### Request body

| Field      | Type             | Default | Description                                                                                    |
| ---------- | ---------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `actions`  | array of objects | —       | One entry per action. Each is `{ "route": string, "params": object }`.                         |
| `verbose`  | boolean          | `false` | Return detailed simulation output — token transfers, gas cost in ETH, and per-action metadata. |
| `skip_sim` | boolean          | `false` | Skip the Tenderly simulation                                                                   |

An empty `actions` array is rejected with `400 No actions provided`.

### Example request

Swap 1,000 USDC into USDT via 1Inch, then supply 500 USDC into Aave V3 — in a single onchain transaction:

```bash
curl -X POST \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  https://api.veda.tech/v1/tx-builder/api/execute-multiple/ethereum/0xA20f97813014129E7609171d2D3AA3da5206259e/0x2322ba43eFF1542b6A7bAeD35e66099Ea0d12Bd1 \
  -d '{
    "actions": [
      {
        "route": "one_inch/swap",
        "params": {
          "token_in": "USDC",
          "token_out": "USDT",
          "amount": 1000,
          "slippage_tolerance": 0.0001
        }
      },
      {
        "route": "aavev3/supply",
        "params": { "token": "USDC", "amount": 500, "market": "core" }
      }
    ],
    "skip_sim": false
  }'
```

### Response structure

Same shape as `/execute`, with two differences:

* `description` is always `"Multiple actions execution"`
* `action_api_response` is an **array** — one entry per action, in the same order as `actions`

### Example response

```json
{
  "encoded_tx_data": "0x244b0f6a...",
  "target": "0x20dCC05A7fE66d6442bE7d50C9EC41c4Bfda7f68",
  "description": "Multiple actions execution",
  "action_api_response": [
    { "expected_out": "999.42 USDT", "tx_payload": { "...": "..." } },
    null
  ],
  "simulation_status": true,
  "tenderly_simulation_url": "https://www.tdly.co/shared/simulation/def456"
}
```

***

## Error handling

The Transaction Builder endpoints predate the standardized error envelope used by the rest of the v1 API. Non-2xx responses come back on a `detail` field, which can be a string (`{ "detail": "Vault not found" }`), an object (`{ "detail": { "error": "...", "details": "..." } }`), or — for `422` validation errors — a Pydantic-style array (`{ "detail": [{ "loc": [...], "msg": "...", "type": "..." }] }`). For authentication failures from the gateway, see [API Reference → Authentication errors](/api/api-reference.md#authentication-errors). And remember that a `200` can still carry `error` / `details` alongside `encoded_tx_data` when calldata was built but simulation failed — always check `simulation_status`.
