Envoy is a multi-chain runtime for AI-native blockchain agents: typed adapters, MCP tools, on-chain session keys, ERC-4626 vaults, Tezos staking and swaps, and Solana liquid staking under one interface.
Envoy is an open-source TypeScript runtime for multi-chain AI agents. It solves the gap between agent autonomy and operator control while exposing a broader execution surface than simple transfers: typed MCP tools, session keys, vaults, staking, swaps, and chain-specific signing flows.
Envoy sits between agent intent and chain execution. Calls can be routed through a policy engine, an MCP server, or direct adapters. Allowed actions are forwarded to the relevant chain or protocol adapter; blocked actions are logged and returned as typed errors.
Install core plus the adapter or MCP package for your target workflow:
npm install @envoy/core @envoy/tezos
npm install @envoy/core @envoy/solana
npm install @envoy/core @envoy/evm
npm install @envoy/core @envoy/etherlink
npm install @envoy/mcp-server
All packages require Node.js 22+ and are ESM-native. Import with import { ... } from '@envoy/core' or start the MCP server with npx @envoy/mcp-server.
Create a policy.yaml file and pass its path to PolicyEngine. All fields are optional unless marked required.
# policy.yaml — full schema
kind: transfer # required — identifies this as a transfer policy
version: "1" # optional schema version
limits:
daily_max_xtz: 500 # max XTZ transferable per 24h rolling window
per_tx_max_xtz: 100 # max XTZ per single transaction
daily_max_sol: 10 # max SOL per 24h (Solana adapter)
per_tx_max_sol: 2 # max SOL per transaction
daily_max_usd: 1000 # USD-equivalent cap (requires price oracle)
allowlist: # optional — if present, only these recipients are allowed
- tz1alice... # Tezos addresses (tz1, tz2, tz3, KT1)
- tz1bob...
- 0xAbcd... # EVM / Etherlink addresses
- So1ana... # Solana base58 pubkey
denylist: # optional — these recipients are always blocked
- tz1hacker...
rate_limit:
max_tx_per_hour: 10 # max transactions per 60-minute rolling window
max_tx_per_day: 50
time_window: # optional — restrict to specific hours (UTC)
allowed_hours:
start: 8 # 08:00 UTC
end: 22 # 22:00 UTC
allowed_days:
- monday
- tuesday
- wednesday
- thursday
- friday
require_confirmation: false # if true, every tx requires out-of-band approval
audit_log: true # default true — log all decisions to audit trail
import { PolicyEngine } from '@envoy/core'
import { TezosAdapter } from '@envoy/tezos'
const engine = new PolicyEngine({
adapter: new TezosAdapter({ rpc: 'https://rpc.tezos.example' }),
policy: './policy.yaml', // path to YAML file, or inline object
auditLog: true, // default true
})
Execute a transfer. Throws PolicyViolationError if blocked by policy.
await engine.transfer({
to: 'tz1alice...', // recipient address
amount: 100, // amount in chain-native unit (XTZ, SOL, etc.)
token: 'XTZ', // optional — default is native token
memo: 'payment', // optional
})
Dry-run policy check without executing the transaction. Returns { allowed: boolean, reasons: string[] }.
const result = await engine.check({
to: 'tz1unknown...',
amount: 50000,
})
// result.allowed === false
// result.reasons === ['exceeds daily_max_xtz (500)', 'recipient not in allowlist']
Returns the audit log as an array of decision records.
const log = engine.getAuditLog()
// [{ timestamp, action: 'transfer', allowed: false, reasons: [...], params: {...} }, ...]
import { TezosAdapter } from '@envoy/tezos'
const adapter = new TezosAdapter({
rpc: 'https://mainnet.api.tez.ie', // Tezos RPC node URL
signer: mySigner, // optional — Taquito InMemorySigner or similar
network: 'mainnet', // 'mainnet' | 'ghostnet' | custom
})
import { SolanaAdapter } from '@envoy/solana'
const adapter = new SolanaAdapter({
rpc: 'https://api.mainnet-beta.solana.com',
keypair: myKeypair, // @solana/web3.js Keypair
})
import { EvmAdapter } from '@envoy/evm'
const adapter = new EvmAdapter({
rpc: 'https://mainnet.infura.io/v3/YOUR_KEY',
privateKey: '0x...', // or use a Signer
chainId: 1, // 1=Ethereum, 137=Polygon, etc.
})
try {
await engine.transfer({ to: 'tz1bad...', amount: 99999 })
} catch (err) {
if (err instanceof PolicyViolationError) {
console.log(err.reasons) // string[] — why it was blocked
console.log(err.action) // 'transfer'
console.log(err.params) // original params
}
}
import { TezosAdapter } from '@envoy/tezos'
const adapter = new TezosAdapter({ rpc: 'https://mainnet.api.tez.ie' })
const balance = await adapter.getBalance('tz1alice...')
console.log(balance) // XTZ in mutez
import { PolicyEngine } from '@envoy/core'
import { TezosAdapter } from '@envoy/tezos'
const engine = new PolicyEngine({
adapter: new TezosAdapter({ rpc: 'https://mainnet.api.tez.ie', signer }),
policy: { limits: { daily_max_xtz: 500 }, allowlist: ['tz1alice...'] },
})
await engine.transfer({ to: 'tz1alice...', amount: 100 }) // OK
await engine.transfer({ to: 'tz1unknown...', amount: 100 }) // throws PolicyViolationError
import { PolicyEngine } from '@envoy/core'
import { SolanaAdapter } from '@envoy/solana'
import { Keypair } from '@solana/web3.js'
const engine = new PolicyEngine({
adapter: new SolanaAdapter({ rpc: 'https://api.mainnet-beta.solana.com', keypair }),
policy: { limits: { per_tx_max_sol: 1 }, rate_limit: { max_tx_per_hour: 5 } },
})
await engine.transfer({ to: 'So1ana...', amount: 0.5 }) // OK
| Chain | Package | Status | Native token | Notes |
|---|---|---|---|---|
| Tezos | @envoy/tezos | stable | XTZ | FA1.2 and FA2 tokens supported |
| Etherlink | @envoy/etherlink | stable | XTZ (via EVM) | Tezos EVM L2 |
| Solana | @envoy/solana | stable | SOL | SPL token support |
| EVM (generic) | @envoy/evm | stable | ETH/MATIC/etc. | Ethereum, Polygon, Arbitrum, Base, etc. |