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

# Upside API Quickstart: Your First Trade in Python

> Make your first signed Upside API call: generate a wallet, register an account, place a limit order, and cancel it using Python.

This guide walks you through the full lifecycle of a trade on Upside — from generating a fresh wallet keypair to placing and cancelling a limit order. You'll write about 50 lines of Python and interact with the QA environment, which runs the SOL1/USDT1 perpetual contract.

## Prerequisites

You need **Python 3.8 or later** and three packages:

```bash theme={"system"}
pip install eth-account eth-utils requests
```

| Package       | Purpose                          |
| ------------- | -------------------------------- |
| `eth-account` | ECDSA key generation and signing |
| `eth-utils`   | Keccak-256 hashing               |
| `requests`    | HTTP calls to the API            |

## Steps

<Steps>
  <Step title="Generate a wallet keypair">
    Use `eth_account.Account.create()` to generate a fresh private key and address. In a production system you would load an existing private key from a secure secret store — never hardcode it.

    ```python theme={"system"}
    from eth_account import Account

    wallet = Account.create()
    PRIVATE_KEY = wallet.key       # bytes
    ADDRESS = wallet.address.lower()  # 0x-prefixed lowercase hex
    print(f"Address: {ADDRESS}")
    ```
  </Step>

  <Step title="Build the signing helper">
    All `POST /exchange` requests must be signed with **EIP-712** (domain `Exchange` / `v1` / chainId `9767`). The signing struct depends on `action.type`: funds and permission actions (`registerAccount`, `approveAgent`, `revokeAgent`, `lockCollateral`, `unlockCollateral`, `transferBetweenDeployers`) use a field-level **typed** struct; everything else uses the **Agent** path, which folds canonical JSON into an `actionHash`.

    ```python theme={"system"}
    import json, time, requests
    from eth_account import Account
    from eth_utils import keccak

    BASE_URL = "https://dev.upsidemax.xyz"
    _CHAIN_ID, _NAME, _VER, _SOURCE = 9767, "Exchange", "1", "b"

    def _u(v):    return int(v).to_bytes(32, "big")
    def _addr(s): return b"\x00" * 12 + bytes.fromhex(s[2:] if s[:2].lower() == "0x" else s)
    def _s(v):    return keccak(str(v).encode())

    _DOMAIN = keccak(
        keccak(b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
        + keccak(_NAME.encode()) + keccak(_VER.encode()) + _u(_CHAIN_ID) + b"\x00" * 32)

    _TYPED = {
        "registerAccount": (b"RegisterAccount(address address,uint64 nonce)",
            lambda a, n: [_addr(a["address"]), _u(n)]),
        # approveAgent / revokeAgent / lockCollateral / unlockCollateral /
        # transferBetweenDeployers — see Authentication for the full set.
    }

    def eip712_digest(action, nonce):
        typed = _TYPED.get(action["type"])
        if typed:
            type_str, build = typed
            struct = keccak(keccak(type_str) + b"".join(build(action, nonce)))
        else:  # Agent path
            canonical = json.dumps(action, sort_keys=True, separators=(",", ":")).encode()
            action_hash = keccak(canonical + int(nonce).to_bytes(8, "big"))
            struct = keccak(keccak(b"Agent(string source,bytes32 actionHash)")
                            + keccak(_SOURCE.encode()) + action_hash)
        return keccak(b"\x19\x01" + _DOMAIN + struct)

    def sign_and_send(action):
        nonce = int(time.time() * 1000)
        sig = Account._sign_hash(eip712_digest(action, nonce), PRIVATE_KEY)
        envelope = {
            "action": action,
            "signature": {
                "r": "0x" + sig.r.to_bytes(32, "big").hex(),
                "s": "0x" + sig.s.to_bytes(32, "big").hex(),
                "v": sig.v if sig.v >= 27 else sig.v + 27,
            },
            "nonce": nonce,
        }
        resp = requests.post(f"{BASE_URL}/exchange", json=envelope, timeout=15)
        return resp.json()
    ```

    See [Authentication](/guide/authentication) for the full typed-struct set and a line-by-line explanation of the signing process.
  </Step>

  <Step title="Register your account">
    Before you can trade, you must register your address on the exchange. This is a one-time operation per address.

    ```python theme={"system"}
    result = sign_and_send({"type": "registerAccount", "address": ADDRESS})
    account_id = result["response"]["accountId"]
    print(f"Registered: accountId={account_id}")
    ```

    <Note>
      Some environments require an `inviteCode` field at the **top level of the envelope** (not inside `action`) for `registerAccount`. If registration returns `INVITE_CODE_INVALID`, add `"inviteCode": "<your-code>"` to the envelope dictionary alongside `"action"`, `"signature"`, and `"nonce"`. The invite code is **not** included in the signed message.
    </Note>
  </Step>

  <Step title="Place a limit buy order">
    Place a limit GTC (Good Till Cancelled) buy order for 10 units of SOL1 at a price of 50 USDT1. On testnet, price and size must be positive integers and price must not exceed 10000.

    ```python theme={"system"}
    result = sign_and_send({
        "type": "order",
        "orders": [
            {
                "a": 1,          # asset ID — 1 = SOL1
                "b": True,       # isBuy
                "p": "50",       # price (string)
                "s": "10",       # size (string)
                "r": False,      # reduceOnly
                "t": {"limit": {"tif": "Gtc"}},
            }
        ],
        "grouping": "na",
    })
    oid = result["response"]["data"]["statuses"][0]["resting"]["oid"]
    print(f"Order placed: oid={oid}")
    ```
  </Step>

  <Step title="Verify the order">
    Confirm the order is live by querying `POST /info` with `userOrders`. No signing is required for read queries.

    ```python theme={"system"}
    info_resp = requests.post(
        f"{BASE_URL}/info",
        json={"type": "userOrders", "accountId": str(account_id), "marketDeployerId": 1},
        timeout=10,
    )
    orders = info_resp.json()
    print(f"Open orders: {orders}")
    ```
  </Step>

  <Step title="Cancel the order">
    Cancel the order using its `oid`. Provide the asset ID (`a`) and order ID (`o`) in the `cancels` array.

    ```python theme={"system"}
    time.sleep(0.5)
    result = sign_and_send({"type": "cancel", "cancels": [{"a": 1, "o": oid}]})
    print(f"Cancel result: {result['response']['data']['statuses']}")
    ```
  </Step>
