SDK Reference
The @enshell/sdk package provides the TypeScript client for interacting with the ENShell firewall. It handles encryption, contract interaction, relay communication, and ENS operations.
npm install @enshell/sdk
Version: 0.9.2 | Module: ESM | Node.js: >= 22.10.0
ENShell Class
The main SDK client. All write operations use the provided signer. All read operations use a separate JsonRpcProvider to avoid WalletConnect eth_call rejection issues.
Constructor
import { ENShell, Network } from '@enshell/sdk';
const client = new ENShell({
network: Network.SEPOLIA,
signer: yourEthersSigner,
contractAddress?: string, // Override default address
rpcUrl?: string, // Override default RPC URL
});
Throws Error("No contract address configured for network {network}") if no address is available (mainnet has no deployment yet).
protect(agentId, options)
The core firewall method. Encrypts the instruction, stores it on the relay, and submits the action on-chain.
const result = await client.protect('my-agent', {
instruction: 'Swap 0.05 ETH for USDC on Uniswap V2',
tx: {
to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
value: '0.05', // ETH string, defaults to "0"
data: '0x', // Hex calldata, defaults to "0x"
},
});
Returns: ProtectResult
interface ProtectResult {
actionId: bigint;
txHash: string;
instructionHash: string; // bytes32 keccak256 of instruction
tx: { to: string; value: string; data: string };
waitForResolution: () => Promise<ResolutionResult>;
}
The returned waitForResolution() closure polls the contract until the CRE oracle resolves the action.
waitForResolution(actionId, pollIntervalMs?, timeoutMs?)
Polls the contract until an action is resolved. Called automatically via the ProtectResult, but can also be called directly.
const resolution = await client.waitForResolution(
actionId,
5000, // poll every 5s (default)
300000, // timeout after 5min (default)
);
Returns: ResolutionResult
interface ResolutionResult {
decision: ActionDecision; // PENDING, APPROVED, ESCALATED, or BLOCKED
analysis?: AnalysisResult; // Present for ESCALATED and BLOCKED
}
interface AnalysisResult {
score: number;
decision: number;
reasoning: string;
instruction: string;
target: string;
agentId: string;
}
For ESCALATED actions, returns immediately so the caller can prompt for human approval. Throws on timeout.
registerAgent(agentId, options)
Registers an agent on-chain + creates ENS subdomain + posts to relay.
await client.registerAgent('my-agent', {
agentAddress: '0xAgentWallet',
spendLimit: '1.0', // ETH string
allowedTargets: ['0xUniswap...'], // Optional
});
Returns: { txHash: string }
Throws Error('Agent "{agentId}" is already registered') if the agent exists.
If allowedTargets is provided, makes a second transaction to call setAllowedTargets(). Posts agent info to the relay (fire-and-forget; relay errors are swallowed).
getAgent(agentId)
Read-only. Returns the full on-chain agent record.
const agent = await client.getAgent('my-agent');
Returns: Agent
interface Agent {
ensNode: string;
agentAddress: string;
owner: string;
spendLimit: bigint;
threatScore: bigint;
strikes: bigint;
active: boolean;
worldIdVerified: boolean;
registeredAt: bigint;
}
getAgentCount()
const count = await client.getAgentCount(); // bigint
deactivateAgent(agentId)
Freezes an active agent. Notifies the relay.
await client.deactivateAgent('my-agent');
Returns: { txHash: string }
Throws if the agent is already deactivated.
reactivateAgent(agentId)
Unfreezes a deactivated agent. Notifies the relay.
await client.reactivateAgent('my-agent');
Returns: { txHash: string }
Throws if the agent is already active.
approveAction(actionId) / rejectAction(actionId)
Resolve escalated actions. Owner only.
await client.approveAction(actionId); // { txHash: string }
await client.rejectAction(actionId); // { txHash: string }
setAllowedTarget(agentId, target, allowed)
await client.setAllowedTarget('my-agent', '0xTarget', true);
Returns: { txHash: string }
isTargetAllowed(agentId, target)
const allowed = await client.isTargetAllowed('my-agent', '0xTarget'); // boolean
checkTrust(checkerAgentId, targetAgentId)
On-chain trust check. Emits TrustChecked event.
const result = await client.checkTrust('my-agent', 'other-agent');
console.log(result.trusted); // boolean
console.log(result.txHash);
isTrusted(agentId)
Gas-free trust check (view function, no event).
const trusted = await client.isTrusted('my-agent'); // boolean
submitAction(agentId, target, value, data, instructionHash)
Low-level action submission. Use protect() instead for the full encryption + relay flow.
const result = await client.submitAction(
'my-agent',
'0xTarget',
'0.05', // ETH string
'0x', // calldata
'0xabcd...', // bytes32 instruction hash
);
// result.actionId: bigint
// result.txHash: string
getQueuedAction(actionId)
const action = await client.getQueuedAction(actionId);
Returns: QueuedAction
interface QueuedAction {
agentId: string;
target: string;
value: bigint;
data: string;
instructionHash: string;
queuedAt: bigint;
resolved: boolean;
decision: number; // 0=PENDING, 1=APPROVED, 2=ESCALATED, 3=BLOCKED
}
Encryption Functions
encryptForOracle(plaintext, publicKeyHex)
ECIES encryption using secp256k1 ECDH + AES-256-GCM. Used internally by protect().
import { encryptForOracle } from '@enshell/sdk';
const encrypted = encryptForOracle(
'Swap 0.05 ETH for USDC',
'02cea1f34f52c8e8a2d7d5bf4a768677e600be906fb5c68985fe635ac1331409ca',
);
// Returns: "0x" + hex-encoded packed bytes
Packed format: ephemeralPubKey (33 bytes) || nonce (12 bytes) || ciphertext (variable)
Non-deterministic -- different output each call due to ephemeral key and random nonce.
decryptAsOracle(encryptedHex, privateKeyHex)
Decrypts an ECIES payload. Used by the CRE workflow on oracle nodes.
import { decryptAsOracle } from '@enshell/sdk';
const plaintext = decryptAsOracle(encrypted, oraclePrivateKey);
getPublicKeyFromPrivate(privateKeyHex)
Derives compressed secp256k1 public key from a private key.
import { getPublicKeyFromPrivate } from '@enshell/sdk';
const pubKey = getPublicKeyFromPrivate(privateKeyHex);
// 66-char hex string (compressed, no 0x prefix)
ENS Functions
computeEnsNode(agentId, network)
Computes the ENS namehash for an agent's subdomain.
import { computeEnsNode, Network } from '@enshell/sdk';
const node = computeEnsNode('my-agent', Network.SEPOLIA);
// namehash("my-agent.enshell.eth")
createSubdomain(agentId, network, signer)
Creates an ENS subdomain via NameWrapper. Idempotent if already owned by the signer.
import { createSubdomain, Network } from '@enshell/sdk';
await createSubdomain('my-agent', Network.SEPOLIA, signer);
Throws if the subdomain is owned by a different address.
RelayClient
HTTP client for the ENShell relay service.
import { RelayClient } from '@enshell/sdk';
const relay = new RelayClient('https://relay.enshell.xyz');
// Store encrypted payload
await relay.put(instructionHash, encryptedPayload);
// Retrieve encrypted payload
const payload = await relay.get(instructionHash); // string | null
// Register agent for dashboard
await relay.registerAgent('my-agent', {
ensName: 'my-agent.enshell.eth',
address: '0x...',
owner: '0x...',
spendLimit: '1.0',
active: true,
});
// Partial update
await relay.updateAgent('my-agent', { active: false });
// Health check
const health = await relay.health();
Enums
Network
enum Network {
MAINNET = "mainnet",
SEPOLIA = "sepolia",
}
ActionDecision
enum ActionDecision {
PENDING = 0,
APPROVED = 1,
ESCALATED = 2,
BLOCKED = 3,
}
Network Configuration
import { NETWORK_CONFIG, Network } from '@enshell/sdk';
const config = NETWORK_CONFIG[Network.SEPOLIA];
| Property | Sepolia Value |
|---|---|
chainId |
11155111 |
rpcUrl |
https://ethereum-sepolia-rpc.publicnode.com |
firewallAddress |
0x410f4D119EF857879E42625381DB131457db78A7 |
relayUrl |
https://relay.enshell.xyz |
oraclePublicKey |
02cea1f34f52c8e8a2d7d5bf4a768677e600be906fb5c68985fe635ac1331409ca |
ensParentDomain |
enshell.eth |
nameWrapperAddress |
0x0635513f179D50A207757E05759CbD106d7dFcE8 |
ensResolverAddress |
0xE99638b40E4Fff0129D56f03b55b6bbC4BBE49b5 |
ensDefaultAvatar |
https://euc.li/sepolia/enshell.eth |
Mainnet is configured but has empty firewallAddress, relayUrl, and oraclePublicKey (not yet deployed).
Contract ABI
The full ABI is exported as AGENT_FIREWALL_ABI for direct contract interaction:
import { AGENT_FIREWALL_ABI, getFirewallContract } from '@enshell/sdk';
const contract = getFirewallContract(address, signerOrProvider);
Dependencies
The SDK has only four dependencies, all audited cryptographic or Ethereum libraries:
| Package | Purpose |
|---|---|
@noble/secp256k1 |
ECDH key exchange |
@noble/ciphers |
AES-256-GCM encryption |
@noble/hashes |
SHA-256 key derivation |
ethers |
Contract interaction, ENS utilities |