> For the complete documentation index, see [llms.txt](https://docs.symm.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.symm.io/trader-documentation/building-a-trading-bot/part-2-trading/opening-a-position.md).

# Opening a position

Opening involves two EIP-712 operations signed and POSTed together, then a wait on the notifications feed for the on-chain confirmation.

### Compute the numbers

Pick a `symbol_id`, a side (`long`/`short`), a leverage (integer ≥ 1), and a slippage tolerance (a percentage, e.g. `5` for 5%). Read the latest mark price from your price-feed cache.

Then derive the worst-acceptable entry price. The whole point of slippage is to bound how much the price can drift between when you sign and when the trade lands; for an open you accept slippage in the direction *against* you:

| Side  | Worst-acceptable entry price                               |
| ----- | ---------------------------------------------------------- |
| LONG  | `price = mark × (1 + slippage / 100)` (will pay more)      |
| SHORT | `price = mark × (1 − slippage / 100)` (will sell for less) |

Now hit the [locked-params endpoint](/api-endpoints-and-deployments/solver-addresses-and-endpoints.md) with this symbol's `name` and your chosen leverage. You get back integer percentages: `cva`, `lf`, `partyAmm`, `partyBmm`. These define what gets locked on-chain when the trade opens. Compute the wei-denominated amounts:

```
notional = price × quantity                        // in collateral units
cva      = notional × cva_pct      / (100 × leverage)
lf       = notional × lf_pct       / (100 × leverage)
partyAmm = notional × partyAmm_pct / (100 × leverage)
partyBmm = notional × partyBmm_pct / 100           // usually 0
```

Round `price` and `quantity` to the symbol's `price_precision` and `quantity_precision` *before* converting to 18-decimal wei. Dust beyond those precisions causes silent rejection.

### Compute `addMargin`

This deserves its own section because the formula is *not* symmetric between longs and shorts. Longs are simple; shorts have an extra cushion that's a UI policy, not a contract requirement.

#### What `addMargin` has to cover

The `addMarginToNextVA` call moves collateral from the sub-account to the soon-to-be-created VA. The VA needs to hold enough to cover:

* The **locked margin** for the trade, `cva + lf + partyAmm` (plus `partyBmm` if the deployment uses it). The four values always sum to `notional / leverage`, because the locked-params percentages respect that constraint at every leverage rung.
* The **trading fee for both sides** of the round-trip. The `trading_fee` field is a per-side fraction (e.g. `0.00012` = 12 bps), so reserve `2 × trading_fee` of the locked notional.
* For shorts, an **adverse-drift buffer** the frontend bakes in (see below).

#### The two formulas

Let `slip` be the user's slippage tolerance as a fraction (`0.05` for 5%) and `fee` the symbol's per-side `trading_fee` fraction.

| Side  | Upper-bound margin price for sizing | Formula                                                    |
| ----- | ----------------------------------- | ---------------------------------------------------------- |
| LONG  | `worst = mark × (1 + slip)`         | `addMargin = worst × qty / leverage × (1 + 2 × fee)`       |
| SHORT | `mark × 1.10`                       | `addMargin = mark × 1.10 × qty / leverage × (1 + 2 × fee)` |

For a LONG, `worst` is the highest price you've consented to pay, so the locked margin sized at `worst` already covers the worst-case fill. No extra buffer needed beyond fees.

For a SHORT, the frontend sizes margin at **`mark × 1.10`, a flat +10% above mark, regardless of slippage or leverage.** This isn't derivable from contract semantics; the contract's strict minimum is just `mark × qty / leverage` (covering a fill at the prevailing mark). The extra +10% is the frontend's hardcoded safety policy for shorts. It pre-funds the position for adverse upward drift between signing and fill, since a short's maintenance margin requirement grows with mark.

Both `addMargin` and the four locked values go into the on-chain calldata. Convert all of them to 18-decimal wei before encoding.

### Sign two EIP-712 `SignedOperation`s

The InstantLayer is the gateway for both ops. Its EIP-712 domain is fixed:

```
domain = {
    name: "SymmioInstantLayer",
    version: "1",
    chainId: <chain id>,
    verifyingContract: <InstantLayer address>
}
```

The `SignedOperation` struct has seven fields, and the order matters because EIP-712 hashes are sensitive to field order:

```
struct SignedOperation {
    address              signer;
    address              target;
    bytes                callData;
    Account              signerAccount;       // (addr, isPartyB)
    FlexField[]          flexFields;          // empty for standard ops
    uint256              maxUses;             // 1 for standard
    ReplayAttackHeader   replayAttackHeader;  // (nonce, deadline, salt)
}
```

Remember to include `flexFields` and `maxUses` in your typed-data: if the EIP-712 hash on your side ends up shorter than what the InstantLayer computes, your signature recovers to a meaningless address, and the solver returns 403 "invalid signature." If you see that, check the schema first.

Both ops are signed by the *session key*, but they hit different contracts and different functions.

**Op 1: pre-fund the VA.**

* `signer`: session key's address
* `target`: AccountLayer
* `signerAccount.addr`: **sub-account** (not the VA, which doesn't exist yet)
* `callData`: selector `0xa6d66852` followed by ABI-encoded `(subAccount: address, vaIsolation: uint8, symbolId: uint256, amountWei: uint256)`. `vaIsolation` is `2` for LONG (`MARKET_LONG`) or `3` for SHORT (`MARKET_SHORT`) under a `MARKET_DIRECTION` sub-account.

**Op 2: send the quote.**

* `signer`: session key's address
* `target`: Symmio Core
* `signerAccount.addr`: **sub-account** (still not the VA)
* `callData`: selector `0xa7f3b34b` followed by the ABI-encoded arguments of `sendQuoteWithAffiliateAndData`:

  ```
  address[]                       partyBsWhiteList,    // [hedger]
  uint256                         symbolId,
  uint8                           positionType,        // 0 = LONG, 1 = SHORT
  uint8                           orderType,           // 1 = MARKET
  uint256                         price,               // worst-acceptable, wei
  uint256                         quantity,            // wei
  uint256                         cva,
  uint256                         lf,
  uint256                         partyAmm,
  uint256                         partyBmm,            // usually 0
  uint256                         deadline,
  address                         affiliate,
  SingleUpnlAndPriceSig           upnlSig,             // EMPTY tuple ★
  bytes                           data                 // abi.encode((string uuid))
  ```

{% hint style="info" %}
★ The `upnlSig` tuple is an empty placeholder `(bytes "", uint256 0, int256 0, uint256 0, bytes "", (uint256 0, address 0, address 0))`. The bind step is what makes the core skip the Muon check on this empty value.

`data` is `abi.encode((string,))` of a UUID string the solver uses, the contract doesn't read it.
{% endhint %}

Both ops share a `replayAttackHeader` with `nonce: 0` (salt-only mode), a `deadline` an hour or so in the future, and a unique random `salt` (32 bytes). `flexFields: []`, `maxUses: 1`.

### Subscribe, then POST

**Subscribe to the** [**Notifications WebSocket**](/api-endpoints-and-deployments/solver-addresses-and-endpoints.md) ***before*****&#x20;you POST.** This is a hard requirement, not advice. The solver fires the `SendQuoteTransaction` notification within seconds of accepting your trade, and if you POST before subscribing, the message can race past your first `recv()` and you'll never see it.

Subscription frame:

```json
{
  "channel_patterns": [{
    "app_name": "<your solver app name>",
    "address": "<subAccount>",
    "primary_identifier": "*",
    "secondary_identifier": "*"
  }]
}
```

Once subscribed, POST `{ "addMargin": op1, "sendQuote": op2 }` to the instant-open endpoint as a JSON *object* (not an array; closes are arrays). No auth header: the EIP-712 signatures *are* the auth. The solver responds:

```json
{ "temp_quote_id": -6188, "partyBmm": "0" }
```

The `temp_quote_id` is **negative and is not your real quote ID.** It's a placeholder the solver uses to correlate this RFQ to the on-chain transaction. Wait on the WS until you see a message matching this `temp_quote_id` whose `last_seen_action == "SendQuoteTransaction"` and `action_status == "success"`. That message carries the *real* `quote_id` and the `va_address`. Record both. Typical latency end-to-end is 5–20 seconds.

If you see `action_status == "failed"` or `state_type == "alert"` instead, exit the wait loop. Common causes:

* `error_code: 2000` with `va_address == ""`: the on-chain `executeTemplate` reverted with `"LibMuon: Expired signature"`. The sub-account isn't bound (see bind the sub-account).
* `error_code: 502` on `InstantRFQ`: transient solver issue. Retry.
* Other `error_code` values surface from the core's revert reasons.

After a successful open the position is identified for the rest of its life by `(quote_id, va_address)`. Save them.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.symm.io/trader-documentation/building-a-trading-bot/part-2-trading/opening-a-position.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