</Steps>

## Complete Example

Paste the full script below to run all six steps end to end:

```python theme={"system"}
import json, time, requests
from eth_account import Account
from eth_utils import keccak

BASE_URL = "https://dev.upsidemax.xyz"
wallet = Account.create()
PRIVATE_KEY = wallet.key
ADDRESS = wallet.address.lower()

# EIP-712 domain: Exchange / v1 / chainId 9767 / verifyingContract 0x0
_CHAIN_ID, _NAME, _VER, _SOURCE = 9767, "Exchange", "1", "b"
def _u(v):    return int(v).to_bytes(32, "big")
def _addr(s): return b"\x00" * 12 + bytes.fromhex(s[2:] if s[:2].lower() == "0x" else s)
def _s(v):    return keccak(str(v).encode())
_DOMAIN = keccak(
    keccak(b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
    + keccak(_NAME.encode()) + keccak(_VER.encode()) + _u(_CHAIN_ID) + b"\x00" * 32)

# Funds / permission actions use a typed struct; everything else uses the Agent path.
_TYPED = {
    "registerAccount": (b"RegisterAccount(address address,uint64 nonce)",
        lambda a, n: [_addr(a["address"]), _u(n)]),
    "approveAgent": (b"ApproveAgent(address agentAddress,string agentName,uint64 validUntil,uint64 nonce)",
        lambda a, n: [_addr(a["agentAddress"]), _s(a["agentName"]), _u(a["validUntil"]), _u(n)]),
    "revokeAgent": (b"RevokeAgent(address agentAddress,uint64 nonce)",
        lambda a, n: [_addr(a["agentAddress"]), _u(n)]),
    "lockCollateral": (b"LockCollateral(uint32 marketDeployerId,uint32 coinId,string amount,uint64 nonce)",
        lambda a, n: [_u(a["marketDeployerId"]), _u(a["coinId"]), _s(a["amount"]), _u(n)]),
    "unlockCollateral": (b"UnlockCollateral(uint32 marketDeployerId,uint32 coinId,string amount,uint64 nonce)",
        lambda a, n: [_u(a["marketDeployerId"]), _u(a["coinId"]), _s(a["amount"]), _u(n)]),
    "transferBetweenDeployers": (b"TransferBetweenDeployers(uint32 fromMarketDeployerId,uint32 toMarketDeployerId,uint32 coinId,string amount,uint64 nonce)",
        lambda a, n: [_u(a["fromMarketDeployerId"]), _u(a["toMarketDeployerId"]), _u(a["coinId"]), _s(a["amount"]), _u(n)]),
}

def eip712_digest(action, nonce):
    typed = _TYPED.get(action["type"])
    if typed:
        type_str, build = typed
        struct = keccak(keccak(type_str) + b"".join(build(action, nonce)))
    else:  # Agent path
        canonical = json.dumps(action, sort_keys=True, separators=(",", ":")).encode()
        action_hash = keccak(canonical + int(nonce).to_bytes(8, "big"))
        struct = keccak(keccak(b"Agent(string source,bytes32 actionHash)")
                        + keccak(_SOURCE.encode()) + action_hash)
    return keccak(b"\x19\x01" + _DOMAIN + struct)

def sign_and_send(action):
    nonce = int(time.time() * 1000)
    sig = Account._sign_hash(eip712_digest(action, nonce), PRIVATE_KEY)
    envelope = {
        "action": action,
        "signature": {
            "r": "0x" + sig.r.to_bytes(32, "big").hex(),
            "s": "0x" + sig.s.to_bytes(32, "big").hex(),
            "v": sig.v if sig.v >= 27 else sig.v + 27,
        },
        "nonce": nonce,
    }
    resp = requests.post(f"{BASE_URL}/exchange", json=envelope, timeout=15)
    return resp.json()

# 1. Register
result = sign_and_send({"type": "registerAccount", "address": ADDRESS})
account_id = result["response"]["accountId"]
print(f"Registered: accountId={account_id}")

# 2. Place limit buy order
result = sign_and_send({
    "type": "order",
    "orders": [{"a": 1, "b": True, "p": "50", "s": "10", "r": False,
                "t": {"limit": {"tif": "Gtc"}}}],
    "grouping": "na",
})
oid = result["response"]["data"]["statuses"][0]["resting"]["oid"]
print(f"Order placed: oid={oid}")

# 3. Verify
info_resp = requests.post(
    f"{BASE_URL}/info",
    json={"type": "userOrders", "accountId": str(account_id), "marketDeployerId": 1},
    timeout=10,
)
print(f"Open orders: {info_resp.json()}")

# 4. Cancel
time.sleep(0.5)
result = sign_and_send({"type": "cancel", "cancels": [{"a": 1, "o": oid}]})
print(f"Cancel result: {result['response']['data']['statuses']}")
```

## Expected Output

```text theme={"system"}
Registered: accountId=7
Order placed: oid=15
Cancel result: ['success']
```

Account IDs and order IDs are assigned sequentially by the server, so your values will differ.
