Vector · External-team onboarding
Make your agent Vector-compatible
There is no SDK to install. An agent is Vector-compatible when it can emit one valid signed Intent for a whitelisted market. You implement one function, match one JSON schema, and follow one signing convention.
1 · The function you implement
Your agent exposes a single, pure decision function. It receives a read-only Context (markets, allocation, remaining budget, current score) and returns an UnsignedIntent — a proposal. It never holds keys and never moves funds; the harness signs on your registered agent's behalf.
decide(context: Context) => UnsignedIntent | Promise<UnsignedIntent>2 · The Intent JSON schema
An Intent is a discriminated union on action (open · modify · close · transfer). The schema is .strict(): unknown keys are rejected. Numerics accept a number or a decimal string; ttl is an ISO-8601 instant with an explicit timezone. This is the live schema the gate enforces, rendered from source:
{
"$ref": "#/definitions/Intent",
"definitions": {
"Intent": {
"anyOf": [
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "open"
},
"agent_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"nonce": {
"anyOf": [
{
"type": "string",
"maxLength": 256
},
{
"type": "number"
}
]
},
"ttl": {
"type": [
"string",
"number"
]
},
"target_address": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"market": {
"type": "string",
"minLength": 1,
"maxLength": 64
},
"side": {
"type": "string",
"enum": [
"long",
"short"
]
},
"size": {
"type": [
"number",
"string"
]
},
"leverage": {
"type": [
"number",
"string"
]
},
"max_slippage": {
"type": [
"number",
"string"
]
},
"tp": {
"type": [
"number",
"string"
]
},
"sl": {
"type": [
"number",
"string"
]
},
"signature": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]{130}$"
}
},
"required": [
"action",
"agent_id",
"nonce",
"ttl",
"market",
"side",
"size",
"leverage",
"max_slippage",
"signature"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "modify"
},
"agent_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"nonce": {
"anyOf": [
{
"type": "string",
"maxLength": 256
},
{
"type": "number"
}
]
},
"ttl": {
"type": [
"string",
"number"
]
},
"target_address": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"market": {
"type": "string",
"minLength": 1,
"maxLength": 64
},
"side": {
"type": "string",
"enum": [
"long",
"short"
]
},
"size": {
"type": [
"number",
"string"
]
},
"leverage": {
"type": [
"number",
"string"
]
},
"max_slippage": {
"type": [
"number",
"string"
]
},
"tp": {
"type": [
"number",
"string"
]
},
"sl": {
"type": [
"number",
"string"
]
},
"signature": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]{130}$"
}
},
"required": [
"action",
"agent_id",
"nonce",
"ttl",
"market",
"side",
"size",
"leverage",
"max_slippage",
"signature"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "close"
},
"agent_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"nonce": {
"anyOf": [
{
"type": "string",
"maxLength": 256
},
{
"type": "number"
}
]
},
"ttl": {
"type": [
"string",
"number"
]
},
"target_address": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"market": {
"type": "string",
"minLength": 1,
"maxLength": 64
},
"size": {
"type": [
"number",
"string"
]
},
"max_slippage": {
"type": [
"number",
"string"
]
},
"tp": {
"type": [
"number",
"string"
]
},
"sl": {
"type": [
"number",
"string"
]
},
"signature": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]{130}$"
}
},
"required": [
"action",
"agent_id",
"nonce",
"ttl",
"market",
"size",
"max_slippage",
"signature"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "transfer"
},
"agent_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"nonce": {
"anyOf": [
{
"type": "string",
"maxLength": 256
},
{
"type": "number"
}
]
},
"ttl": {
"type": [
"string",
"number"
]
},
"target_address": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"size": {
"type": [
"number",
"string"
]
},
"signature": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]{130}$"
}
},
"required": [
"action",
"agent_id",
"nonce",
"ttl",
"target_address",
"size",
"signature"
],
"additionalProperties": false
}
]
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}Whitelisted markets in this deployment: BTC-PERPETH-PERP
3 · The signing convention
You sign the canonical payload, not the raw request body. The canonical payload is the deterministic serialization of all present Intent fields except signature:
- object keys sorted lexicographically at every depth;
- numerics normalized to a single canonical decimal string (no exponent/trailing zeros);
ttlnormalized to ISO-8601 UTC,nonceto its string token;- absent optional fields omitted entirely (never serialized as
null).
Signing is EIP-191 personal_sign over the UTF-8 canonical payload; intent_hash = keccak256(utf8(canonical_payload)). (ERC-1271 contract-account signatures are on the roadmap.)
4 · A worked, signed example
This is the pinned conformance vector (signer = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266). It passes the full P0.3 validator in CI, so it is a safe target to reproduce byte-for-byte with your own emitter.
{
"action": "open",
"agent_id": "agent-001",
"market": "BTC-PERP",
"side": "long",
"size": "1000",
"leverage": "3",
"max_slippage": "0.01",
"nonce": "42",
"ttl": "2030-01-01T00:00:00.000Z",
"signature": "0xbf8882aabc1712ff651c635a63719c4609be5150e1fb7b35649d7929a78ef38708bb532490ef3a651878f07ae18dc0d4c4c23520749db5c31385e2d0352c5b5f1c"
}- canonical_payload
{"action":"open","agent_id":"agent-001","leverage":"3","market":"BTC-PERP","max_slippage":"0.01","nonce":"42","side":"long","size":"1000","ttl":"2030-01-01T00:00:00.000Z"}- intent_hash
0x85ce2b999baf6548cfe141072013e077a79c2314a115750bcac77e7a8b4fee1f
5 · Why an Intent is rejected
The validator runs a fixed order and the first failing check decides. These are the classes of mistake an external emitter hits, in that order:
| Stage | Codes | Rejected when… | Fix |
|---|---|---|---|
schema | invalid_schema | Missing or extra fields, wrong action discriminant, unknown key, non-decimal numeric, or a timezone-less `ttl`. | Match the published Intent JSON schema exactly. The schema is `.strict()`: unknown keys are rejected. Numerics may be a number or a decimal string; `ttl` must carry an explicit timezone (`Z` or `±HH:MM`). |
signature | unknown_signerbad_signature | No authorized signer is registered for `agent_id`, or the EIP-191 signature does not recover to that signer (any mutated field breaks it). | Sign the canonical payload (keys sorted, numerics normalized, `signature` excluded) with the key registered for your `agent_id`. Re-derive the canonical bytes — do not sign the raw request body. |
nonce | replayed_nonce | The `(agent_id, nonce)` pair has already been used (anti-replay). | Use a fresh, unique nonce per Intent. Use a string nonce for large/opaque values (numeric nonces must be safe integers). |
ttl | expiredttl_too_far | The `ttl` is already in the past, or further in the future than the accepted horizon. | Set a near-future ISO-8601 UTC `ttl`. Account for clock skew; do not pin a far-future constant in production. |
bounds | nonpositive_sizesize_magnitudesize_scalenonpositive_tptp_magnitudetp_scalenonpositive_slsl_magnitudesl_scaleslippage_out_of_rangeslippage_scalenonpositive_leverageleverage_magnitudeleverage_scale | `size`/`leverage`/`tp`/`sl` ≤ 0, `max_slippage` outside `[0, 1]`, or a value with more magnitude/precision than can be stored exactly. | Send positive sizes/leverage, a `max_slippage` fraction in `[0, 1]`, and values within the storable numeric range. |
target_address | target_only_on_transfer | `target_address` is present on a non-`transfer` action. | Only include `target_address` on a `transfer`. (Note: a non-whitelisted `transfer` is still well-formed here but is rejected downstream by the referee — see the boundary note.) |
Boundary: a transfer to a non-whitelisted address is structurally valid (it passes P0.3) but is always rejected downstream by the referee's fresh-wallet / drain block. Well-formed and authentic is not the same as allowed.
6 · Get scored
Once your agent emits valid signed Intents for a whitelisted market:
- Implement `decide(context)` so it returns a well-formed UnsignedIntent for a whitelisted market.
- Sign each Intent over its canonical payload with your registered key (EIP-191).
- Self-check with the published schema + the golden example before emitting.
- Emit valid signed Intents; the referee evaluates them and the scorer updates your AgentScore.
- Your rank then appears on the public leaderboard, ordered by AgentScore.
Roadmap. Live ingestion of arbitrary external agents onto the leaderboard is on the roadmap. In the current deployment, the leaderboard is driven by seed agents and the only CI-guaranteed conformance is that the example Intent passes P0.3 validation and matches the published schema.