diff --git a/README.md b/README.md index 33457b1..78c61f6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,36 @@ Best-execution routing across Jupiter Perps, Pacifica, Drift, and Flash Trade. Your agent decides. Toreva executes. Every action receipted. +## Agentic perps setup + +Use `toreva_establish` before perps execution when an agent needs delegated +authority for a human wallet. The standard perps pattern is: + +```text +human wallet + -> Toreva/Swig master authority + -> venue-specific child capability + -> Pacifica API agent wallet when Pacifica is selected +``` + +The human wallet remains the root owner. The Swig authority is the policy and +capital-management control layer. Pacifica uses a separate API agent wallet +because Pacifica REST orders require an on-curve Ed25519 signer. Toreva can +create, bind, fund, route, monitor, and revoke that child capability through +Toreva surfaces; the user does not need to open Pacifica. + +For best execution, omit `venue` on `toreva_perps_long` or +`toreva_perps_short`. Toreva will compare enabled venues and route by estimated +all-in cost. Set `venue` only when you intentionally want a specific venue. +Perps tools use the Gateway MCP field contract: `walletAddress`, `token`, +`sizeUsd`, `leverage`, `collateralToken`, and `collateralAmount`. + +The public integration packet lives in this repo: + +- [Agentic perps integration patterns](./docs/agentic-perps-integration-patterns.md) +- [OpenAPI-style relay examples](./docs/toreva-perps.openapi.json) +- [Claude Code agent prompt](./docs/claude-code-agent-prompt.md) + ## Install The fastest path — wires Toreva into your MCP-aware client (Claude @@ -62,7 +92,9 @@ export TOREVA_AUTH_TOKEN=your_token npx toreva login # writes the token to ~/.config/toreva/config.json ``` -Request a token at [toreva.com/docs](https://toreva.com/docs). +Use `toreva login` for the standard device-code flow, or request an integration +token from your Toreva contact. The Kit repository is the public source of +truth for agent, SDK, CLI, Skills, and MCP integration details. ### Environment variables @@ -103,6 +135,7 @@ Trades routed to Drift via toreva receive a 5% fee discount. | Tool | What it does | | --- | --- | | `toreva_strategies` | Browse strategy catalog with pricing | +| `toreva_establish` | Attach a delegated agent authority and child capabilities to a wallet | | `toreva_earn` | Deploy USDC to yield across venues | | `toreva_scan` | Survey portfolio state | | `toreva_simulate` | Dry-run without execution | diff --git a/docs/agentic-perps-integration-patterns.md b/docs/agentic-perps-integration-patterns.md new file mode 100644 index 0000000..3717dd9 --- /dev/null +++ b/docs/agentic-perps-integration-patterns.md @@ -0,0 +1,403 @@ +# Agentic Perps Integration Patterns + +This repo is the public source of truth for Toreva agent integrations while the +consumer website keeps Day-1 surfaces intentionally narrow. + +Toreva provides non-custodial execution primitives for Solana with +best-execution routing across Jupiter Perps, Pacifica, Drift, and Flash Trade. +Perps opens are charged at 1 bps by Toreva. Lifecycle verbs are free. + +This page is for builders integrating through API, CLI, SDK, Skills, or MCP. +It is execution infrastructure, not financial advice or strategy advice. + +## Capability Model + +```text +human wallet + -> Toreva/Swig master authority + -> venue-specific child capability + -> Pacifica API agent wallet when Pacifica is selected +``` + +The human wallet is the root owner. The Swig authority is the policy and +capital-management control layer. It can fund or withdraw from attached +satellite wallets within user-approved policy. + +Pacifica requires a separate API agent wallet because Pacifica REST orders +require an on-curve Ed25519 signer. A Swig PDA does not sign Pacifica REST +orders. Toreva binds the Pacifica API agent wallet into the same identity and +capability graph, then routes, monitors, receipts, and can revoke that +capability from Toreva surfaces. The user does not need to open Pacifica. + +## Canonical Relay Envelope + +All direct API examples use the same relay envelope: + +```http +POST https://gateway.toreva.com/relay +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "type": "", + "toolName": "", + "requestId": "", + "payload": {} +} +``` + +MCP clients call the same `toolName` with the same payload. SDK clients call +`client.relay(...)` or `perps.call(...)` and the SDK creates this envelope. + +Do not use old field aliases for perps. Use these Gateway MCP fields: + +| Old alias | Canonical field | +| --- | --- | +| `wallet` | `walletAddress` | +| `market` | `token` plus optional `venue` | +| `notionalUsd` | `sizeUsd` | + +Never ask a user for raw private keys, seed phrases, API secrets, or signer +material. Public keys, signer references, and receipt IDs are acceptable. + +## Required Workflow + +1. Establish the human wallet's delegated authority and child perps capability. +2. Query venues and markets. +3. Simulate the trade. +4. Ask for human approval when policy expands, funding moves, signer binding is + created, or execution would use real funds. +5. Execute the chosen perps verb. +6. Monitor positions, fills, funding, receipts, balances, and revocation state. +7. Close, cancel, settle, withdraw, or revoke as directed by policy. + +## Exact Payloads + +### 1. Establish Swig Master And Pacifica Child Capability + +```json +{ + "type": "intent.establish", + "toolName": "toreva_establish", + "requestId": "establish-perps-001", + "payload": { + "walletAddress": "", + "prompt": "Create a Toreva perps agent capability for SOL and BTC with low notional limits.", + "agent_authority": { + "delegation_provider": "swig", + "network": "solana", + "signer_kind": "delegated_authority", + "policy_id": "perps_agent_v1" + }, + "capabilities": [ + { + "capability_type": "perps", + "delegation_provider": "venue_api", + "network": "solana", + "venue": "pacifica", + "execution_adapter": "pacifica", + "signer_kind": "venue_api_agent", + "guardrails": { + "max_notional_usd": 500, + "max_leverage": 1.5, + "markets": ["SOL-PERP", "BTC-PERP"], + "collateral_tokens": ["USDC"], + "requires_human_approval_for_funding": true, + "requires_human_approval_for_leverage_increase": true + }, + "provider_metadata": { + "agent_wallet_public_key": "" + } + } + ] + } +} +``` + +`provider_metadata` may include public identifiers and opaque receipt +references. It must not include private keys, seed phrases, API secrets, or raw +signer material. + +### 2. Query Venues + +```json +{ + "type": "perps.query_venues", + "toolName": "toreva_perps_query_venues", + "requestId": "query-venues-001", + "payload": {} +} +``` + +### 3. Simulate + +```json +{ + "type": "perps.simulate", + "toolName": "toreva_perps_simulate", + "requestId": "simulate-sol-long-001", + "payload": { + "walletAddress": "", + "token": "SOL", + "direction": "long", + "sizeUsd": 180, + "leverage": 1.2, + "collateralToken": "USDC", + "collateralAmount": 150 + } +} +``` + +### 4. Open Long + +Omit `venue` to let Toreva route by estimated all-in cost. Set `venue` only +when the human or policy explicitly chooses a venue. + +```json +{ + "type": "perps.open_long", + "toolName": "toreva_perps_long", + "requestId": "open-sol-long-001", + "payload": { + "walletAddress": "", + "token": "SOL", + "sizeUsd": 180, + "leverage": 1.2, + "collateralToken": "USDC", + "collateralAmount": 150, + "maxSlippageBps": 50, + "clientRequestId": "strategy-run-2026-05-06-001" + } +} +``` + +### 5. Open Short + +```json +{ + "type": "perps.open_short", + "toolName": "toreva_perps_short", + "requestId": "open-btc-short-001", + "payload": { + "walletAddress": "", + "token": "BTC", + "sizeUsd": 200, + "leverage": 1.1, + "collateralToken": "USDC", + "collateralAmount": 182, + "venue": "pacifica", + "maxSlippageBps": 50, + "clientRequestId": "strategy-run-2026-05-06-002" + } +} +``` + +### 6. Close + +Position lifecycle verbs require the venue that owns the position. + +```json +{ + "type": "perps.close", + "toolName": "toreva_perps_close", + "requestId": "close-sol-long-001", + "payload": { + "walletAddress": "", + "venue": "pacifica", + "positionId": "", + "token": "SOL", + "side": "long", + "maxSlippageBps": 50, + "clientRequestId": "strategy-run-2026-05-06-003" + } +} +``` + +### 7. Cancel Order + +```json +{ + "type": "perps.cancel_order", + "toolName": "toreva_perps_cancel_order", + "requestId": "cancel-order-001", + "payload": { + "walletAddress": "", + "venue": "pacifica", + "positionId": "", + "orderId": "", + "clientRequestId": "strategy-run-2026-05-06-004" + } +} +``` + +### 8. Funding Settle + +```json +{ + "type": "perps.funding_settle", + "toolName": "toreva_perps_funding_settle", + "requestId": "funding-settle-001", + "payload": { + "walletAddress": "", + "venue": "pacifica", + "positionId": "", + "token": "SOL", + "side": "long", + "clientRequestId": "strategy-run-2026-05-06-005" + } +} +``` + +## Additional Lifecycle Payloads + +### Add Margin + +```json +{ + "type": "perps.add_margin", + "toolName": "toreva_perps_add_margin", + "requestId": "add-margin-001", + "payload": { + "walletAddress": "", + "venue": "pacifica", + "positionId": "", + "token": "USDC", + "amount": 25, + "clientRequestId": "strategy-run-2026-05-06-006" + } +} +``` + +### Remove Margin + +```json +{ + "type": "perps.remove_margin", + "toolName": "toreva_perps_remove_margin", + "requestId": "remove-margin-001", + "payload": { + "walletAddress": "", + "venue": "pacifica", + "positionId": "", + "token": "USDC", + "amount": 25, + "clientRequestId": "strategy-run-2026-05-06-007" + } +} +``` + +### Query Position + +```json +{ + "type": "perps.query_position", + "toolName": "toreva_perps_query_position", + "requestId": "query-position-001", + "payload": { + "walletAddress": "", + "venue": "pacifica" + } +} +``` + +## CLI Pattern + +```bash +toreva perps toreva_perps_query_venues '{}' +toreva perps toreva_perps_simulate '{"walletAddress":"","token":"SOL","direction":"long","sizeUsd":180,"leverage":1.2,"collateralToken":"USDC","collateralAmount":150}' +toreva perps toreva_perps_long '{"walletAddress":"","token":"SOL","sizeUsd":180,"leverage":1.2,"collateralToken":"USDC","collateralAmount":150}' +``` + +## SDK Pattern + +```ts +import { PerpsApi, TorevaClient } from '@toreva/sdk'; + +const client = new TorevaClient({ + relayAuthToken: process.env.TOREVA_AUTH_TOKEN! +}); + +await client.relay({ + type: 'intent.establish', + toolName: 'toreva_establish', + requestId: 'establish-perps-001', + payload: { + walletAddress: '', + agent_authority: { + delegation_provider: 'swig', + network: 'solana', + signer_kind: 'delegated_authority', + policy_id: 'perps_agent_v1' + }, + capabilities: [ + { + capability_type: 'perps', + delegation_provider: 'venue_api', + network: 'solana', + venue: 'pacifica', + execution_adapter: 'pacifica', + signer_kind: 'venue_api_agent', + guardrails: { + max_notional_usd: 500, + max_leverage: 1.5, + markets: ['SOL-PERP', 'BTC-PERP'], + collateral_tokens: ['USDC'] + } + } + ] + } +}); + +const perps = new PerpsApi(client); + +await perps.call('toreva_perps_long', { + walletAddress: '', + token: 'SOL', + sizeUsd: 180, + leverage: 1.2, + collateralToken: 'USDC', + collateralAmount: 150 +}); +``` + +## MCP / Agent Operating Rule + +Give MCP-aware agents this rule: + +```text +Use Toreva Kit as the source of truth. Use toreva_establish before perps +execution when no perps capability is attached. Use walletAddress, token, +sizeUsd, leverage, collateralToken, and collateralAmount for opens. Omit venue +unless the user explicitly chooses one. Never request raw private keys, seed +phrases, API secrets, or signer material. Ask for approval before funding, +signer binding, policy expansion, leverage expansion, or real-funds execution. +``` + +## Why Route Through Toreva Instead Of Venue-Direct + +Toreva is the agentic execution layer. Venue-direct integrations can open a +position, but they do not provide a provider-neutral authority graph, policy +scoping across attached wallets, best-execution routing, unified receipts, +approval-gated funding, revocation handling, and cross-venue monitoring in one +place. + +For Pacifica specifically, Toreva treats the Pacifica API agent wallet as a +child capability bound to the human wallet and Swig master authority. The +strategy agent can ask Toreva to route or execute, but the policy boundary and +funding authority remain with the Toreva identity/capability graph. + +## Skill Pattern + +Use `skills/toreva-establish-perps-agent.md` during setup and the perps verb +skills for execution. The skill boundary is the same as the API boundary: +establish the authority graph first, then use best-execution perps verbs. + +## Product Boundary + +This public packet is integration guidance for agent builders. Consumer perps +availability, production limits, venue enablement, and real-funds canaries are +controlled by Toreva policy, approvals, and deployment state. Do not claim that +a strategy is battle-tested, mainnet operational, or safe for customer funds +unless the corresponding receipts and approval records exist. diff --git a/docs/claude-code-agent-prompt.md b/docs/claude-code-agent-prompt.md new file mode 100644 index 0000000..56dce2a --- /dev/null +++ b/docs/claude-code-agent-prompt.md @@ -0,0 +1,41 @@ +# Claude Code Agent Prompt + +Use this prompt when asking a coding agent to integrate Toreva perps. + +```text +Use the public Toreva Kit repo as the source of truth: + +- https://github.com/toreva/kit +- docs/agentic-perps-integration-patterns.md +- docs/toreva-perps.openapi.json +- examples/agentic-perps-workflow/ + +Do not use toreva.com/docs as the integration source. The consumer website is +intentionally narrow for the current launch phase; Kit is the public SDK, +MCP, Skills, CLI, and API contract surface. + +Build a Toreva integration that: + +1. Calls toreva_establish to bind a human wallet to a Toreva/Swig master + authority and a Pacifica child perps capability. +2. Calls toreva_perps_query_venues and toreva_perps_simulate before execution. +3. Opens positions with toreva_perps_long or toreva_perps_short using: + walletAddress, token, sizeUsd, leverage, collateralToken, collateralAmount. +4. Uses toreva_perps_close, toreva_perps_cancel_order, and + toreva_perps_funding_settle for lifecycle management. +5. Omits venue for best-execution routing unless the user explicitly chooses a + venue. If Pacifica is selected, treat the Pacifica API agent wallet as a + child capability bound to the human wallet through Toreva. +6. Never asks for raw private keys, seed phrases, API secrets, or signer + material. Use only public keys, signer references, approvals, receipts, and + relay tokens. +7. Requires explicit human approval before funding moves, signer binding, + leverage increases, policy expansion, or real-funds execution. + +Validate all payloads against docs/toreva-perps.openapi.json and the schemas in +packages/types/src/intents.ts and packages/types/src/perps.ts. Then run: + +pnpm test +pnpm typecheck +pnpm build +``` diff --git a/docs/toreva-perps.openapi.json b/docs/toreva-perps.openapi.json new file mode 100644 index 0000000..66afbb9 --- /dev/null +++ b/docs/toreva-perps.openapi.json @@ -0,0 +1,264 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Toreva Agentic Perps Relay", + "version": "0.1.0", + "description": "Public relay packet for Toreva establish and perps agent integrations. Toreva Kit is the public source of truth while consumer website docs remain intentionally narrow." + }, + "servers": [ + { + "url": "https://gateway.toreva.com" + } + ], + "paths": { + "/relay": { + "post": { + "summary": "Relay a Toreva tool request", + "operationId": "relayToolRequest", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelayRequest" + }, + "examples": { + "establish": { + "summary": "Establish Swig master authority and Pacifica child capability", + "value": { + "type": "intent.establish", + "toolName": "toreva_establish", + "requestId": "establish-perps-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "prompt": "Create a Toreva perps agent capability for SOL and BTC with low notional limits.", + "agent_authority": { + "delegation_provider": "swig", + "network": "solana", + "signer_kind": "delegated_authority", + "policy_id": "perps_agent_v1" + }, + "capabilities": [ + { + "capability_type": "perps", + "delegation_provider": "venue_api", + "network": "solana", + "venue": "pacifica", + "execution_adapter": "pacifica", + "signer_kind": "venue_api_agent", + "guardrails": { + "max_notional_usd": 500, + "max_leverage": 1.5, + "markets": [ + "SOL-PERP", + "BTC-PERP" + ], + "collateral_tokens": [ + "USDC" + ], + "requires_human_approval_for_funding": true, + "requires_human_approval_for_leverage_increase": true + }, + "provider_metadata": { + "agent_wallet_public_key": "PacificaAgentWallet11111111111111111111" + } + } + ] + } + } + }, + "query_venues": { + "summary": "Query enabled venues", + "value": { + "type": "perps.query_venues", + "toolName": "toreva_perps_query_venues", + "requestId": "query-venues-001", + "payload": {} + } + }, + "simulate": { + "summary": "Simulate SOL long", + "value": { + "type": "perps.simulate", + "toolName": "toreva_perps_simulate", + "requestId": "simulate-sol-long-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "token": "SOL", + "direction": "long", + "sizeUsd": 180, + "leverage": 1.2, + "collateralToken": "USDC", + "collateralAmount": 150 + } + } + }, + "perps_long": { + "summary": "Open a routed SOL long", + "value": { + "type": "perps.open_long", + "toolName": "toreva_perps_long", + "requestId": "open-sol-long-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "token": "SOL", + "sizeUsd": 180, + "leverage": 1.2, + "collateralToken": "USDC", + "collateralAmount": 150, + "maxSlippageBps": 50, + "clientRequestId": "strategy-run-2026-05-06-001" + } + } + }, + "perps_short": { + "summary": "Open a Pacifica BTC short", + "value": { + "type": "perps.open_short", + "toolName": "toreva_perps_short", + "requestId": "open-btc-short-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "token": "BTC", + "sizeUsd": 200, + "leverage": 1.1, + "collateralToken": "USDC", + "collateralAmount": 182, + "venue": "pacifica", + "maxSlippageBps": 50, + "clientRequestId": "strategy-run-2026-05-06-002" + } + } + }, + "close": { + "summary": "Close a Pacifica position", + "value": { + "type": "perps.close", + "toolName": "toreva_perps_close", + "requestId": "close-sol-long-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "venue": "pacifica", + "positionId": "position-1", + "token": "SOL", + "side": "long", + "maxSlippageBps": 50, + "clientRequestId": "strategy-run-2026-05-06-003" + } + } + }, + "cancel_order": { + "summary": "Cancel a Pacifica order", + "value": { + "type": "perps.cancel_order", + "toolName": "toreva_perps_cancel_order", + "requestId": "cancel-order-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "venue": "pacifica", + "positionId": "position-1", + "orderId": "order-1", + "clientRequestId": "strategy-run-2026-05-06-004" + } + } + }, + "funding_settle": { + "summary": "Settle funding on a Pacifica position", + "value": { + "type": "perps.funding_settle", + "toolName": "toreva_perps_funding_settle", + "requestId": "funding-settle-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "venue": "pacifica", + "positionId": "position-1", + "token": "SOL", + "side": "long", + "clientRequestId": "strategy-run-2026-05-06-005" + } + } + }, + "add_margin": { + "summary": "Add margin to a position", + "value": { + "type": "perps.add_margin", + "toolName": "toreva_perps_add_margin", + "requestId": "add-margin-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "venue": "pacifica", + "positionId": "position-1", + "token": "USDC", + "amount": 25, + "clientRequestId": "strategy-run-2026-05-06-006" + } + } + }, + "remove_margin": { + "summary": "Remove margin from a position", + "value": { + "type": "perps.remove_margin", + "toolName": "toreva_perps_remove_margin", + "requestId": "remove-margin-001", + "payload": { + "walletAddress": "11111111111111111111111111111111", + "venue": "pacifica", + "positionId": "position-1", + "token": "USDC", + "amount": 25, + "clientRequestId": "strategy-run-2026-05-06-007" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Relay accepted and returned a typed result or typed error." + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "RelayRequest": { + "type": "object", + "required": [ + "type", + "toolName", + "payload" + ], + "properties": { + "type": { + "type": "string" + }, + "toolName": { + "type": "string" + }, + "requestId": { + "type": "string", + "description": "Idempotency key supplied by the caller." + }, + "payload": { + "type": "object" + } + }, + "additionalProperties": false + } + } + } +} diff --git a/examples/agentic-perps-workflow/README.md b/examples/agentic-perps-workflow/README.md new file mode 100644 index 0000000..db0ce65 --- /dev/null +++ b/examples/agentic-perps-workflow/README.md @@ -0,0 +1,14 @@ +# Agentic Perps Workflow + +End-to-end no-secret workflow for an MCP or code agent: + +1. Establish a Toreva/Swig master authority and Pacifica child perps capability. +2. Query venues. +3. Simulate. +4. Open a routed long. +5. Query position state. +6. Close the position. + +The example never asks for private keys, seed phrases, API secrets, or raw +signer material. Real-funds execution still requires Toreva approval, +configured signer boundaries, venue availability, funding, and policy caps. diff --git a/examples/agentic-perps-workflow/index.ts b/examples/agentic-perps-workflow/index.ts new file mode 100644 index 0000000..4b5c861 --- /dev/null +++ b/examples/agentic-perps-workflow/index.ts @@ -0,0 +1,85 @@ +import { PerpsApi, TorevaClient } from '@toreva/sdk'; + +const walletAddress = process.env.TOREVA_HUMAN_WALLET ?? 'ExampleWallet111111111111111111111111111111111'; + +const client = new TorevaClient({ + relayUrl: process.env.RELAY_URL ?? 'https://gateway.toreva.com', + relayAuthToken: process.env.TOREVA_AUTH_TOKEN ?? '' +}); + +const perps = new PerpsApi(client); + +async function main() { + await client.relay({ + type: 'intent.establish', + toolName: 'toreva_establish', + requestId: 'example-establish-perps-agent', + payload: { + walletAddress, + prompt: 'Create a capped Toreva perps agent for SOL with Pacifica available as a child capability.', + agent_authority: { + delegation_provider: 'swig', + network: 'solana', + signer_kind: 'delegated_authority', + policy_id: 'perps_agent_v1' + }, + capabilities: [ + { + capability_type: 'perps', + delegation_provider: 'venue_api', + network: 'solana', + venue: 'pacifica', + execution_adapter: 'pacifica', + signer_kind: 'venue_api_agent', + guardrails: { + max_notional_usd: 500, + max_leverage: 1.5, + markets: ['SOL-PERP'], + collateral_tokens: ['USDC'], + requires_human_approval_for_funding: true + } + } + ] + } + }); + + await perps.call('toreva_perps_query_venues', {}); + + await perps.call('toreva_perps_simulate', { + walletAddress, + token: 'SOL', + direction: 'long', + sizeUsd: 180, + leverage: 1.2, + collateralToken: 'USDC', + collateralAmount: 150 + }); + + await perps.call('toreva_perps_long', { + walletAddress, + token: 'SOL', + sizeUsd: 180, + leverage: 1.2, + collateralToken: 'USDC', + collateralAmount: 150, + maxSlippageBps: 50, + clientRequestId: 'example-agentic-perps-workflow-open' + }); + + await perps.call('toreva_perps_query_position', { + walletAddress, + venue: 'pacifica' + }); + + await perps.call('toreva_perps_close', { + walletAddress, + venue: 'pacifica', + positionId: '', + token: 'SOL', + side: 'long', + maxSlippageBps: 50, + clientRequestId: 'example-agentic-perps-workflow-close' + }); +} + +void main(); diff --git a/examples/open-perps-position/README.md b/examples/open-perps-position/README.md index d615066..2b26945 100644 --- a/examples/open-perps-position/README.md +++ b/examples/open-perps-position/README.md @@ -1,3 +1,13 @@ -# open perps position +# Open Perps Position Execute a perpetual futures position using `@toreva/sdk`. + +This example uses the Gateway MCP field contract: + +- `walletAddress` for the human/root wallet +- `token` for the perps market token +- `sizeUsd` for position notional +- `collateralToken` and `collateralAmount` for collateral + +Omit `venue` to let Toreva route by estimated all-in cost. Set `venue` only +when the user or policy explicitly chooses a venue. diff --git a/examples/open-perps-position/index.ts b/examples/open-perps-position/index.ts index 3eff9a3..6d99727 100644 --- a/examples/open-perps-position/index.ts +++ b/examples/open-perps-position/index.ts @@ -2,14 +2,16 @@ import { PerpsApi, TorevaClient } from '@toreva/sdk'; const client = new TorevaClient({ relayUrl: process.env.RELAY_URL ?? 'https://gateway.toreva.com', - relayAuthToken: process.env.RELAY_AUTH_TOKEN ?? '' + relayAuthToken: process.env.TOREVA_AUTH_TOKEN ?? '' }); const perps = new PerpsApi(client); void perps.call('toreva_perps_long', { - wallet: 'ExampleWallet111111111111111111111111111111111', - market: 'SOL-PERP', - notionalUsd: 100, - venue: 'drift' + walletAddress: 'ExampleWallet111111111111111111111111111111111', + token: 'SOL', + sizeUsd: 100, + leverage: 1.2, + collateralToken: 'USDC', + collateralAmount: 84 }); diff --git a/packages/mcp/src/__tests__/inputSchemas.test.ts b/packages/mcp/src/__tests__/inputSchemas.test.ts index 7a8330f..1bd58f6 100644 --- a/packages/mcp/src/__tests__/inputSchemas.test.ts +++ b/packages/mcp/src/__tests__/inputSchemas.test.ts @@ -21,6 +21,14 @@ function findTool(tools: Tool[], name: string): Tool { } describe('inputSchemas — typed per tool', () => { + it('toreva_establish requires walletAddress and accepts child capabilities', async () => { + const tools = await listTools(); + const schema = findTool(tools, 'toreva_establish').inputSchema as any; + expect(schema.required).toEqual(['walletAddress']); + expect(schema.properties).toHaveProperty('capabilities'); + expect(schema.properties).toHaveProperty('agent_authority'); + }); + it('toreva_scan inputSchema has required=[wallet, prompt]', async () => { const tools = await listTools(); const schema = findTool(tools, 'toreva_scan').inputSchema; @@ -28,18 +36,26 @@ describe('inputSchemas — typed per tool', () => { expect(schema.required).toHaveLength(2); }); - it('toreva_perps_long inputSchema has required=[wallet, market, notionalUsd]', async () => { + it('toreva_perps_long inputSchema matches Gateway open input contract', async () => { const tools = await listTools(); const schema = findTool(tools, 'toreva_perps_long').inputSchema; - expect(schema.required).toEqual(expect.arrayContaining(['wallet', 'market', 'notionalUsd'])); - expect(schema.required).toHaveLength(3); + expect(schema.required).toEqual( + expect.arrayContaining([ + 'walletAddress', + 'token', + 'sizeUsd', + 'leverage', + 'collateralToken', + 'collateralAmount' + ]) + ); }); - it('toreva_perps_long inputSchema has leverage as optional (not in required)', async () => { + it('toreva_perps_long inputSchema has venue as optional', async () => { const tools = await listTools(); const schema = findTool(tools, 'toreva_perps_long').inputSchema as any; - expect(schema.required).not.toContain('leverage'); - expect(schema.properties).toHaveProperty('leverage'); + expect(schema.required).not.toContain('venue'); + expect(schema.properties).toHaveProperty('venue'); }); it('toreva_perps_query_venues has no required properties', async () => { diff --git a/packages/mcp/src/__tests__/newFamilies.test.ts b/packages/mcp/src/__tests__/newFamilies.test.ts index f132f7e..a4c101b 100644 --- a/packages/mcp/src/__tests__/newFamilies.test.ts +++ b/packages/mcp/src/__tests__/newFamilies.test.ts @@ -68,8 +68,8 @@ describe('staking tools registration', () => { }); describe('total tool count', () => { - it('registers exactly 24 tools (20 existing + 4 new)', async () => { + it('registers exactly 25 tools including establish, perps, wallet, and staking families', async () => { const tools = await listTools(); - expect(tools).toHaveLength(24); + expect(tools).toHaveLength(25); }); }); diff --git a/packages/mcp/src/__tests__/server.test.ts b/packages/mcp/src/__tests__/server.test.ts index 613ded7..9b253c9 100644 --- a/packages/mcp/src/__tests__/server.test.ts +++ b/packages/mcp/src/__tests__/server.test.ts @@ -15,9 +15,9 @@ describe('createServer', () => { throw new Error('Could not find tools/list handler on server'); } - it('registers exactly 24 tools', async () => { + it('registers exactly 25 tools', async () => { const tools = await listTools(); - expect(tools).toHaveLength(24); + expect(tools).toHaveLength(25); }); it('all tool names start with toreva_', async () => { diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index 68fc3b8..8c1fd07 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -29,6 +29,7 @@ const allSchemas: Record = { }; const toolDescriptions: Array<{ name: string; description: string }> = [ + { name: 'toreva_establish', description: 'Establish a Toreva delegated agent authority for a human wallet. Use it to attach Swig master authority and venue-specific child capabilities, such as a Pacifica API agent wallet for perps, under user-approved policy. Non-custodial. Every setup action should be receipted.' }, { name: 'toreva_scan', description: 'Scan a Solana wallet to find idle capital, yield opportunities, and risk flags. Returns actionable recommendations ranked by expected return. Non-custodial.' }, { name: 'toreva_simulate', description: 'Simulate execution of a strategy without committing funds. Returns projected returns, fees, venue selection, and risk assessment. Use before toreva_execute to preview what will happen.' }, { name: 'toreva_execute', description: 'Execute a strategy on Solana. Supports earning yield on USDC, staking SOL, rebalancing portfolios, and more. Non-custodial. Every action receipted.' }, diff --git a/packages/types/src/__tests__/intents.test.ts b/packages/types/src/__tests__/intents.test.ts index 2f546bf..e2d2df5 100644 --- a/packages/types/src/__tests__/intents.test.ts +++ b/packages/types/src/__tests__/intents.test.ts @@ -2,6 +2,22 @@ import { describe, it, expect } from 'vitest'; import { intentToolSchemas, INTENT_RELAY_TYPES } from '../intents.js'; describe('intentToolSchemas', () => { + describe('toreva_establish', () => { + it('uses Gateway MCP walletAddress naming', () => { + const result = intentToolSchemas.toreva_establish.safeParse({ + walletAddress: '11111111111111111111111111111111', + }); + expect(result.success).toBe(true); + }); + + it('rejects the old wallet alias on establish', () => { + const result = intentToolSchemas.toreva_establish.safeParse({ + wallet: '11111111111111111111111111111111', + }); + expect(result.success).toBe(false); + }); + }); + describe('toreva_scan', () => { it('parses valid input', () => { const result = intentToolSchemas.toreva_scan.safeParse({ @@ -29,6 +45,10 @@ describe('intentToolSchemas', () => { }); describe('INTENT_RELAY_TYPES', () => { + it('maps toreva_establish to intent.establish', () => { + expect(INTENT_RELAY_TYPES.toreva_establish).toBe('intent.establish'); + }); + it('maps toreva_scan to intent.scan', () => { expect(INTENT_RELAY_TYPES.toreva_scan).toBe('intent.scan'); }); @@ -37,10 +57,10 @@ describe('INTENT_RELAY_TYPES', () => { expect(INTENT_RELAY_TYPES.toreva_execute).toBe('intent.execute'); }); - it('all 5 intent tools have corresponding relay types', () => { + it('all 6 intent tools have corresponding relay types', () => { const schemaKeys = Object.keys(intentToolSchemas).sort(); const relayKeys = Object.keys(INTENT_RELAY_TYPES).sort(); expect(schemaKeys).toEqual(relayKeys); - expect(schemaKeys).toHaveLength(5); + expect(schemaKeys).toHaveLength(6); }); }); diff --git a/packages/types/src/__tests__/perps.test.ts b/packages/types/src/__tests__/perps.test.ts index e0f69cd..13cec30 100644 --- a/packages/types/src/__tests__/perps.test.ts +++ b/packages/types/src/__tests__/perps.test.ts @@ -5,28 +5,56 @@ describe('perpsToolSchemas', () => { describe('toreva_perps_long', () => { it('parses valid input', () => { const result = perpsToolSchemas.toreva_perps_long.safeParse({ - wallet: 'abc', - market: 'SOL-PERP', - notionalUsd: 1000, + walletAddress: '11111111111111111111111111111111', + token: 'SOL', + sizeUsd: 1000, + leverage: 2, + collateralToken: 'USDC', + collateralAmount: 500, }); expect(result.success).toBe(true); }); - it('rejects negative notionalUsd', () => { + it('rejects negative sizeUsd', () => { const result = perpsToolSchemas.toreva_perps_long.safeParse({ - wallet: 'abc', - market: 'SOL-PERP', - notionalUsd: -100, + walletAddress: '11111111111111111111111111111111', + token: 'SOL', + sizeUsd: -100, + leverage: 2, + collateralToken: 'USDC', + collateralAmount: 500, }); expect(result.success).toBe(false); }); it('rejects leverage > 101', () => { const result = perpsToolSchemas.toreva_perps_long.safeParse({ - wallet: 'abc', + walletAddress: '11111111111111111111111111111111', + token: 'SOL', + sizeUsd: 1000, + leverage: 102, + collateralToken: 'USDC', + collateralAmount: 500, + }); + expect(result.success).toBe(false); + }); + + it('rejects the old Kit wallet/market/notional aliases', () => { + const result = perpsToolSchemas.toreva_perps_long.safeParse({ + wallet: '11111111111111111111111111111111', market: 'SOL-PERP', notionalUsd: 1000, - leverage: 102, + leverage: 2, + }); + expect(result.success).toBe(false); + }); + }); + + describe('toreva_perps_close', () => { + it('requires the venue from the position venue', () => { + const result = perpsToolSchemas.toreva_perps_close.safeParse({ + walletAddress: '11111111111111111111111111111111', + positionId: 'position-1', }); expect(result.success).toBe(false); }); diff --git a/packages/types/src/__tests__/publicDocs.test.ts b/packages/types/src/__tests__/publicDocs.test.ts new file mode 100644 index 0000000..485fbf3 --- /dev/null +++ b/packages/types/src/__tests__/publicDocs.test.ts @@ -0,0 +1,86 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { INTENT_RELAY_TYPES, intentToolSchemas } from '../intents.js'; +import { PERPS_RELAY_TYPES, perpsToolSchemas } from '../perps.js'; + +const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..'); + +function readRootFile(path: string) { + return readFileSync(resolve(rootDir, path), 'utf8'); +} + +function collectKeys(value: unknown): string[] { + if (!value || typeof value !== 'object') { + return []; + } + + if (Array.isArray(value)) { + return value.flatMap(collectKeys); + } + + return Object.entries(value).flatMap(([key, child]) => [key, ...collectKeys(child)]); +} + +describe('public perps docs', () => { + it('OpenAPI relay examples validate against the exported tool schemas', () => { + const doc = JSON.parse(readRootFile('docs/toreva-perps.openapi.json')); + const examples = doc.paths['/relay'].post.requestBody.content['application/json'].examples; + + expect(Object.keys(examples)).toEqual(expect.arrayContaining([ + 'establish', + 'query_venues', + 'simulate', + 'perps_long', + 'perps_short', + 'close', + 'cancel_order', + 'funding_settle', + 'add_margin', + 'remove_margin' + ])); + + for (const example of Object.values(examples)) { + const value = (example as { value: { toolName: string; type: string; payload: unknown } }).value; + + if (value.toolName in intentToolSchemas) { + const toolName = value.toolName as keyof typeof intentToolSchemas; + expect(value.type).toBe(INTENT_RELAY_TYPES[toolName]); + expect(intentToolSchemas[toolName].safeParse(value.payload).success).toBe(true); + continue; + } + + const toolName = value.toolName as keyof typeof perpsToolSchemas; + expect(value.type).toBe(PERPS_RELAY_TYPES[toolName]); + expect(perpsToolSchemas[toolName].safeParse(value.payload).success).toBe(true); + } + }); + + it('public examples do not publish legacy perps aliases', () => { + const files = [ + readRootFile('examples/open-perps-position/index.ts'), + readRootFile('examples/agentic-perps-workflow/index.ts'), + JSON.stringify(JSON.parse(readRootFile('docs/toreva-perps.openapi.json'))) + ].join('\n'); + + expect(files).not.toMatch(/\bwallet\s*:/); + expect(files).not.toMatch(/\bmarket\s*:/); + expect(files).not.toMatch(/\bnotionalUsd\s*:/); + }); + + it('public relay examples do not include raw signer or secret material keys', () => { + const doc = JSON.parse(readRootFile('docs/toreva-perps.openapi.json')); + const examples = doc.paths['/relay'].post.requestBody.content['application/json'].examples; + const keys = Object.values(examples).flatMap((example) => collectKeys((example as { value: unknown }).value)); + + expect(keys.map((key) => key.toLowerCase())).not.toEqual(expect.arrayContaining([ + 'privatekey', + 'private_key', + 'seedphrase', + 'seed_phrase', + 'apisecret', + 'api_secret' + ])); + }); +}); diff --git a/packages/types/src/intents.ts b/packages/types/src/intents.ts index 39e18cc..de11eb5 100644 --- a/packages/types/src/intents.ts +++ b/packages/types/src/intents.ts @@ -5,7 +5,43 @@ const baseIntentInputSchema = z.object({ prompt: z.string().min(1) }); +const walletAddressSchema = z.string().min(32).max(88); +const signerKindSchema = z.enum([ + 'delegated_authority', + 'human_wallet', + 'venue_api_agent', + 'mpc', + 'multisig', + 'smart_account', + 'exchange_api_agent', + 'simulated' +]); + +const establishCapabilitySchema = z.object({ + capability_type: z.string().min(1), + delegation_provider: z.string().min(1).optional(), + network: z.string().min(1).optional(), + venue: z.string().min(1).optional(), + execution_adapter: z.string().min(1).optional(), + signer_kind: signerKindSchema.optional(), + guardrails: z.record(z.unknown()).optional(), + provider_metadata: z.record(z.unknown()).optional() +}); + export const intentToolSchemas = { + toreva_establish: z.object({ + walletAddress: walletAddressSchema, + prompt: z.string().min(1).optional(), + agent_authority: z.object({ + delegation_provider: z.string().min(1).default('swig'), + network: z.string().min(1).default('solana'), + signer_kind: signerKindSchema.optional(), + policy_id: z.string().optional(), + policy_hash: z.string().optional(), + provider_metadata: z.record(z.unknown()).optional() + }).optional(), + capabilities: z.array(establishCapabilitySchema).optional() + }), toreva_scan: baseIntentInputSchema, toreva_simulate: baseIntentInputSchema, toreva_execute: baseIntentInputSchema, @@ -16,6 +52,7 @@ export const intentToolSchemas = { } as const; export const INTENT_RELAY_TYPES = { + toreva_establish: 'intent.establish', toreva_scan: 'intent.scan', toreva_simulate: 'intent.simulate', toreva_execute: 'intent.execute', diff --git a/packages/types/src/perps.ts b/packages/types/src/perps.ts index 9869a05..23f1bf2 100644 --- a/packages/types/src/perps.ts +++ b/packages/types/src/perps.ts @@ -2,40 +2,62 @@ import { z } from 'zod'; export const venueSchema = z.enum(['drift', 'jupiter-perps', 'pacifica', 'flash']); -const walletSchema = z.object({ wallet: z.string() }); -const marketSchema = z.object({ market: z.string() }); +const walletAddressSchema = z.object({ walletAddress: z.string().min(32).max(88) }); +const tokenSchema = z.object({ token: z.string().min(1) }); const venueOptionalSchema = z.object({ venue: venueSchema.optional() }); +const venueRequiredSchema = z.object({ venue: venueSchema }); +const openBaseSchema = walletAddressSchema.merge(tokenSchema).merge(venueOptionalSchema).extend({ + sizeUsd: z.number().positive(), + leverage: z.number().min(1).max(101), + collateralToken: z.string().min(1), + collateralAmount: z.number().positive(), + agentWalletAddress: z.string().min(32).max(88).optional(), + maxSlippageBps: z.number().int().min(0).max(1000).optional(), + stopLoss: z.number().positive().optional(), + takeProfit: z.number().positive().optional(), + marginMode: z.enum(['cross', 'isolated']).optional(), + builderCode: z.string().regex(/^[A-Za-z0-9]{1,16}$/).optional(), + clientRequestId: z.string().min(1).max(128).optional() +}); +const positionSchema = walletAddressSchema.merge(venueRequiredSchema).extend({ + positionId: z.string(), + orderId: z.string().optional(), + token: z.string().min(1).optional(), + side: z.enum(['long', 'short']).optional(), + agentWalletAddress: z.string().min(32).max(88).optional(), + maxSlippageBps: z.number().int().min(0).max(1000).optional(), + clientRequestId: z.string().min(1).max(128).optional(), + expiryWindowMs: z.number().int().min(1000).max(120000).optional() +}); export const perpsToolSchemas = { - toreva_perps_long: walletSchema.merge(marketSchema).merge(venueOptionalSchema).extend({ - notionalUsd: z.number().positive(), - leverage: z.number().positive().max(101).optional() + toreva_perps_long: openBaseSchema, + toreva_perps_short: openBaseSchema, + toreva_perps_close: positionSchema, + toreva_perps_add_margin: positionSchema.extend({ + amount: z.number().positive(), + token: z.string().min(1) }), - toreva_perps_short: walletSchema.merge(marketSchema).merge(venueOptionalSchema).extend({ - notionalUsd: z.number().positive(), - leverage: z.number().positive().max(101).optional() + toreva_perps_remove_margin: positionSchema.extend({ + amount: z.number().positive(), + token: z.string().min(1) }), - toreva_perps_close: walletSchema.merge(marketSchema).merge(venueOptionalSchema), - toreva_perps_add_margin: walletSchema.merge(marketSchema).merge(venueOptionalSchema).extend({ - amountUsd: z.number().positive() - }), - toreva_perps_remove_margin: walletSchema.merge(marketSchema).merge(venueOptionalSchema).extend({ - amountUsd: z.number().positive() - }), - toreva_perps_cancel_order: walletSchema.merge(venueOptionalSchema).extend({ - orderId: z.string() - }), - toreva_perps_funding_settle: walletSchema.merge(marketSchema).merge(venueOptionalSchema), - toreva_perps_query_position: walletSchema.merge(marketSchema).merge(venueOptionalSchema), - toreva_perps_query_funding: walletSchema.merge(marketSchema).merge(venueOptionalSchema), + toreva_perps_cancel_order: positionSchema, + toreva_perps_funding_settle: positionSchema, + toreva_perps_query_position: walletAddressSchema.merge(venueOptionalSchema), + toreva_perps_query_funding: tokenSchema, toreva_perps_query_venues: z.object({}), toreva_perps_query_markets: venueOptionalSchema, - toreva_perps_simulate: walletSchema.merge(marketSchema).merge(venueOptionalSchema).extend({ - side: z.enum(['long', 'short']), - notionalUsd: z.number().positive() + toreva_perps_simulate: walletAddressSchema.partial().merge(tokenSchema).extend({ + direction: z.enum(['long', 'short']), + sizeUsd: z.number().positive(), + leverage: z.number().min(1).max(101), + collateralToken: z.string().min(1), + collateralAmount: z.number().positive() }), - toreva_perps_explain: walletSchema.merge(marketSchema).merge(venueOptionalSchema).extend({ - context: z.string().optional() + toreva_perps_explain: walletAddressSchema.partial().merge(venueOptionalSchema).extend({ + positionId: z.string().optional(), + txSignature: z.string().optional() }) } as const; diff --git a/skills/toreva-establish-perps-agent.md b/skills/toreva-establish-perps-agent.md new file mode 100644 index 0000000..3536e3a --- /dev/null +++ b/skills/toreva-establish-perps-agent.md @@ -0,0 +1,26 @@ +# toreva-establish-perps-agent + +Non-custodial execution primitives for Solana. Best-execution routing across Jupiter Perps, Pacifica, Drift, and Flash Trade. 1 bps to open. Everything else is free. + +Use this before perps execution when an agent needs a delegated authority graph. + +Recommended pattern: + +```text +human wallet + -> Toreva/Swig master authority + -> perps child capability + -> Pacifica API agent wallet if Pacifica is selected +``` + +The human wallet remains root owner. The Pacifica API agent wallet is a +venue-specific child signer for Pacifica REST orders. It is governed by Toreva +policy, approvals, receipts, monitoring, and revocation. + +For open-long/open-short, omit `venue` unless the user explicitly asks for one. +Toreva will compare enabled venues and route by estimated all-in cost. + +Use Gateway MCP fields: `walletAddress` for the human wallet, and for opens +use `token`, `sizeUsd`, `leverage`, `collateralToken`, and `collateralAmount`. + +Execution only — not financial advice. diff --git a/skills/toreva-perps-long.md b/skills/toreva-perps-long.md index f2dbe19..89efbdc 100644 --- a/skills/toreva-perps-long.md +++ b/skills/toreva-perps-long.md @@ -2,4 +2,11 @@ Non-custodial execution primitives for Solana. Best-execution routing across Jupiter Perps, Pacifica, Drift, and Flash Trade. 1 bps to open. Everything else is free. +Omit `venue` for best-execution routing. Set `venue` only when the user +explicitly chooses a venue. If Pacifica is selected, Toreva uses the +policy-bound Pacifica API agent wallet attached through `toreva_establish`. + +Use Gateway MCP fields: `walletAddress`, `token`, `sizeUsd`, `leverage`, +`collateralToken`, and `collateralAmount`. + Execution only — not financial advice. diff --git a/skills/toreva-perps-short.md b/skills/toreva-perps-short.md index ad94fba..9a295d7 100644 --- a/skills/toreva-perps-short.md +++ b/skills/toreva-perps-short.md @@ -2,4 +2,11 @@ Non-custodial execution primitives for Solana. Best-execution routing across Jupiter Perps, Pacifica, Drift, and Flash Trade. 1 bps to open. Everything else is free. +Omit `venue` for best-execution routing. Set `venue` only when the user +explicitly chooses a venue. If Pacifica is selected, Toreva uses the +policy-bound Pacifica API agent wallet attached through `toreva_establish`. + +Use Gateway MCP fields: `walletAddress`, `token`, `sizeUsd`, `leverage`, +`collateralToken`, and `collateralAmount`. + Execution only — not financial advice.