Skip to content
Merged
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
119 changes: 0 additions & 119 deletions app/api/vibenet/validity/candles/route.ts

This file was deleted.

52 changes: 21 additions & 31 deletions app/vibenet/demos/validity/ValidityDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
helperApproveCalls,
inventoryMints,
reservesFromSyncLog,
SYNC_EVENT,
tokenBalance,
} from './lib/amm';
import {
Expand Down Expand Up @@ -65,16 +66,14 @@ import {
} from './lib/quote';
import {
describeValidityError,
fetchTape,
makePublicClient,
publishTape,
sendValidityTransaction,
VIBENET_CHAIN,
type RpcSend,
} from './lib/rpc';
import { connectJsonRpcStream, headNumber, type StreamHead, type StreamLog } from './lib/stream';
import { probeSingleton } from './lib/singleton';
import { mergeTape } from './lib/tape';
import { backfillBlocks, backfillSamples, mergeTape } from './lib/tape';
import { createState, loadState, saveState, type StoredState } from './lib/store';
import type { PlacedOrder, Rectangle, Reserves, Side } from './lib/types';

Expand Down Expand Up @@ -145,8 +144,6 @@ function ValidityDemoInner() {
ordersRef.current = orders;
const reservesRef = useRef<Reserves | null>(null);
reservesRef.current = reserves;
const samplesRef = useRef<PriceSample[]>([]);
samplesRef.current = samples;
const stateRef = useRef<StoredState | null>(null);
stateRef.current = state;

Expand Down Expand Up @@ -195,38 +192,31 @@ function ValidityDemoInner() {
const tapeVibeToken0 = Boolean(state?.deployment && vibeIsToken0(state.deployment));

useEffect(() => {
if (!hydrated || !pair) return;
const client = publicRef.current;
if (!hydrated || !pair || !client) return;
let cancelled = false;
void fetchTape(pair, tapeVibeToken0)
.then((remote) => {
if (cancelled || remote.length === 0) return;
setSamples((prev) => mergeTape(remote, prev));
})
.catch(() => {});
void (async () => {
const latestBlock = await client.getBlockNumber();
const lookback = backfillBlocks();
const [logs, current] = await Promise.all([
client.getLogs({
address: pair,
event: SYNC_EVENT,
fromBlock: latestBlock > lookback ? latestBlock - lookback : 0n,
toBlock: latestBlock,
}),
getReserves(client, pair),
]);
if (cancelled) return;
const mid = Number(quoteWad(current.reserve0, current.reserve1, tapeVibeToken0)) / 1e18;
const history = backfillSamples({ logs, vibeToken0: tapeVibeToken0, latestBlock, now: Date.now(), mid });
setSamples((prev) => mergeTape(history, prev));
})().catch(() => {});
return () => {
cancelled = true;
};
}, [hydrated, pair, tapeVibeToken0]);

useEffect(() => {
if (!hydrated || !pair) return;
const flush = () => {
void publishTape(pair, samplesRef.current).catch(() => {});
};
const id = window.setInterval(flush, 2_000);
const onHide = () => {
if (document.visibilityState === 'hidden') flush();
};
document.addEventListener('visibilitychange', onHide);
window.addEventListener('pagehide', flush);
return () => {
window.clearInterval(id);
document.removeEventListener('visibilitychange', onHide);
window.removeEventListener('pagehide', flush);
flush();
};
}, [hydrated, pair]);

useEffect(() => {
let cancelled = false;
const client = makePublicClient(() => rpcSendRef.current);
Expand Down
12 changes: 6 additions & 6 deletions app/vibenet/demos/validity/components/PriceCandles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isUpCandle, toCandles, type PriceSample } from './PriceCandles';
const BUCKET = CANDLE_BUCKET_MS;

describe('toCandles', () => {
it('builds a wick when 200ms prints reverse inside a 5s bucket', () => {
it('builds a wick when 200ms prints reverse inside a 1s bucket', () => {
const t0 = 1_000_000;
const samples: PriceSample[] = [
{ t: t0, price: 1.0 },
Expand Down Expand Up @@ -50,15 +50,15 @@ describe('toCandles', () => {
expect(isUpCandle(candles[1], candles[0])).toBe(false);
});

it('fills empty 5s buckets so a 15s gap does not leave a hole', () => {
it('fills empty 1s buckets so a 3s gap does not leave a hole', () => {
const t0 = 3_000_000;
const samples: PriceSample[] = [
{ t: t0, price: 0.07 },
{ t: t0 + 15_000, price: 0.071 },
{ t: t0 + 3 * BUCKET, price: 0.071 },
];
const candles = toCandles(samples, { now: t0 + 15_000, windowMs: 20_000 });
expect(candles.map((candle) => candle.t)).toEqual([t0, t0 + 5_000, t0 + 10_000, t0 + 15_000]);
expect(candles[1]).toEqual({ t: t0 + 5_000, o: 0.07, h: 0.07, l: 0.07, c: 0.07 });
const candles = toCandles(samples, { now: t0 + 3 * BUCKET, windowMs: 4 * BUCKET });
expect(candles.map((candle) => candle.t)).toEqual([t0, t0 + BUCKET, t0 + 2 * BUCKET, t0 + 3 * BUCKET]);
expect(candles[1]).toEqual({ t: t0 + BUCKET, o: 0.07, h: 0.07, l: 0.07, c: 0.07 });
expect(candles[3].o).toBe(0.07);
expect(candles[3].c).toBe(0.071);
});
Expand Down
2 changes: 1 addition & 1 deletion app/vibenet/demos/validity/components/PriceCandles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export function PriceCandles({ samples, levels = [], fills = [] }: Props) {
<VibeMark />
<div>
<div className="font-mono text-[13px] tracking-[0.08em] text-foreground">VIBE / USDV</div>
<div className="font-mono text-[11px] text-bds-gray-50">simulated pool · 5s candles</div>
<div className="font-mono text-[11px] text-bds-gray-50">simulated pool · 1s candles</div>
</div>
</div>
<div className="text-right">
Expand Down
4 changes: 4 additions & 0 deletions app/vibenet/demos/validity/lib/amm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
encodeEventTopics,
encodeFunctionData,
parseAbi,
parseEventLogs,
Expand All @@ -13,6 +14,9 @@ const pairEvents = parseAbi([
'event Sync(uint112 reserve0, uint112 reserve1)',
]);

export const SYNC_EVENT = pairEvents[1];
export const SYNC_TOPIC = encodeEventTopics({ abi: pairEvents, eventName: 'Sync' })[0];

import { QUOTE_SCALE, TRADER_USDV, TRADER_VIBE, erc20Abi, helperAbi, minterAbi, pairAbi } from './constants';
import { sqrt } from './predicates';
import { quoteFromPreSwapReserves } from './quote';
Expand Down
9 changes: 4 additions & 5 deletions app/vibenet/demos/validity/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export const minterAbi = parseAbi([
'function mint(address token, address to, uint256 amount)',
]);

export const CANDLES_PATH = '/api/vibenet/validity/candles';

export const STORAGE_KEY = 'vibenet.validity.v7';

export const WAD = 10n ** 18n;
Expand All @@ -70,10 +68,11 @@ export const MAX_EXPIRY_SECONDS = 60;
* are on committed 200ms blocks, not 2s pre-Denim heads or 250ms flashblocks.
*/
export const BLOCK_SECONDS = 0.2;
/** Stamp the mid on each 200ms head so the live 5s candle can wick. */
/** Stamp the mid on each 200ms head so the live 1s candle can wick. */
export const CANDLE_SAMPLE_MS = 200;
export const CANDLE_BUCKET_MS = 5_000;
export const CANDLE_WINDOW_MS = 180_000;
export const CANDLE_BUCKET_MS = 1_000;
export const CANDLE_WINDOW_MS = 30_000;
export const CANDLE_BACKFILL_MS = 10_000;

/**
* Finite box span around the target point on the current hyperbola.
Expand Down
22 changes: 0 additions & 22 deletions app/vibenet/demos/validity/lib/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import {

import { VIBENET_RPC_URL, VIBENET_WS_URL } from '../../../library/config';
import { VIBENET } from '../../account/library/chains';
import { CANDLES_PATH } from './constants';
import { parseTapeSamples, type TapeSample } from './tape';
import type { ValidityPredicate } from './types';

export type RpcSend = (method: string, params: unknown[]) => Promise<unknown>;
Expand Down Expand Up @@ -69,26 +67,6 @@ export function makeWalletClient(account: Account): WalletClient {
return createWalletClient({ chain: VIBENET_CHAIN, account, transport: custom(eip1193()) });
}

export async function fetchTape(pair: Address, vibeToken0: boolean): Promise<TapeSample[]> {
const response = await fetch(
`${CANDLES_PATH}?pair=${pair}&vibeToken0=${vibeToken0 ? '1' : '0'}`,
{ cache: 'no-store' },
);
if (!response.ok) return [];
const body = (await response.json().catch(() => null)) as { samples?: unknown } | null;
return parseTapeSamples(body?.samples);
}

export async function publishTape(pair: Address, samples: readonly TapeSample[]): Promise<void> {
if (samples.length === 0) return;
await fetch(CANDLES_PATH, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pair, samples }),
keepalive: true,
});
}

export function describeValidityError(err: unknown): string {
const record = err as {
shortMessage?: string;
Expand Down
18 changes: 18 additions & 0 deletions app/vibenet/demos/validity/lib/singleton.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest';

import { pickLivePair } from './singleton';

describe('pickLivePair', () => {
it('follows the most recently traded seeded pool over an idle duplicate', () => {
const idle = { id: 'idle', reserve0: 1n, reserve1: 1n, lastTradeAt: 100 };
const live = { id: 'live', reserve0: 1n, reserve1: 1n, lastTradeAt: 900 };
expect(pickLivePair([idle, live])?.id).toBe('live');
});

it('skips unseeded pools', () => {
const empty = { id: 'empty', reserve0: 0n, reserve1: 5n, lastTradeAt: 999 };
const seeded = { id: 'seeded', reserve0: 1n, reserve1: 1n, lastTradeAt: 1 };
expect(pickLivePair([empty, seeded])?.id).toBe('seeded');
expect(pickLivePair([empty])).toBeNull();
});
});
Loading
Loading