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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ This app uses Vercel Web Analytics. Two things must stay in place:
| `trackExplorerActiveBlockJump(chain, jump)` | `app/internal-explorer/components/ActiveBlockButton.tsx` — zeronet latest/previous active block |
| `trackValidityOrder(side, status)` | `app/vibenet/demos/validity/ValidityDemo.tsx` — conditional swap submit / include / expiry / replace |
| `trackValidityRace(attempt, status)` | `app/vibenet/demos/validity/race-the-agent/RaceTheAgentDemo.tsx` — validity/manual comparison attempts |
| `trackReflexRun(status)` | `app/vibenet/demos/reflex/ReflexGame.tsx` — reaction-game starts and outcomes |

Add a helper (and a row here) for a new key journey; remove the helper if you
remove its surface. Confirm the wiring with `grep -rn "analytics/events" app`.
Expand Down
6 changes: 6 additions & 0 deletions app/analytics/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,9 @@ export function trackValidityRace(
): void {
track('validity_race', { attempt, status });
}

// --- 200ms Reflex demo ---

export function trackReflexRun(status: 'started' | 'human' | 'block' | 'tie' | 'error'): void {
track('reflex_run', { status });
}
25 changes: 19 additions & 6 deletions app/vibenet/demos/_shared/InclusionBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,35 @@
// timestamp minus that of the newest block the page had seen at broadcast.
// Vibenet runs Cobalt today; on chains without it the slot is omitted.

import Link from 'next/link';

import { cn } from '../../../components/ui/cn';
import { Text } from '../../../components/ui/Text';
import { blocksAfterSendLabel, type Inclusion, latencyLabel, slotLabel } from './inclusion';

/** Unlisted reaction game; this footnote is the only way in. */
const REFLEX_PATH = '/vibenet/demos/reflex';

/**
* The line under the badge in a transaction result: the claim, in words. Only
* for blocks that carry Cobalt's millisecond timestamp, so it never shows on a
* chain without 200 ms blocks.
* The line under the badge in a transaction result: the claim, in words, plus
* a footnote linking the Reflex easter egg. Only for blocks that carry Cobalt's
* millisecond timestamp, so it never shows on a chain without 200 ms blocks.
*/
export function InclusionTagline({ inclusion }: { inclusion: Inclusion }) {
if (inclusion.blockTimestampMs === null) return null;
const blocks = blocksAfterSendLabel(inclusion);
return (
<Text variant="label.regular" tone="muted">
{blocks ? `Sealed ${blocks} after broadcast. ` : ''}Brought to you by 200 ms blocks on Base.
</Text>
<>
<Text variant="label.regular" tone="muted">
{blocks ? `Sealed ${blocks} after broadcast. ` : ''}Brought to you by 200 ms blocks on Base.
</Text>
<Text variant="footnote" tone="muted">
Think you&apos;re faster?{' '}
<Link href={REFLEX_PATH} className="underline underline-offset-2 transition-colors hover:text-foreground">
Race a block
</Link>
</Text>
</>
);
}

Expand Down
34 changes: 28 additions & 6 deletions app/vibenet/demos/account/useAccountEngine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,9 @@ function useAccountEngineCore() {
return vibenetApi.account.balances(acct.address, 'vibenet').catch(() => null);
};

