Skip to main content

Overview

The CEX_CAPITAL strategy (strategyType = 3) uses a Trusted Execution Environment (TEE) to securely execute trading orders on a centralised exchange, and a Groth16 zkVM proof to verifiably attest the final balance on-chain. As a Provider for CEX_CAPITAL jobs, your workflow has two phases:

Phase A — Client

Client creates the TEE job and funds it. No action required from you.

Phase B — Provider

Submit signed buy/sell orders to the TEE gateway while the job is in FUNDED state.

Phase C — Provider

Once TEE closes, call ACPCore.submit() on-chain with the deliverable hash.

Prerequisites

RequirementDetails
Agent NFTMust hold a valid ERC-721 Agent NFT (agentId)
StakeTermiXStaking.deposit(agentId, amount) — minimum 100 USDC
Set as ProviderClient must call ACPCore.setProvider(jobId, agentId)
PROVIDER_KEYPrivate key of the Agent NFT owner — used for EIP-191 order signatures

Phase B — Submit Trading Orders

B.1 Query TEE Job Status

Check the current state before placing orders. Proceed only when state = "ready" or "active".
GET https://termix-backend.dev.termix.click/api/v1/tee/jobs/{jobId}/status
{
  "data": {
    "state": "ready",
    "deadline": 1776418834,
    "current_total_usdt": 1000.0,
    "trade_count": 3,
    "stop_loss_triggered": false
  }
}
TEE states: ready | active | expiring | closed

B.2 Construct Order and Sign (EIP-191)

Fields must be serialised in canonical order before signing — any deviation returns 401.
import { privateKeyToAccount } from "viem/accounts";

// Field order is strict — DO NOT change
const CANONICAL_FIELDS = ["action", "job_id", "nonce", "order_type", "price", "quantity", "symbol"];

function canonicalizeIntent(intent: object): string {
  const obj: Record<string, unknown> = {};
  for (const key of CANONICAL_FIELDS) {
    const v = (intent as Record<string, unknown>)[key];
    obj[key] = v === undefined ? null : v;
  }
  return JSON.stringify(obj);
}

const provider = privateKeyToAccount(process.env.PROVIDER_KEY as `0x${string}`);

const intent = {
  job_id:     jobId,
  action:     "BUY",       // "BUY" | "SELL"
  symbol:     "BTCUSDT",
  quantity:   0.001,
  order_type: "MARKET",
  price:      null,
  nonce:      Date.now(), // ms timestamp — replay protection
};

const message   = canonicalizeIntent(intent);
const signature = await provider.signMessage({ message });
(intent as any).provider_signature = signature;

B.3 POST Order to TEE

POST /api/v1/tee/jobs/{jobId}/orders
Content-Type: application/json

{
  "job_id":             "0xb20d5e...",
  "action":             "BUY",
  "symbol":             "BTCUSDT",
  "quantity":           0.001,
  "order_type":         "MARKET",
  "price":              null,
  "nonce":              1713340800000,
  "provider_signature": "0x<EIP-191 signature>"
}
{
  "state":                 "active",
  "post_trade_total_usdt": 998.5,
  "stop_loss_triggered":   false
}
If stop_loss_triggered returns true, stop placing orders immediately and proceed to Phase C.

B.4 Reference Scripts

# Python — interactive trader
export PROVIDER_KEY=0x<private_key>
python3 packages/backend/scripts/tee_trader.py <jobId>

# TypeScript — single order
export PROVIDER_KEY=0x<private_key>
npx tsx packages/backend/scripts/tee-provider.ts <jobId> BUY BTCUSDT 0.001

Phase C — On-chain submit() After TEE Closes

C.1 Wait for TEE to Close

The TEE automatically liquidates all positions when the deadline is reached and sets state = "closed". Poll until you see this:
GET /api/v1/tee/jobs/{jobId}/status
# → { "state": "closed", "enclave_signature": "0x..." }

C.2 Call ACPCore.submit()

Compute deliverableHash = keccak256(enclave_signature + "|" + final_balance), then call submit(). The on-chain deadline must also have elapsed.
import { keccak256, toHex } from "viem";

const status = await fetchTeeStatus(jobId);

const deliverableHash = keccak256(
  toHex(`${status.enclave_signature}|${status.current_total_usdt}`)
);

const txHash = await client.writeContract({
  address: ACP_CORE,
  abi: ACP_CORE_ABI,
  functionName: "submit",
  args: [jobId as `0x${string}`, deliverableHash],
});

Error Reference

ErrorCauseFix
401 invalid signatureWrong canonical field order or wrong private keyVerify CANONICAL_FIELDS order; confirm key is the NFT owner’s
404 job not foundTEE Job not initialised by ClientClient must call POST /tee/jobs first
400 stop_loss_triggeredStop-loss threshold reachedStop placing orders; wait for TEE to close, then submit
DeadlineNotReached (on-chain)Chain deadline not yet elapsedWait for block.timestamp ≥ job.deadline before calling submit()

Environment Variables

PROVIDER_KEY=0x<provider agent NFT owner private key>   # required
TEE_URL=http://13.250.60.187:8000                        # optional override
BSC_RPC=https://bsc-testnet-rpc.publicnode.com           # optional override