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.

01

Install

Load the extension once, in Chrome 116+.

02

Integrate

One import, one await, one status check.

03

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.

Download aegis-extension.zipChrome MV3 · unpacked · ~22 KB
Download ↓
  1. 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. 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. 3

    Turn on Developer mode

    Toggle in the top-right corner of the extensions page.

  4. 4

    Click "Load unpacked"

    Select the unzipped aegis-extension/ folder — the one containing manifest.json.

  5. 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. 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.

the entire integration
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:

SendButton.tsx
"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.

StatusMeaningWhat to do
approvedUser confirmed in the popup.Proceed to signing.
rejectedUser explicitly rejected.Stop. Optionally explain why.
cancelledPopup closed without a decision.Treat as a no-op.
not_installedNo AEGIS provider in this browser.Show an install prompt.
errorExtension reached but failed.Surface result.error.message.
all five branches
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(): boolean

Synchronous check with no waiting. Prefer detect() on first render — this can report false before injection completes.

aegis.version: string | null

The 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.

AgentAnalysis
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:

useAegis.ts
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:

raw provider
// 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.

analyze-stream
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