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