Getting started
Ship pre-execution security in one function call.
AEGIS is a browser extension that simulates and risk-scores a Sui transaction before your user's wallet ever opens. Your dApp hands it a PTB and gets back a decision — the extension renders the review, the agent does the analysis, and you never build verdict UI or maintain risk rules.
Install
Load the extension once, in Chrome 116+.
Integrate
One import, one await, one status check.
Ship
Every transaction reviewed before signature.
Prerequisites
- • Chrome 116+ (the extension uses MAIN-world content scripts)
- • A Sui dApp that builds Programmable Transaction Blocks
- • An AEGIS agent server running on
http://localhost:3001
Installation
Install the extension
AEGIS isn't on the Chrome Web Store yet, so it installs unpacked. That takes about thirty seconds and gives you a normal browser extension — toolbar icon, popup windows, the lot.
- 1
Download the extension
Grab the archive and unzip it anywhere you like. Keep the folder — Chrome loads it from disk, so deleting it uninstalls the extension.
- 2
Open chrome://extensions
Paste it into the address bar. Chrome blocks links to internal pages, so it can't be a button here.
- 3
Turn on Developer mode
Toggle in the top-right corner of the extensions page.
- 4
Click "Load unpacked"
Select the unzipped aegis-extension/ folder — the one containing manifest.json.
- 5
Pin AEGIS to your toolbar
Click the puzzle-piece icon in Chrome's toolbar, then the pin next to AEGIS. The confirmation is an anchored panel, so it opens under this icon — unpinned, it drops out of the overflow menu instead and is easy to miss.
- 6
Reload your dApp tab
Content scripts only inject on fresh page loads. After the reload, window.aegis is available.
Click the AEGIS toolbar icon at any point to check whether the agent server is reachable, or to point the extension at a different one.
Quick start
Add it to your dApp
The SDK is a single dependency-free TypeScript file. Copy src/lib/aegis-sdk.ts into your project — it talks to the injected provider and nothing else.
import { aegis } from "@/lib/aegis-sdk";
const result = await aegis.analyze({
transaction: await tx.toJSON(),
sender: account.address,
network: "testnet",
});
if (result.status === "approved") {
await signAndExecuteTransaction({ transaction: tx });
}In context, with @mysten/dapp-kit-react:
"use client";
import { useState } from "react";
import { useCurrentAccount, useDAppKit } from "@mysten/dapp-kit-react";
import { Transaction } from "@mysten/sui/transactions";
import { aegis } from "@/lib/aegis-sdk";
export function SendButton() {
const account = useCurrentAccount();
const dAppKit = useDAppKit();
const [needsExtension, setNeedsExtension] = useState(false);
async function send() {
if (!account) return;
// 1. Build the transaction exactly as you would today.
const tx = new Transaction();
tx.setSender(account.address);
const [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(50_000_000n)]);
tx.transferObjects([coin], tx.pure.address(RECIPIENT));
const payload = await tx.toJSON();
// 2. Hand it to AEGIS. The extension popup opens; this awaits the user.
const result = await aegis.analyze({
transaction: payload,
sender: account.address,
network: "testnet",
label: "Send 0.05 SUI",
});
if (result.status === "not_installed") {
setNeedsExtension(true);
return;
}
if (result.status !== "approved") return; // rejected or cancelled
// 3. Sign the exact payload AEGIS analyzed — never a rebuilt one.
await dAppKit.signAndExecuteTransaction({
transaction: Transaction.from(payload),
});
}
return <button onClick={send}>Send</button>;
}Sign what was analyzed
Pass the same serialized payload to both analyze() and Transaction.from(). Rebuilding the transaction after approval means the user signs something AEGIS never saw — which defeats the point.
Result handling
Handling every status
analyze() never throws. Every outcome — including a missing extension — comes back as a status, so there is no try/catch to forget.
| Status | Meaning | What to do |
|---|---|---|
approved | User confirmed in the popup. | Proceed to signing. |
rejected | User explicitly rejected. | Stop. Optionally explain why. |
cancelled | Popup closed without a decision. | Treat as a no-op. |
not_installed | No AEGIS provider in this browser. | Show an install prompt. |
error | Extension reached but failed. | Surface result.error.message. |
const result = await aegis.analyze({ transaction, sender, network });
switch (result.status) {
case "approved":
await signAndExecuteTransaction({ transaction: tx });
break;
case "rejected":
toast("AEGIS flagged this transaction and you rejected it.");
break;
case "cancelled":
// Popup closed without deciding — usually a no-op.
break;
case "not_installed":
showInstallPrompt();
break;
case "error":
toast.error(result.error!.message);
break;
}API reference
The SDK surface
aegis.analyze(params): Promise<AegisResult>Opens the confirmation popup and resolves once the user decides.
transactionstringRequired. The JSON from tx.toJSON().senderstringRequired. The signing address; also the simulation sender.networkstringOptional, defaults to "testnet".labelstringOptional. Human label shown in the popup.aegis.detect(timeoutMs?): Promise<AegisProvider | null>Resolves the injected provider, waiting up to timeoutMs (default 1000ms) for a cold extension start-up. Returns null if AEGIS isn't installed.
aegis.isInstalled(): booleanSynchronous check with no waiting. Prefer detect() on first render — this can report false before injection completes.
aegis.version: string | nullThe installed extension's version, or null.
AgentAnalysis
Present on result.analysis for approved and rejected outcomes, so you can log or display what the user was shown.
interface AgentAnalysis {
explanation: string;
riskScore: number; // 0–100
riskFlags: string[];
recommendation: "approve" | "caution" | "reject";
operations: string[];
protocols: {
packageId: string;
name: string;
category: string;
audited: boolean;
risk: string;
}[];
simulation: {
status: string; // "simulated" | "estimated" | …
balanceChanges: { coinType: string; amount: string }[];
objectChanges: string[];
gasUsed: { computationCost: string; storageCost: string };
events: string[];
} | null;
similarPatterns?: {
description: string;
category: string;
riskLevel: string;
similarity: number; // 0–1
}[];
}Detection
Detecting the extension
Show an install prompt before the user commits to a flow, rather than after. A small hook covers it:
import { useEffect, useState } from "react";
import { aegis } from "@/lib/aegis-sdk";
function useAegis() {
const [version, setVersion] = useState<string | null>(null);
const [checked, setChecked] = useState(false);
useEffect(() => {
let cancelled = false;
aegis.detect().then((provider) => {
if (cancelled) return;
setVersion(provider?.version ?? null);
setChecked(true);
});
return () => { cancelled = true; };
}, []);
return { installed: version !== null, version, checked };
}If you'd rather not use the SDK, the provider is a plain object on window.aegis, injected at document_start:
// Without the SDK — the raw injected provider.
if (window.aegis?.isAegis) {
const { status, analysis } = await window.aegis.analyze({
transaction: await tx.toJSON(),
sender: address,
network: "testnet",
});
}
// Injected at document_start, and announced for late listeners:
window.addEventListener("aegis#initialized", (e) => {
console.log("AEGIS ready", e.detail); // { name, rdns, version }
});Backend
Agent server contract
The extension calls the agent directly. Point it anywhere from the toolbar popup — any host other than localhost:3001 also needs an entry in host_permissions.
POST http://localhost:3001/analyze-stream
Content-Type: application/json
{ "rawPtb": "<tx.toJSON() string>", "walletAddress": "0x…" }
--- response: text/event-stream ---
data: {"type":"tool_start","tool":"parse_ptb","label":"Decoding PTB"}
data: {"type":"tool_end","tool":"parse_ptb","summary":"4 commands, 2 packages"}
data: {"type":"thought","text":"Checking the target against …","source":"gemini"}
data: {"type":"result","data":{ … AgentAnalysis … }}
data: {"type":"error","message":"…"}A plain JSON response of the same AgentAnalysis shape also works, so a non-streaming agent needs no changes.
Troubleshooting
Common problems
Reload the dApp tab. Content scripts inject at document_start, so a tab opened before the extension was loaded has no provider. Also confirm the page origin matches the manifest's content_scripts.matches (localhost, 127.0.0.1, and https by default).
The extension calls the agent server directly, not through your app. Start it on http://localhost:3001, or click the AEGIS toolbar icon and point it elsewhere. Any host other than localhost:3001 also needs an entry in host_permissions in manifest.json.
One confirmation at a time, like a wallet. The open popup is focused instead of stacking a second one — finish it and call analyze() again.
That's how Chrome's anchored panels work — they dismiss on focus loss. AEGIS reports it as cancelled so nothing hangs, and the whole review → analyze → confirm flow lives inside the panel, so you never need to click outside it. On Chrome below 127 there is no panel API and AEGIS opens a standalone window instead, which stays put.
Pin AEGIS to the toolbar. The panel anchors to the extension icon; unpinned, it opens inside the puzzle-piece overflow menu and is easy to miss entirely.
It always resolves. Dismissing the panel without deciding comes back as cancelled, and a reloaded extension rejects in-flight requests with DISCONNECTED.
Chrome says this when the folder moved or files changed on disk. Re-run Load unpacked against the current folder.
Installed it? Try the flow end to end.
Open the live demo