# API Endpoints Source: https://docs.usewraith.xyz/api-reference/endpoints Full HTTP API reference for the managed platform The TEE server exposes a REST API. The SDK's `Wraith` and `WraithAgent` classes wrap these endpoints — most developers don't call them directly. ## Authentication All requests require an API key: ``` Authorization: Bearer wraith_live_abc123 ``` Optional BYOM headers: ``` X-AI-Provider: openai X-AI-Key: sk-... ``` *** ## Agent Endpoints ### Create Agent ``` POST /agent/create ``` Create a new agent with a `.wraith` name. **Request:** ```typescript theme={null} { name: string; // "alice" (becomes alice.wraith) chain: string; // "horizen" | "stellar" | "ethereum" | ... wallet: string; // owner wallet address signature: string; // EIP-191 or ed25519 signature message?: string; // signed message (for verification) } ``` **Response:** ```typescript theme={null} { id: string; // UUID name: string; // "alice" chain: string; // "horizen" address: string; // agent's on-chain address metaAddress: string; // stealth meta-address } ``` ### List Agents ``` GET /agents ``` Returns all agents for the authenticated API key. **Response:** `AgentInfo[]` ### Get Agent by ID ``` GET /agent/:id ``` **Response:** `AgentInfo` ### Get Agent by Name ``` GET /agent/info/:name ``` Look up an agent by `.wraith` name. **Response:** `AgentInfo` ### Get Agent by Wallet ``` GET /agent/wallet/:address ``` Look up an agent by owner wallet address. **Response:** `AgentInfo` ### Get Agent Status ``` GET /agent/:id/status ``` Returns balance, pending invoices, and other stats. **Response:** ```typescript theme={null} { balance: string; tokens: Record; pendingInvoices: number; address: string; metaAddress: string; chain: string; } ``` ### Export Private Key ``` POST /agent/:id/export ``` Export the agent's private key. Requires a fresh wallet signature. **Request:** ```typescript theme={null} { signature: string; // fresh signature from owner wallet message: string; // signed message } ``` **Response:** ```typescript theme={null} { secret: string; // "0x..." — the agent's private key } ``` *** ## Chat ### Send Message ``` POST /agent/:id/chat ``` Send a natural language message to the AI agent. **Request:** ```typescript theme={null} { message: string; conversationId?: string; // continue existing conversation } ``` **Response:** ```typescript theme={null} { response: string; // agent's text reply toolCalls?: ToolCall[]; // tools the agent executed conversationId: string; // conversation ID for continuity } ``` ```typescript theme={null} interface ToolCall { name: string; // "send_payment", "scan_payments", etc. status: string; // "success" or "error" detail?: string; // JSON string with tool result } ``` *** ## Conversations ### List Conversations ``` GET /agent/:id/conversations ``` **Response:** `Conversation[]` ```typescript theme={null} interface Conversation { id: string; title: string; createdAt: string; updatedAt: string; } ``` ### Create Conversation ``` POST /agent/:id/conversations ``` **Response:** `Conversation` ### Get Messages ``` GET /agent/:id/conversations/:convId/messages ``` **Response:** ```typescript theme={null} Array<{ role: "user" | "agent" | "tool" | "system"; text: string; createdAt: string; }> ``` ### Delete Conversation ``` DELETE /agent/:id/conversations/:convId ``` *** ## Invoices ### Get Invoice ``` GET /invoice/:id ``` **Response:** ```typescript theme={null} { id: string; agentName: string; amount: string; asset: string; memo: string; status: "pending" | "paid"; txHash: string | null; paymentLink: string; createdAt: string; } ``` ### Mark Invoice Paid ``` POST /invoice/:id/paid ``` Idempotent — calling multiple times doesn't create duplicate notifications. *** ## Notifications ### List Notifications ``` GET /agent/:id/notifications ``` **Response:** ```typescript theme={null} { notifications: Notification[]; unreadCount: number; } ``` ```typescript theme={null} interface Notification { id: number; type: string; title: string; body: string; read: boolean; createdAt: string; } ``` ### Mark All Read ``` POST /agent/:id/notifications/read ``` ### Clear All ``` DELETE /agent/:id/notifications ``` *** ## TEE ### Health Check ``` GET /health ``` **Response:** ```typescript theme={null} { status: "ok"; } ``` ### TEE Info ``` GET /tee/info ``` Returns TEE environment information. ### Remote Attestation ``` GET /tee/attest/:agentId ``` Returns cryptographic proof that the TEE is running authentic code and the agent's keys were derived correctly. *** ## Error Responses All errors return JSON with a `message` field: ```typescript theme={null} { message: string; statusCode: number; } ``` | Status Code | Meaning | | ----------- | ----------------------------------------------- | | 400 | Bad request (missing params, invalid signature) | | 401 | Unauthorized (invalid API key) | | 404 | Agent/invoice/conversation not found | | 409 | Conflict (name already taken) | | 500 | Server error | ### Example ```typescript theme={null} // 400 Bad Request { "message": "Name must be 3-32 characters, lowercase alphanumeric and hyphens only", "statusCode": 400 } // 401 Unauthorized { "message": "Invalid API key", "statusCode": 401 } // 409 Conflict { "message": "Name 'alice' is already registered", "statusCode": 409 } ``` # TypeScript Types Source: https://docs.usewraith.xyz/api-reference/types Type definitions and interfaces All types exported from `@wraith-protocol/sdk`. ## Agent Client Types Exported from `@wraith-protocol/sdk`: ```typescript theme={null} import type { WraithConfig, AgentConfig, AgentInfo, ChatResponse, ToolCall, Balance, Payment, Invoice, Schedule, TxResult, PrivacyReport, Notification, Conversation, } from "@wraith-protocol/sdk"; ``` ### `Chain` ```typescript theme={null} enum Chain { Horizen = "horizen", Ethereum = "ethereum", Polygon = "polygon", Base = "base", Stellar = "stellar", Solana = "solana", All = "all", } ``` ### `WraithConfig` ```typescript theme={null} interface WraithConfig { apiKey: string; baseUrl?: string; ai?: { provider: "gemini" | "openai" | "claude"; apiKey: string; }; } ``` ### `AgentConfig` ```typescript theme={null} interface AgentConfig { name: string; chain: Chain | Chain[]; wallet: string; signature: string; message?: string; } ``` ### `AgentInfo` ```typescript theme={null} interface AgentInfo { id: string; name: string; chains: Chain[]; addresses: Record; metaAddresses: Record; } ``` ### `ChatResponse` ```typescript theme={null} interface ChatResponse { response: string; toolCalls?: ToolCall[]; conversationId: string; } ``` ### `ToolCall` ```typescript theme={null} interface ToolCall { name: string; status: string; detail?: string; } ``` ### `Balance` ```typescript theme={null} interface Balance { native: string; tokens: Record; } ``` ### `Payment` ```typescript theme={null} interface Payment { stealthAddress: string; balance: string; ephemeralPubKey: string; } ``` ### `Invoice` ```typescript theme={null} interface Invoice { id: string; agentName: string; amount: string; asset: string; memo: string; status: "pending" | "paid"; txHash: string | null; paymentLink: string; createdAt: string; } ``` ### `Schedule` ```typescript theme={null} interface Schedule { id: string; recipient: string; amount: string; asset: string; interval: "daily" | "weekly" | "monthly"; status: "active" | "paused" | "cancelled"; nextRun: string; } ``` ### `TxResult` ```typescript theme={null} interface TxResult { txHash: string; txLink: string; } ``` ### `PrivacyReport` ```typescript theme={null} interface PrivacyReport { score: number; issues: Array<{ severity: "info" | "low" | "medium" | "high" | "critical"; issue: string; recommendation: string; }>; bestPractices: string[]; } ``` ### `Notification` ```typescript theme={null} interface Notification { id: number; type: string; title: string; body: string; read: boolean; createdAt: string; } ``` ### `Conversation` ```typescript theme={null} interface Conversation { id: string; title: string; createdAt: string; updatedAt: string; } ``` *** ## EVM Chain Types Exported from `@wraith-protocol/sdk/chains/evm`: ```typescript theme={null} import type { HexString, StealthKeys, StealthMetaAddress, GeneratedStealthAddress, Announcement, MatchedAnnouncement, } from "@wraith-protocol/sdk/chains/evm"; ``` ### `HexString` ```typescript theme={null} type HexString = `0x${string}`; ``` ### `StealthKeys` ```typescript theme={null} interface StealthKeys { spendingKey: HexString; // 32-byte private key viewingKey: HexString; // 32-byte private key spendingPubKey: HexString; // 33-byte compressed public key viewingPubKey: HexString; // 33-byte compressed public key } ``` ### `StealthMetaAddress` ```typescript theme={null} interface StealthMetaAddress { spendingPubKey: HexString; // 33-byte compressed public key viewingPubKey: HexString; // 33-byte compressed public key } ``` ### `GeneratedStealthAddress` ```typescript theme={null} interface GeneratedStealthAddress { stealthAddress: HexString; // 20-byte EVM address ephemeralPubKey: HexString; // 33-byte compressed public key viewTag: number; // 0-255 } ``` ### `Announcement` ```typescript theme={null} interface Announcement { schemeId: bigint; stealthAddress: HexString; caller: HexString; ephemeralPubKey: HexString; metadata: HexString; // first byte is view tag } ``` ### `MatchedAnnouncement` ```typescript theme={null} interface MatchedAnnouncement extends Announcement { stealthPrivateKey: HexString; } ``` *** ## Stellar Chain Types Exported from `@wraith-protocol/sdk/chains/stellar`: ```typescript theme={null} import type { HexString, StealthKeys, StealthMetaAddress, GeneratedStealthAddress, Announcement, MatchedAnnouncement, } from "@wraith-protocol/sdk/chains/stellar"; ``` ### `StealthKeys` ```typescript theme={null} interface StealthKeys { spendingKey: Uint8Array; // 32-byte seed spendingScalar: bigint; // clamped scalar from SHA-512(seed) viewingKey: Uint8Array; // 32-byte seed viewingScalar: bigint; // clamped scalar spendingPubKey: Uint8Array; // 32-byte ed25519 public key viewingPubKey: Uint8Array; // 32-byte ed25519 public key } ``` ### `GeneratedStealthAddress` ```typescript theme={null} interface GeneratedStealthAddress { stealthAddress: string; // Stellar G... address ephemeralPubKey: Uint8Array; // 32-byte ed25519 public key viewTag: number; // 0-255 } ``` ### `Announcement` ```typescript theme={null} interface Announcement { schemeId: number; stealthAddress: string; // G... address caller: string; // G... address ephemeralPubKey: string; // hex-encoded 32 bytes metadata: string; // hex-encoded, first byte = view tag } ``` ### `MatchedAnnouncement` ```typescript theme={null} interface MatchedAnnouncement extends Announcement { stealthPrivateScalar: bigint; stealthPubKeyBytes: Uint8Array; } ``` *** ## Chain Connector Types Internal types used by the TEE server. Documented here for developers building custom chain connectors. ### `ChainConnector` ```typescript theme={null} interface ChainConnector { readonly chain: string; readonly nativeAsset: string; readonly addressFormat: "evm" | "stellar" | "solana"; deriveKeys(seed: Uint8Array): Promise; sendPayment(params: SendPaymentParams): Promise; scanPayments(stealthKeys: ChainStealthKeys): Promise; getBalance(address: string): Promise; withdraw(params: WithdrawParams): Promise; withdrawAll(params: WithdrawAllParams): Promise; registerName(name: string, stealthKeys: ChainStealthKeys): Promise; resolveName(name: string): Promise; fundWallet(address: string): Promise; getExplorerUrl(type: "tx" | "address", value: string): string; } ``` ### `DerivedKeys` ```typescript theme={null} interface DerivedKeys { address: string; stealthKeys: ChainStealthKeys; metaAddress: string; } ``` ### `ChainBalance` ```typescript theme={null} interface ChainBalance { native: string; tokens: Record; } ``` ### `DetectedPayment` ```typescript theme={null} interface DetectedPayment { stealthAddress: string; balance: string; ephemeralPubKey: string; } ``` ### `WithdrawAllResult` ```typescript theme={null} interface WithdrawAllResult { results: Array<{ address: string } & (TxResult | { error: string })>; count: number; totalWithdrawn: string; } ``` ### `ResolvedName` ```typescript theme={null} interface ResolvedName { name: string; metaAddress: string; address?: string; } ``` # Chain Connectors Source: https://docs.usewraith.xyz/architecture/chain-connectors ChainConnector interface and adding new chains Chain connectors are the abstraction layer between Wraith's chain-agnostic agent core and blockchain-specific logic. Each connector implements a standard interface, allowing the same agent engine to operate across any supported chain. ## Interface Every chain connector implements `ChainConnector`: ```typescript theme={null} interface ChainConnector { readonly chain: string; readonly nativeAsset: string; readonly addressFormat: "evm" | "stellar" | "solana" | "ckb"; deriveKeys(seed: Uint8Array): Promise; sendPayment(params: SendPaymentParams): Promise; scanPayments(stealthKeys: ChainStealthKeys): Promise; getBalance(address: string): Promise; withdraw(params: WithdrawParams): Promise; withdrawAll(params: WithdrawAllParams): Promise; registerName(name: string, stealthKeys: ChainStealthKeys): Promise; resolveName(name: string): Promise; fundWallet(address: string): Promise; getExplorerUrl(type: "tx" | "address", value: string): string; } ``` ### Supporting Types ```typescript theme={null} interface DerivedKeys { address: string; stealthKeys: ChainStealthKeys; metaAddress: string; } interface ChainStealthKeys { [key: string]: unknown; // opaque to the core — each chain stores what it needs } interface SendPaymentParams { senderAddress: string; senderStealthKeys: ChainStealthKeys; recipientMetaAddress: string; amount: string; asset?: string; } interface WithdrawParams { stealthKeys: ChainStealthKeys; from: string; to: string; amount?: string; // undefined = withdraw max } interface DetectedPayment { stealthAddress: string; balance: string; ephemeralPubKey: string; } interface ChainBalance { native: string; tokens: Record; } interface TxResult { txHash: string; txLink: string; } ``` *** ## EVM Connector A single `EVMConnector` class covers all EVM-compatible chains. Different chains use different configuration — same code. ### Configuration ```typescript theme={null} interface EVMConnectorConfig { chainId: number; rpcUrl: string; explorerUrl: string; contracts: { announcer: `0x${string}`; registry: `0x${string}`; sender: `0x${string}`; names: `0x${string}`; }; subgraphUrl?: string; faucetUrl?: string; tokens?: Record; } ``` ### How Methods Map to SDK Primitives | Interface Method | Implementation | | ------------------ | --------------------------------------------------------------------------------------------------------------- | | `deriveKeys(seed)` | SHA-256 seed -> `privateKeyToAccount` -> sign message -> `deriveStealthKeys(sig)` -> `encodeStealthMetaAddress` | | `sendPayment` | `decodeStealthMetaAddress` -> `generateStealthAddress` -> `writeContract(WraithSender, "sendETH")` | | `scanPayments` | Query subgraph for Announcement events -> `scanAnnouncements(events, ...)` -> fetch balances | | `getBalance` | `publicClient.getBalance` + `readContract(erc20, balanceOf)` per token | | `withdraw` | `deriveStealthPrivateKey` -> `privateKeyToAccount` -> `sendTransaction` | | `registerName` | `signNameRegistration(name, metaBytes, spendingKey)` -> `writeContract(WraithNames, "register")` | | `resolveName` | `readContract(WraithNames, "resolve", [name])` -> decode meta-address | | `fundWallet` | POST to faucet API (testnet) | ### Adding a New EVM Chain No code changes required. Register a new chain with its config: ```typescript theme={null} import { Chain } from "@wraith-protocol/sdk"; // Horizen Testnet chainRegistry.register(Chain.Horizen, new EVMConnector({ chainId: 2651420, rpcUrl: "https://horizen-testnet.rpc.caldera.xyz/http", explorerUrl: "https://horizen-testnet.explorer.caldera.xyz", contracts: { announcer: "0x8AE65c05E7eb48B9bA652781Bc0a3DBA09A484F3", registry: "0x953E6cEdcdfAe321796e7637d33653F6Ce05c527", sender: "0x226C5eb4e139D9fa01cc09eA318638b090b12095", names: "0x3d46f709a99A3910f52bD292211Eb5D557F882D6", }, subgraphUrl: "https://api.goldsky.com/api/public/...", tokens: { ETH: { address: "native", decimals: 18 }, ZEN: { address: "0x4b36cb6E...", decimals: 18 }, USDC: { address: "0x01c7AEb2...", decimals: 6 }, }, })); // Ethereum Mainnet — same connector, different config chainRegistry.register(Chain.Ethereum, new EVMConnector({ chainId: 1, rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/...", explorerUrl: "https://etherscan.io", contracts: { announcer: "0x...", registry: "0x...", sender: "0x...", names: "0x...", }, })); ``` **Requirements for each new EVM chain:** 1. Deploy the 4 Solidity contracts (Announcer, Registry, Sender, Names) 2. Set up a subgraph to index Announcement events 3. Add config to the registry *** ## Stellar Connector ### How Methods Map to SDK Primitives | Interface Method | Implementation | | ------------------ | -------------------------------------------------------------------------------------------------------- | | `deriveKeys(seed)` | SHA-256 seed -> ed25519 seed -> sign message -> `deriveStealthKeys(sig)` -> `encodeStealthMetaAddress` | | `sendPayment` | `decodeStealthMetaAddress` -> `generateStealthAddress` -> `Operation.createAccount` -> Soroban announcer | | `scanPayments` | Fetch Soroban events -> `scanAnnouncements(events, ...)` -> fetch balances from Horizon | | `getBalance` | `GET /accounts/{key}` from Horizon | | `withdraw` | `deriveStealthPrivateScalar` -> build payment tx -> `signStellarTransaction` -> submit to Horizon | | `registerName` | Call Soroban WraithNames `register(name, metaAddress)` | | `resolveName` | Simulate Soroban WraithNames `resolve(name)` | | `fundWallet` | Stellar Friendbot `GET /friendbot?addr={key}` | ### Stellar-Specific Considerations * **Account creation:** Stellar requires accounts to exist with a minimum balance (1 XLM). Sending to a new stealth address uses `Operation.createAccount`, not `Operation.payment`. * **Signing:** Stealth private keys are derived scalars. Must use `signWithScalar()` from the SDK — can't use `Keypair.fromRawEd25519Seed()`. * **Events:** Soroban contract events are fetched via `sorobanServer.getEvents()`, not a subgraph. ### Configuration ```typescript theme={null} const connector = new StellarConnector({ networkPassphrase: Networks.TESTNET, horizonUrl: "https://horizon-testnet.stellar.org", sorobanUrl: "https://soroban-testnet.stellar.org", contracts: { announcer: "CCJLJ2QRBJAAKIG6ELNQVXLLWMKKWVN5O2FKWUETHZGMPAD4MHK7WVWL", names: "CC...", }, }); ``` *** ## Solana Connector Handles Solana using ed25519 and Anchor programs. ### How Methods Map to SDK Primitives | Interface Method | Implementation | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | `deriveKeys(seed)` | SHA-256 seed -> ed25519 seed -> sign message -> `deriveStealthKeys(sig)` -> `encodeStealthMetaAddress` | | `sendPayment` | `decodeStealthMetaAddress` -> `generateStealthAddress` -> Anchor instruction `wraith_sender.send_sol` (transfer + announce atomically) | | `scanPayments` | Fetch announcer program transaction logs -> parse Anchor events -> `scanAnnouncements(events, ...)` -> fetch balances | | `getBalance` | `connection.getBalance(address)` for SOL, `connection.getTokenAccountsByOwner()` for SPL tokens | | `withdraw` | `deriveStealthPrivateScalar` -> `signWithScalar` to sign `SystemProgram.transfer` -> `sendRawTransaction` | | `registerName` | Anchor instruction `wraith_names.register` with name + 64-byte meta-address | | `resolveName` | Derive PDA from `["name", nameBytes]` -> fetch and decode `NameRecord` account | | `fundWallet` | `connection.requestAirdrop(address, lamports)` (devnet) | ### Solana-Specific Considerations * **No account deployment:** An ed25519 public key is a valid Solana address. Send SOL directly — no `createAccount` needed. * **Rent exemption:** Accounts need \~0.00089 SOL minimum. When withdrawing all, send `balance - 5000 lamports` (tx fee). * **SPL tokens:** Use associated token accounts (ATAs). The ATA must be created for the stealth address before transferring SPL tokens. * **Signing:** Uses `signWithScalar()` from the SDK — stealth scalars can't be used with standard `Keypair`. * **Events:** Anchor `emit!()` writes to program logs. Parse via transaction history, not subgraph. ### Configuration ```typescript theme={null} interface SolanaConnectorConfig { rpcUrl: string; explorerUrl: string; contracts: { announcer: string; // program ID (base58) sender: string; names: string; }; cluster: "devnet" | "testnet" | "mainnet-beta"; } ``` ```typescript theme={null} import { Chain } from "@wraith-protocol/sdk"; chainRegistry.register(Chain.Solana, new SolanaConnector({ rpcUrl: "https://api.devnet.solana.com", explorerUrl: "https://explorer.solana.com", contracts: { announcer: "...", // deployed program ID sender: "...", names: "...", }, cluster: "devnet", })); ``` *** ## CKB Connector Handles Nervos CKB using secp256k1 and the UTXO-based Cell model. CKB is architecturally unique: there is no separate announcer contract. Cells **are** the announcements — the ephemeral public key and stealth address hash are embedded directly in the Cell's lock script args. ### How Methods Map to SDK Primitives | Interface Method | Implementation | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | | `deriveKeys(seed)` | SHA-256 seed -> `privateKeyToAccount` -> sign message -> `deriveStealthKeys(sig)` -> `encodeStealthMetaAddress` | | `sendPayment` | `decodeStealthMetaAddress` -> `generateStealthAddress` -> create Cell with `stealth-lock` (args = ephemeral\_pub \|\| blake160) | | `scanPayments` | `get_cells` RPC filtered by stealth-lock code hash -> `scanStealthCells(cells, ...)` -> return matched Cells with balances | | `getBalance` | Sum capacity of all matched stealth Cells | | `withdraw` | `deriveStealthPrivateKey` -> sign transaction consuming stealth Cell -> create destination Cell | | `registerName` | `buildRegisterName({ name, spendingPubKey, viewingPubKey })` -> create Cell with `wraith-names-type` type script, lock = owner | | `resolveName` | `buildResolveName({ name })` -> `get_cells` RPC filtered by type script -> `metaAddressFromNameData(data)` | | `fundWallet` | CKB testnet faucet | ### CKB-Specific Considerations * **UTXO model:** CKB uses Cells (UTXOs), not accounts. Sending creates a Cell; withdrawing consumes it. There's no balance to query on an address — you query live Cells. * **No separate announcer:** The Cell's lock script args contain the ephemeral public key. No event infrastructure needed. * **Minimum capacity:** A stealth-lock Cell requires at least 61 CKB due to the 53-byte args field. * **blake160:** Address hashing uses blake2b with `"ckb-default-hash"` personalization, truncated to 20 bytes. * **SHA-256 for shared secret:** Unlike EVM (keccak256), CKB uses SHA-256 for hashing the ECDH shared secret. * **No view tags:** Every Cell is fully checked. The `get_cells` RPC pre-filters by code hash, keeping the scan set small. * **Name ownership via lock script:** Unlike EVM/Solana where names are owned by the spending key, CKB name ownership is determined by the Cell's lock script. Whoever can spend the Cell controls the name. ### Configuration ```typescript theme={null} interface CKBConnectorConfig { rpcUrl: string; explorerUrl: string; contracts: { stealthLockCodeHash: string; namesTypeCodeHash: string; }; cellDeps: { stealthLock: { txHash: string; index: number }; namesType: { txHash: string; index: number }; }; network: "testnet" | "mainnet"; } ``` ```typescript theme={null} import { Chain } from "@wraith-protocol/sdk"; chainRegistry.register(Chain.CKB, new CKBConnector({ rpcUrl: "https://testnet.ckbapp.dev", explorerUrl: "https://pudge.explorer.nervos.org", contracts: { stealthLockCodeHash: "0x...", namesTypeCodeHash: "0xc133817d433f72ea16a2404adaf961524e9572c8378829a21968710d6182e20d", }, cellDeps: { stealthLock: { txHash: "0x...", index: 0 }, namesType: { txHash: "0x9acd640d35eadd893b358dddd415f4061fe81cb249e8ace51a866fee314141b8", index: 0 }, }, network: "testnet", })); ``` *** ## Chain Registry The TEE server maintains a registry of available chain connectors: ```typescript theme={null} class ChainRegistry { private connectors = new Map(); register(chain: string, connector: ChainConnector): void { this.connectors.set(chain, connector); } get(chain: string): ChainConnector { const c = this.connectors.get(chain); if (!c) throw new Error(`Unsupported chain: "${chain}". Available: ${this.supportedChains().join(", ")}`); return c; } supportedChains(): string[] { return Array.from(this.connectors.keys()); } } ``` ### Usage in Agent Service ```typescript theme={null} async sendPayment(agentId: string, recipient: string, amount: string) { const agent = await this.db.agents.findOneBy({ id: agentId }); const connector = this.chainRegistry.get(agent.chain); const stealthKeys = await this.tee.deriveAgentStealthKeys(agentId, agent.chain); return connector.sendPayment({ senderAddress: agent.address, senderStealthKeys: stealthKeys, recipientMetaAddress: recipient, amount, }); } ``` *** ## Adding a New Chain Family To add support for a completely new chain family (e.g., Solana): 1. **Create a connector class** implementing `ChainConnector` 2. **Implement all methods** using the chain's SDK and cryptographic primitives 3. **Write the crypto module** at `@wraith-protocol/sdk/chains/solana` 4. **Deploy stealth address contracts** on the target chain 5. **Register the connector** in the chain registry The agent core, AI engine, storage, notifications, and scheduling work automatically — no changes needed. ### Key Differences Between Chain Families | Aspect | EVM | Stellar | Solana | CKB | | ------------------- | --------------------- | ------------------------ | ------------------ | ------------------------- | | Curve | secp256k1 | ed25519 | ed25519 | secp256k1 | | Address format | `0x...` (20 bytes) | `G...` (56 chars) | Base58 | bech32m | | Meta-address prefix | `st:eth:0x` | `st:xlm:` | `st:sol:` | `st:ckb:` | | Contracts | Solidity | Soroban (Rust) | Solana Programs | RISC-V Lock Script | | Announcements | EVM events / subgraph | Soroban events / Horizon | Program log events | **Embedded in Cell args** | | Native asset | ETH | XLM | SOL | CKB | | Account model | Balance-based | Account must exist first | Balance-based | UTXO (Cell) | # Architecture Source: https://docs.usewraith.xyz/architecture/overview System overview and how components fit together Wraith has three layers: the SDK (client library), the TEE infrastructure (managed by Wraith), and the blockchains. ## System Diagram System Architecture Developers never run servers, manage keys, or handle chain-specific crypto. They install the SDK, get an API key, and build. ## TEE Server Architecture One NestJS server deployment with a chain connector registry. The core agent engine is chain-agnostic. Chain-specific logic lives behind a pluggable interface. TEE Server Architecture ## Module Structure ``` src/ main.ts <-- NestJS bootstrap app.module.ts <-- root module config/ configuration.ts <-- env vars config tee/ tee.service.ts <-- DStack key derivation tee.controller.ts <-- attestation endpoints agent/ agent.controller.ts <-- HTTP API (create, chat, export) agent.service.ts <-- agent lifecycle, chat orchestration tools/ tool-definitions.ts <-- AI function declarations + system prompt agent-tools.service.ts <-- tool execution storage/ database.service.ts <-- TypeORM repository access entities/ <-- agent, conversation, invoice, etc. notifications/ notification.service.ts <-- create, list, mark read scheduler/ scheduler.service.ts <-- cron-based scheduled payments health/ health.controller.ts <-- /health endpoint ``` ## Agent Lifecycle ### Creation ``` 1. Client sends POST /agent/create { name, wallet, signature, chain } 2. Server verifies wallet signature (EIP-191 for EVM, ed25519 for Stellar) 3. Generate UUID for agent 4. Derive keys inside TEE: seed -> chain connector -> { address, stealthKeys, metaAddress } 5. Fund agent wallet via chain faucet (testnet) 6. Store agent in DB (id, name, chain, ownerWallet, address, metaAddress) 7. Register .wraith name on-chain 8. Return { id, name, chain, address, metaAddress } ``` ### Chat Orchestration The chat method runs a multi-turn tool-calling loop: Chat Orchestration The AI can call multiple tools in one response. A cap (10 iterations) prevents infinite loops. ### Tool Execution Each tool call routes to the appropriate chain connector: ```typescript theme={null} import { Chain } from "@wraith-protocol/sdk"; // The agent service resolves the connector automatically async executeTool(toolName: string, args: Record, agent: Agent) { const connector = this.chainRegistry.get(agent.chain); const stealthKeys = await this.tee.deriveStealthKeys(agent.id, agent.chain); switch (toolName) { case "send_payment": return connector.sendPayment({ senderAddress: agent.address, senderStealthKeys: stealthKeys, recipientMetaAddress: args.recipient as string, amount: args.amount as string, }); case "scan_payments": return connector.scanPayments(stealthKeys); case "get_balance": return connector.getBalance(agent.address); // ... 13 more tools } } ``` ## Database Schema ### agents | Column | Type | Description | | ----------- | --------- | ------------------------ | | id | UUID (PK) | Agent identifier | | name | VARCHAR | `.wraith` name (unique) | | chain | VARCHAR | Target chain identifier | | ownerWallet | VARCHAR | Owner's wallet address | | address | VARCHAR | Agent's on-chain address | | metaAddress | TEXT | Stealth meta-address | | createdAt | TIMESTAMP | Creation time | ### conversations | Column | Type | Description | | --------- | --------- | ----------------------- | | id | UUID (PK) | Conversation identifier | | agentId | UUID (FK) | References agents.id | | title | VARCHAR | Conversation title | | createdAt | TIMESTAMP | Created | | updatedAt | TIMESTAMP | Last updated | ### messages | Column | Type | Description | | -------------- | ----------- | ------------------------------------ | | id | SERIAL (PK) | Message identifier | | conversationId | UUID (FK) | References conversations.id | | role | VARCHAR | `user`, `agent`, `tool`, or `system` | | text | TEXT | Message content | | createdAt | TIMESTAMP | Created | ### invoices | Column | Type | Description | | --------- | --------- | ---------------------------------- | | id | UUID (PK) | Invoice identifier | | agentId | UUID (FK) | References agents.id | | amount | VARCHAR | Payment amount | | asset | VARCHAR | Asset symbol | | memo | TEXT | Invoice description | | status | VARCHAR | `pending` or `paid` | | txHash | VARCHAR | Transaction hash (null until paid) | | createdAt | TIMESTAMP | Created | ### notifications | Column | Type | Description | | --------- | ----------- | ----------------------- | | id | SERIAL (PK) | Notification identifier | | agentId | UUID (FK) | References agents.id | | type | VARCHAR | Notification type | | title | VARCHAR | Title | | body | TEXT | Body | | read | BOOLEAN | Read status | | createdAt | TIMESTAMP | Created | ### scheduled\_payments | Column | Type | Description | | --------- | --------- | ---------------------------------- | | id | UUID (PK) | Schedule identifier | | agentId | UUID (FK) | References agents.id | | recipient | VARCHAR | `.wraith` name or meta-address | | amount | VARCHAR | Payment amount | | asset | VARCHAR | Asset symbol | | interval | VARCHAR | `daily`, `weekly`, or `monthly` | | status | VARCHAR | `active`, `paused`, or `cancelled` | | lastRun | TIMESTAMP | Last execution (null if never) | | nextRun | TIMESTAMP | Next scheduled execution | | createdAt | TIMESTAMP | Created | ## Platform Model Wraith operates as a managed service, similar to Privy or Turnkey. ### What Wraith Manages * TEE infrastructure (Phala deployment) * Key derivation and security * Chain connectors and RPC endpoints * Database and session storage * Name registration and gas sponsorship * Payment scanning and notifications ### AI Model Options | Option | Description | | -------------- | ---------------------------------------------------- | | Default | Wraith's hosted Gemini model (included in API usage) | | Bring Your Own | Pass your own OpenAI/Claude/Gemini API key | ```typescript theme={null} import { Wraith } from "@wraith-protocol/sdk"; // Default — uses Wraith's Gemini const wraith = new Wraith({ apiKey: "wraith_..." }); // Bring your own model const wraith = new Wraith({ apiKey: "wraith_...", ai: { provider: "openai", apiKey: "sk-..." }, }); ``` # TEE Security Source: https://docs.usewraith.xyz/architecture/tee Trusted Execution Environment and key derivation Wraith runs inside Trusted Execution Environment (TEE) hardware. Agent private keys are derived deterministically inside Intel TDX enclaves and never stored on disk or exported without authorization. ## What Is a TEE? A TEE is a hardware-isolated compute environment. Code and data inside the enclave are protected from the host operating system, hypervisor, and even the hardware operator. Intel TDX (Trust Domain Extensions) provides: * **Memory encryption** — data in enclave memory is encrypted at the hardware level * **Code integrity** — the enclave can prove it's running specific, unmodified code * **Remote attestation** — clients can verify the enclave's identity and integrity remotely Wraith uses Phala Network for TEE deployment, which provides the DStack SDK for deterministic key derivation. ## Key Derivation Agent keys are derived deterministically from the TEE's hardware root secret. They are never stored — re-derived on demand for every operation. ### Derivation Path Key Derivation The path includes the chain identifier to prevent key collision. The same agent gets different keys on different chains. ### Implementation ```typescript theme={null} class TeeService { async deriveAgentPrivateKey(agentId: string, chain: string): Promise { // DStack derives key from hardware root secret + path const raw = await dstack.getKey(`wraith/agent/${agentId}/${chain}`); return sha256(raw); } async deriveAgentKeypair(agentId: string, chain: string) { const connector = this.chainRegistry.get(chain); const seed = await this.deriveAgentPrivateKey(agentId, chain); return connector.deriveKeys(seed); } } ``` ### Per-Chain Key Derivation The chain connector determines how the raw seed becomes usable keys: | Chain | Curve | Flow | | ------- | --------- | -------------------------------------------------------------------------------------------- | | EVM | secp256k1 | seed → SHA-256 → `privateKeyToAccount` → sign message → `deriveStealthKeys` → `st:eth:0x...` | | Stellar | ed25519 | seed → SHA-256 → ed25519 seed → sign message → `deriveStealthKeys` → `st:xlm:...` | ## Security Properties ### Keys Are Never Stored Every time an operation needs agent keys (sending a payment, scanning, withdrawing), the keys are re-derived from the TEE root secret. No private key material touches disk. ```typescript theme={null} // Every chat message re-derives keys async chat(agentId: string, message: string) { const agent = await this.db.agents.findOneBy({ id: agentId }); const stealthKeys = await this.tee.deriveAgentKeypair(agent.id, agent.chain); // Use keys for this operation, then they're garbage collected } ``` ### Wallet Ownership Verification Agent creation requires an EIP-191 signature (EVM) or ed25519 signature (Stellar) from the owner wallet. This proves the creator controls the wallet without revealing any private key. ```typescript theme={null} // Verification flow const recoveredAddress = recoverMessageAddress({ message: body.message, signature: body.signature, }); if (recoveredAddress !== body.wallet) { throw new UnauthorizedException("Signature does not match wallet"); } ``` ### Key Export Exporting an agent's private key requires a fresh wallet signature: ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_..." }); const agent = await wraith.getAgentByName("alice"); // Requires fresh wallet signature const sig = await wallet.signMessage("Export private key for agent " + agent.info.id); const { secret } = await agent.exportKey(sig, "Export private key for agent " + agent.info.id); ``` The TEE verifies the signature matches the owner wallet before releasing the key. Without the wallet, nobody — including Wraith — can export the key. ### Remote Attestation Clients can verify the TEE is running authentic, unmodified code: ```typescript theme={null} // GET /tee/attest/{agentId} const attestation = await fetch(`${baseUrl}/tee/attest/${agentId}`); // Returns cryptographic proof of code integrity ``` This proves: * The code inside the TEE matches the published source * Keys were derived by the legitimate Wraith software * No unauthorized modifications have been made ## Privacy Properties ### Stealth Addresses Every payment goes to a fresh one-time stealth address. On-chain observers see random addresses with no link to the sender or receiver. Stealth Address Privacy ### View Tags View tags enable efficient scanning. Each announcement includes a 1-byte tag derived from the shared secret. The recipient can reject \~255/256 non-matching announcements by checking just the tag — without computing the full stealth address. ### AI Privacy Guardian The agent proactively monitors for privacy risks: * **Timing analysis** — warns if transactions happen too close together * **Amount patterns** — flags identical payment amounts * **Address correlation** — warns about withdrawing to a known wallet * **Consolidation risk** — alerts when too many stealth addresses are unspent ```typescript theme={null} await agent.chat("withdraw all to 0xMyMainWallet"); // "Privacy concern — withdrawing all stealth addresses to a single // known wallet links every payment to your identity..." ``` ## Deployment ### Docker ```bash theme={null} docker buildx build --platform linux/amd64 \ -t wraith-tee:latest \ --push \ -f packages/tee/Dockerfile . ``` The container must target `linux/amd64` for TEE hardware compatibility. ### Environment Variables | Variable | Description | | ---------------- | -------------------------------------------- | | `DATABASE_URL` | PostgreSQL connection string | | `GEMINI_API_KEY` | Default AI model key | | `DEPLOYER_KEY` | Key for deploying contracts / funding agents | | `CHAIN_ID` | Target chain ID | | `RPC_URL` | Chain RPC endpoint | ### Deploy to Phala ```bash theme={null} phala deploy --cvm-id -c docker-compose.yml ``` The DStack socket (`/var/run/dstack.sock`) connects the container to the TEE hardware for key derivation. # CKB Contracts Source: https://docs.usewraith.xyz/contracts/ckb Lock script specs and the Cell model for stealth addresses on Nervos CKB CKB scripts (smart contracts) for stealth address operations on the Nervos CKB network. Written in Rust and compiled to RISC-V binaries that run on CKB-VM. ## The Cell Model — Why CKB Is Different CKB uses a UTXO-based **Cell model**, not accounts. Every piece of on-chain state is a Cell: ``` Cell { capacity: 200_00000000, // 200 CKB (in shannons) lock: { // who can spend this Cell code_hash: "0xabc...", // hash of the lock script code hash_type: "data2", args: "0x...", // arguments passed to the lock script }, type: null, // optional type script data: "0x", // arbitrary data } ``` ### No Separate Announcer On EVM chains, stealth payments require two operations: transfer funds and emit an announcement event. On CKB, **the Cell itself is the announcement**. The lock script args contain both the ephemeral public key and the stealth address hash: ``` lock.args = ephemeral_pubkey (33 bytes) || blake160(stealth_pubkey) (20 bytes) ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ announcement data stealth address identifier ``` This 53-byte args field serves as both the lock condition and the announcement. No separate contract call, no event indexing infrastructure — just create a Cell and the recipient can find it. ### Scanning = Querying Cells CKB has a built-in indexer with an RPC method `get_cells` that filters by lock script code hash. To scan for incoming payments, query all live Cells using the stealth-lock code hash. Each Cell's args contains the ephemeral key needed for the ECDH check. ### Spending = Consuming Cells To withdraw funds, build a transaction that: 1. **Inputs:** The stealth Cell being consumed 2. **Outputs:** A new Cell at the destination address 3. **Witness:** secp256k1 signature with the stealth private key The stealth lock script verifies the signature against the pubkey hash in the args. *** ## Script Set | Script | Type | Purpose | | ------------------- | ----------- | ----------------------------------------------- | | wraith-stealth-lock | Lock Script | Verifies secp256k1 signatures for stealth Cells | | wraith-names-type | Type Script | `.wraith` name registration and resolution | `wraith-stealth-lock` handles stealth payments. `wraith-names-type` handles `.wraith` name registration. *** ## wraith-stealth-lock A lock script that verifies secp256k1 signatures against the stealth public key hash embedded in the script args. ### Args Format 53 bytes total: ``` [0:33] — ephemeral_pubkey (33-byte compressed secp256k1 public key) [33:53] — blake160(stealth_pub) (20-byte blake2b hash of stealth public key) ``` ### Verification Flow ```rust theme={null} fn program_entry() -> i8 { let script = load_script(); let args = script.args().raw_data(); // 1. Args must be exactly 53 bytes if args.len() != 53 { return ERROR_ARGS_LENGTH; } // 2. Extract the stealth pubkey hash let pubkey_hash = &args[33..53]; // blake160 of stealth pubkey // 3. Load signature from witness (65 bytes: r || s || v) let witness_args = load_witness_args(0, Source::GroupInput); let signature = witness_args.lock(); // 4. Load transaction hash as the message to verify let tx_hash = load_tx_hash(); // 5. Recover public key from signature // 6. Compute blake160(recovered_pubkey) // 7. Compare with pubkey_hash from args verify_secp256k1(tx_hash, signature, pubkey_hash) } ``` The script uses `ckb-std` crypto syscalls or `ckb-auth` for secp256k1 signature recovery and verification. ### blake160 CKB's standard address hashing: ```rust theme={null} fn blake160(data: &[u8]) -> [u8; 20] { let hash = blake2b_256_with_personalization(data, b"ckb-default-hash"); hash[0..20] } ``` The `"ckb-default-hash"` personalization is required. Without it, hashes won't match CKB's standard address format. ### How It Differs from Standard CKB Locks CKB's default lock script (`secp256k1-blake160-sighash-all`) uses 20-byte blake160 args containing the owner's pubkey hash. The stealth lock extends this to 53 bytes by prepending the ephemeral public key. The verification logic is the same — recover the public key from the signature and check the blake160 hash. ### Usage ```typescript theme={null} import { generateStealthAddress, getDeployment } from "@wraith-protocol/sdk/chains/ckb"; const deployment = getDeployment("ckb"); const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); // Create a Cell with stealth-lock const output = { capacity: "0x" + (200_00000000n).toString(16), // 200 CKB lock: { codeHash: deployment.contracts.stealthLockCodeHash, hashType: "data2", args: stealth.lockArgs, // 53 bytes: ephemeral_pub || blake160(stealth_pub) }, }; ``` *** ## wraith-names-type A Type Script for `.wraith` name registration on CKB. Each name is stored as a live Cell. The type script validates creation, update, and destruction of name Cells. ### Deployed Code Hash ``` 0xc133817d433f72ea16a2404adaf961524e9572c8378829a21968710d6182e20d ``` ### Cell Dep ``` tx: 0x9acd640d35eadd893b358dddd415f4061fe81cb249e8ace51a866fee314141b8 index: 0 ``` ### Cell Layout A `.wraith` name Cell has three meaningful fields: | Field | Content | | ----------- | ------------------------------------------------------------------------------- | | `type.args` | 32-byte blake2b hash of the name string (personalization: `"ckb-default-hash"`) | | `data` | 66 bytes: `spendingPubKey (33 bytes) \|\| viewingPubKey (33 bytes)` | | `lock` | Owner's lock script — whoever can spend this Cell owns the name | The type script `args` uniquely identify the name. Two Cells cannot have the same type script (same code hash + same args) because the type script enforces uniqueness at creation time. ### How It Works The type script runs on every transaction that creates, updates, or destroys a name Cell. It inspects the transaction's inputs and outputs to determine the operation: * **Create:** The type script appears in an output but not in any input. A new name is being registered. * **Update:** The type script appears in both an input and an output. The name's data is being changed. * **Destroy:** The type script appears in an input but not in any output. The name is being released. ### Validation Rules **Data must be exactly 66 bytes.** The data field holds two 33-byte compressed secp256k1 public keys (spending + viewing). The type script rejects any Cell with data that is not exactly 66 bytes. This applies to both creation and update. **Create:** When a new name Cell is created, the type script verifies: 1. No existing live Cell has the same type script (same code hash and args). CKB enforces this natively — two live Cells cannot share the same type script. 2. The output data is exactly 66 bytes. **Update:** When a name Cell is consumed and recreated in the same transaction: 1. The new output data must be exactly 66 bytes. 2. The lock script on the input Cell must be satisfied (the owner authorized the transaction). **Destroy:** When a name Cell is consumed without being recreated: 1. The lock script on the input Cell must be satisfied (the owner authorized the destruction). 2. No further validation — the name is released and can be re-registered. ### Ownership Model On EVM and Solana, name ownership is proven by a signature from the spending key embedded in the meta-address. The contract verifies this signature on every register/update/release call. On CKB, ownership is proven by the Cell's **lock script**. The lock script determines who can spend (consume) the Cell. This is more flexible: * A standard secp256k1 lock means a single key controls the name. * A multisig lock means multiple parties must agree to update or transfer the name. * Any custom lock script can control a name. To **transfer** a name, consume the name Cell and create a new one with the same type script but a different lock script. The new lock script's owner now controls the name. ### Verification Flow ```rust theme={null} fn program_entry() -> i8 { let script = load_script(); let mut has_input = false; let mut has_output = false; // Check inputs for Cells with this type script for i in 0.. { match load_cell_type(i, Source::GroupInput) { Ok(Some(t)) if t.as_slice() == script.as_slice() => { has_input = true; break; } Err(SysError::IndexOutOfBound) => break, _ => continue, } } // Check outputs for Cells with this type script for i in 0.. { match load_cell_type(i, Source::GroupOutput) { Ok(Some(t)) if t.as_slice() == script.as_slice() => { has_output = true; // Validate data is exactly 66 bytes let data = load_cell_data(i, Source::GroupOutput); if data.len() != 66 { return ERROR_DATA_LENGTH; } break; } Err(SysError::IndexOutOfBound) => break, _ => continue, } } // Create: output exists, no input -> OK (CKB prevents duplicate type scripts) // Update: both exist -> OK (lock script on input already verified) // Destroy: input exists, no output -> OK (lock script on input already verified) 0 } ``` ### Usage ```typescript theme={null} import { hashName, buildRegisterName, buildResolveName, metaAddressFromNameData, getDeployment, } from "@wraith-protocol/sdk/chains/ckb"; const deployment = getDeployment("ckb"); // Register a name const { typeScript, data } = buildRegisterName({ name: "alice", spendingPubKey: keys.spendingPubKey, viewingPubKey: keys.viewingPubKey, }); const output = { capacity: "0x" + (130_00000000n).toString(16), lock: walletLockScript, type: { codeHash: typeScript.codeHash, hashType: typeScript.hashType, args: typeScript.args, }, }; // Cell dep: { txHash: "0x9acd640d...", index: 0, depType: "code" } // Output data: data (66 bytes) // Resolve a name const { typeScript: resolveType } = buildResolveName({ name: "alice" }); // Query get_cells with script_type: "type" and the resolveType script // Parse the Cell's output_data with metaAddressFromNameData() ``` *** ## Project Structure ``` contracts/ ckb/ Cargo.toml contracts/ wraith-stealth-lock/ Cargo.toml src/main.rs wraith-names-type/ Cargo.toml src/main.rs tests/ src/tests.rs ``` ## Deployment ### Build Compile to RISC-V binary: ```bash theme={null} cargo build --target riscv64imac-unknown-none-elf --release ``` ### Deploy Deploy the compiled binary as a Cell on CKB testnet. The Cell's data hash becomes the `code_hash` used in stealth lock scripts. ```bash theme={null} # Deploy using ckb-cli or a deployment script ckb-cli deploy gen-txs \ --deployment-config deployment.toml \ --migration-dir migrations ``` ### Record Deployment Info After deployment, record: * **Code hash:** The blake2b hash of the deployed binary (used as `lock.code_hash` in stealth Cells) * **Cell dep:** The transaction hash and index where the script binary Cell lives (required in every transaction that uses the lock) Update the SDK's `deployments.ts`: ```typescript theme={null} export const DEPLOYMENTS = { ckb: { network: "testnet", rpcUrl: "https://testnet.ckbapp.dev", explorerUrl: "https://pudge.explorer.nervos.org", contracts: { stealthLockCodeHash: "0x...", namesTypeCodeHash: "0xc133817d433f72ea16a2404adaf961524e9572c8378829a21968710d6182e20d", }, cellDeps: { stealthLock: { txHash: "0x...", index: 0, }, namesType: { txHash: "0x9acd640d35eadd893b358dddd415f4061fe81cb249e8ace51a866fee314141b8", index: 0, }, }, }, }; ``` ### Testing Tests use CKB's native testing framework with `ckb-testtool`: **wraith-stealth-lock:** * **Args validation** — rejects args that aren't exactly 53 bytes * **Signature verification** — valid stealth key signature passes * **Wrong key rejection** — signature from a different key fails * **blake160 correctness** — hash matches CKB's standard implementation **wraith-names-type:** * **Data validation** — rejects data that isn't exactly 66 bytes * **Create** — allows creating a name Cell with valid 66-byte data * **Update** — allows updating data when the owner's lock is satisfied * **Destroy** — allows destroying a name Cell when the owner's lock is satisfied * **Invalid data on create** — rejects Cell creation with wrong data length * **Invalid data on update** — rejects Cell update with wrong data length *** ## Differences from Other Chains | Aspect | EVM (Solidity) | Stellar (Soroban) | Solana (Anchor) | CKB | | ------------------ | ----------------- | ----------------- | ---------------- | ---------------------------- | | Model | Account | Account | Account | UTXO (Cell) | | Language | Solidity | Rust | Rust (Anchor) | Rust (RISC-V) | | Announcement | Separate event | Separate event | Separate event | **Embedded in Cell** | | Scanning source | Subgraph | Soroban RPC | Program logs | `get_cells` RPC | | Script count | 4 contracts | 4 contracts | 3 programs | **2 scripts** (lock + type) | | Address hash | keccak256 | N/A | N/A | blake2b ("ckb-default-hash") | | Shared secret hash | keccak256 | SHA-256 | SHA-256 | SHA-256 | | Min balance | None | 1 XLM | \~0.00089 SOL | 61 CKB | | Names | On-chain contract | On-chain contract | On-chain program | Type Script (Cell-based) | The key architectural difference: CKB needs only **two scripts** to support stealth payments and names. The stealth lock handles payments (announcements are implicit in the Cell), and the names type script handles `.wraith` name registration. Other chains need 3-4 separate contracts for the same functionality. # EVM Contracts Source: https://docs.usewraith.xyz/contracts/evm Solidity contract specs and deployment Solidity smart contracts for stealth address operations on EVM-compatible chains. Four contracts are deployed per chain. ## Contract Set | Contract | Purpose | | ---------------- | ----------------------------------------- | | ERC5564Announcer | Emits stealth address announcements | | ERC6538Registry | Maps addresses to stealth meta-addresses | | WraithSender | Atomic send + announce in one transaction | | WraithNames | `.wraith` name to meta-address mapping | | WraithWithdrawer | Gas-sponsored withdrawals (EIP-7702) | *** ## ERC5564Announcer Minimal singleton. No storage, no access control. Just emits events. ### Event ```solidity theme={null} event Announcement( uint256 indexed schemeId, address indexed stealthAddress, address indexed caller, bytes ephemeralPubKey, bytes metadata // first byte = view tag ); ``` ### Function ```solidity theme={null} function announce( uint256 schemeId, address stealthAddress, bytes memory ephemeralPubKey, bytes memory metadata ) external; ``` **Deployment:** One per chain. No constructor args. No proxy needed. ### Usage ```typescript theme={null} import { SCHEME_ID } from "@wraith-protocol/sdk/chains/evm"; // After generating a stealth address, announce it await announcer.write.announce([ SCHEME_ID, stealthAddress, ephemeralPubKey, metadata, // view tag as first byte ]); ``` *** ## ERC6538Registry Maps addresses to stealth meta-addresses per ERC-6538. Supports direct and delegated registration via EIP-712 signatures. ### Functions ```solidity theme={null} // Direct registration function registerKeys(uint256 schemeId, bytes calldata stealthMetaAddress) external; // Delegated registration via signature function registerKeysOnBehalf( address registrant, uint256 schemeId, bytes calldata stealthMetaAddress, bytes calldata signature ) external; // Lookup function stealthMetaAddressOf( address registrant, uint256 schemeId ) external view returns (bytes memory); // Replay prevention function incrementNonce(address registrant) external; function nonceOf(address registrant) external view returns (uint256); ``` ### Usage ```typescript theme={null} import { SCHEME_ID, metaAddressToBytes } from "@wraith-protocol/sdk/chains/evm"; // Register meta-address const metaBytes = metaAddressToBytes(metaAddress); await registry.write.registerKeys([SCHEME_ID, metaBytes]); // Look up const stored = await registry.read.stealthMetaAddressOf([address, SCHEME_ID]); ``` *** ## WraithSender Atomically transfers assets to stealth addresses and publishes announcements. Uses `ReentrancyGuard`. ### Functions ```solidity theme={null} // Send native ETH function sendETH( uint256 schemeId, address stealthAddress, bytes memory ephemeralPubKey, bytes memory metadata ) external payable; // Send ERC-20 tokens function sendERC20( uint256 schemeId, address stealthAddress, bytes memory ephemeralPubKey, bytes memory metadata, address token, uint256 amount, uint256 gasTip // optional ETH tip for gas at stealth address ) external payable; // Batch send native ETH function batchSendETH( uint256 schemeId, address[] calldata stealthAddresses, bytes[] calldata ephemeralPubKeys, bytes[] calldata metadatas, uint256[] calldata amounts ) external payable; ``` **Constructor:** Takes announcer contract address. ### Usage ```typescript theme={null} import { generateStealthAddress, SCHEME_ID } from "@wraith-protocol/sdk/chains/evm"; const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); const metadata = `0x${stealth.viewTag.toString(16).padStart(2, "0")}`; // Send ETH + announce in one tx await sender.write.sendETH( [SCHEME_ID, stealth.stealthAddress, stealth.ephemeralPubKey, metadata], { value: parseEther("0.1") } ); ``` *** ## WraithNames Privacy-preserving name registry. Maps human-readable names to stealth meta-addresses. Ownership proven via secp256k1 signature from the spending key embedded in the meta-address. ### Functions ```solidity theme={null} function register( string calldata name, bytes calldata metaAddress, bytes calldata signature ) external; function registerOnBehalf( string calldata name, bytes calldata metaAddress, bytes calldata signature ) external; function update( string calldata name, bytes calldata newMetaAddress, bytes calldata signature ) external; function release( string calldata name, bytes calldata signature ) external; function resolve(string calldata name) external view returns (bytes memory); function nameOf(bytes calldata metaAddress) external view returns (string memory); ``` ### Name Rules * 3-32 characters * Lowercase alphanumeric and hyphens only * No leading or trailing hyphens ### Signature Verification The contract decompresses the spending public key from the first 33 bytes of the meta-address and verifies an ECDSA signature over `keccak256(name || metaAddress)` with Ethereum signed message prefix. Includes on-chain point decompression (`y = sqrt(x^3 + 7) mod p`). ### Usage ```typescript theme={null} import { signNameRegistration, metaAddressToBytes, } from "@wraith-protocol/sdk/chains/evm"; const metaBytes = metaAddressToBytes(metaAddress); const sig = signNameRegistration("alice", metaBytes, keys.spendingKey); await names.write.register(["alice", metaBytes, sig]); // Resolve const resolved = await names.read.resolve(["alice"]); // Reverse lookup const name = await names.read.nameOf([metaBytes]); ``` *** ## WraithWithdrawer EIP-7702 delegation target for gas-sponsored stealth address withdrawals. A sponsor pays gas on behalf of the stealth address. ### Functions ```solidity theme={null} // Sponsored withdrawals (sponsor pays gas) function withdrawETH(address payable to, uint256 sponsorFee) external; function withdrawERC20(address token, address to, uint256 sponsorFee) external; // Direct withdrawals (stealth address pays own gas) function withdrawETHDirect(address payable to) external; function withdrawERC20Direct(address token, address to) external; ``` *** ## Deployment ### Deploy Script ```bash theme={null} npx hardhat run scripts/deploy.ts --network ``` ### Deployment Order 1. Deploy ERC5564Announcer (no constructor args) 2. Deploy ERC6538Registry (no constructor args) 3. Deploy WraithSender (arg: announcer address) 4. Deploy WraithNames (no constructor args) 5. Deploy WraithWithdrawer (no constructor args) — optional, if EIP-7702 supported ### Indexing Set up a subgraph to index `Announcement` events from the announcer contract: ```graphql theme={null} type Announcement @entity { id: ID! schemeId: BigInt! stealthAddress: Bytes! caller: Bytes! ephemeralPubKey: Bytes! metadata: Bytes! blockNumber: BigInt! transactionHash: Bytes! } ``` ### Deployed Addresses (Horizen Testnet) | Contract | Address | | ---------------- | -------------------------------------------- | | ERC5564Announcer | `0x8AE65c05E7eb48B9bA652781Bc0a3DBA09A484F3` | | ERC6538Registry | `0x953E6cEdcdfAe321796e7637d33653F6Ce05c527` | | WraithSender | `0x226C5eb4e139D9fa01cc09eA318638b090b12095` | | WraithNames | `0x3d46f709a99A3910f52bD292211Eb5D557F882D6` | # Solana Contracts Source: https://docs.usewraith.xyz/contracts/solana Anchor program specs and deployment Solana programs (smart contracts) for stealth address operations, written in Rust with the Anchor framework. ## Program Set | Program | Purpose | | ---------------- | ----------------------------------------- | | wraith-announcer | Emits stealth address announcement events | | wraith-sender | Atomic SOL/SPL transfer + announcement | | wraith-names | `.wraith` name to meta-address mapping | *** ## wraith-announcer Emits announcement events via Anchor's `emit!()` macro. Stateless — no on-chain storage. ### Instruction ```rust theme={null} pub fn announce( ctx: Context, scheme_id: u32, stealth_address: Pubkey, ephemeral_pub_key: [u8; 32], metadata: Vec, ) -> Result<()> ``` ### Accounts ```rust theme={null} #[derive(Accounts)] pub struct Announce<'info> { #[account(mut)] pub caller: Signer<'info>, } ``` ### Event ```rust theme={null} #[event] pub struct AnnouncementEvent { pub scheme_id: u32, pub stealth_address: Pubkey, pub caller: Pubkey, pub ephemeral_pub_key: [u8; 32], pub metadata: Vec, } ``` ### Usage ```typescript theme={null} import { SCHEME_ID } from "@wraith-protocol/sdk/chains/solana"; import { Program } from "@coral-xyz/anchor"; const program = new Program(idl, programId, provider); await program.methods .announce( SCHEME_ID, stealthAddressPubkey, Array.from(ephemeralPubKey), Array.from(metadata), ) .accounts({ caller: wallet.publicKey }) .rpc(); ``` *** ## wraith-sender Atomic SOL or SPL token transfer + announcement in one instruction. Sends funds to the stealth address and emits an announcement event. ### Instructions #### `send_sol` Transfer SOL to a stealth address and emit an announcement. ```rust theme={null} pub fn send_sol( ctx: Context, amount: u64, scheme_id: u32, stealth_address: Pubkey, ephemeral_pub_key: [u8; 32], metadata: Vec, ) -> Result<()> ``` **Accounts:** ```rust theme={null} #[derive(Accounts)] pub struct SendSol<'info> { #[account(mut)] pub sender: Signer<'info>, /// CHECK: stealth address, receives SOL #[account(mut)] pub stealth_account: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } ``` #### `send_spl` Transfer SPL tokens to a stealth address's associated token account and emit an announcement. ```rust theme={null} pub fn send_spl( ctx: Context, amount: u64, scheme_id: u32, stealth_address: Pubkey, ephemeral_pub_key: [u8; 32], metadata: Vec, ) -> Result<()> ``` **Accounts:** ```rust theme={null} #[derive(Accounts)] pub struct SendSpl<'info> { #[account(mut)] pub sender: Signer<'info>, #[account(mut)] pub sender_token_account: Account<'info, TokenAccount>, #[account(mut)] pub stealth_token_account: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, } ``` ### Usage ```typescript theme={null} import { generateStealthAddress, SCHEME_ID } from "@wraith-protocol/sdk/chains/solana"; import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); const metadata = [stealth.viewTag]; // first byte is view tag // Send SOL + announce atomically await program.methods .sendSol( new BN(0.1 * LAMPORTS_PER_SOL), SCHEME_ID, new PublicKey(stealth.stealthAddress), Array.from(stealth.ephemeralPubKey), metadata, ) .accounts({ sender: wallet.publicKey, stealthAccount: new PublicKey(stealth.stealthAddress), systemProgram: SystemProgram.programId, }) .rpc(); ``` *** ## wraith-names PDA-based name to meta-address mapping. Names are stored in Program Derived Addresses (PDAs) seeded by `["name", nameBytes]`. ### Instructions #### `register` Register a new `.wraith` name. ```rust theme={null} pub fn register( ctx: Context, name: String, meta_address: [u8; 64], ) -> Result<()> ``` #### `update` Update the meta-address for a name you own. ```rust theme={null} pub fn update( ctx: Context, new_meta_address: [u8; 64], ) -> Result<()> ``` #### `release` Release a name. Closes the PDA account and returns rent to the owner. ```rust theme={null} pub fn release(ctx: Context) -> Result<()> ``` #### `resolve` Look up a name's meta-address. Read-only. ```rust theme={null} pub fn resolve(ctx: Context) -> Result<[u8; 64]> ``` ### Account Structure ```rust theme={null} #[account] pub struct NameRecord { pub name: String, // max 32 bytes pub meta_address: [u8; 64], // spending pub + viewing pub pub owner: Pubkey, pub created_at: i64, } ``` ### PDA Derivation Name records are stored at PDAs derived from the name: ```rust theme={null} seeds = [b"name", name.as_bytes()] ``` This means names are globally unique and can be resolved without knowing the owner. ### Validation * Name: 3-32 characters, lowercase alphanumeric and hyphens only * Meta-address: must be exactly 64 bytes (two 32-byte ed25519 public keys) * Only the owner can update or release a name ### Error Codes ```rust theme={null} #[error_code] pub enum WraithError { #[msg("Name must be 3-32 characters")] InvalidNameLength, #[msg("Name must be lowercase alphanumeric or hyphens")] InvalidNameCharacter, #[msg("Only the owner can modify this name")] NotOwner, } ``` ### Usage ```typescript theme={null} import { encodeStealthMetaAddress } from "@wraith-protocol/sdk/chains/solana"; import { PublicKey } from "@solana/web3.js"; const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // Derive PDA for the name const [nameRecordPda] = PublicKey.findProgramAddressSync( [Buffer.from("name"), Buffer.from("alice")], programId, ); // Register await program.methods .register("alice", Array.from(metaAddressBytes)) .accounts({ nameRecord: nameRecordPda, owner: wallet.publicKey, systemProgram: SystemProgram.programId, }) .rpc(); // Resolve const record = await program.account.nameRecord.fetch(nameRecordPda); console.log(record.metaAddress); // [u8; 64] ``` *** ## Project Structure ``` contracts/ solana/ Anchor.toml Cargo.toml programs/ wraith-announcer/ Cargo.toml src/lib.rs wraith-sender/ Cargo.toml src/lib.rs wraith-names/ Cargo.toml src/lib.rs tests/ wraith-announcer.ts wraith-sender.ts wraith-names.ts ``` ## Deployment ### Build ```bash theme={null} anchor build ``` ### Deploy to Devnet ```bash theme={null} anchor deploy --provider.cluster devnet ``` ### Test ```bash theme={null} anchor test ``` Tests cover: * **wraith-announcer** — event emission, multiple callers, metadata preservation * **wraith-sender** — `send_sol` transfers + emits, `send_spl` token transfer + emits, insufficient funds rejection * **wraith-names** — register/resolve, name validation (too short, invalid chars), update by owner, update by non-owner (rejected), release and re-register *** ## Differences from EVM and Stellar Contracts | Aspect | EVM (Solidity) | Stellar (Soroban) | Solana (Anchor) | | --------------------- | -------------------------------- | -------------------------- | ----------------------------------- | | Language | Solidity | Rust | Rust (Anchor) | | Contract model | Bytecode deployment | WASM deployment | BPF program deployment | | Name storage | Contract storage | Contract storage | PDAs (Program Derived Addresses) | | Name sig verification | On-chain ECDSA recovery | Caller auth | Signer constraint | | Event indexing | Subgraph / The Graph | Soroban RPC `getEvents` | Program transaction logs | | Account model | Address always exists | Must `createAccount` first | Address always valid, no deployment | | Token transfers | `msg.value` / `safeTransferFrom` | Soroban token contract | SystemProgram / SPL Token | | Gas sponsorship | EIP-7702 (WraithWithdrawer) | Not applicable | Fee payer pattern | | Min balance | None | 1 XLM | \~0.00089 SOL (rent exemption) | # Stellar Contracts Source: https://docs.usewraith.xyz/contracts/stellar Soroban contract specs and deployment Soroban smart contracts (Rust) for stealth address operations on the Stellar network. ## Contract Set | Contract | Purpose | | ----------------- | ---------------------------------------- | | stealth-announcer | Emits stealth address announcements | | stealth-registry | Maps addresses to stealth meta-addresses | | stealth-sender | Atomic send + announce | | wraith-names | `.wraith` name to meta-address mapping | *** ## stealth-announcer Emits announcement events. No persistent storage. ### Interface ```rust theme={null} pub fn announce( env: Env, caller: Address, scheme_id: u32, stealth_address: Address, ephemeral_pub_key: BytesN<32>, metadata: Bytes, ); ``` ### Event Emits a contract event with topic `"announce"`: ```rust theme={null} // Event: ("announce", caller, scheme_id, stealth_address, ephemeral_pub_key, metadata) ``` ### Usage ```typescript theme={null} import { SCHEME_ID, bytesToHex } from "@wraith-protocol/sdk/chains/stellar"; // After generating a stealth address const tx = new TransactionBuilder(account, { fee: "100" }) .addOperation(contract.call( "announce", xdr.ScVal.scvAddress(callerAddress), xdr.ScVal.scvU32(SCHEME_ID), xdr.ScVal.scvAddress(stealthAddress), xdr.ScVal.scvBytes(ephemeralPubKey), xdr.ScVal.scvBytes(metadata), )) .build(); ``` *** ## stealth-registry Maps addresses to 64-byte stealth meta-addresses. ### Interface ```rust theme={null} pub fn register_keys( env: Env, registrant: Address, scheme_id: u32, stealth_meta_address: Bytes, ); pub fn stealth_meta_address_of( env: Env, registrant: Address, scheme_id: u32, ) -> Bytes; ``` ### Validation * Enforces 64-byte meta-address length (32 bytes spend + 32 bytes view) * Requires auth from `registrant` ### Usage ```typescript theme={null} import { encodeStealthMetaAddress } from "@wraith-protocol/sdk/chains/stellar"; const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // Register the raw 64-byte key material (without the "st:xlm:" prefix) ``` *** ## stealth-sender Atomic send + announce. Initializes with the announcer contract address. ### Interface ```rust theme={null} pub fn init(env: Env, admin: Address, announcer: Address); pub fn send( env: Env, caller: Address, token: Address, stealth_address: Address, amount: i128, scheme_id: u32, ephemeral_pub_key: BytesN<32>, metadata: Bytes, ); pub fn batch_send( env: Env, caller: Address, token: Address, stealth_addresses: Vec
, amounts: Vec, scheme_id: u32, ephemeral_pub_keys: Vec>, metadatas: Vec, ); ``` The `send` function: 1. Transfers `amount` of `token` from `caller` to `stealth_address` 2. Calls the announcer contract to emit an event ### Usage ```typescript theme={null} import { generateStealthAddress, SCHEME_ID } from "@wraith-protocol/sdk/chains/stellar"; const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); // Send XLM + announce in one tx const tx = new TransactionBuilder(account, { fee: "100" }) .addOperation(senderContract.call( "send", callerAddress, xlmTokenAddress, stealth.stealthAddress, amount, SCHEME_ID, stealth.ephemeralPubKey, viewTagMetadata, )) .build(); ``` *** ## wraith-names Name to meta-address mapping. Names are hashed via SHA-256 for storage keys. ### Interface ```rust theme={null} pub fn register(env: Env, caller: Address, name: String, meta_address: Bytes); pub fn update(env: Env, caller: Address, name: String, new_meta_address: Bytes); pub fn release(env: Env, caller: Address, name: String); pub fn resolve(env: Env, name: String) -> Bytes; pub fn name_of(env: Env, meta_address: Bytes) -> String; ``` ### Validation * Name: 3-32 characters, lowercase alphanumeric only * Meta-address: must be exactly 64 bytes * Caller auth required for register, update, release ### Usage ```typescript theme={null} import { encodeStealthMetaAddress } from "@wraith-protocol/sdk/chains/stellar"; const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // Register const tx = new TransactionBuilder(account, { fee: "100" }) .addOperation(namesContract.call("register", callerAddress, "alice", metaBytes)) .build(); // Resolve const resolved = await namesContract.call("resolve", "alice"); ``` *** ## Deployment ### Build ```bash theme={null} cd contracts/stealth-announcer && soroban contract build cd contracts/stealth-registry && soroban contract build cd contracts/stealth-sender && soroban contract build cd contracts/wraith-names && soroban contract build ``` ### Deploy ```bash theme={null} soroban contract deploy \ --wasm target/wasm32-unknown-unknown/release/stealth_announcer.wasm \ --network testnet \ --source ``` ### Initialize stealth-sender After deploying both the announcer and sender: ```bash theme={null} soroban contract invoke \ --id \ --network testnet \ --source \ -- init \ --admin \ --announcer ``` ### Event Fetching Stellar announcements are fetched via Soroban RPC, not a subgraph: ```typescript theme={null} const events = await sorobanServer.getEvents({ startLedger: lastProcessedLedger, filters: [{ type: "contract", contractIds: [announcerContractId], topics: [["announce"]], }], }); ``` *** ## Differences from EVM Contracts | Aspect | EVM (Solidity) | Stellar (Soroban) | | --------------------- | -------------------------------- | ------------------------------ | | Language | Solidity | Rust | | Name sig verification | On-chain ECDSA recovery | Caller auth (Soroban built-in) | | Event indexing | Subgraph / The Graph | Soroban RPC `getEvents` | | Account model | Address always exists | Must `createAccount` first | | Token transfers | `msg.value` / `safeTransferFrom` | Soroban token contract calls | | Gas sponsorship | EIP-7702 (WraithWithdrawer) | Not applicable | # Getting Started Source: https://docs.usewraith.xyz/getting-started Install the SDK, create your first agent, and send a private payment Install the SDK, create your first agent, and send a private payment in under 5 minutes. ## Prerequisites * Node.js 18+ * A Wraith API key (sign up at wraith.dev) * A wallet with a signing capability (MetaMask, viem, ethers, etc.) ## Install ```bash theme={null} npm install @wraith-protocol/sdk ``` ## Initialize the Client ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_live_abc123", }); ``` The `Wraith` client handles all communication with the managed TEE infrastructure. You never touch servers, keys, or chain-specific crypto directly. ## Create an Agent An agent is your identity on the Wraith network. It has a `.wraith` name, an on-chain address, and stealth keys — all derived inside TEE hardware. ```typescript theme={null} // 1. Sign a message with the owner wallet to prove ownership const message = "Sign to create Wraith agent"; const signature = await wallet.signMessage(message); // 2. Create the agent const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: "0xYourWalletAddress", signature: signature, message: message, }); console.log(agent.info.id); // UUID console.log(agent.info.addresses[Chain.Horizen]); // "0x..." console.log(agent.info.metaAddresses[Chain.Horizen]); // "st:eth:0x..." ``` The agent is now live. It has: * A `.wraith` name (`alice.wraith`) registered on-chain * A stealth meta-address for receiving private payments * An AI agent inside the TEE ready to process commands ## Fund the Agent On testnet, the agent is automatically funded via faucet. You can also request funds manually: ```typescript theme={null} const response = await agent.chat("fund my wallet"); // Agent requests testnet tokens from the faucet ``` ## Send a Payment Use natural language to send a stealth payment: ```typescript theme={null} const response = await agent.chat("send 0.1 ETH to bob.wraith"); console.log(response.response); // "Payment sent — 0.1 ETH to bob.wraith via stealth address 0x7a3f..." console.log(response.toolCalls); // [{ name: "send_payment", status: "success", detail: "..." }] ``` Behind the scenes, the agent: 1. Resolves `bob.wraith` to a stealth meta-address 2. Generates a one-time stealth address from the meta-address 3. Sends ETH to the stealth address 4. Publishes an announcement so `bob.wraith` can detect the payment ## Scan for Incoming Payments ```typescript theme={null} const response = await agent.chat("scan for incoming payments"); // Agent scans the chain for announcements matching your stealth keys ``` ## Check Balance ```typescript theme={null} const response = await agent.chat("what's my balance?"); // Agent reports native + token balances ``` Or programmatically: ```typescript theme={null} const balance = await agent.getBalance(); console.log(balance.native); // "1.5" console.log(balance.tokens); // { ZEN: "100.0", USDC: "50.0" } ``` ## Withdraw Move funds from stealth addresses to a destination: ```typescript theme={null} const response = await agent.chat("withdraw all to 0xMyWallet"); // Agent warns about privacy risks before executing ``` ## Connect to an Existing Agent If you already have an agent, reconnect without creating a new one: ```typescript theme={null} // By agent ID const agent = wraith.agent("agent-uuid-here"); // By owner wallet const agent = await wraith.getAgentByWallet("0xYourWallet"); // By .wraith name const agent = await wraith.getAgentByName("alice"); ``` ## Error Handling All methods throw on failure. Errors include the server's error message: ```typescript theme={null} try { await agent.chat("send 100 ETH to bob.wraith"); } catch (err) { console.error(err.message); // "Insufficient balance" } ``` ## Next Steps * [Single-Chain Agent Guide](guides/single-chain-agent) — deeper walkthrough * [Multichain Agent Guide](guides/multichain-agent) — deploy across multiple chains * [Bring Your Own Model](guides/bring-your-own-model) — use OpenAI or Claude instead of Gemini * [SDK Reference](sdk/agent-client) — full API documentation # Bring Your Own Model Source: https://docs.usewraith.xyz/guides/bring-your-own-model Use OpenAI, Claude, or other AI providers By default, Wraith agents use a hosted Gemini model. You can replace it with OpenAI, Claude, or your own Gemini key to reduce cost or use a specific model. ## Configuration Pass the `ai` option when creating the `Wraith` client: ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_live_abc123", ai: { provider: "openai", apiKey: "sk-...", }, }); ``` All agents created from this client use your specified AI provider. ## Supported Providers | Provider | `ai.provider` | API Key Format | | ---------------- | ------------- | ----------------- | | Google Gemini | `"gemini"` | Gemini API key | | OpenAI | `"openai"` | `sk-...` | | Anthropic Claude | `"claude"` | Anthropic API key | ### OpenAI ```typescript theme={null} const wraith = new Wraith({ apiKey: "wraith_live_abc123", ai: { provider: "openai", apiKey: "sk-proj-...", }, }); const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: "0x...", signature: "0x...", }); // Chat uses your OpenAI key const res = await agent.chat("send 0.1 ETH to bob.wraith"); ``` ### Claude ```typescript theme={null} const wraith = new Wraith({ apiKey: "wraith_live_abc123", ai: { provider: "claude", apiKey: "sk-ant-...", }, }); ``` ### Your Own Gemini Key ```typescript theme={null} const wraith = new Wraith({ apiKey: "wraith_live_abc123", ai: { provider: "gemini", apiKey: "AIza...", }, }); ``` ## How It Works The AI configuration is passed to the TEE server via HTTP headers on every request: | Header | Value | | --------------- | ----------------------------------------- | | `Authorization` | `Bearer wraith_...` (your Wraith API key) | | `X-AI-Provider` | `openai`, `claude`, or `gemini` | | `X-AI-Key` | Your AI provider API key | The TEE server: 1. Receives your chat message 2. Builds the system prompt with agent identity and tools 3. Sends the conversation to your specified AI provider 4. Executes any tool calls via the chain connector 5. Returns the final response Your AI key is only used server-side in the TEE. It's never stored — used for the duration of the request only. ## Default vs. BYOM | Aspect | Default (Gemini) | Bring Your Own | | ------------- | ---------------------------- | --------------------------------- | | Cost | Included in Wraith API usage | You pay your AI provider directly | | Model | Wraith's chosen Gemini model | Your choice | | Configuration | None | Pass `ai` option | ## Tool Compatibility All tools work identically regardless of the AI provider. The tool declarations are adapted to each provider's function-calling format by the TEE server. Your code doesn't change. ```typescript theme={null} // Works the same with Gemini, OpenAI, or Claude const res = await agent.chat("send 0.1 ETH to bob.wraith"); const res = await agent.chat("run a privacy check"); const res = await agent.chat("create an invoice for 1 ETH"); ``` # Multichain Agent Source: https://docs.usewraith.xyz/guides/multichain-agent Create an agent across multiple chains A multichain agent has one `.wraith` name with identities on multiple chains. The AI agent is chain-aware — chat naturally and it routes to the right chain. ## Create a Multichain Agent Pass an array of chains instead of a single chain: ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_live_abc123" }); const agent = await wraith.createAgent({ name: "alice", chain: [Chain.Horizen, Chain.Stellar, Chain.Ethereum], wallet: "0xYourWallet", signature: "0xSignature", }); ``` The agent gets a separate address and stealth meta-address on each chain: ```typescript theme={null} console.log(agent.info.chains); // [Chain.Horizen, Chain.Stellar, Chain.Ethereum] console.log(agent.info.addresses); // { // horizen: "0xabc...", // stellar: "GABC...", // ethereum: "0xdef..." // } console.log(agent.info.metaAddresses); // { // horizen: "st:eth:0x...", // stellar: "st:xlm:...", // ethereum: "st:eth:0x..." // } ``` ## Deploy on All Chains Use `Chain.All` to deploy on every chain the platform supports: ```typescript theme={null} const agent = await wraith.createAgent({ name: "alice", chain: Chain.All, wallet: "0xYourWallet", signature: "0xSignature", }); ``` ## Chain-Aware Chat The AI agent infers the chain from context. When it can't, it asks. ### Explicit Chain ```typescript theme={null} await agent.chat("send 0.1 ETH to bob.wraith on horizen"); await agent.chat("send 10 XLM to carol.wraith on stellar"); ``` ### Inferred Chain ```typescript theme={null} await agent.chat("send 10 XLM to carol.wraith"); // XLM -> Stellar (unambiguous) await agent.chat("send 100 ZEN to bob.wraith"); // ZEN -> Horizen (unambiguous) ``` ### Ambiguous Cases ```typescript theme={null} await agent.chat("send 0.1 ETH to bob.wraith"); // "ETH exists on both Horizen and Ethereum. Which chain do you mean?" await agent.chat("horizen"); // "Payment sent — 0.1 ETH to bob.wraith on Horizen..." ``` ## Cross-Chain Balance ```typescript theme={null} await agent.chat("what's my balance on all chains?"); // "Balances: // Horizen: 1.5 ETH, 100 ZEN, 50 USDC // Stellar: 500 XLM // Ethereum: 0.3 ETH" ``` ## Cross-Chain Scanning ```typescript theme={null} await agent.chat("scan for payments on all chains"); // "Incoming payments: // Horizen: // - 0.1 ETH at 0xabc... // Stellar: // - 10 XLM at GABC..." ``` ## How It Works Under the hood, a multichain agent is a single entity with multiple chain registrations. The TEE derives different keys per chain using the derivation path: ``` wraith/agent/{agentId}/horizen -> secp256k1 keys wraith/agent/{agentId}/stellar -> ed25519 keys wraith/agent/{agentId}/ethereum -> secp256k1 keys ``` The AI agent's system prompt includes all chain identities, so it knows which chains the agent operates on and can route tool calls to the correct chain connector. ## Privacy Across Chains Each chain has independent stealth address activity. Payments on Horizen are not visible on Stellar. The privacy check analyzes each chain separately: ```typescript theme={null} await agent.chat("run privacy check on all chains"); // "Privacy Analysis: // Horizen: Score 85/100 // - 5 unspent stealth addresses // Stellar: Score 95/100 // - All clear" ``` ## Single vs. Multichain | Feature | Single-Chain | Multichain | | -------------- | ------------------ | ------------------ | | Chains | One | Multiple | | Addresses | One | One per chain | | Meta-addresses | One | One per chain | | Chat | Direct routing | AI infers chain | | Key derivation | One path | One path per chain | | Privacy | One chain analysis | Per-chain analysis | Both use the same SDK interface. The only difference is the `chain` parameter at creation time. # Privacy Best Practices Source: https://docs.usewraith.xyz/guides/privacy-best-practices Scoring, timing patterns, and address hygiene Stealth addresses provide strong privacy by default. But certain usage patterns can degrade that privacy. This guide explains what to avoid and how the Wraith agent helps. ## Privacy Scoring The Wraith agent includes a privacy check tool that scores your stealth address activity: ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_..." }); const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: "0x...", signature: "0x...", }); const res = await agent.chat("run a privacy check"); // Privacy Score: 85/100 // Issues: // - (medium) 7 unspent stealth addresses // - (high) All recent payments are the same amount // Best Practices: // - Use a fresh destination for each withdrawal // - Space withdrawals at least 1 hour apart ``` ### Scoring Algorithm The privacy check starts at 100 points and deducts for observed risks: | Condition | Deduction | Severity | | ----------------------------------------------- | --------- | -------- | | More than 5 unspent stealth addresses | -10 | Medium | | All payment amounts identical | -15 | High | | Consecutive payments less than 60 seconds apart | -20 | High | | Never withdrawn any payments | -5 | Info | | Connected wallet is the agent address | -5 | Info | ## What to Avoid ### 1. Withdrawing to the Same Address **Bad:** Withdraw all stealth addresses to one known wallet. ``` Stealth Address 1 (0.1 ETH) -> 0xMyMainWallet Stealth Address 2 (0.2 ETH) -> 0xMyMainWallet Stealth Address 3 (0.5 ETH) -> 0xMyMainWallet Observer: "These three stealth addresses all belong to the same person" ``` **Good:** Use a different destination for each withdrawal. ``` Stealth Address 1 -> 0xFresh1 Stealth Address 2 -> 0xFresh2 Stealth Address 3 -> 0xFresh3 Observer: "Three unrelated withdrawals to three unrelated addresses" ``` The Wraith agent warns you automatically: ```typescript theme={null} await agent.chat("withdraw all to 0xMyMainWallet"); // "Privacy concern — withdrawing all stealth addresses to a single // known wallet links every payment to your identity." ``` ### 2. Timing Correlation **Bad:** Withdraw multiple stealth addresses within seconds. ``` 14:00:00 — Withdraw from 0xStealth1 14:00:02 — Withdraw from 0xStealth2 14:00:04 — Withdraw from 0xStealth3 ``` **Good:** Space withdrawals hours or days apart. ### 3. Amount Fingerprinting **Bad:** Always send the exact same amount. ``` Send 0.100000 ETH to Stealth1 Send 0.100000 ETH to Stealth2 Send 0.100000 ETH to Stealth3 ``` **Good:** Vary amounts slightly. ``` Send 0.098372 ETH to Stealth1 Send 0.102841 ETH to Stealth2 Send 0.099127 ETH to Stealth3 ``` ### 4. Address Reuse Stealth addresses are one-time by design. The Wraith protocol enforces this — each payment generates a fresh address automatically. If you're building custom integrations with the [EVM crypto primitives](/sdk/chains/evm), call `generateStealthAddress()` for each payment. ### 5. Linking On-Chain Identity **Bad:** Withdraw from a stealth address directly to a wallet tied to your ENS, `.wraith` name, or known identity. **Good:** Use an intermediate address with no on-chain identity. ## How the Agent Helps The Wraith AI agent is privacy-paranoid by design. It will: * **Warn before risky actions** — explains the risk before executing * **Suggest alternatives** — recommends fresh addresses, spacing, and varied amounts * **Run privacy checks** — analyzes your activity and flags patterns * **Remember context** — factors past risky moves into future advice * **Respect your decision** — executes after warning if you insist ### Example: Agent Warning ```typescript theme={null} await agent.chat("withdraw all to 0xMyMainWallet"); ``` Response: ``` Privacy concern — withdrawing all stealth addresses to a single known wallet links every payment to your identity. Observers can trace all incoming stealth payments back to you. Recommendations: - Use a fresh address for each withdrawal - Space withdrawals hours apart - Withdraw to different destinations Proceed anyway? ``` ### Example: Proactive Detection ```typescript theme={null} await agent.chat("send 0.1 ETH to bob.wraith"); // ... later ... await agent.chat("send 0.1 ETH to carol.wraith"); // ... later ... await agent.chat("send 0.1 ETH to dave.wraith"); ``` Response on the third send: ``` Payment sent. Note: your last 3 payments were all exactly 0.1 ETH. Identical amounts create a fingerprint. Consider varying the amount. ``` ## Best Practices Summary | Practice | Why | | ------------------------------------------- | -------------------------------------------------- | | Use a fresh destination for each withdrawal | Prevents linking stealth addresses to one identity | | Space withdrawals at least 1 hour apart | Defeats timing correlation analysis | | Never withdraw to your connected wallet | Keeps your identity separate from stealth activity | | Vary payment amounts slightly | Prevents amount-based fingerprinting | | Use different times of day | Avoids timezone-based profiling | | Consolidate stealth addresses periodically | Reduces on-chain footprint | # Single-Chain Agent Source: https://docs.usewraith.xyz/guides/single-chain-agent Create an agent on one blockchain Create an agent on one chain and interact with it. This guide walks through the full lifecycle: creation, funding, payments, scanning, withdrawals, invoicing, and privacy. ## Create the Agent ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_live_abc123" }); // Sign a message to prove wallet ownership const message = "Sign to create Wraith agent"; const signature = await wallet.signMessage(message); const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: walletAddress, signature: signature, message: message, }); ``` The agent is now live on Horizen with: * A `.wraith` name: `alice.wraith` * An on-chain address for receiving direct transfers * A stealth meta-address for receiving private payments * An AI agent inside the TEE ```typescript theme={null} console.log(agent.info.name); // "alice" console.log(agent.info.chains); // [Chain.Horizen] console.log(agent.info.addresses[Chain.Horizen]); // "0x..." console.log(agent.info.metaAddresses[Chain.Horizen]); // "st:eth:0x..." ``` ## Fund the Agent On testnet, request tokens from the faucet: ```typescript theme={null} const res = await agent.chat("fund my wallet"); console.log(res.response); // "Wallet funded with testnet ETH. Balance: 0.5 ETH" ``` ## Check Balance ```typescript theme={null} const res = await agent.chat("what's my balance?"); // "Balance: 0.5 ETH, 0 ZEN, 0 USDC" ``` Or programmatically: ```typescript theme={null} const balance = await agent.getBalance(); console.log(balance.native); // "0.5" console.log(balance.tokens); // { ZEN: "0", USDC: "0" } ``` ## Send a Stealth Payment ```typescript theme={null} const res = await agent.chat("send 0.1 ETH to bob.wraith"); console.log(res.response); // "Payment sent — 0.1 ETH to bob.wraith via stealth address 0x7a3f..." ``` The agent: 1. Resolves `bob.wraith` to a stealth meta-address 2. Generates a one-time stealth address 3. Sends ETH to the stealth address 4. Publishes an on-chain announcement ## Scan for Incoming Payments ```typescript theme={null} const res = await agent.chat("scan for payments"); console.log(res.response); // "Found 2 incoming payments: // - 0.1 ETH at 0xabc... (balance: 0.1 ETH) // - 0.05 ETH at 0xdef... (balance: 0.05 ETH)" ``` ## Withdraw ### From a specific stealth address ```typescript theme={null} const res = await agent.chat("withdraw 0.05 ETH from 0xabc... to 0xMyWallet"); ``` ### Withdraw everything ```typescript theme={null} const res = await agent.chat("withdraw all to 0xMyWallet"); // The agent warns about privacy risks first ``` ## Create an Invoice ```typescript theme={null} const res = await agent.chat("create an invoice for 0.5 ETH with memo design work"); console.log(res.response); // "Invoice created — [Pay 0.5 ETH](https://pay.wraith.dev/invoice/uuid)" ``` Check invoice status: ```typescript theme={null} const res = await agent.chat("check my invoices"); ``` ## Schedule Recurring Payments ```typescript theme={null} const res = await agent.chat("schedule 0.1 ETH to bob.wraith every week"); console.log(res.response); // "Scheduled: 0.1 ETH to bob.wraith weekly, next run in 7 days" ``` Manage schedules: ```typescript theme={null} await agent.chat("list my schedules"); await agent.chat("cancel schedule abc-123"); ``` ## Privacy Check ```typescript theme={null} const res = await agent.chat("run a privacy check"); console.log(res.response); // "Privacy Score: 90/100 // Issues: // - (info) 3 unspent stealth addresses // Best Practices: // - Use a fresh destination for each withdrawal // - Space withdrawals at least 1 hour apart // - Vary payment amounts slightly" ``` ## Notifications ```typescript theme={null} const { notifications, unreadCount } = await agent.getNotifications(); console.log(unreadCount); // 2 await agent.markNotificationsRead(); await agent.clearNotifications(); ``` ## Conversations The agent maintains conversation history for context: ```typescript theme={null} // Start a conversation const res1 = await agent.chat("send 0.1 ETH to bob.wraith"); const convId = res1.conversationId; // Continue the same conversation const res2 = await agent.chat("what was the tx hash?", convId); // List and manage conversations const conversations = await agent.getConversations(); const messages = await agent.getMessages(convId); await agent.deleteConversation(convId); ``` ## Export Private Key ```typescript theme={null} const exportSig = await wallet.signMessage( "Export private key for agent " + agent.info.id ); const { secret } = await agent.exportKey( exportSig, "Export private key for agent " + agent.info.id ); console.log(secret); // "0x..." — the agent's private key ``` Requires a fresh wallet signature from the owner. ## Reconnecting ```typescript theme={null} // By agent ID const agent = wraith.agent("agent-uuid"); // By owner wallet const agent = await wraith.getAgentByWallet("0xMyWallet"); // By .wraith name const agent = await wraith.getAgentByName("alice"); ``` # How Stealth Payments Work Source: https://docs.usewraith.xyz/guides/stealth-payments Visual guide to stealth address cryptography Stealth addresses let you receive payments at one-time addresses that can't be linked to your identity. This guide explains the cryptography in plain language. ## The Problem On most blockchains, addresses are public. If you share your address to receive a payment, anyone can see every transaction you've received, track your total balance, and link your identity to all your financial activity. ## The Solution: Stealth Addresses Instead of receiving payments at a single reusable address, each payment goes to a **fresh one-time address** that only you can detect and spend from. Normal vs Stealth ## Key Concepts ### Stealth Meta-Address A stealth meta-address is what you publish or share. It contains two public keys: ``` st:eth:0x{spendingPubKey}{viewingPubKey} ``` * **Spending public key** — used to derive stealth addresses. The private key lets you spend. * **Viewing public key** — used to detect incoming payments. The private key lets you scan. This two-key design means you can give a third party the viewing key to detect payments on your behalf without giving them the ability to spend. ### How a Payment Works Stealth Payment Flow **Step 1: Sender generates a stealth address** ```typescript theme={null} import { generateStealthAddress } from "@wraith-protocol/sdk/chains/evm"; const { stealthAddress, ephemeralPubKey, viewTag } = generateStealthAddress( recipientSpendingPubKey, recipientViewingPubKey ); // stealthAddress: fresh one-time address // ephemeralPubKey: publish on-chain // viewTag: publish on-chain (1 byte for fast filtering) ``` The sender: 1. Generates a random ephemeral key pair `(r, R)` 2. Computes a shared secret `S = r * viewingPubKey` (ECDH) 3. Hashes the secret: `h = hash(S)` 4. Computes stealth address = `spendingPubKey + h * G` 5. Sends funds to the stealth address 6. Publishes announcement: `(R, viewTag)` **Step 2: Announcement** The sender publishes an on-chain announcement containing the ephemeral public key `R` and a view tag. This is public, but reveals nothing about who the recipient is. **Step 3: Recipient scans for payments** ```typescript theme={null} import { scanAnnouncements } from "@wraith-protocol/sdk/chains/evm"; const matched = scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, keys.spendingKey ); // matched: announcements that belong to you, with private keys ``` The recipient checks each announcement: 1. Compute shared secret `S = viewingKey * R` (same ECDH, other side) 2. Check view tag — if it doesn't match, skip (rejects \~255/256 non-matches) 3. Compute expected address = `spendingPubKey + hash(S) * G` 4. If expected address matches announced address, it's yours **Step 4: Recipient spends** ```typescript theme={null} import { deriveStealthPrivateKey } from "@wraith-protocol/sdk/chains/evm"; const privateKey = deriveStealthPrivateKey( keys.spendingKey, ephemeralPubKey, keys.viewingKey ); // privateKey controls the stealth address ``` The private key: `stealthPrivateKey = spendingKey + hash(sharedSecret) mod n` ## View Tags: Fast Filtering Without view tags, scanning requires a full ECDH computation + point addition for every announcement. View tags add a 1-byte shortcut: ``` With 1000 announcements: Without view tags: 1000 full checks With view tags: 1000 byte comparisons + ~4 full checks ``` The view tag eliminates \~255/256 non-matching announcements with a single byte comparison. ## EVM vs. Stellar The same concept works on both chains, adapted to their cryptographic primitives: | Step | EVM (secp256k1) | Stellar (ed25519) | | -------------- | -------------------------------------------- | ---------------------------------------- | | Key derivation | `keccak256(r)`, `keccak256(s)` of wallet sig | `SHA-256("wraith:spending:" \|\| sig)` | | ECDH | `secp256k1.getSharedSecret` | X25519 (Montgomery form) | | Hash to scalar | `keccak256(S) mod n` | `SHA-256("wraith:scalar:" \|\| S) mod L` | | View tag | `keccak256(S)[0]` | `SHA-256("wraith:tag:" \|\| S)[0]` | | Address format | `0x...` (20 bytes) | `G...` (56 chars) | | Signing | secp256k1 ECDSA | ed25519 with raw scalar | ## Standards EVM stealth addresses are based on: * **ERC-5564** — Stealth Address Messenger (announcement format) * **ERC-6538** — Stealth Meta-Address Registry (meta-address storage) Wraith extends these with: * **WraithNames** — human-readable `.wraith` name to meta-address mapping * **WraithSender** — atomic send + announce in one transaction * **WraithWithdrawer** — gas-sponsored withdrawals via EIP-7702 ## Visual Flow Stealth Payment Full Flow ## Using the Managed Agent With the Wraith agent, all of this is handled automatically: ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_..." }); const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: "0x...", signature: "0x...", }); // Sending handles stealth address generation + announcement await agent.chat("send 0.1 ETH to bob.wraith"); // Scanning handles announcement detection + key derivation await agent.chat("scan for payments"); // Withdrawing handles private key derivation + transaction signing await agent.chat("withdraw all to 0xMyWallet"); ``` # Introduction Source: https://docs.usewraith.xyz/introduction Multichain stealth address platform for private payments Wraith is a multichain stealth address platform. Send and receive private payments across EVM chains, Stellar, Solana, and CKB through a single SDK. An AI agent handles chain-specific crypto, key management, and privacy analysis inside Trusted Execution Environment hardware. ## How It Works Architecture You install the SDK, get an API key, and build. No servers to run. No keys to manage. No chain-specific crypto to implement. ## Quick Start ### 1. Install ```bash theme={null} npm install @wraith-protocol/sdk ``` ### 2. Create an Agent ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_..." }); const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: "0x...", signature: "0x...", }); ``` ### 3. Send a Private Payment ```typescript theme={null} const response = await agent.chat("send 0.1 ETH to bob.wraith"); console.log(response.response); // "Payment sent — 0.1 ETH to bob.wraith via stealth address 0x7a3f..." ``` ### 4. Scan for Incoming Payments ```typescript theme={null} const response = await agent.chat("scan for incoming payments"); // Agent detects payments sent to your stealth addresses ``` ## Supported Chains | Chain | Family | Status | Native Asset | | -------- | --------------- | ------- | ------------ | | Horizen | EVM | Live | ETH | | Ethereum | EVM | Planned | ETH | | Polygon | EVM | Planned | MATIC | | Base | EVM | Planned | ETH | | Stellar | Stellar | Live | XLM | | Solana | Solana | Live | SOL | | CKB | CKB (Cell/UTXO) | Live | CKB | Adding a new EVM chain requires only configuration (RPC URL + contract addresses). Adding a new chain family requires implementing the `ChainConnector` interface. ## Key Features * **Stealth addresses** — every payment goes to a fresh one-time address. No on-chain link between sender and receiver. * **Multichain** — one agent, multiple chains. Chat naturally and the AI routes to the right chain. * **AI-powered** — natural language interface for payments, scanning, withdrawals, and privacy analysis. * **TEE security** — private keys derived inside Intel TDX hardware enclaves. Never stored on disk. * **Privacy scoring** — the agent proactively warns about timing analysis, address correlation, and other privacy risks. ## Documentation ### Getting Started * [Getting Started](getting-started) — install, create an agent, send your first payment ### SDK Reference * [SDK Overview](sdk/overview) — package structure, entry points, `Chain` enum * [Agent Client](sdk/agent-client) — `Wraith` and `WraithAgent` class API * [EVM Crypto Primitives](sdk/chains/evm) — low-level secp256k1 stealth address functions * [Stellar Crypto Primitives](sdk/chains/stellar) — low-level ed25519 stealth address functions for Stellar * [Solana Crypto Primitives](sdk/chains/solana) — low-level ed25519 stealth address functions for Solana * [CKB Crypto Primitives](sdk/chains/ckb) — low-level secp256k1 stealth address functions for Nervos CKB ### Architecture * [Architecture Overview](architecture/overview) — system diagram, modules, database schema * [Chain Connectors](architecture/chain-connectors) — `ChainConnector` interface, adding chains * [TEE Security](architecture/tee) — key derivation, attestation, privacy model ### Guides * [Single-Chain Agent](guides/single-chain-agent) — full lifecycle on one chain * [Multichain Agent](guides/multichain-agent) — one name, multiple chains * [Bring Your Own Model](guides/bring-your-own-model) — use OpenAI/Claude instead of Gemini * [Stealth Payments](guides/stealth-payments) — how stealth addresses work (with visuals) * [Privacy Best Practices](guides/privacy-best-practices) — scoring, what to avoid ### Contracts * [EVM Contracts](contracts/evm) — Solidity contract specs and deployment * [Stellar Contracts](contracts/stellar) — Soroban contract specs and deployment * [Solana Contracts](contracts/solana) — Anchor program specs and deployment * [CKB Contracts](contracts/ckb) — RISC-V lock script and the Cell model ### API Reference * [HTTP Endpoints](api-reference/endpoints) — full TEE server API * [TypeScript Types](api-reference/types) — all SDK types # Roadmap Source: https://docs.usewraith.xyz/roadmap Development phases and upcoming features ## Phase 1 — Foundation (done) The core protocol is complete: stealth address cryptography, smart contracts, AI agent, and TEE deployment. * [x] **Stealth address cryptography** — secp256k1 (EVM/CKB) and ed25519 (Stellar/Solana). Full key derivation, stealth address generation, scanning, and spending. * [x] **ERC-5564 and ERC-6538 implementations** — Stealth Address Messenger (announcement format) and Stealth Meta-Address Registry (meta-address storage) standards. * [x] **Smart contracts** — Solidity (EVM), Soroban/Rust (Stellar), Anchor (Solana), and RISC-V (CKB). Announcer, registry, sender, and name contracts on all four chain families. * [x] **Atomic send-and-announce (WraithSender)** — transfer funds to a stealth address and publish the announcement in one transaction. No separate steps for the sender. * [x] **Batch send and batch withdraw** — multiple stealth addresses in one transaction. Reduces gas cost and simplifies bulk operations. * [x] **Gas-sponsored withdrawals via EIP-7702 (WraithWithdrawer)** — a sponsor pays gas on behalf of the stealth address. The stealth address holder doesn't need native tokens for gas. * [x] **Human-readable .wraith names** — on-chain name registry mapping names to stealth meta-addresses. Deployed on all four chains. * [x] **Subgraph indexing via Goldsky** — real-time indexing of Announcement events for efficient payment scanning on EVM chains. * [x] **AI agent system** — Gemini integration with 17 tools. Natural language stealth payments, scanning, withdrawals, invoicing, scheduling, and privacy analysis. * [x] **TEE deployment** — Phala TEE (Intel TDX) with DStack key derivation. Agent private keys derived inside the enclave and never stored on disk. * [x] **Payment links and invoicing** — shareable payment URLs, QR codes, and payment status tracking. Agents create invoices and get notified when paid. * [x] **Scheduled payments** — recurring stealth payments with daily/weekly/monthly intervals. Pause, resume, and cancel support. * [x] **Privacy analysis** — scoring engine that detects timing patterns, identical amounts, address correlation, and consolidation risks. The AI agent warns proactively. ## Phase 2 — Unified Platform (done) Consolidating chain-specific implementations into a single SDK and multichain TEE server. * [x] **Unified SDK (`@wraith-protocol/sdk`)** — single npm package with subpath exports: `chains/evm`, `chains/stellar`, `chains/solana`, and `chains/ckb`. * [x] **EVM chain crypto module (`@wraith-protocol/sdk/chains/evm`)** — secp256k1 stealth address primitives. Key derivation, stealth address generation, scanning, spending, meta-address encoding, name signing, and transaction builders. * [x] **Stellar chain crypto module (`@wraith-protocol/sdk/chains/stellar`)** — ed25519 stealth address primitives. X25519 ECDH, domain-separated hashing, scalar math, and raw scalar signing. * [x] **Solana chain crypto module (`@wraith-protocol/sdk/chains/solana`)** — ed25519 stealth address primitives. Base58 address encoding, Anchor program integration, and transaction signing. * [x] **CKB chain crypto module (`@wraith-protocol/sdk/chains/ckb`)** — secp256k1 stealth address primitives adapted for CKB's Cell model. SHA-256 shared secret hashing, blake160 address hashing, Cell-based scanning. * [x] **Spectre TEE server** — multichain NestJS server with the `ChainConnector` interface. One deployment handles all chains. * [x] **EVM chain connector** — single `EVMConnector` class covering all EVM chains. * [x] **Stellar chain connector** — `StellarConnector` handling Stellar-specific operations. * [x] **Solana chain connector** — `SolanaConnector` implementing the `ChainConnector` interface. * [x] **CKB chain connector** — `CKBConnector` implementing the `ChainConnector` interface. `get_cells` RPC scanning, Cell-based announcements. * [x] **Smart contracts on 4 chains** — EVM (Solidity), Stellar (Soroban/Rust), Solana (Anchor), CKB (RISC-V). Full test suites. * [x] **Demo app** — multichain demo at [demo.usewraith.xyz](https://demo.usewraith.xyz) with wallet integration for all 4 chains (RainbowKit, Freighter, Solana Wallet Adapter, CCC). * [x] **Developer documentation** — documentation site at [docs.usewraith.xyz](https://docs.usewraith.xyz) with SDK reference, architecture guides, and API reference. ## Phase 3 — Platform Launch (in progress) Public developer API with managed infrastructure. Consumer-facing products. * [ ] **Managed API** — developer API keys, authentication, and rate limiting. Developers sign up, get a key, and build. * [ ] **Developer console** — usage analytics, agent management, and billing at [console.usewraith.xyz](https://console.usewraith.xyz). * [ ] **Mainnet deployments** — deploy contracts to mainnet on all supported chains. * [ ] **Mobile app** — consumer wallet for sending and receiving stealth payments. Simple send/receive interface that handles stealth address mechanics behind the scenes. * [ ] **Fiat onramp/offramp** — buy crypto and have it land directly in a stealth address. Sell from stealth addresses with the same unlinkability. * [ ] **Fiber Network integration** — stealth addresses over CKB's payment channel network for instant, near-free private payments. * [ ] **Additional EVM chain deployments** — each new EVM chain is config + contract deployment with no code changes: * Ethereum mainnet * Base * Polygon * Arbitrum * Optimism * [ ] **Cross-chain agent operations** — a single agent operating across multiple chains simultaneously. One `.wraith` name, multiple chain identities, AI routes to the correct chain. * [ ] **Mobile SDK compatibility** — ensure `@wraith-protocol/sdk` works in React Native and Expo environments. ### Why EVM expansion is fast Adding a new EVM chain requires: 1. Deploy the same 4 Solidity contracts (Announcer, Registry, Sender, Names) 2. Set up announcement indexing (subgraph or custom indexer) 3. Register the chain config in the connector registry No new code. The `EVMConnector` handles all EVM chains with the same logic — only the RPC URL, chain ID, and contract addresses differ. ```typescript theme={null} import { Chain } from "@wraith-protocol/sdk"; // Adding Arbitrum — same connector, new config chainRegistry.register(Chain.Arbitrum, new EVMConnector({ chainId: 42161, rpcUrl: "https://arb1.arbitrum.io/rpc", explorerUrl: "https://arbiscan.io", contracts: { announcer: "0x...", registry: "0x...", sender: "0x...", names: "0x...", }, })); ``` ## Phase 4 — Chain Expansion New chain families beyond the current four. Each requires a new `ChainConnector` implementation and a contract set, but the agent core, AI engine, storage, and tools remain identical. * [ ] **Hedera** — EVM-compatible smart contracts via Hedera Smart Contract Service. HTS for token transfers, HCS for announcement messages, Mirror Nodes for indexing. * [ ] **Starknet** — STARK-friendly curve (different cryptographic primitives). Cairo contracts. * [ ] **Sui** — ed25519 (crypto reusable from Stellar module). Move contracts. * [ ] **Aptos** — ed25519 (crypto reusable from Stellar module). Move contracts. * [ ] **TON** — ed25519 (crypto reusable from Stellar module). FunC/Tact contracts. * [ ] **On-chain private messaging** — ECDH encrypted messages using existing stealth meta-address keys. No new key infrastructure needed. * [ ] **ERC-4337 Paymaster** — alternative to EIP-7702 for smart contract wallet compatibility. ### Non-EVM chain effort Unlike EVM chains (which are config-only), each new chain family requires: 1. A new `ChainConnector` class implementing the standard interface 2. Stealth address contracts deployed on the target chain 3. Chain-specific transaction building and signing logic However, the ed25519 cryptographic primitives from `@wraith-protocol/sdk/chains/stellar` are reusable for Sui, Aptos, and TON. Only the address encoding, transaction format, and contract layer differ. Starknet is a special case requiring a different curve entirely. ## Phase 5 — Research * [ ] **FHE-DKSAP** — Fully Homomorphic Encryption-based Dual Key Stealth Address Protocol. Enables trustless outsourced scanning: a third-party scanning service can find incoming transfers for a user without ever seeing the viewing key. The scanning computation happens on encrypted data. # Agent Client Source: https://docs.usewraith.xyz/sdk/agent-client Wraith and WraithAgent classes for the managed platform The `Wraith` and `WraithAgent` classes are the primary interface for building with Wraith. They handle all communication with the managed TEE infrastructure. ## `Wraith` Class ### Constructor ```typescript theme={null} import { Wraith } from "@wraith-protocol/sdk"; const wraith = new Wraith({ apiKey: "wraith_live_abc123", }); ``` #### `WraithConfig` | Parameter | Type | Required | Description | | ------------- | ---------------------------------- | -------- | ---------------------------------------------------------------- | | `apiKey` | `string` | Yes | Your Wraith platform API key | | `baseUrl` | `string` | No | Override the API base URL (defaults to `https://api.wraith.dev`) | | `ai` | `object` | No | Bring your own AI model configuration | | `ai.provider` | `"gemini" \| "openai" \| "claude"` | No | AI provider name | | `ai.apiKey` | `string` | No | Your AI provider API key | ### `wraith.createAgent(config)` Create a new agent with a `.wraith` name and stealth keys. ```typescript theme={null} import { Chain } from "@wraith-protocol/sdk"; const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: "0xYourWallet", signature: "0xSignatureFromWallet", }); ``` #### `AgentConfig` | Parameter | Type | Required | Description | | ----------- | ------------------ | -------- | ------------------------------------------ | | `name` | `string` | Yes | Agent name (becomes `name.wraith`) | | `chain` | `Chain \| Chain[]` | Yes | Target chain(s) for the agent | | `wallet` | `string` | Yes | Owner wallet address | | `signature` | `string` | Yes | EIP-191 signature proving wallet ownership | | `message` | `string` | No | Message that was signed (for verification) | Returns a `WraithAgent` instance. ### `wraith.agent(agentId)` Connect to an existing agent by ID. Does not make a network call. ```typescript theme={null} const agent = wraith.agent("agent-uuid-here"); ``` ### `wraith.getAgentByWallet(walletAddress)` Look up an agent by its owner wallet address. ```typescript theme={null} const agent = await wraith.getAgentByWallet("0xYourWallet"); ``` ### `wraith.getAgentByName(name)` Look up an agent by its `.wraith` name. ```typescript theme={null} const agent = await wraith.getAgentByName("alice"); ``` ### `wraith.listAgents()` List all agents associated with your API key. ```typescript theme={null} const agents = await wraith.listAgents(); // AgentInfo[] ``` *** ## `WraithAgent` Class Returned by `wraith.createAgent()`, `wraith.getAgentByWallet()`, and `wraith.getAgentByName()`. Provides all agent operations. ### `agent.info` The agent's identity information. ```typescript theme={null} console.log(agent.info.id); // "uuid-..." console.log(agent.info.name); // "alice" console.log(agent.info.chains); // [Chain.Horizen] console.log(agent.info.addresses[Chain.Horizen]); // "0x..." console.log(agent.info.metaAddresses[Chain.Horizen]); // "st:eth:0x..." ``` ### `agent.chat(message, conversationId?)` Natural language interaction with the AI agent. ```typescript theme={null} const response = await agent.chat("send 0.1 ETH to bob.wraith"); ``` | Parameter | Type | Required | Description | | ---------------- | -------- | -------- | --------------------------------- | | `message` | `string` | Yes | Natural language message | | `conversationId` | `string` | No | Continue an existing conversation | Returns a `ChatResponse`: ```typescript theme={null} const res = await agent.chat("send 0.1 ETH to bob.wraith"); console.log(res.response); // "Payment sent — 0.1 ETH to bob.wraith..." console.log(res.toolCalls); // [{ name: "send_payment", status: "success" }] console.log(res.conversationId); // "conv-uuid" // Continue the conversation const followUp = await agent.chat("what was the tx hash?", res.conversationId); ``` ### `agent.getStatus()` Get the agent's current status including balance and stats. ```typescript theme={null} const status = await agent.getStatus(); ``` ### `agent.getBalance()` Get the agent's balance across all assets. ```typescript theme={null} const balance = await agent.getBalance(); console.log(balance.native); // "1.5" console.log(balance.tokens); // { ZEN: "100.0", USDC: "50.0" } ``` ### `agent.scanPayments()` Scan for incoming stealth payments via the AI agent. ```typescript theme={null} const payments = await agent.scanPayments(); // Payment[] ``` ### `agent.exportKey(signature, message)` Export the agent's private key. Requires a fresh wallet signature for verification. ```typescript theme={null} const sig = await wallet.signMessage("Export private key for agent " + agent.info.id); const { secret } = await agent.exportKey(sig, "Export private key for agent " + agent.info.id); console.log(secret); // "0x..." ``` ### `agent.getConversations()` List all conversations with this agent. ```typescript theme={null} const conversations = await agent.getConversations(); ``` ### `agent.getMessages(conversationId)` Get messages from a specific conversation. ```typescript theme={null} const messages = await agent.getMessages("conv-uuid"); // [{ role: "user", text: "..." }, { role: "agent", text: "..." }] ``` ### `agent.deleteConversation(conversationId)` Delete a conversation and its messages. ```typescript theme={null} await agent.deleteConversation("conv-uuid"); ``` ### `agent.getNotifications()` Get notifications for this agent. ```typescript theme={null} const { notifications, unreadCount } = await agent.getNotifications(); ``` ### `agent.markNotificationsRead()` Mark all notifications as read. ```typescript theme={null} await agent.markNotificationsRead(); ``` ### `agent.clearNotifications()` Delete all notifications. ```typescript theme={null} await agent.clearNotifications(); ``` *** ## AI-Powered Tools When you use `agent.chat()`, the AI agent has access to these tools: | Tool | What It Does | | ------------------ | ---------------------------------------------------------- | | `send_payment` | Send a stealth payment to a meta-address or `.wraith` name | | `pay_agent` | Pay another agent by `.wraith` name | | `scan_payments` | Scan for incoming stealth payments | | `get_balance` | Check wallet balance (native + tokens) | | `create_invoice` | Create a payment invoice with a shareable link | | `check_invoices` | Check invoice payment statuses | | `withdraw` | Withdraw from a specific stealth address | | `withdraw_all` | Withdraw from all stealth addresses | | `schedule_payment` | Schedule a recurring payment | | `list_schedules` | List scheduled payments | | `cancel_schedule` | Cancel a scheduled payment | | `resolve_name` | Look up a `.wraith` name | | `register_name` | Register a `.wraith` name on-chain | | `get_agent_info` | Get full agent identity and TEE status | | `fund_wallet` | Request testnet tokens from faucet | | `privacy_check` | Run a privacy analysis with scoring | ### Example: Invoicing ```typescript theme={null} const res = await agent.chat("create an invoice for 1.0 ETH with memo consulting fee"); console.log(res.response); // "Invoice created — [Pay 1.0 ETH](https://pay.wraith.dev/invoice/...)" ``` ### Example: Privacy Check ```typescript theme={null} const res = await agent.chat("run a privacy check"); console.log(res.response); // "Privacy Score: 85/100 // Issues: // - (medium) 7 unspent stealth addresses — consider consolidating // Best Practices: // - Use a fresh destination for each withdrawal // - Space withdrawals at least 1 hour apart" ``` *** ## Authentication Every request includes: | Header | Value | Purpose | | --------------- | ------------------------------------ | ----------------------------------- | | `Authorization` | `Bearer wraith_...` | Platform API key | | `X-AI-Provider` | `"openai"` / `"claude"` / `"gemini"` | Optional — for bring your own model | | `X-AI-Key` | `sk-...` | Optional — your AI provider key | ## Error Handling All methods throw on failure. The error message comes from the server: ```typescript theme={null} try { await agent.chat("send 100 ETH to bob.wraith"); } catch (err) { console.error(err.message); // "Insufficient balance" } ``` # CKB Primitives Source: https://docs.usewraith.xyz/sdk/chains/ckb Stealth address crypto for Nervos CKB using secp256k1 and the Cell model Low-level stealth address functions for Nervos CKB using secp256k1. Import from `@wraith-protocol/sdk/chains/ckb`. Most developers should use the [Agent Client](/sdk/agent-client) instead. These primitives are for power users building custom stealth address integrations on CKB. ## The Cell Model CKB is fundamentally different from account-based chains like EVM or Solana. CKB uses a **UTXO-based Cell model** where all state is stored in Cells. A Cell has four fields: | Field | Purpose | | ---------- | -------------------------------------------------------------- | | `capacity` | Amount of CKB stored (like a UTXO value) | | `lock` | Script that must be satisfied to spend (like an address/owner) | | `type` | Optional script for additional validation | | `data` | Arbitrary data | ### Cells Are Announcements On EVM chains, stealth address announcements are separate events emitted by an Announcer contract. On CKB, **the Cell itself is the announcement**. There is no separate announcer. The stealth lock script `args` contain both the ephemeral public key and the stealth address hash: ``` lock.args = ephemeral_pubkey (33 bytes) || blake160(stealth_pubkey) (20 bytes) ``` This is 53 bytes total. The announcement data is embedded directly in the Cell's lock script — no separate transaction or event needed. ### Scanning = Querying Live Cells Instead of querying a subgraph or parsing event logs, scanning on CKB means querying all live Cells that use the `stealth-lock` code hash. CKB's built-in indexer RPC (`get_cells`) supports filtering by lock script code hash, so only stealth Cells are returned. ### Spending = Consuming Cells To withdraw from a stealth address, you consume the stealth Cell and create a new Cell at the destination. The stealth lock script verifies the secp256k1 signature against the pubkey hash in the args. ## Installation ```bash theme={null} npm install @wraith-protocol/sdk ``` No additional peer dependencies required. CKB RPC calls use native `fetch`. Address hashing uses `@noble/hashes` (blake2b), which is already a direct dependency. ## Import ```typescript theme={null} import { deriveStealthKeys, generateStealthAddress, checkStealthCell, scanStealthCells, deriveStealthPrivateKey, encodeStealthMetaAddress, decodeStealthMetaAddress, blake160, fetchStealthCells, hashName, buildRegisterName, buildResolveName, metaAddressFromNameData, getDeployment, DEPLOYMENTS, STEALTH_SIGNING_MESSAGE, SCHEME_ID, META_ADDRESS_PREFIX, } from "@wraith-protocol/sdk/chains/ckb"; ``` ## Types ```typescript theme={null} type HexString = `0x${string}`; interface StealthKeys { spendingKey: HexString; // 32-byte private key viewingKey: HexString; // 32-byte private key spendingPubKey: HexString; // 33-byte compressed secp256k1 viewingPubKey: HexString; // 33-byte compressed secp256k1 } interface GeneratedStealthAddress { stealthPubKey: HexString; // 33-byte compressed stealth public key stealthPubKeyHash: HexString; // 20-byte blake160 hash ephemeralPubKey: HexString; // 33-byte compressed ephemeral public key lockArgs: HexString; // 53 bytes: ephemeral_pub || blake160(stealth_pub) } // CKB doesn't have separate announcements — Cells ARE the announcements interface StealthCell { txHash: HexString; index: number; capacity: bigint; // in shannons (1 CKB = 10^8 shannons) lockArgs: HexString; // 53 bytes ephemeralPubKey: HexString; // extracted from lockArgs[0:33] stealthPubKeyHash: HexString; // extracted from lockArgs[33:53] } interface MatchedStealthCell extends StealthCell { stealthPrivateKey: HexString; } ``` ## Key Differences from EVM CKB uses the same secp256k1 curve as EVM but different hash functions and a completely different state model. | Aspect | EVM | CKB | | ------------------ | --------------------------------- | ------------------------------------ | | Model | Account-based | UTXO (Cell) | | Curve | secp256k1 | secp256k1 (same) | | Announcement | Separate Announcer contract event | Embedded in Cell lock script args | | Address hash | `keccak256(pubkey)[12:32]` | `blake2b(pubkey)[0:20]` (blake160) | | Shared secret hash | `keccak256(ECDH_shared)` | `SHA-256(ECDH_shared)` | | Scanning | Query subgraph for events | Query live Cells via `get_cells` RPC | | Address format | `0x` + 20-byte hex | bech32m CKB address | | Min balance | None (EOA) | 61 CKB for cell capacity | | Spending | `sendTransaction` | Consume Cell, create new Cell | | View tags | Yes (fast rejection) | No (check every Cell) | ## Constants ```typescript theme={null} const STEALTH_SIGNING_MESSAGE = "Sign this message to generate your Wraith stealth keys.\n\nChain: CKB\nNote: This signature is used for key derivation only and does not authorize any transaction."; const SCHEME_ID = 1; const META_ADDRESS_PREFIX = "st:ckb:"; ``` *** ## Functions ### `deriveStealthKeys(signature)` Derive spending and viewing key pairs from a 65-byte ECDSA signature. Identical to the EVM module. ```typescript theme={null} const signature = await wallet.signMessage(STEALTH_SIGNING_MESSAGE); const keys = deriveStealthKeys(signature as HexString); console.log(keys.spendingKey); // "0x..." (32-byte private key) console.log(keys.viewingKey); // "0x..." (32-byte private key) console.log(keys.spendingPubKey); // "0x02..." (33-byte compressed) console.log(keys.viewingPubKey); // "0x03..." (33-byte compressed) ``` **Algorithm:** Same as EVM — split signature `r`/`s`, `keccak256` each to get spending and viewing keys. ### `generateStealthAddress(spendingPubKey, viewingPubKey, ephemeralKey?)` Generate a one-time stealth address with lock script args for CKB. ```typescript theme={null} const result = generateStealthAddress( keys.spendingPubKey, keys.viewingPubKey ); console.log(result.stealthPubKey); // "0x02..." (33-byte stealth public key) console.log(result.stealthPubKeyHash); // "0x..." (20-byte blake160) console.log(result.ephemeralPubKey); // "0x03..." (33-byte ephemeral key) console.log(result.lockArgs); // "0x..." (53 bytes: ephemeral || blake160) ``` **Algorithm:** 1. Generate random ephemeral key pair `(r, R = r * G)` 2. Compute ECDH shared secret `S = r * viewingPubKey` (compressed) 3. `hashedSecret = SHA-256(S)` — **not** keccak256 like EVM 4. `stealthPubKey = spendingPubKey + hashedSecret * G` 5. `stealthPubKeyHash = blake160(stealthPubKey)` — blake2b with "ckb-default-hash", first 20 bytes 6. `lockArgs = ephemeralPubKey || stealthPubKeyHash` ### `blake160(data)` CKB's address hashing function. blake2b with `"ckb-default-hash"` personalization, truncated to 20 bytes. ```typescript theme={null} const hash = blake160(publicKeyBytes); // Uint8Array (20 bytes) ``` The personalization string is critical — without it, hashes won't match CKB's on-chain verification. ### `checkStealthCell(cell, viewingKey, spendingPubKey)` Check if a stealth Cell belongs to you. ```typescript theme={null} const result = checkStealthCell(cell, keys.viewingKey, keys.spendingPubKey); if (result.isMatch) { console.log("This cell is ours"); } ``` **Algorithm:** 1. Extract `ephemeralPubKey = cell.lockArgs[0:33]` 2. Compute ECDH shared secret `S = viewingKey * ephemeralPubKey` 3. `hashedSecret = SHA-256(S)` 4. `expectedPubKey = spendingPubKey + hashedSecret * G` 5. `expectedHash = blake160(expectedPubKey)` 6. Compare `expectedHash` with `cell.lockArgs[33:53]` No view tag optimization — every Cell is fully checked. This is acceptable because CKB's `get_cells` RPC already filters to only stealth-lock Cells. ### `scanStealthCells(cells, viewingKey, spendingPubKey, spendingKey)` Scan an array of stealth Cells and return the ones that belong to you. ```typescript theme={null} const cells = await fetchStealthCells("ckb"); const matched = scanStealthCells( cells, keys.viewingKey, keys.spendingPubKey, keys.spendingKey ); for (const m of matched) { console.log(m.txHash); // Cell outpoint console.log(m.capacity); // CKB amount in shannons console.log(m.stealthPrivateKey); // private key to spend this Cell } ``` ### `deriveStealthPrivateKey(spendingKey, ephemeralPubKey, viewingKey)` Compute the private key that controls a specific stealth Cell. ```typescript theme={null} const privateKey = deriveStealthPrivateKey( keys.spendingKey, cell.ephemeralPubKey, keys.viewingKey ); // Use this key to sign the transaction that consumes the stealth Cell ``` **Algorithm:** 1. `S = viewingKey * ephemeralPubKey` (shared secret) 2. `hashedSecret = SHA-256(S)` — **not** keccak256 3. `stealthPrivateKey = (spendingKey + hashedSecret) mod n` ### `encodeStealthMetaAddress(spendingPubKey, viewingPubKey)` Encode two public keys into a CKB stealth meta-address string. ```typescript theme={null} const metaAddress = encodeStealthMetaAddress( keys.spendingPubKey, keys.viewingPubKey ); // "st:ckb:02abc...03def..." ``` Format: `st:ckb:{spendingPubKeyHex}{viewingPubKeyHex}` — 132 hex chars (two 33-byte compressed secp256k1 keys), same structure as EVM. ### `decodeStealthMetaAddress(metaAddress)` Decode a CKB meta-address back into its component public keys. ```typescript theme={null} const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress( "st:ckb:02abc...03def..." ); ``` *** ## End-to-End Flow ```typescript theme={null} import { deriveStealthKeys, generateStealthAddress, scanStealthCells, deriveStealthPrivateKey, encodeStealthMetaAddress, decodeStealthMetaAddress, fetchStealthCells, STEALTH_SIGNING_MESSAGE, } from "@wraith-protocol/sdk/chains/ckb"; import type { HexString } from "@wraith-protocol/sdk/chains/ckb"; // 1. Recipient: derive keys from wallet signature const sig = await wallet.signMessage(STEALTH_SIGNING_MESSAGE); const keys = deriveStealthKeys(sig as HexString); // 2. Recipient: publish stealth meta-address const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // Share "st:ckb:..." or register as a .wraith name via buildRegisterName() // 3. Sender: generate stealth address from meta-address const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress(metaAddress); const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); // 4. Sender: create a Cell with stealth-lock // lock.code_hash = stealth-lock code hash // lock.args = stealth.lockArgs (53 bytes: ephemeral_pub || blake160) // capacity = payment amount (minimum 61 CKB) // 5. Recipient: scan stealth Cells const cells = await fetchStealthCells("ckb"); const matched = scanStealthCells( cells, keys.viewingKey, keys.spendingPubKey, keys.spendingKey ); // 6. Recipient: spend from stealth Cell for (const m of matched) { // Build a CKB transaction: // Input: the matched stealth Cell // Output: new Cell at destination with regular lock // Witness: signature with m.stealthPrivateKey // Submit via CKB RPC } ``` *** ## Names `.wraith` name registration on CKB uses the Cell model. Each name is a live Cell whose type script identifies the name and whose data holds the stealth meta-address. Ownership is proven by the Cell's lock script — whoever can spend the Cell owns the name. This is fundamentally different from EVM and Solana, where ownership is proven by a signature from the spending key. ### `hashName(name)` Compute the blake2b hash of a name string. The result is used as the type script `args` to identify the name Cell. ```typescript theme={null} const nameHash = hashName("alice"); // "0x..." (32-byte blake2b hash with "ckb-default-hash" personalization) ``` ### `buildRegisterName({ name, spendingPubKey, viewingPubKey })` Build the type script and Cell data for creating a `.wraith` name Cell. Returns the type script object and the 66-byte data payload. ```typescript theme={null} import { buildRegisterName, deriveStealthKeys, getDeployment, STEALTH_SIGNING_MESSAGE, } from "@wraith-protocol/sdk/chains/ckb"; import type { HexString } from "@wraith-protocol/sdk/chains/ckb"; const sig = await wallet.signMessage(STEALTH_SIGNING_MESSAGE); const keys = deriveStealthKeys(sig as HexString); const deployment = getDeployment("ckb"); const { typeScript, data } = buildRegisterName({ name: "alice", spendingPubKey: keys.spendingPubKey, viewingPubKey: keys.viewingPubKey, }); // Create a Cell with: // lock: your wallet's lock script (you are the owner) // type: typeScript (wraith-names-type with args = blake2b("alice")) // data: 66 bytes (spending_pub || viewing_pub) // capacity: enough to cover the Cell size (minimum ~130 CKB) ``` The type script returned uses the `wraith-names-type` code hash from the deployment config. The `args` field is `hashName(name)`. ### `buildResolveName({ name })` Build the type script for querying a name Cell. Use this with the CKB `get_cells` RPC to find the Cell that holds a name's meta-address. ```typescript theme={null} import { buildResolveName, metaAddressFromNameData } from "@wraith-protocol/sdk/chains/ckb"; const { typeScript } = buildResolveName({ name: "alice" }); // Query CKB for live Cells with this type script const result = await fetch(rpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: 0, jsonrpc: "2.0", method: "get_cells", params: [{ script: { code_hash: typeScript.codeHash, hash_type: typeScript.hashType, args: typeScript.args, }, script_type: "type", }, "asc", "0x1"], }), }); const data = await result.json(); const cell = data.result?.objects?.[0]; if (cell) { const metaAddress = metaAddressFromNameData(cell.output_data); // { spendingPubKey: "0x02...", viewingPubKey: "0x03..." } } ``` ### `metaAddressFromNameData(data)` Parse the 66-byte Cell data from a name Cell into its component public keys. ```typescript theme={null} const { spendingPubKey, viewingPubKey } = metaAddressFromNameData(cellData); // spendingPubKey: "0x02..." (33-byte compressed secp256k1) // viewingPubKey: "0x03..." (33-byte compressed secp256k1) ``` The data format is `spendingPubKey (33 bytes) || viewingPubKey (33 bytes)` = 66 bytes total. ### Registering a Name ```typescript theme={null} import { deriveStealthKeys, buildRegisterName, getDeployment, STEALTH_SIGNING_MESSAGE, } from "@wraith-protocol/sdk/chains/ckb"; import type { HexString } from "@wraith-protocol/sdk/chains/ckb"; const sig = await wallet.signMessage(STEALTH_SIGNING_MESSAGE); const keys = deriveStealthKeys(sig as HexString); const deployment = getDeployment("ckb"); const { typeScript, data } = buildRegisterName({ name: "alice", spendingPubKey: keys.spendingPubKey, viewingPubKey: keys.viewingPubKey, }); // Build a CKB transaction: // Output: { // capacity: "0x" + (130_00000000n).toString(16), // lock: walletLockScript, // your wallet's lock = you own the name // type: { // codeHash: typeScript.codeHash, // hashType: typeScript.hashType, // args: typeScript.args, // blake2b("alice") // }, // } // Output data: data // 66 bytes meta-address // Cell dep: deployment.cellDeps.namesType // Submit via CKB RPC ``` Ownership is determined by the Cell's lock script. Whoever can spend the Cell controls the name. To transfer a name, consume the Cell and create a new one with a different lock script. No signature verification against the spending key is needed — the lock script handles authorization. ### Resolving a Name ```typescript theme={null} import { buildResolveName, metaAddressFromNameData, generateStealthAddress, getDeployment, } from "@wraith-protocol/sdk/chains/ckb"; const deployment = getDeployment("ckb"); const { typeScript } = buildResolveName({ name: "alice" }); // Query for the name Cell const response = await fetch(deployment.rpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: 0, jsonrpc: "2.0", method: "get_cells", params: [{ script: { code_hash: typeScript.codeHash, hash_type: typeScript.hashType, args: typeScript.args, }, script_type: "type", }, "asc", "0x1"], }), }); const result = await response.json(); const cell = result.result?.objects?.[0]; if (cell) { const { spendingPubKey, viewingPubKey } = metaAddressFromNameData(cell.output_data); const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); // Send a stealth payment to alice.wraith on CKB } ``` ### CKB Names vs Other Chains | Aspect | EVM / Solana | CKB | | --------------- | ------------------------------------------ | --------------------------------------- | | Ownership proof | Spending key signature | Cell lock script (whoever can spend) | | Storage | Contract state / PDA | Live Cell | | Lookup | `resolve(name)` contract call | `get_cells` RPC filtered by type script | | Transfer | Not supported (re-register) | Consume Cell, create with new lock | | Data size | 64 bytes (ed25519) or 66 bytes (secp256k1) | 66 bytes (secp256k1) | | Name hash | keccak256 (EVM), SHA-256 (Solana/Stellar) | blake2b with "ckb-default-hash" | ### CKB-Specific Considerations * **Minimum capacity:** A stealth-lock Cell requires at least 61 CKB due to the 53-byte args. Senders must send at least this amount. * **No view tags:** Every Cell must be fully checked. CKB's `get_cells` RPC already filters by lock script code hash, so only stealth Cells are examined. * **blake2b personalization:** CKB uses `"ckb-default-hash"` as the blake2b personalization parameter. This **must** be included or hashes won't match on-chain verification. * **UTXO spending:** Withdrawing means consuming the Cell and creating a new one at the destination. The transaction fee is deducted from the Cell's capacity. * **Name ownership:** On CKB, name ownership is proven by the Cell's lock script, not by a signature from the spending key like on EVM. Whoever can spend the name Cell controls the name. ## Chain Deployments ### `getDeployment(chain)` ```typescript theme={null} const deployment = getDeployment("ckb"); // { // network: "testnet", // rpcUrl: "https://testnet.ckbapp.dev", // explorerUrl: "https://pudge.explorer.nervos.org", // contracts: { // stealthLockCodeHash: "0x...", // namesTypeCodeHash: "0xc133817d433f72ea16a2404adaf961524e9572c8378829a21968710d6182e20d", // }, // cellDeps: { // stealthLock: { txHash: "0x...", index: 0 }, // namesType: { txHash: "0x9acd640d35eadd893b358dddd415f4061fe81cb249e8ace51a866fee314141b8", index: 0 }, // }, // } ``` ### Supported Networks | Network | Status | | ------------------- | ------ | | CKB Testnet (Pudge) | Live | ## Fetching Stealth Cells ### `fetchStealthCells(chain?)` Fetches all live stealth Cells from CKB using the `get_cells` RPC method, filtered by the stealth-lock code hash. Handles pagination automatically. ```typescript theme={null} const cells = await fetchStealthCells("ckb"); // Returns StealthCell[] — ready to pass to scanStealthCells() ``` This replaces the subgraph/event-based scanning used on other chains. CKB's native indexer makes this efficient without external infrastructure. # EVM Primitives Source: https://docs.usewraith.xyz/sdk/chains/evm Stealth address crypto for EVM chains using secp256k1 Low-level stealth address functions for EVM-compatible chains (Horizen, Ethereum, Polygon, Base, etc.) using secp256k1. Import from `@wraith-protocol/sdk/chains/evm`. Most developers should use the [Agent Client](/sdk/agent-client) instead. These primitives are for power users building custom stealth address integrations. ## Import ```typescript theme={null} import { // Stealth kit (crypto) deriveStealthKeys, generateStealthAddress, checkStealthAddress, scanAnnouncements, deriveStealthPrivateKey, encodeStealthMetaAddress, decodeStealthMetaAddress, signNameRegistration, signNameRegistrationOnBehalf, signNameUpdate, signNameRelease, metaAddressToBytes, // Builders (transactions) buildSendStealth, buildSendERC20, buildRegisterName, buildUpdateName, buildReleaseName, buildRegisterMetaAddress, buildAnnounce, buildResolveName, // Chain data fetchAnnouncements, getDeployment, DEPLOYMENTS, // ABIs (if you need raw encoding) SENDER_ABI, NAMES_ABI, REGISTRY_ABI, ANNOUNCER_ABI, // Constants STEALTH_SIGNING_MESSAGE, SCHEME_ID, META_ADDRESS_PREFIX, } from "@wraith-protocol/sdk/chains/evm"; ``` ## Types ```typescript theme={null} type HexString = `0x${string}`; interface StealthKeys { spendingKey: HexString; // 32-byte private key viewingKey: HexString; // 32-byte private key spendingPubKey: HexString; // 33-byte compressed public key viewingPubKey: HexString; // 33-byte compressed public key } interface GeneratedStealthAddress { stealthAddress: HexString; // 20-byte EVM address ephemeralPubKey: HexString; // 33-byte compressed public key viewTag: number; // 0-255 } interface Announcement { schemeId: bigint; stealthAddress: HexString; caller: HexString; ephemeralPubKey: HexString; metadata: HexString; // first byte is view tag } interface MatchedAnnouncement extends Announcement { stealthPrivateKey: HexString; } ``` ## Constants ```typescript theme={null} const STEALTH_SIGNING_MESSAGE = "Sign this message to generate your Wraith stealth keys.\n\nChain: Horizen\nNote: This signature is used for key derivation only and does not authorize any transaction."; const SCHEME_ID = 1n; // bigint for on-chain compatibility const META_ADDRESS_PREFIX = "st:eth:0x"; ``` *** ## Functions ### `deriveStealthKeys(signature)` Derive spending and viewing key pairs from a wallet signature. ```typescript theme={null} const signature = await wallet.signMessage(STEALTH_SIGNING_MESSAGE); const keys = deriveStealthKeys(signature as HexString); console.log(keys.spendingKey); // "0x..." (32-byte private key) console.log(keys.viewingKey); // "0x..." (32-byte private key) console.log(keys.spendingPubKey); // "0x02..." (33-byte compressed) console.log(keys.viewingPubKey); // "0x03..." (33-byte compressed) ``` **Algorithm:** 1. Split the 65-byte signature: `r = sig[0:32]`, `s = sig[32:64]` 2. `spendingKey = keccak256(r)` 3. `viewingKey = keccak256(s)` 4. Derive compressed public keys from each private key Same signature always produces the same keys. The spending key is never equal to the viewing key. ### `generateStealthAddress(spendingPubKey, viewingPubKey, ephemeralKey?)` Generate a one-time stealth address for a recipient. ```typescript theme={null} const result = generateStealthAddress( keys.spendingPubKey, keys.viewingPubKey ); console.log(result.stealthAddress); // "0x..." (fresh one-time address) console.log(result.ephemeralPubKey); // "0x..." (publish with announcement) console.log(result.viewTag); // 0-255 (publish with announcement) ``` **Algorithm:** 1. Generate random ephemeral key pair `(r, R = r * G)` 2. Compute ECDH shared secret `S = r * viewingPubKey` 3. `hashedSecret = keccak256(S)` 4. `viewTag = hashedSecret[0]` 5. `stealthPoint = spendingPubKey + hash(S) * G` 6. `stealthAddress = keccak256(stealthPoint)[12:32]` Each call produces a different address (new ephemeral key). Pass an explicit `ephemeralKey` for deterministic testing. ### `checkStealthAddress(ephemeralPubKey, viewingKey, spendingPubKey, viewTag)` Check if a stealth address announcement belongs to you. ```typescript theme={null} const result = checkStealthAddress( announcement.ephemeralPubKey, keys.viewingKey, keys.spendingPubKey, viewTag ); if (result.isMatch) { console.log(result.stealthAddress); // the address that matched } ``` Uses view tag for fast rejection — eliminates \~255/256 non-matching announcements without computing the full stealth address. ### `scanAnnouncements(announcements, viewingKey, spendingPubKey, spendingKey)` Scan an array of on-chain announcements and return the ones that belong to you. ```typescript theme={null} const announcements: Announcement[] = [ // from subgraph query or chain events ]; const matched = scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, keys.spendingKey ); for (const m of matched) { console.log(m.stealthAddress); // address you control console.log(m.stealthPrivateKey); // private key for this address } ``` For each announcement: 1. Skip if `schemeId` doesn't match 2. Extract view tag from metadata 3. Check if it matches using `checkStealthAddress` 4. If matched, derive the stealth private key ### `deriveStealthPrivateKey(spendingKey, ephemeralPubKey, viewingKey)` Compute the private key that controls a specific stealth address. ```typescript theme={null} const privateKey = deriveStealthPrivateKey( keys.spendingKey, stealth.ephemeralPubKey, keys.viewingKey ); // Use this key to sign transactions from the stealth address import { privateKeyToAccount } from "viem/accounts"; const account = privateKeyToAccount(privateKey); console.log(account.address); // matches the stealth address ``` **Algorithm:** 1. `S = viewingKey * ephemeralPubKey` (shared secret) 2. `hashScalar = keccak256(S) mod n` 3. `stealthPrivateKey = (spendingKey + hashScalar) mod n` ### `encodeStealthMetaAddress(spendingPubKey, viewingPubKey)` Encode two public keys into a stealth meta-address string. ```typescript theme={null} const metaAddress = encodeStealthMetaAddress( keys.spendingPubKey, keys.viewingPubKey ); // "st:eth:0x02abc...03def..." ``` Both keys must be 33-byte compressed secp256k1 points. ### `decodeStealthMetaAddress(metaAddress)` Decode a meta-address back into its component public keys. ```typescript theme={null} const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress( "st:eth:0x02abc...03def..." ); ``` Validates the prefix, length, and that both keys are valid curve points. ### `metaAddressToBytes(metaAddress)` Strip the `st:eth:` prefix from a meta-address, returning the raw hex bytes. ```typescript theme={null} const bytes = metaAddressToBytes("st:eth:0x02abc...03def..."); // "0x02abc...03def..." ``` ### `signNameRegistration(name, metaAddressBytes, spendingKey)` Sign a message for on-chain `.wraith` name registration. ```typescript theme={null} const metaBytes = metaAddressToBytes(metaAddress); const sig = signNameRegistration("alice", metaBytes, keys.spendingKey); // 65-byte hex signature for the WraithNames contract ``` ### `signNameRegistrationOnBehalf(name, metaAddressBytes, spendingKey, nonce)` Sign with a nonce for delegated registration. ```typescript theme={null} const sig = signNameRegistrationOnBehalf("alice", metaBytes, keys.spendingKey, 0n); ``` ### `signNameUpdate(name, newMetaAddressBytes, spendingKey)` Sign a name update to point to a new meta-address. ```typescript theme={null} const sig = signNameUpdate("alice", newMetaBytes, keys.spendingKey); ``` ### `signNameRelease(name, spendingKey)` Sign a name release to give up ownership. ```typescript theme={null} const sig = signNameRelease("alice", keys.spendingKey); ``` *** ## End-to-End Flow ```typescript theme={null} import { deriveStealthKeys, generateStealthAddress, scanAnnouncements, deriveStealthPrivateKey, encodeStealthMetaAddress, decodeStealthMetaAddress, STEALTH_SIGNING_MESSAGE, SCHEME_ID, } from "@wraith-protocol/sdk/chains/evm"; import type { HexString, Announcement } from "@wraith-protocol/sdk/chains/evm"; // 1. Recipient: derive keys from wallet signature const sig = await wallet.signMessage(STEALTH_SIGNING_MESSAGE); const keys = deriveStealthKeys(sig as HexString); // 2. Recipient: publish stealth meta-address const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // Share "st:eth:0x..." or register as a .wraith name // 3. Sender: generate stealth address from meta-address const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress(metaAddress); const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); // 4. Sender: send ETH to stealth.stealthAddress // Sender: call announcer contract with (stealth.ephemeralPubKey, stealth.viewTag) // 5. Recipient: fetch and scan announcements const announcements = await fetchAnnouncements("horizen"); const matched = scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, keys.spendingKey ); // 6. Recipient: spend from stealth address for (const m of matched) { const account = privateKeyToAccount(m.stealthPrivateKey); // Sign and submit transactions from account.address } ``` ## Transaction Builders Builders return `{ to, data, value? }` objects — submit with any library (viem, ethers, wagmi, raw RPC). ### `buildSendStealth(params)` Send ETH privately via stealth address. Uses the WraithSender contract for atomic send + announce. ```typescript theme={null} import { buildSendStealth } from "@wraith-protocol/sdk/chains/evm"; const { transaction, stealthAddress, ephemeralPubKey, viewTag } = buildSendStealth({ recipientMetaAddress: "st:eth:0x...", amount: "0.1", chain: "horizen", }); // Submit with viem await walletClient.sendTransaction(transaction); // Or ethers await signer.sendTransaction(transaction); // Or wagmi await sendTransaction(transaction); ``` Returns: `{ transaction: { to, data, value }, stealthAddress, ephemeralPubKey, viewTag }` ### `buildSendERC20(params)` Send an ERC-20 token privately. The sender must have approved the WraithSender contract first. ```typescript theme={null} import { buildSendERC20 } from "@wraith-protocol/sdk/chains/evm"; const { transaction } = buildSendERC20({ recipientMetaAddress: "st:eth:0x...", token: "0x4b36cb6E...", // token contract address amount: 1000000n, // raw token amount (with decimals) chain: "horizen", gasTip: "0.001", // optional ETH tip for recipient's gas }); await walletClient.sendTransaction(transaction); ``` ### `buildRegisterName(params)` Register a `.wraith` name on-chain. The name is bound to the spending key. ```typescript theme={null} import { buildRegisterName, deriveStealthKeys } from "@wraith-protocol/sdk/chains/evm"; const keys = deriveStealthKeys(signature); const tx = buildRegisterName({ name: "alice", stealthKeys: keys, chain: "horizen", }); await walletClient.sendTransaction(tx); ``` ### `buildUpdateName(params)` Update a `.wraith` name's meta-address. Must be signed by the current owner. ```typescript theme={null} import { buildUpdateName } from "@wraith-protocol/sdk/chains/evm"; const tx = buildUpdateName({ name: "alice", newStealthKeys: newKeys, currentSpendingKey: oldKeys.spendingKey, chain: "horizen", }); await walletClient.sendTransaction(tx); ``` ### `buildReleaseName(params)` Release a `.wraith` name. After release, anyone can register it. ```typescript theme={null} import { buildReleaseName } from "@wraith-protocol/sdk/chains/evm"; const tx = buildReleaseName({ name: "alice", spendingKey: keys.spendingKey, chain: "horizen", }); await walletClient.sendTransaction(tx); ``` ### `buildRegisterMetaAddress(params)` Register a stealth meta-address in the ERC-6538 registry. Makes it discoverable by wallet address. ```typescript theme={null} import { buildRegisterMetaAddress } from "@wraith-protocol/sdk/chains/evm"; const tx = buildRegisterMetaAddress({ metaAddress: "st:eth:0x...", chain: "horizen", }); await walletClient.sendTransaction(tx); ``` ### `buildAnnounce(params)` Publish a stealth address announcement on-chain. Use this if you're sending assets directly (not via WraithSender) and need to announce separately. ```typescript theme={null} import { buildAnnounce } from "@wraith-protocol/sdk/chains/evm"; const tx = buildAnnounce({ stealthAddress: "0x...", ephemeralPubKey: "0x...", viewTag: 42, chain: "horizen", }); await walletClient.sendTransaction(tx); ``` ### `buildResolveName(params)` Build calldata to resolve a `.wraith` name. Returns data for a static call (read-only, no transaction). ```typescript theme={null} import { buildResolveName } from "@wraith-protocol/sdk/chains/evm"; const call = buildResolveName({ name: "alice", chain: "horizen" }); // Use with a static call const result = await publicClient.call({ to: call.to, data: call.data }); ``` ### Contract ABIs The raw ABIs are exported if you need to encode calls yourself: ```typescript theme={null} import { SENDER_ABI, NAMES_ABI, REGISTRY_ABI, ANNOUNCER_ABI } from "@wraith-protocol/sdk/chains/evm"; ``` ## Chain Deployments The SDK ships with deployed contract addresses and subgraph URLs for supported chains. No need to deploy contracts yourself. ### `getDeployment(chain)` Returns the full deployment config for a chain: ```typescript theme={null} const deployment = getDeployment("horizen"); // { // chainId: 2651420, // name: "Horizen Testnet", // rpcUrl: "https://horizen-testnet.rpc.caldera.xyz/http", // explorerUrl: "https://horizen-testnet.explorer.caldera.xyz", // subgraphUrl: "https://api.goldsky.com/api/public/...", // contracts: { // announcer: "0x8AE65c05E7eb48B9bA652781Bc0a3DBA09A484F3", // registry: "0x953E6cEdcdfAe321796e7637d33653F6Ce05c527", // sender: "0x226C5eb4e139D9fa01cc09eA318638b090b12095", // names: "0x3d46f709a99A3910f52bD292211Eb5D557F882D6", // }, // } ``` ### `DEPLOYMENTS` Access all deployments directly: ```typescript theme={null} import { DEPLOYMENTS } from "@wraith-protocol/sdk/chains/evm"; console.log(Object.keys(DEPLOYMENTS)); // ["horizen"] ``` ### Supported EVM Chains | Chain | Chain ID | Status | | --------------- | -------- | ------ | | Horizen Testnet | 2651420 | Live | ## Fetching Announcements ### `fetchAnnouncements(chain, subgraphUrl?)` Fetches all stealth address announcements from the Goldsky subgraph for the specified chain. Handles pagination automatically. ```typescript theme={null} const announcements = await fetchAnnouncements("horizen"); // Returns Announcement[] — ready to pass to scanAnnouncements() ``` Override the subgraph URL if needed: ```typescript theme={null} const announcements = await fetchAnnouncements("horizen", "https://my-custom-subgraph.com/graphql"); ``` # Solana Primitives Source: https://docs.usewraith.xyz/sdk/chains/solana Stealth address crypto for Solana using ed25519 Low-level stealth address functions for Solana using ed25519. Import from `@wraith-protocol/sdk/chains/solana`. Most developers should use the [Agent Client](/sdk/agent-client) instead. These primitives are for power users building custom stealth address integrations on Solana. ## Installation ```bash theme={null} npm install @wraith-protocol/sdk # Solana Web3.js is an optional peer dependency — install it too npm install @solana/web3.js ``` ## Import ```typescript theme={null} import { deriveStealthKeys, generateStealthAddress, computeSharedSecret, computeViewTag, checkStealthAddress, scanAnnouncements, deriveStealthPrivateScalar, signSolanaTransaction, signWithScalar, encodeStealthMetaAddress, decodeStealthMetaAddress, seedToScalar, hashToScalar, deriveStealthPubKey, pubKeyToSolanaAddress, bytesToHex, hexToBytes, fetchAnnouncements, getDeployment, DEPLOYMENTS, STEALTH_SIGNING_MESSAGE, SCHEME_ID, META_ADDRESS_PREFIX, L, } from "@wraith-protocol/sdk/chains/solana"; ``` ## Types ```typescript theme={null} interface StealthKeys { spendingKey: Uint8Array; // 32-byte seed spendingScalar: bigint; // clamped scalar from SHA-512(seed) viewingKey: Uint8Array; // 32-byte seed viewingScalar: bigint; // clamped scalar spendingPubKey: Uint8Array; // 32-byte ed25519 public key viewingPubKey: Uint8Array; // 32-byte ed25519 public key } interface GeneratedStealthAddress { stealthAddress: string; // base58-encoded Solana address ephemeralPubKey: Uint8Array; // 32-byte ed25519 public key viewTag: number; // 0-255 } interface Announcement { schemeId: number; stealthAddress: string; // base58 Solana address caller: string; // base58 Solana address ephemeralPubKey: string; // hex-encoded 32 bytes metadata: string; // hex-encoded, first byte = view tag } interface MatchedAnnouncement extends Announcement { stealthPrivateScalar: bigint; stealthPubKeyBytes: Uint8Array; } ``` ## Relationship to Stellar Module Solana uses the same ed25519 curve as Stellar. The cryptographic algorithms are identical — only the address encoding and transaction format differ. | Aspect | Stellar | Solana | | ------------------- | ---------------------------------- | ----------------------------------------- | | Curve | ed25519 | ed25519 (same) | | ECDH | X25519 | X25519 (same) | | Hash to scalar | SHA-256 domain-separated | SHA-256 domain-separated (same) | | View tag | `SHA-256("wraith:tag:" \|\| S)[0]` | `SHA-256("wraith:tag:" \|\| S)[0]` (same) | | Address format | `G...` (StrKey) | Base58 (32-byte pubkey) | | Meta-address prefix | `st:xlm:` | `st:sol:` | | Account model | Must `createAccount` first | Send SOL directly, no deployment needed | | Min balance | 1 XLM | \~0.00089 SOL (rent exemption) | ## Constants ```typescript theme={null} const STEALTH_SIGNING_MESSAGE = "Sign this message to generate your Wraith stealth keys.\n\nChain: Solana\nNote: This signature is used for key derivation only and does not authorize any transaction."; const SCHEME_ID = 1; const META_ADDRESS_PREFIX = "st:sol:"; const L = 2n**252n + 27742317777372353535851937790883648493n; // ed25519 group order ``` *** ## Functions ### `deriveStealthKeys(signature)` Derive spending and viewing key pairs from a 64-byte ed25519 signature. ```typescript theme={null} const signature = await wallet.signMessage(Buffer.from(STEALTH_SIGNING_MESSAGE)); const keys = deriveStealthKeys(signature); console.log(keys.spendingKey); // Uint8Array (32-byte seed) console.log(keys.spendingScalar); // bigint (clamped scalar) console.log(keys.viewingKey); // Uint8Array (32-byte seed) console.log(keys.viewingScalar); // bigint (clamped scalar) console.log(keys.spendingPubKey); // Uint8Array (32-byte public key) console.log(keys.viewingPubKey); // Uint8Array (32-byte public key) ``` **Algorithm:** 1. `spendingKey = SHA-256("wraith:spending:" || signature)` — 32-byte seed 2. `viewingKey = SHA-256("wraith:viewing:" || signature)` — 32-byte seed 3. Each seed is expanded via `seedToScalar()` (SHA-512 + clamping) 4. Public keys derived via `ed25519.getPublicKey(seed)` ### `seedToScalar(seed)` Convert a 32-byte ed25519 seed to its clamped scalar. ```typescript theme={null} const scalar = seedToScalar(seed); // bigint — the clamped scalar used for point multiplication ``` ### `computeSharedSecret(privateKey, publicKey)` Compute an ECDH shared secret using X25519 (Montgomery form conversion). ```typescript theme={null} const shared = computeSharedSecret(keys.viewingKey, ephemeralPubKey); // Uint8Array (32 bytes) ``` ### `computeViewTag(sharedSecret)` Compute the view tag from a shared secret. ```typescript theme={null} const tag = computeViewTag(sharedSecret); // number (0-255) ``` ### `hashToScalar(sharedSecret)` Hash a shared secret to a scalar value for stealth address derivation. ```typescript theme={null} const scalar = hashToScalar(sharedSecret); // bigint (reduced mod L) ``` ### `generateStealthAddress(spendingPubKey, viewingPubKey, ephemeralSeed?)` Generate a one-time stealth address for a Solana recipient. ```typescript theme={null} const result = generateStealthAddress( keys.spendingPubKey, keys.viewingPubKey ); console.log(result.stealthAddress); // "7xKX..." (base58 Solana address) console.log(result.ephemeralPubKey); // Uint8Array (32 bytes) console.log(result.viewTag); // 0-255 ``` **Algorithm:** 1. Generate random ephemeral ed25519 seed 2. Compute shared secret via X25519 ECDH 3. `viewTag = computeViewTag(sharedSecret)` 4. `hScalar = hashToScalar(sharedSecret)` 5. `stealthPoint = spendingPubKey + hScalar * G` (ed25519 point addition) 6. Encode as base58 Solana address via `PublicKey` ### `checkStealthAddress(ephemeralPubKey, viewingKey, spendingPubKey, viewTag)` Check if an announcement belongs to you. ```typescript theme={null} const result = checkStealthAddress( ephemeralPubKey, keys.viewingKey, keys.spendingPubKey, viewTag ); if (result.isMatch) { console.log(result.stealthAddress); // "7xKX..." console.log(result.hashScalar); // bigint console.log(result.stealthPubKeyBytes); // Uint8Array } ``` ### `scanAnnouncements(announcements, viewingKey, spendingPubKey, spendingScalar)` Scan announcements and return matches with their private scalars. ```typescript theme={null} const matched = scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, keys.spendingScalar ); for (const m of matched) { console.log(m.stealthAddress); // "7xKX..." console.log(m.stealthPrivateScalar); // bigint console.log(m.stealthPubKeyBytes); // Uint8Array } ``` The fourth argument is `spendingScalar` (bigint), same as the Stellar module. ### `deriveStealthPrivateScalar(spendingScalar, viewingKey, ephemeralPubKey)` Derive the private scalar for a specific stealth address. ```typescript theme={null} const scalar = deriveStealthPrivateScalar( keys.spendingScalar, keys.viewingKey, ephemeralPubKey ); // bigint — (spendingScalar + hashScalar) mod L ``` ### `signWithScalar(message, scalar, publicKey)` Sign a message using a raw scalar instead of a seed. ```typescript theme={null} const signature = signWithScalar(messageBytes, stealthScalar, stealthPubKey); // Uint8Array (64-byte ed25519 signature) ``` ### `signSolanaTransaction(txMessageBytes, stealthScalar, stealthPubKey)` Sign a Solana transaction message with a stealth private scalar. ```typescript theme={null} const sig = signSolanaTransaction(txMessageBytes, stealthScalar, stealthPubKey); // Uint8Array (64-byte signature) // Add to the transaction before sending ``` ### `encodeStealthMetaAddress(spendingPubKey, viewingPubKey)` Encode two 32-byte public keys into a Solana stealth meta-address. ```typescript theme={null} const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // "st:sol:abc123...def456..." ``` ### `decodeStealthMetaAddress(metaAddress)` Decode a Solana meta-address back into its component keys. ```typescript theme={null} const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress("st:sol:abc123...def456..."); // Both are Uint8Array (32 bytes) ``` ### `pubKeyToSolanaAddress(publicKey)` Convert a 32-byte ed25519 public key to a base58 Solana address. ```typescript theme={null} const address = pubKeyToSolanaAddress(publicKeyBytes); // "7xKXaJoV..." ``` ### `bytesToHex(bytes)` / `hexToBytes(hex)` Utility functions for converting between `Uint8Array` and hex strings. ```typescript theme={null} const hex = bytesToHex(new Uint8Array([0xab, 0xcd])); // "abcd" const bytes = hexToBytes("abcd"); // Uint8Array [0xab, 0xcd] ``` *** ## End-to-End Flow ```typescript theme={null} import { deriveStealthKeys, generateStealthAddress, scanAnnouncements, deriveStealthPrivateScalar, signSolanaTransaction, encodeStealthMetaAddress, decodeStealthMetaAddress, pubKeyToSolanaAddress, fetchAnnouncements, STEALTH_SIGNING_MESSAGE, } from "@wraith-protocol/sdk/chains/solana"; import { Connection, Transaction, SystemProgram, PublicKey } from "@solana/web3.js"; // 1. Recipient: derive keys from Solana wallet signature const sig = await wallet.signMessage(Buffer.from(STEALTH_SIGNING_MESSAGE)); const keys = deriveStealthKeys(sig); // 2. Recipient: publish stealth meta-address const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // Share "st:sol:..." or register as a .wraith name // 3. Sender: generate stealth address from meta-address const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress(metaAddress); const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); // stealth.stealthAddress is a base58 Solana address // 4. Sender: send SOL to stealth address + call announcer program // 5. Recipient: scan announcements const announcements = await fetchAnnouncements("solana"); const matched = scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, keys.spendingScalar ); // 6. Recipient: withdraw from stealth address for (const m of matched) { const stealthPubKey = new PublicKey(m.stealthPubKeyBytes); const tx = new Transaction().add( SystemProgram.transfer({ fromPubkey: stealthPubKey, toPubkey: new PublicKey(destinationAddress), lamports: withdrawAmount, }) ); const messageBytes = tx.serializeMessage(); const sig = signSolanaTransaction(messageBytes, m.stealthPrivateScalar, m.stealthPubKeyBytes); tx.addSignature(stealthPubKey, Buffer.from(sig)); const connection = new Connection("https://api.devnet.solana.com"); await connection.sendRawTransaction(tx.serialize()); } ``` ### Solana-Specific Considerations * **No account deployment:** Unlike Stellar, Solana doesn't require `createAccount`. An ed25519 public key is a valid address — send SOL directly to it. * **Rent exemption:** Accounts need \~0.00089 SOL to be rent-exempt. When withdrawing all, send `balance - txFee` (5000 lamports). The account is garbage collected when balance drops below rent exemption. * **SPL tokens:** Token transfers use associated token accounts (ATAs), not direct transfers. The ATA must be created for the stealth address before transferring SPL tokens. * **Announcements:** Fetched from program transaction logs via `fetchAnnouncements("solana")`, not a subgraph. * **Signing:** Same `signWithScalar()` as Stellar — stealth scalars are derived and can't be used with standard Solana `Keypair`. ## Chain Deployments The SDK ships with deployed program IDs and RPC URLs for supported Solana networks. ### `getDeployment(chain)` ```typescript theme={null} const deployment = getDeployment("solana"); // { // cluster: "devnet", // rpcUrl: "https://api.devnet.solana.com", // explorerUrl: "https://explorer.solana.com", // contracts: { // announcer: "...", // program ID // sender: "...", // names: "...", // }, // } ``` ### Supported Networks | Network | Status | | ------------- | ------ | | Solana Devnet | Live | ## Fetching Announcements ### `fetchAnnouncements(chain?)` Fetches all stealth address announcements from the announcer program's transaction history. Parses Anchor event logs automatically. ```typescript theme={null} const announcements = await fetchAnnouncements("solana"); // Returns Announcement[] — ready to pass to scanAnnouncements() ``` # Stellar Primitives Source: https://docs.usewraith.xyz/sdk/chains/stellar Stealth address crypto for Stellar using ed25519 Low-level stealth address functions for Stellar using ed25519. Import from `@wraith-protocol/sdk/chains/stellar`. Most developers should use the [Agent Client](/sdk/agent-client) instead. These primitives are for power users building custom stealth address integrations on Stellar. ## Installation ```bash theme={null} npm install @wraith-protocol/sdk # Stellar SDK is an optional peer dependency — install it too npm install @stellar/stellar-sdk ``` ## Import ```typescript theme={null} import { deriveStealthKeys, generateStealthAddress, computeSharedSecret, computeViewTag, checkStealthAddress, scanAnnouncements, deriveStealthPrivateScalar, signStellarTransaction, signWithScalar, encodeStealthMetaAddress, decodeStealthMetaAddress, seedToScalar, hashToScalar, deriveStealthPubKey, pubKeyToStellarAddress, bytesToHex, hexToBytes, fetchAnnouncements, getDeployment, DEPLOYMENTS, STEALTH_SIGNING_MESSAGE, SCHEME_ID, META_ADDRESS_PREFIX, L, } from "@wraith-protocol/sdk/chains/stellar"; ``` ## Types ```typescript theme={null} interface StealthKeys { spendingKey: Uint8Array; // 32-byte seed spendingScalar: bigint; // clamped scalar from SHA-512(seed) viewingKey: Uint8Array; // 32-byte seed viewingScalar: bigint; // clamped scalar spendingPubKey: Uint8Array; // 32-byte ed25519 public key viewingPubKey: Uint8Array; // 32-byte ed25519 public key } interface GeneratedStealthAddress { stealthAddress: string; // Stellar G... address ephemeralPubKey: Uint8Array; // 32-byte ed25519 public key viewTag: number; // 0-255 } interface Announcement { schemeId: number; stealthAddress: string; // G... address caller: string; // G... address ephemeralPubKey: string; // hex-encoded 32 bytes metadata: string; // hex-encoded, first byte = view tag } interface MatchedAnnouncement extends Announcement { stealthPrivateScalar: bigint; stealthPubKeyBytes: Uint8Array; } ``` ## Key Differences from EVM | Aspect | EVM | Stellar | | ------------------ | --------------------------- | -------------------------- | | Curve | secp256k1 | ed25519 | | Key format | `HexString` (0x-prefixed) | `Uint8Array` (raw bytes) | | ECDH | `secp256k1.getSharedSecret` | X25519 (Montgomery form) | | Address format | `0x...` (20 bytes) | `G...` (56 chars) | | Pub key size | 33 bytes (compressed) | 32 bytes | | Meta-address | `st:eth:0x{66}{66}` | `st:xlm:{64}{64}` | | Hash function | keccak256 | SHA-256 (domain-separated) | | Private key output | hex string | bigint scalar | | Signing | secp256k1 ECDSA | ed25519 with raw scalar | ## Constants ```typescript theme={null} const STEALTH_SIGNING_MESSAGE = "Sign this message to generate your Wraith stealth keys.\n\nChain: Stellar\nNote: This signature is used for key derivation only and does not authorize any transaction."; const SCHEME_ID = 1; const META_ADDRESS_PREFIX = "st:xlm:"; const L = 2n**252n + 27742317777372353535851937790883648493n; // ed25519 group order ``` *** ## Functions ### `deriveStealthKeys(signature)` Derive spending and viewing key pairs from a 64-byte ed25519 signature. ```typescript theme={null} const signature = stellarKeypair.sign(Buffer.from(STEALTH_SIGNING_MESSAGE)); const keys = deriveStealthKeys(signature); console.log(keys.spendingKey); // Uint8Array (32-byte seed) console.log(keys.spendingScalar); // bigint (clamped scalar) console.log(keys.viewingKey); // Uint8Array (32-byte seed) console.log(keys.viewingScalar); // bigint (clamped scalar) console.log(keys.spendingPubKey); // Uint8Array (32-byte public key) console.log(keys.viewingPubKey); // Uint8Array (32-byte public key) ``` **Algorithm:** 1. `spendingKey = SHA-256("wraith:spending:" || signature)` — 32-byte seed 2. `viewingKey = SHA-256("wraith:viewing:" || signature)` — 32-byte seed 3. Each seed is expanded via `seedToScalar()` (SHA-512 + clamping) 4. Public keys derived via `ed25519.getPublicKey(seed)` Domain-separated hashing is used instead of the EVM approach of splitting `r`/`s` components, because ed25519 signature components don't have the same independence. ### `seedToScalar(seed)` Convert a 32-byte ed25519 seed to its clamped scalar. Mirrors standard ed25519 private key expansion. ```typescript theme={null} const scalar = seedToScalar(seed); // bigint — the clamped scalar used for point multiplication ``` **Algorithm:** 1. `h = SHA-512(seed)` — 64 bytes 2. `a = h[0:32]` — lower half 3. Clamp: `a[0] &= 248; a[31] &= 127; a[31] |= 64` 4. Interpret as little-endian bigint ### `computeSharedSecret(privateKey, publicKey)` Compute an ECDH shared secret using X25519 (Montgomery form conversion). ```typescript theme={null} const shared = computeSharedSecret(keys.viewingKey, ephemeralPubKey); // Uint8Array (32 bytes) ``` ed25519 keys are converted to X25519 (Montgomery form) before performing Diffie-Hellman: 1. `privX = edwardsToMontgomeryPriv(privateKey)` 2. `pubX = edwardsToMontgomeryPub(publicKey)` 3. `shared = x25519.getSharedSecret(privX, pubX)` ### `computeViewTag(sharedSecret)` Compute the view tag from a shared secret. ```typescript theme={null} const tag = computeViewTag(sharedSecret); // number (0-255) ``` `SHA-256("wraith:tag:" || sharedSecret)[0]` ### `hashToScalar(sharedSecret)` Hash a shared secret to a scalar value for stealth address derivation. ```typescript theme={null} const scalar = hashToScalar(sharedSecret); // bigint (reduced mod L) ``` `SHA-256("wraith:scalar:" || sharedSecret)` interpreted as little-endian bigint, reduced mod L. ### `generateStealthAddress(spendingPubKey, viewingPubKey, ephemeralSeed?)` Generate a one-time stealth address for a Stellar recipient. ```typescript theme={null} const result = generateStealthAddress( keys.spendingPubKey, keys.viewingPubKey ); console.log(result.stealthAddress); // "G..." (Stellar address) console.log(result.ephemeralPubKey); // Uint8Array (32 bytes) console.log(result.viewTag); // 0-255 ``` **Algorithm:** 1. Generate random ephemeral ed25519 seed 2. Compute shared secret via X25519 ECDH 3. `viewTag = computeViewTag(sharedSecret)` 4. `hScalar = hashToScalar(sharedSecret)` 5. `stealthPoint = spendingPubKey + hScalar * G` (ed25519 point addition) 6. Encode as Stellar `G...` address via `StrKey.encodeEd25519PublicKey` ### `checkStealthAddress(ephemeralPubKey, viewingKey, spendingPubKey, viewTag)` Check if an announcement belongs to you. ```typescript theme={null} const result = checkStealthAddress( ephemeralPubKey, keys.viewingKey, keys.spendingPubKey, viewTag ); if (result.isMatch) { console.log(result.stealthAddress); // "G..." console.log(result.hashScalar); // bigint console.log(result.stealthPubKeyBytes); // Uint8Array } ``` ### `scanAnnouncements(announcements, viewingKey, spendingPubKey, spendingScalar)` Scan announcements and return matches with their private scalars. ```typescript theme={null} const matched = scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, keys.spendingScalar ); for (const m of matched) { console.log(m.stealthAddress); // "G..." console.log(m.stealthPrivateScalar); // bigint console.log(m.stealthPubKeyBytes); // Uint8Array } ``` The fourth argument is `spendingScalar` (bigint), not `spendingKey` like in the EVM module. ### `deriveStealthPrivateScalar(spendingScalar, viewingKey, ephemeralPubKey)` Derive the private scalar for a specific stealth address. ```typescript theme={null} const scalar = deriveStealthPrivateScalar( keys.spendingScalar, keys.viewingKey, ephemeralPubKey ); // bigint — (spendingScalar + hashScalar) mod L ``` ### `signWithScalar(message, scalar, publicKey)` Sign a message using a raw scalar instead of a seed. Required because stealth private keys are derived scalars that can't be used with `Keypair.fromRawEd25519Seed()`. ```typescript theme={null} const signature = signWithScalar(messageBytes, stealthScalar, stealthPubKey); // Uint8Array (64-byte ed25519 signature) ``` The stealth scalar `(spendingScalar + hashScalar) % L` is not necessarily clamped, so standard Stellar signing functions don't work. ### `signStellarTransaction(txHash, stealthScalar, stealthPubKey)` Sign a Stellar transaction hash with a stealth private scalar. ```typescript theme={null} const sig = signStellarTransaction(txHash, stealthScalar, stealthPubKey); // Uint8Array (64-byte signature) // Add this to the transaction envelope before submitting ``` ### `encodeStealthMetaAddress(spendingPubKey, viewingPubKey)` Encode two 32-byte public keys into a Stellar stealth meta-address. ```typescript theme={null} const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // "st:xlm:abc123...def456..." ``` ### `decodeStealthMetaAddress(metaAddress)` Decode a Stellar meta-address back into its component keys. ```typescript theme={null} const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress("st:xlm:abc123...def456..."); // Both are Uint8Array (32 bytes) ``` ### `deriveStealthPubKey(spendingPubKey, hashScalar)` Derive a stealth public key from a spending public key and hash scalar. ```typescript theme={null} const stealthPub = deriveStealthPubKey(spendingPubKey, hashScalar); // Uint8Array (32 bytes) ``` ### `pubKeyToStellarAddress(publicKey)` Convert a 32-byte ed25519 public key to a Stellar `G...` address. ```typescript theme={null} const address = pubKeyToStellarAddress(publicKeyBytes); // "GABC..." ``` ### `bytesToHex(bytes)` / `hexToBytes(hex)` Utility functions for converting between `Uint8Array` and hex strings. ```typescript theme={null} const hex = bytesToHex(new Uint8Array([0xab, 0xcd])); // "abcd" const bytes = hexToBytes("abcd"); // Uint8Array [0xab, 0xcd] ``` *** ## End-to-End Flow ```typescript theme={null} import { deriveStealthKeys, generateStealthAddress, scanAnnouncements, deriveStealthPrivateScalar, signStellarTransaction, encodeStealthMetaAddress, decodeStealthMetaAddress, pubKeyToStellarAddress, STEALTH_SIGNING_MESSAGE, SCHEME_ID, } from "@wraith-protocol/sdk/chains/stellar"; // 1. Recipient: derive keys from Stellar wallet signature const sig = stellarKeypair.sign(Buffer.from(STEALTH_SIGNING_MESSAGE)); const keys = deriveStealthKeys(sig); // 2. Recipient: publish stealth meta-address const metaAddress = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); // Share "st:xlm:..." or register as a .wraith name // 3. Sender: generate stealth address from meta-address const { spendingPubKey, viewingPubKey } = decodeStealthMetaAddress(metaAddress); const stealth = generateStealthAddress(spendingPubKey, viewingPubKey); // stealth.stealthAddress is a G... address // 4. Sender: send XLM via createAccount tx to stealth.stealthAddress // Sender: call Soroban announcer contract // 5. Recipient: scan announcements (from Soroban events) const matched = scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, keys.spendingScalar ); // 6. Recipient: sign transaction from stealth address for (const m of matched) { const txHash = transaction.hash(); // 32-byte tx hash const sig = signStellarTransaction(txHash, m.stealthPrivateScalar, m.stealthPubKeyBytes); transaction.addSignature(pubKeyToStellarAddress(m.stealthPubKeyBytes), sig); // Submit to Horizon } ``` ### Stellar-Specific Considerations * **Account creation:** Stellar requires accounts to exist with a minimum balance (1 XLM). Sending to a new stealth address uses `Operation.createAccount`, not `Operation.payment`. * **Announcements:** Come from Soroban contract events via `sorobanServer.getEvents()`, not a subgraph. Use `fetchAnnouncements("stellar")` to handle this automatically. * **Signing:** Must use `signWithScalar()` / `signStellarTransaction()` because stealth scalars aren't valid seeds for `Keypair.fromRawEd25519Seed()`. ## Chain Deployments The SDK ships with deployed contract addresses and RPC URLs for supported Stellar networks. ### `getDeployment(chain)` ```typescript theme={null} const deployment = getDeployment("stellar"); // { // network: "testnet", // networkPassphrase: "Test SDF Network ; September 2015", // horizonUrl: "https://horizon-testnet.stellar.org", // sorobanUrl: "https://soroban-testnet.stellar.org", // contracts: { // announcer: "CCJLJ2QRBJAAKIG6ELNQVXLLWMKKWVN5O2FKWUETHZGMPAD4MHK7WVWL", // names: "CDEMB3MAE62ZOCCKZPTYSXR5CS5WVENPOU5MDVK4PNKTZXFVDC74AFBV", // }, // } ``` ### Supported Stellar Networks | Network | Status | | --------------- | ------ | | Stellar Testnet | Live | ## Fetching Announcements ### `fetchAnnouncements(chain?, sorobanUrl?)` Fetches all stealth address announcements from the Soroban RPC for the specified network. Handles ledger range detection and pagination automatically. ```typescript theme={null} const announcements = await fetchAnnouncements("stellar"); // Returns Announcement[] — ready to pass to scanAnnouncements() ``` This replaces the need to manually query `sorobanServer.getEvents()` and parse XDR-encoded event data. # SDK Overview Source: https://docs.usewraith.xyz/sdk/overview Package structure, entry points, and Chain enum `@wraith-protocol/sdk` is a single npm package with multiple entry points. Most developers use the root import. Power users building custom stealth address integrations use the chain-specific imports. ## Installation ```bash theme={null} npm install @wraith-protocol/sdk ``` ## Entry Points | Import | Purpose | Audience | | ------------------------------------- | -------------------------------------------- | -------------------------------------------- | | `@wraith-protocol/sdk` | Agent client — create agents, chat, payments | Most developers | | `@wraith-protocol/sdk/chains/evm` | Raw secp256k1 stealth crypto primitives | Power users building custom EVM integrations | | `@wraith-protocol/sdk/chains/stellar` | Raw ed25519 stealth crypto for Stellar | Power users building on Stellar | | `@wraith-protocol/sdk/chains/solana` | Raw ed25519 stealth crypto for Solana | Power users building on Solana | | `@wraith-protocol/sdk/chains/ckb` | Raw secp256k1 stealth crypto for CKB | Power users building on Nervos CKB | ### Root Import — Agent Client ```typescript theme={null} import { Wraith, Chain } from "@wraith-protocol/sdk"; ``` This is the managed platform client. It talks to Wraith's hosted TEE infrastructure over HTTP. Zero heavy dependencies — just `fetch` and TypeScript types. No crypto libraries, no database drivers, no native modules. ### Chain Imports — Crypto Primitives ```typescript theme={null} // EVM chains (secp256k1) import { generateStealthAddress, deriveStealthKeys, scanAnnouncements, deriveStealthPrivateKey, } from "@wraith-protocol/sdk/chains/evm"; // Stellar (ed25519) import { generateStealthAddress, deriveStealthKeys, scanAnnouncements, deriveStealthPrivateScalar, } from "@wraith-protocol/sdk/chains/stellar"; // Solana (ed25519) import { generateStealthAddress, deriveStealthKeys, scanAnnouncements, deriveStealthPrivateScalar, } from "@wraith-protocol/sdk/chains/solana"; // CKB (secp256k1 + Cell model) import { generateStealthAddress, deriveStealthKeys, scanStealthCells, deriveStealthPrivateKey, } from "@wraith-protocol/sdk/chains/ckb"; ``` Each chain module exports the same conceptual functions adapted to that chain's cryptographic scheme and address format. You only need these if you're building custom stealth address integrations without the managed agent platform. ## The `Chain` Enum Always use the `Chain` enum when specifying chains. Never pass raw strings. ```typescript theme={null} import { Chain } from "@wraith-protocol/sdk"; // Single chain const agent = await wraith.createAgent({ name: "alice", chain: Chain.Horizen, wallet: "0x...", signature: "0x...", }); // Multiple chains const agent = await wraith.createAgent({ name: "bob", chain: [Chain.Horizen, Chain.Stellar, Chain.Ethereum], wallet: "0x...", signature: "0x...", }); // All supported chains const agent = await wraith.createAgent({ name: "carol", chain: Chain.All, wallet: "0x...", signature: "0x...", }); ``` ### Available Values ```typescript theme={null} enum Chain { Horizen = "horizen", Ethereum = "ethereum", Polygon = "polygon", Base = "base", Stellar = "stellar", Solana = "solana", CKB = "ckb", All = "all", } ``` ## Dependencies | Package | Used By | Purpose | | ---------------------- | ----------------------------------- | --------------------------------------- | | `@noble/curves` | EVM + Stellar + Solana + CKB chains | Elliptic curve operations | | `@noble/hashes` | EVM + Stellar + Solana + CKB chains | SHA-256, SHA-512, keccak256, blake2b | | `viem` | EVM chains + agent client | EVM utilities, address encoding | | `@stellar/stellar-sdk` | Stellar chain (optional peer dep) | StrKey encoding for Stellar addresses | | `@solana/web3.js` | Solana chain (optional peer dep) | PublicKey encoding for Solana addresses | `@stellar/stellar-sdk` and `@solana/web3.js` are optional peer dependencies. They're only needed if you import their respective chain modules. The CKB module has no additional peer dependencies — it uses raw `fetch` for RPC calls and `@noble/hashes` for blake2b. ## Package Exports The SDK uses `package.json` exports to expose multiple entry points from a single package: ```json theme={null} { "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" }, "./chains/evm": { "types": "./dist/chains/evm/index.d.ts", "import": "./dist/chains/evm/index.js", "require": "./dist/chains/evm/index.cjs" }, "./chains/stellar": { "types": "./dist/chains/stellar/index.d.ts", "import": "./dist/chains/stellar/index.js", "require": "./dist/chains/stellar/index.cjs" }, "./chains/solana": { "types": "./dist/chains/solana/index.d.ts", "import": "./dist/chains/solana/index.js", "require": "./dist/chains/solana/index.cjs" }, "./chains/ckb": { "types": "./dist/chains/ckb/index.d.ts", "import": "./dist/chains/ckb/index.js", "require": "./dist/chains/ckb/index.cjs" } } } ``` Both ESM and CJS formats are supported. TypeScript declarations are included. ## Next Steps * [Agent Client API](agent-client) — full reference for `Wraith` and `WraithAgent` classes * [EVM Crypto Primitives](chains/evm) — low-level stealth address functions for EVM * [Stellar Crypto Primitives](chains/stellar) — low-level stealth address functions for Stellar * [Solana Crypto Primitives](chains/solana) — low-level stealth address functions for Solana * [CKB Crypto Primitives](chains/ckb) — low-level stealth address functions for Nervos CKB