Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions dashboard/hooks/use-simulation-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,19 @@ export function useSimulationRunner(policy: PolicySummary) {
store.getState().pushLog({ ...entryBase, signature: sig, status: "success", error: null });
} catch (e: unknown) {
store.getState().incrementFailed();
const msg = e instanceof Error ? e.message : String(e);
const err = e as { message?: string; logs?: string[] };
const baseMsg = err?.message ?? String(e);
const logs = Array.isArray(err?.logs) ? err.logs : [];
const programLog = logs.find((l) => /custom program error|Error Code|Error Number|AnchorError/i.test(l));
const msg = programLog ? `${baseMsg} — ${programLog}` : baseMsg;
Comment on lines +186 to +190

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Redundant log re-extraction produces a duplicated program-log in the displayed error.

browserGuardedSolTransfer now throws errors whose .message already contains the matched program log line (e.g., "SendTransactionError: … — Program log: Error Code: PolicyPaused…"). The hook then reads the same log entry from err.logs and appends it again, producing:

"SendTransactionError: … — Program log: Error Code: PolicyPaused… — Program log: Error Code: PolicyPaused…"

The stop-condition checks on lines 193/198 still work correctly, but pushLog writes the duplicated string to the UI simulation log.

Since browserGuardedSolTransfer is the only call site in the try block and it now owns the enrichment, the hook can trust err.message directly:

🐛 Proposed fix
     } catch (e: unknown) {
       store.getState().incrementFailed();
-      const err = e as { message?: string; logs?: string[] };
-      const baseMsg = err?.message ?? String(e);
-      const logs = Array.isArray(err?.logs) ? err.logs : [];
-      const programLog = logs.find((l) => /custom program error|Error Code|Error Number|AnchorError/i.test(l));
-      const msg = programLog ? `${baseMsg} — ${programLog}` : baseMsg;
+      const msg = e instanceof Error ? e.message : String(e);
       store.getState().pushLog({ ...entryBase, signature: null, status: "failed", error: msg });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const err = e as { message?: string; logs?: string[] };
const baseMsg = err?.message ?? String(e);
const logs = Array.isArray(err?.logs) ? err.logs : [];
const programLog = logs.find((l) => /custom program error|Error Code|Error Number|AnchorError/i.test(l));
const msg = programLog ? `${baseMsg}${programLog}` : baseMsg;
const msg = e instanceof Error ? e.message : String(e);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/hooks/use-simulation-runner.ts` around lines 186 - 190, The hook
currently re-extracts a program log from err.logs and appends it to baseMsg,
causing duplication because browserGuardedSolTransfer already enriches
err.message; update the block that computes err/baseMsg/msg to trust err.message
(baseMsg) directly and remove the logs extraction and programLog append logic
(remove use of logs and programLog), so msg = baseMsg (or err.message) is used
when calling pushLog; keep existing stop-condition checks intact and only change
the computation of msg in this hook.

store.getState().pushLog({ ...entryBase, signature: null, status: "failed", error: msg });

if (msg.includes("6000")) {
if (msg.includes("6000") || /PolicyPaused/i.test(msg)) {
store.getState().setStopReason("Policy paused (error 6000)");
stop();
return;
}
if (msg.includes("6004")) {
if (msg.includes("6004") || /DailyBudgetExceeded/i.test(msg)) {
store.getState().setStopReason("Daily budget exceeded (error 6004)");
stop();
return;
Expand Down
62 changes: 49 additions & 13 deletions dashboard/lib/simulation/build-transfer-ix-data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Keypair, PublicKey, SystemProgram } from "@solana/web3.js";
import { Keypair, PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import type { GuardrailsClient } from "@/lib/sdk/client";

Expand All @@ -18,18 +18,54 @@ export async function browserGuardedSolTransfer(
lamports: number,
): Promise<string> {
const ixData = buildTransferIxData(BigInt(lamports));
return client.guardedExecute(
agentKeypair,
policyPda,
trackerPda,
SystemProgram.programId,
{
instructionData: ixData,
amountHint: new BN(lamports),
inputAccountIndex: null,
},
[{ pubkey: destination, isSigner: false, isWritable: true }],
);
const args = {
instructionData: ixData,
amountHint: new BN(lamports),
inputAccountIndex: null,
};

const ix = await (client.program.methods as any)
.guardedExecute(args)
.accounts({
agent: agentKeypair.publicKey,
policy: policyPda,
spendTracker: trackerPda,
targetProgram: SystemProgram.programId,
systemProgram: SystemProgram.programId,
})
.remainingAccounts([{ pubkey: destination, isSigner: false, isWritable: true }])
.instruction();

const connection = client.program.provider.connection;
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
const tx = new Transaction({ feePayer: agentKeypair.publicKey, blockhash, lastValidBlockHeight }).add(ix);
tx.sign(agentKeypair);

const raw = tx.serialize();
let sig: string;
try {
sig = await connection.sendRawTransaction(raw, { skipPreflight: false, maxRetries: 0 });
} catch (e: unknown) {
const sendErr = e as { message?: string; logs?: string[] };
const logs = Array.isArray(sendErr.logs) ? sendErr.logs : [];
const programLog = logs.find((l) => /custom program error|Error Code|Error Number|AnchorError/i.test(l));
const baseMsg = sendErr.message ?? String(e);
const err = new Error(programLog ? `${baseMsg} — ${programLog}` : baseMsg) as Error & { logs: string[] };
err.logs = logs;
throw err;
}

const conf = await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, "confirmed");
if (conf.value.err) {
const txDetails = await connection.getTransaction(sig, { commitment: "confirmed" });
const logs = txDetails?.meta?.logMessages ?? [];
const programLog = logs.find((l) => /custom program error|Error Code|Error Number|AnchorError/i.test(l));
const errStr = typeof conf.value.err === "string" ? conf.value.err : JSON.stringify(conf.value.err);
const err = new Error(programLog ? `${errStr} — ${programLog}` : errStr) as Error & { logs: string[] };
err.logs = logs;
throw err;
}
return sig;
}

export function randomBetween(min: number, max: number): number {
Expand Down
180 changes: 127 additions & 53 deletions landingPage/app/components/defense-bento-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,129 @@ function Tag({
)
}

function ProgramAllowListIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
className="h-6 w-6"
aria-hidden="true"
>
<path
d="M12 3.25 19 6.5v5.15c0 4.58-2.86 8.4-7 9.6-4.14-1.2-7-5.02-7-9.6V6.5l7-3.25Z"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
<path
d="M9 10.25h5.5M9 13h3.75"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
/>
<path
d="m14.25 15.25 1.25 1.25 2.35-2.75"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}

function SpendingBudgetIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
className="h-6 w-6"
aria-hidden="true"
>
<path
d="M5.5 8.25h13A2.5 2.5 0 0 1 21 10.75v6A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5V7.75A2.75 2.75 0 0 1 5.75 5h9"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M16.25 12.25h4.25v3h-4.25a1.5 1.5 0 0 1 0-3Z"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
<path
d="M8 10.5v4.25M11.25 9v5.75"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
/>
</svg>
)
}

function KillSwitchIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
className="h-6 w-6"
aria-hidden="true"
>
<path
d="m9 3.5 6 .01 4.25 4.24v6L15 18H9l-4.25-4.25v-6L9 3.5Z"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
<path
d="M9.25 9.25v3.5M14.75 9.25v3.5"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
/>
<path
d="M8 21h8"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
/>
</svg>
)
}

function MonitoringReportsIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
className="h-6 w-6"
aria-hidden="true"
>
<path
d="M5.75 4h9.5L19 7.75v11A2.25 2.25 0 0 1 16.75 21h-11A2.25 2.25 0 0 1 3.5 18.75V6.25A2.25 2.25 0 0 1 5.75 4Z"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
<path
d="M15 4v4h4M7 15.25h2.15l1.25-3.5 1.7 5 1.2-2.5H17"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7 9.25h4.5"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
/>
</svg>
)
}

export default function DefenseBentoSection() {
const sectionRef = useRef<HTMLElement>(null)

Expand Down Expand Up @@ -98,20 +221,7 @@ export default function DefenseBentoSection() {
/>
<div className="relative">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-[14px] bg-[rgb(var(--primary-rgb)/0.15)] text-primary">
<svg viewBox="0 0 24 24" fill="none" className="h-6 w-6">
<path
d="M12 2l8 4v6c0 5.5-3.8 10.7-8 12-4.2-1.3-8-6.5-8-12V6l8-4z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M9 12l2 2 4-4"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<ProgramAllowListIcon />
</div>
<h3 className="text-xl font-bold text-white">
Program Allow-Listing
Expand Down Expand Up @@ -162,21 +272,7 @@ export default function DefenseBentoSection() {
/>
<div className="relative">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-[14px] bg-[rgb(var(--accent-rgb)/0.12)] text-accent">
<svg viewBox="0 0 24 24" fill="none" className="h-6 w-6">
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M12 6v6l4 2"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
<SpendingBudgetIcon />
</div>
<h3 className="text-xl font-bold text-white">Spending Budgets</h3>
<p className="mt-2 mb-5 text-[15px] leading-7 text-foreground-dim">
Expand Down Expand Up @@ -216,14 +312,7 @@ export default function DefenseBentoSection() {
/>
<div className="relative">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-[14px] bg-[rgb(var(--danger-rgb)/0.12)] text-danger">
<svg viewBox="0 0 24 24" fill="none" className="h-6 w-6">
<path
d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
</svg>
<KillSwitchIcon />
</div>
<h3 className="text-xl font-bold text-white">AI Kill Switch</h3>
<p className="mt-2 mb-5 text-[15px] leading-7 text-foreground-dim">
Expand Down Expand Up @@ -257,22 +346,7 @@ export default function DefenseBentoSection() {
/>
<div className="relative">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-[14px] bg-[rgb(var(--violet-rgb)/0.12)] text-violet">
<svg viewBox="0 0 24 24" fill="none" className="h-6 w-6">
<rect
x="3"
y="3"
width="18"
height="18"
rx="3"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M3 9h18M9 3v18"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
<MonitoringReportsIcon />
</div>
<h3 className="text-xl font-bold text-white">
Real-Time Monitoring &amp; Incident Reports
Expand Down
Loading
Loading