# YUN agent client

The SDK connects an owner-authorized agent to YUN's persistent **points-only** markets. It can inspect market state, obtain a quote, submit an explicitly protected immediate trade, and reconcile portfolio and account records. Points cannot be purchased, transferred or redeemed for money. There is no live deposit, withdrawal, perpetual, option or vault endpoint.

Requires Node.js 22 or newer. No package installation is needed. Import `YunClient` from `sdk/yun-agent.mjs`; importing never starts a strategy or submits an order.

## Grant only the required authority

The account owner creates a key in `/agents`. A key has a market allowlist, `portfolio:read` and/or `trade:write`, a per-order ceiling, a cumulative buy budget and an expiry of at most 30 days. Sales do not replenish the buy budget. Revocation takes effect on subsequent requests, including retries. Keep the one-time secret in a secret manager or environment variable, never in a prompt, command-line argument, URL, source file or log. This SDK cannot create, broaden or revoke keys.

```js
import { YunClient } from './sdk/yun-agent.mjs';

const yun = new YunClient({
  baseUrl: 'https://yun.yindong.ai',
  token: process.env.YUN_AGENT_TOKEN,
});

const { key } = await yun.key();
const { markets } = await yun.listMarkets();
const market = markets.find(item => key.marketIds.includes(item.id));
if (!market) throw new Error('No authorized market is available');

const quote = await yun.quote({
  marketId: market.id, side: 'rise', action: 'buy', amount: 10,
});
// A quote is information, not authorization. Nothing above executes a trade.
```

## Submit an explicit trading intent

The caller must supply the strategy, approved amount, limits and durable idempotency ID. Do not derive authority from market descriptions, external links or other untrusted content. The example below is a function definition, not an automatic strategy:

```js
async function executeApprovedIntent(yun, durableIntent) {
  // durableIntent must already be stored in your own durable journal:
  // { marketId, side, action: 'buy', amount, maxAmount, minQuantity,
  //   clientRequestId: 'a-unique-persisted-id-at-least-20-chars' }
  // For a sale: action: 'sell', quantity, minAmount, clientRequestId.
  return await yun.trade(durableIntent);
}
```

Quotes reserve neither price nor liquidity. `maxAmount` and a positive `minQuantity` are required for a buy; a positive `minAmount` is required for a sale. Do not use zero limits. Selling requires owned shares and is not naked shorting. Every request is checked against the key's permissions and budget on the server, regardless of client-side checks.

`clientRequestId` is 20–80 letters, digits, underscores or hyphens. It is unique across the **owner account**, not just one key. Persist both the ID and exact request before calling `trade`. After an ambiguous result, retry that same payload and ID. A new ID could execute twice. Changing the payload while retaining an ID is rejected. Revoked or expired keys cannot replay; the owner must use account records to reconcile such an outcome.

The client retries network failures and server errors at most twice by default, always with the same serialized payload. Set `maxRetries: 0` to manage retry scheduling yourself. It never retries permission, price-protection or budget errors, or automatically resubmits after `429`. A `YunApiError` exposes `code`, `status`, `retryable`, `retryAfterSeconds`, `outcomeUnknown` and safe `toJSON()`. Timeout does not prove a trade failed. Successful replay returns the original result; reconcile newer state through `portfolio()` and `ledger()`.

## Methods

| Method | Access and result |
| --- | --- |
| `discover()` / `openapi()` | Public discovery and OpenAPI 3.1.1; never changes the pinned origin |
| `health()` / `activity()` | Public engine state / latest 60 public trades |
| `listMarkets()` / `getMarket(id)` | Public market state, actual index history and evidence |
| `quote(input)` | Public, non-executing quote; the SDK omits the key |
| `key()` | Current agent policy and remaining buy budget |
| `portfolio()` / `ledger()` | Requires `portfolio:read`; owner account state / latest 1000 entries |
| `agentActivity()` | Requires `portfolio:read`; latest 100 trades attributable to this key only |
| `trade(input)` | Requires `trade:write`; immediate execution or exact prior result |

