# ELYX > The financial layer for autonomous agents. Accounts, vaults, payments, policies and execution for autonomous software on Elysium (Kinetiq's Hyperliquid L2, EVM, chain id 99901, gas paid in HYPE). An AI agent can sign up, fund, configure and operate entirely over HTTPS. No browser, wallet extension or human click is required. Machine-readable API: https://www.elyxsi.tech/openapi.json Single-file TypeScript SDK (no dependencies): https://www.elyxsi.tech/sdk/elyx.ts Human guide: https://www.elyxsi.tech/developers ## Facts to rely on - Network: Elysium mainnet only (chain id 99901, RPC https://rpc.elysium.kinetiq.xyz). Real funds. There is no test network. - Safe practice: POST /api/v1/intents/preview evaluates every policy and quotes fees without creating or signing anything. - Every vault is a Safe 1.3.0 smart account (L2 singleton 0x3E5c63644E683549055b9Be8653de26E0B4CD36E). Owners and threshold are the account's. Funds stay in the vault. - An agent gets an executor key that ELYX derives server-side. It is never a Safe owner. It moves vault funds only through the ELYX spending limit Safe module (amount per period, per token, to named destinations), which only Safe owners can configure. The module address is in GET /api/v1/context under `contracts.spendingLimitModule`. - ELYX never asks for, stores or receives private keys or seed phrases. The owner key stays in your runtime; you only send signatures. - Every financial action is evaluated by a deterministic policy engine: ALLOW, REQUIRE_APPROVAL or BLOCK, with machine-readable reasons. A BLOCKED execution is a successful request, not an HTTP error. ## Credentials | Prefix | What | How to get it | Can | | --- | --- | --- | --- | | eot_ | Owner token | Sign a challenge with the owner key (below) | Everything an owner can, including preparing transactions the owner key must sign | | ek_live_ | API key | POST /api/v1/developers/keys (owner) | Read, submit intents and payments, pause agents, manage counterparties and webhooks | | eas_ | Agent session token | POST /api/v1/developers/sessions (owner) | One agent only: /context, /intents, /intents/preview, its own /executions, /approvals and /agents/{id}, /agents/{id}/requests, /prices. Everything else returns 403 | Send as `Authorization: Bearer `. ## Units | Where | Unit | Example | | --- | --- | --- | | Request amounts (`amount` in intents, payments, deposits, limits) | Decimal string in token units | "12.5" USDC, "0.25" HYPE | | Balances, `intent.amount`, limits, simulation deltas | Integer string in smallest unit | USDC 6 decimals: "12500000" = 12.5; HYPE 18 decimals | | `amountUsd`, policy amounts, allowances | Integer USD cents | 250000 = $2,500.00 | | Fees (`feeQuote`, preview `fee`) | Integer string micro-USD (1e-6) | "1879000" = $1.879 | | `maxSlippageBps`, MAX_SLIPPAGE | Basis points | 50 = 0.50% | Assets: USDC and HYPE (payable from vaults), plus WHYPE and KNTQ (swaps). Addresses are 0x, 40 hex characters; ELYX returns them checksummed. ## Prepared transactions (the one pattern to learn) Endpoints that change on-chain state for an owner return a PreparedTransaction: `{ id, kind, transaction: { chainId, from, to, data, value, gas, maxFeePerGas, maxPriorityFeePerGas, nonce }, description, simulation: { ok, error } }` (EIP-1559, bigints as decimal strings). On vaults that need more than one owner signature they return `{ kind: "AWAIT_OWNERS", approvalId, safeTxHash, required }` instead (see Approvals). 1. If `simulation.ok` is false, stop: nothing will work and nothing was sent. 2. Inspect before signing. Your owner key authorizes whatever the transaction says, so never sign blindly. Decode `data`: account creation must call GnosisSafeProxyFactory 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2 createProxyWithNonce with the canonical L2 singleton, exactly the owners and threshold you asked for, no setup delegatecall and no payment; a deposit must be a plain HYPE transfer or an ERC-20 transfer of exactly your amount to your vault; anything else must be `execTransaction` on your own vault Safe with zero gas-refund parameters and your sender-approval signature, whose inner calls (directly or through MultiSendCallOnly 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D) only enable the ELYX module, set or remove limits for the agent you are configuring, send gas to that agent's executor, or pay the withdrawal destination you chose. The TypeScript SDK (`/sdk/elyx.ts`) does all of this in `owner.submit` and refuses otherwise (`inspectTransaction`, `assertSafeToSign`). 3. Sign it as an EIP-1559 transaction with the owner key (e.g. viem `account.signTransaction`). 4. POST /api/v1/tx/{id}/submit with `{ "transaction": "0x02..." }` (the raw signed bytes). ELYX verifies it matches what it prepared, broadcasts, waits for confirmation and returns `{ signature: , result }`. Browser wallets can broadcast themselves and send `{ "hash": "0x..." }` instead. ## Autonomous onboarding, from zero Prerequisite outside ELYX: the owner address needs HYPE on Elysium for gas (about 0.01 HYPE plus whatever you deposit; bridge at https://elysium.kinetiq.xyz/bridge). Someone has to send it once. 1. Generate a secp256k1 private key (the owner key). Keep it in your runtime. 2. POST /api/v1/auth/challenge {address} -> {message, nonceToken}. The message is an EIP-4361 (Sign-In with Ethereum) message for this host and chain id 99901. 3. personal_sign (EIP-191) the message -> 65-byte 0x signature. 4. POST /api/v1/auth/token {address, message, signature, nonceToken, organizationName?} -> {token: "eot_..."}. The organization is created now. 5. POST /api/v1/accounts {name, type:"AGENT", coOwners:[], threshold:1, vaults:[{name:"Operating Vault", type:"OPERATING", description:""}]} -> PreparedTransaction. Sign and submit. Result: {accountId, vaultId, vaultAddress}. To keep a human in control, put their wallet in coOwners and set threshold 2. 6. POST /api/v1/vaults/{vaultId}/deposit {asset:"HYPE", amount:"0.05"} -> PreparedTransaction. Sign and submit. 7. POST /api/v1/counterparties {name, type, address, trust:"VERIFIED", approvedFor:["PAYMENT"]} for each payee. 8. POST /api/v1/agents {accountId, vaultId, name, kind, limits:[{asset:"USDC", amount:"500"}], period:"DAY", counterpartyIds:[...], gasHype:"0.01", policy:{approvalThresholdUsd:25000}} -> PreparedTransaction (one Safe transaction: enable module, set limits and destinations, send gas from the vault to the executor). Sign and submit. Result: {agentId, executed}. The executor pays its own gas. Keep it funded: POST /api/v1/agents/{id}/topup {hype} (SDK: owner.topUp). Revoking an agent removes its limits and sweeps its working wallet back to the vault (retry with POST /api/v1/agents/{id}/sweep). Any change to a vault Safe's nonce invalidates earlier unsigned Safe transactions for that vault; ELYX cancels them, resubmit those intents. 9. Optional: POST /api/v1/developers/sessions {agentId, allowedActions:["PAYMENT","SWAP","ALLOCATE","RETURN"], spendingAllowanceUsd:100000} -> {token:"eas_..."} for a separate agent runtime. 10. Operate: preview, then POST /api/v1/intents, then poll GET /api/v1/executions/{id}. ## Intents POST /api/v1/intents (header `idempotency-key`: reuse the same key on retries) `{ agentId, action: "PAYMENT"|"TRANSFER"|"ALLOCATE"|"RETURN"|"SWAP", asset, amount, counterpartyId?, outputAsset?, maxSlippageBps?, memo?, justification? }` - PAYMENT/TRANSFER: vault to counterparty. ALLOCATE: vault to the agent's working wallet. RETURN: working wallet to vault. SWAP: in the working wallet via KyberSwap (requires outputAsset and maxSlippageBps). - Response 202 {execution}. Poll until state is SETTLED, BLOCKED, FAILED, CANCELLED or AWAITING_APPROVAL. - States: CREATED, VALIDATING, POLICY_CHECK, AWAITING_APPROVAL, APPROVED, SIMULATING, READY, SUBMITTED, CONFIRMED, SETTLED, BLOCKED, FAILED, CANCELLED. - BLOCKED: read `execution.policy.triggeredPolicies[].reason` and `.message`. FAILED with `fundsMayHaveMoved: true`: check `signature` (the transaction hash) on-chain before retrying. ## Approvals - Vault funds (settlement.kind SAFE_TX or SAFE_CONFIG): owners sign the Safe transaction itself, gasless. GET /api/v1/approvals/{id}/safe-typed-data -> {typedData, safeTxHash}. Check it matches `approval.settlement.tx` (to, value, data, operation, nonce) and has zero gas-refund fields, sign it with eth_signTypedData_v4, then POST /api/v1/approvals/{id}/safe-vote {decision:"APPROVE", signature}. To reject: POST /api/v1/approvals/{id}/message {decision:"REJECT"}, personal_sign it, POST safe-vote {decision:"REJECT", signature}. - Agent payments: once enough owners signed, ELYX executes automatically. Owner configuration changes and withdrawals: any owner executes with POST /api/v1/approvals/{id}/execute -> PreparedTransaction (SDK: owner.execute). - Off-chain approvals (swaps from the working wallet, change requests): POST /api/v1/approvals/{id}/message {decision}, personal_sign, POST /api/v1/approvals/{id}/vote {decision, signature}. ## Rate limits Per minute: owner/user 120, API key 600, agent session as configured (default 30), organization 2000. 429 responses include `Retry-After` (seconds) and `error.retryAfterSeconds`. Loop protection per agent: 12 actions per minute, 3 identical intents per 5 minutes, 500 per day; tripping it blocks the intent and pauses the agent. Durable cap: 20 intents per agent per minute across all instances. Sign-in endpoints: 60 per minute per network. Swaps above 3% max slippage need owner approval. ## Errors `{ "error": { "code", "message" } }`. 400 VALIDATION_ERROR (field list in message), 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND, 400 TX_TAMPERED (signed transaction differs from what ELYX prepared), 409 IDEMPOTENCY_CONFLICT, 429 RATE_LIMITED, 503 PRICE_UNAVAILABLE. Nothing is created or signed on 4xx/5xx. Exception: POST /api/v1/tx/{id}/submit returns 202 with `pending: true` and a `signature` (tx hash) when the transaction was broadcast but is not final yet; never resubmit it, ELYX finishes the step automatically. ## TypeScript (viem for the key, SDK has no dependencies) ```ts // curl -o elyx.ts https://www.elyxsi.tech/sdk/elyx.ts import { Elyx, signInWithKey, viemSigner } from "./elyx"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; const owner = viemSigner(privateKeyToAccount(generatePrivateKey())); // persist the key securely; fund owner.address with HYPE first const base = "https://www.elyxsi.tech"; const { token } = await signInWithKey(base, owner, { organizationName: "My agent" }); const elyx = new Elyx({ apiKey: token, baseUrl: base }); const { result: { accountId, vaultId, vaultAddress } } = await elyx.owner.createAccount({ name: "Agent Ops" }, owner); await elyx.owner.deposit(vaultId, { asset: "HYPE", amount: "0.05" }, owner, { vaultAddress }); const payee = await elyx.owner.createCounterparty({ name: "Vendor", type: "BUSINESS", address: "0x..." }); const onboarding = await elyx.owner.createAgent({ accountId, vaultId, name: "Atlas", kind: "PAYMENT", limits: [{ asset: "HYPE", amount: "0.02" }], period: "DAY", counterpartyIds: [payee.id], gasHype: "0.01" }, owner, { vaultAddress }); if (onboarding.status !== "CONFIRMED") throw new Error("co-owners must sign first"); const { execution } = await elyx.intents.submit({ agentId: onboarding.result.agentId, action: "PAYMENT", asset: "HYPE", amount: "0.003", counterpartyId: payee.id }); console.log(await elyx.executions.wait(execution.id)); ``` ## Python (requests, eth-account) ```python import requests from eth_account import Account from eth_account.messages import encode_defunct BASE = "https://www.elyxsi.tech" acct = Account.create() # persist acct.key securely; fund acct.address with HYPE on Elysium ch = requests.post(f"{BASE}/api/v1/auth/challenge", json={"address": acct.address}).json() sig = "0x" + acct.sign_message(encode_defunct(text=ch["message"])).signature.hex().removeprefix("0x") tok = requests.post(f"{BASE}/api/v1/auth/token", json={"address": acct.address, "message": ch["message"], "signature": sig, "nonceToken": ch["nonceToken"]}).json()["token"] H = {"Authorization": f"Bearer {tok}"} def sign_and_submit(prepared): assert prepared["simulation"]["ok"], prepared["simulation"] # Production: decode and check tx["data"] before signing (see "Prepared transactions" step 2). t = prepared["transaction"] signed = acct.sign_transaction({"type": 2, "chainId": t["chainId"], "to": t["to"], "data": t["data"], "value": int(t["value"]), "gas": int(t["gas"]), "maxFeePerGas": int(t["maxFeePerGas"]), "maxPriorityFeePerGas": int(t["maxPriorityFeePerGas"]), "nonce": t["nonce"]}) raw = "0x" + signed.raw_transaction.hex().removeprefix("0x") r = requests.post(f"{BASE}/api/v1/tx/{prepared['id']}/submit", headers=H, json={"transaction": raw}) r.raise_for_status(); return r.json() out = sign_and_submit(requests.post(f"{BASE}/api/v1/accounts", headers=H, json={"name": "Agent Ops", "type": "AGENT", "coOwners": [], "threshold": 1, "vaults": [{"name": "Operating Vault", "type": "OPERATING", "description": ""}]}).json()) print(out["result"]["accountId"], out["result"]["vaultAddress"]) ``` ## Optional - https://www.elyxsi.tech/developers: full guide with curl examples - https://www.elyxsi.tech/security: trust model - https://www.elyxsi.tech/docs: product concepts