---
title: "Prepare a position — returns UNSIGNED transactions for your wallet to sign"
method: POST
path: "/v1/positions"
tags: ["positions"]
---

# Prepare a position — returns UNSIGNED transactions for your wallet to sign

`POST /v1/positions`

## Request

- Base URL: `https://yield.trdefi.com`
- URL: `https://yield.trdefi.com/v1/positions`
- Auth: HTTP bearer

The only endpoint that needs a write key, and even it cannot execute anything.

**What it does.** Authenticates the key, validates the intent, embeds the 0.05% protocol fee into the order, reads your live allowance (and optionally balance) from the chain, and returns the complete, ordered set of *unsigned* transactions required to create the position. Nothing is broadcast, nothing is signed, no allowance is created.

**What you do.** Sign each transaction in `transactions[]` with the wallet you named, in the order given, then broadcast. We are not involved in either step.

**Approvals are conditional.** The `approve` steps are omitted when your live allowance already covers the amount, so the same logical position may come back as one step or three.

**No expiry, deliberately.** A prepared payload holds no reservation and no server-side lock; it is a pure function of its inputs plus a salt. The salt is derived from `(your key, Idempotency-Key)`, so a retry returns the *same* `strategyHash` and cannot create a second position. Omit the header and every call is a new position.

**Fees.** The `fee` object reports what is already embedded in the unsigned order data. There is no parameter that removes it.

## Headers

- `Idempotency-Key` string, uuid

## Request body

- PositionRequest
  - `chain` 'base' | 'arc' | 'baseSepolia', required: Chain key. `base` = 8453, `arc` = 5042, `baseSepolia` = 84532. A test key may only name `baseSepolia`; a live key may not.
  - `wallet` string, required: The address that will sign. It must be yours. We only ever prepare for the address you name here.
  - `deposits` object[], required: Exactly two tokens. Each `amount` is an integer string in **base units** — never a float. You supply both sides, so we never have to guess a price.
    - `symbol` string, required: Token symbol or address. Supported — base: USDC, USDT, EURC · arc: USDC, EURC · baseSepolia: USDC, USDT.
    - `amount` string, required: Amount in base units, as a string.
  - `makerFeeBps` integer: **Your** commission, in the protocol's 1e7 fee scale (`30000` = 0.30%). Independent of, and on top of, the 0.05% protocol fee.
  - `includeApprovals` boolean: Set `false` to receive only the `ship` step. By default the `approve` steps are included **only where your live allowance is short**.
  - `checkBalances` boolean: Add live balance checks to `preflight` so you can tell a human the transfer will succeed before asking them to approve.
  - `metadata` object: Bounded free-form map (no secrets). Echoed back; returned in webhooks and exports.

## Response `200`

Prepared position. Inert: nothing has been signed or broadcast.

- PreparedPosition
  - `data` object, required
    - `status` 'prepared', required: Always `prepared`. Nothing is signed or broadcast by this call.
    - `chain` string, required
    - `chainId` integer
    - `wallet` string, required: The address that must sign, echoed back.
    - `strategyHash` string, required: keccak256 of the order you are about to sign. The position's identity, and checkable by you from the returned `data`.
    - `transactions` PreparedTransaction[], required: Ordered. `approve` steps appear only where your live allowance is short.
      - `step` integer, required: 1-based. Execute in this order.
      - `purpose` 'approve' | 'ship' | 'swap' | 'dock', required: What this transaction does.
      - `to` string, required: Target contract.
      - `value` string, required: Native value in wei, as a string. Almost always `0`.
      - `data` string, required: Calldata. This is the payload you sign.
      - `description` string: Plain-language explanation of this step, suitable for showing to a human approver.
    - `fee` FeeBreakdown, required
      - `protocolFeeBps` 5000, required: The protocol fee in the protocol's 1e7 scale. 5000 = 0.05%. A constant, not a parameter.
      - `protocolFeePercent` '0.05', required
      - `makerFeeBps` integer: The integrator's own commission, as supplied.
      - `makerFeePercent` string
      - `totalFeePercent` string
      - `embeddedIn` 'signed_order_data': The fee lives inside the order data you sign. It is not a separate transfer or an invoice.
      - `removable` false, required: Always false. No parameter, tier or agreement waives the protocol fee.
      - `explanation` string
    - `position` object
      - `tokenA` object: Lower address of the two, with symbol, address, amount and decimals.
      - `tokenB` object
      - `impliedRate` number, nullable: amountA / amountB, for your own sanity check. Not used in the order.
      - `programBytes` integer: Size of the strategy program that carries the fee.
    - `preflight` object
      - `status` 'ready' | 'review': `review` means one of `checks` failed — tell a human before asking them to approve.
      - `checks` object[]
        - `name` string
        - `ok` boolean
        - `detail` string
    - `idempotent` boolean: True when an Idempotency-Key was supplied. The salt is derived from it, so the same key returns the same `strategyHash`.
  - `meta` Meta, required
    - `requestId` string, required
    - `generatedAt` string, date-time, required
    - `apiVersion` string, required
    - `freshnessSeconds` integer, nullable: How old the underlying roll-up is, in seconds. Null when not derived.
    - `cached` boolean: True when this response was served from our short-lived cache.

## Other responses

- `400`: The request was refused and NO payment was taken. Validation runs before settlement, so a rejected body costs nothing. `charged` is always false here and no PAYMENT-RESPONSE header is returned.
- `401`: Missing, malformed, revoked or out-of-scope key. The envelope never says which — a wrong key and a revoked key are indistinguishable.
- `402`: No credential was presented, so the endpoint answered with its x402 payment terms instead of an error. The body carries `accepts` (price, network, asset, payTo) and a `bazaar` extension describing this route. Pay with your own wallet, then retry the same request with the base64 payload in the `X-PAYMENT` header. An API key works instead and costs nothing.
- `403`: Origin or scope not allowed — the endpoint is restricted to TRDEFI pages, or the key does not carry the `prepare` scope.
- `422`: The request was refused and NO payment was taken. Validation runs before settlement, so a rejected body costs nothing. `charged` is always false here and no PAYMENT-RESPONSE header is returned.
- `429`: Rate limit exceeded. `Retry-After` is always present.
- `503`: Our data source is temporarily unavailable. Retry shortly.

---

[API](https://skmtc.dev/trdefi/apis/trdefi-liquidity-api.md) · [All operations](https://skmtc.dev/trdefi/apis/trdefi-liquidity-api/llms.txt) · [OpenAPI document](https://skmtc.dev/trdefi/apis/trdefi-liquidity-api/revisions/0175596aa1cb?raw)