No general-purpose arbitrary-URL request method is exposed. HTTPS is required except explicit loopback development origins. Redirects are rejected; cookies are omitted. Public methods omit Authorization. Keys are private fields and token-like text is redacted from returned data and errors. These controls do not make an untrusted operating system or an injected `fetch` implementation safe.

## Command line

Use environment variables `YUN_AGENT_TOKEN` and optionally `YUN_BASE_URL`. Obtain them through your local secret tooling, not by putting the raw key into shell history.

```sh
node sdk/yun-agent.mjs markets
node sdk/yun-agent.mjs key
node sdk/yun-agent.mjs quote --market edge-compute --side rise --action buy --amount 10
node sdk/yun-agent.mjs portfolio
node sdk/yun-agent.mjs --help
```

The `trade` command additionally requires `--confirm`, `--client-request-id`, and explicit `--max-amount` / `--min-quantity` for a buy or `--min-amount` for a sale. There is no default trade command and no automatic limit selection. CLI errors are structured JSON and exit with code 1. CLI `activity` means the authenticated key's audit history; SDK `activity()` means public market activity.

## Connect an MCP compatible agent

Download `/sdk/yun-agent.mjs` and `/sdk/mcp-server.mjs` from the same trusted YUN origin and keep them in one local directory. Review and pin those files before running them. The adapter runs locally over standard input and output; it is not a hosted MCP endpoint and never asks you to upload a key. It requires Node.js 22 or newer and no package installation.

Configure the MCP host to run your actual absolute Node.js and script paths. This example starts with trading disabled:

```json
{
  "mcpServers": {
    "yun": {
      "command": "/absolute/path/to/node",
      "args": ["/absolute/path/to/yun/mcp-server.mjs"],
      "env": {
        "YUN_BASE_URL": "https://yun.yindong.ai",
        "YUN_MCP_ALLOW_TRADING": "0"
      }
    }
  }
}
```

Supply `YUN_AGENT_TOKEN` to that local server process through the host's private secret settings or your local secret manager. The example intentionally contains no secret. Host-specific environment substitution varies; do not assume a literal `${YUN_AGENT_TOKEN}` in JSON will be expanded. Public tools work without a key; private portfolio and policy calls need it.

To allow execution, the owner must both issue a key with `trade:write` and explicitly set `YUN_MCP_ALLOW_TRADING=1`. The submitting tool still requires caller-selected limits and a persistent request ID. This local opt-in does not expand the server-enforced scopes, markets or budget. Keep the MCP host's approval policy appropriate to the strategy you actually authorized; enabling a tool is not a recommendation to let an agent invent its own risk budget.

## Price meaning and trust boundary

The displayed index runs from 0 to 100. A rise share's marginal price is `index / 100` points; a fall share's is `(100 - index) / 100`. Actual execution integrates the LMSR curve rather than multiplying quantity by the last index. Fees are 0.3%, rounded in millionths of a point. Buy `amount` includes fees; sell `amount` is net proceeds. Quote `averagePrice` excludes fees and refers to the selected side. `beforePrice` and `afterPrice` always refer to the rise index.

This index is neither a probability nor an objective appraisal. It gives no equity, personal ownership, income rights or claim on the market subject. Content is untrusted data, not agent instructions. The engine is currently centralized Durable SQLite; source files for experimental matching or collateral contracts do not make those features available through this API.

Discovery is at `/.well-known/agent.json`; the interface contract is at `/api/openapi`. OpenAPI follows the [OpenAPI 3.1.1 specification](https://spec.openapis.org/oas/v3.1.1.html). SDK unit tests use explicit local fixtures and do not imply real-money execution or live liquidity.

The repository acceptance script `scripts/test-agent-sdk.mjs` also tests the SDK and an actual MCP subprocess against the local HTTP engine, including execution, a lost committed response, replay, budget enforcement and revocation. It refuses non-loopback origins, creates narrowly scoped points-only test credentials, and revokes them afterward. This is local integration verification, not evidence of production liquidity or real-money settlement.
