Documentation Index
Fetch the complete documentation index at: https://docs.usewraith.xyz/llms.txt
Use this file to discover all available pages before exploring further.
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:
interface ChainConnector {
readonly chain: string;
readonly nativeAsset: string;
readonly addressFormat: "evm" | "stellar" | "solana" | "ckb";
deriveKeys(seed: Uint8Array): Promise<DerivedKeys>;
sendPayment(params: SendPaymentParams): Promise<TxResult>;
scanPayments(stealthKeys: ChainStealthKeys): Promise<DetectedPayment[]>;
getBalance(address: string): Promise<ChainBalance>;
withdraw(params: WithdrawParams): Promise<TxResult>;
withdrawAll(params: WithdrawAllParams): Promise<WithdrawAllResult>;
registerName(name: string, stealthKeys: ChainStealthKeys): Promise<TxResult>;
resolveName(name: string): Promise<ResolvedName | null>;
fundWallet(address: string): Promise<TxResult>;
getExplorerUrl(type: "tx" | "address", value: string): string;
}
Supporting Types
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<string, string>;
}
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
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<string, { address: string; decimals: number }>;
}
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:
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:
- Deploy the 4 Solidity contracts (Announcer, Registry, Sender, Names)
- Set up a subgraph to index Announcement events
- 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
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
interface SolanaConnectorConfig {
rpcUrl: string;
explorerUrl: string;
contracts: {
announcer: string; // program ID (base58)
sender: string;
names: string;
};
cluster: "devnet" | "testnet" | "mainnet-beta";
}
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
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";
}
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:
class ChainRegistry {
private connectors = new Map<string, ChainConnector>();
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
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):
- Create a connector class implementing
ChainConnector
- Implement all methods using the chain’s SDK and cryptographic primitives
- Write the crypto module at
@wraith-protocol/sdk/chains/solana
- Deploy stealth address contracts on the target chain
- 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) |