const requestFaucet = async () => {
if (!acct) return;
/** Drip ETH + USDV to the active account and wait for the ETH to land. Resolves true once credited. */
const requestFaucet = async (): Promise<boolean> => {
if (!acct) return false;
setFaucetBusy('eth+usdv');
try {
// Capture the pre-drip balance as the baseline for the "did it credit?" poll.
Expand All @@ -310,8 +311,10 @@ function useAccountEngineCore() {
} else {
toast.error("Top up didn't go through — Vibenet may be down for maintenance. Please try again shortly.");
}
return credited;
} catch {
toast.error('Top up failed');
return false;
} finally {
setFaucetBusy(null);
}
Expand Down Expand Up @@ -397,7 +400,7 @@ function useAccountEngineCore() {
// Wait for a broadcast tx to be included and check that it — and every 8130
// phase in it — succeeded. Throws TxPendingError if it is still not included
// when the timeout runs out, a plain Error if anything reverted.
const awaitInclusion = async (txHash: Hex, timeout = 30_000): Promise<Hex> => {
const awaitInclusion = async (txHash: Hex, timeout = 30_000, onReceipt?: () => void): Promise<Hex> => {
const client = makeRpcClient();
const w = ensureWatcher();
// The receipt arrives on the socket the moment its block is sealed; over
Expand All @@ -409,6 +412,7 @@ function useAccountEngineCore() {
if (err instanceof ReceiptTimeoutError) throw new TxPendingError(txHash);
throw err;
}
onReceipt?.();
// Status and 8130 phase results come from the account RPC, whose replica
// can trail the socket's node by a block: a few short retries, then the
// pushed receipt itself.
Expand Down Expand Up @@ -453,8 +457,15 @@ function useAccountEngineCore() {

// Broadcast a signed 8130 tx and wait for inclusion. Throws TxPendingError on
// timeout (submitted but unconfirmed), a plain Error if any phase reverts.
const broadcast8130 = async (signedTx: Hex, onStatus?: (s: 'submitting' | 'confirming') => void): Promise<Hex> => {
const broadcast8130 = async (
signedTx: Hex,
onStatus?: (s: 'submitting' | 'confirming') => void,
onBroadcast?: (hash: Hex) => void,
beforeBroadcast?: () => void | Promise<void>,
onReceipt?: () => void,
): Promise<Hex> => {
const client = makeRpcClient();
await beforeBroadcast?.();
onStatus?.('submitting');
// Read the anchor before the send leaves.
const anchor = latestAnchor();
Expand All @@ -463,8 +474,9 @@ function useAccountEngineCore() {
params: [signedTx],
})) as Hex;
anchorSend(txHash, anchor);
onBroadcast?.(txHash);
onStatus?.('confirming');
return awaitInclusion(txHash);
return awaitInclusion(txHash, 30_000, onReceipt);
};

// Wait for a tx a hosted payer broadcast (`payer_sendTransaction`), measured
Expand Down Expand Up @@ -592,11 +604,20 @@ function useAccountEngineCore() {
calls,
tokenGas,
metadata,
onBroadcast,
beforeBroadcast,
onReceipt,
}: {
calls: { to: Address; data: Hex; value?: string }[];
tokenGas?: { token: Address; decimals: number; payer: Signer; fee: bigint };
/** Optional top-level signed app data attached to the transaction. */
metadata?: string;
/** Called as soon as the RPC accepts the signed transaction, before inclusion. */
onBroadcast?: (hash: Hex) => void;
/** Runs after signing and immediately before the transaction is sent to the RPC. */
beforeBroadcast?: () => void | Promise<void>;
/** Called when the receipt first arrives, before follow-up receipt parsing. */
onReceipt?: () => void;
}): Promise<{ hash: Hex; serialized: Hex; mode: 'self' | 'token'; inclusion?: Inclusion }> => {
if (!acct) throw new Error('Select an account before you continue.');
if (!calls.length) throw new Error('No calls to send.');
Expand All @@ -611,7 +632,7 @@ function useAccountEngineCore() {
}
: undefined,
});
const hash = await broadcast8130(serialized);
const hash = await broadcast8130(serialized, undefined, onBroadcast, beforeBroadcast, onReceipt);
return { hash, serialized, mode: tokenGas ? 'token' : 'self', inclusion: inclusionFor(hash) };
};

Expand Down Expand Up @@ -643,6 +664,7 @@ function useAccountEngineCore() {
// Faucet
faucetBusy,
requestFaucet,
refreshVibenetBalances,

// Key + account-building primitives (used by CreateAccountModal)
usedSignerIds,
Expand Down
14 changes: 14 additions & 0 deletions app/vibenet/demos/catalogue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ export const DEMOS: DemoEntry[] = [
available: true,
listed: false,
},
{
// Easter egg: linked only from the inclusion line in transaction popups.
href: '/vibenet/demos/reflex',
title: 'Reflex',
summary:
'Send a real transaction from your browser account, then race to hit the target before Vibenet seals it in a 200 ms block.',
points: [
'Starts on transaction broadcast',
'Compares your reaction with real inclusion',
'Uses your existing Vibenet account',
],
available: true,
listed: false,
},
];

/** `smart-wallet` -> `Smart Wallet`. Fallback for a route with no catalogue entry. */
Expand Down
Loading
Loading