repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | import type { Candle, State } from "./state.js"; |
| #2 | |
| #3 | const MAINNET_HOSTNAMES = [ |
| #4 | "api.mainnet-beta.solana.com", |
| #5 | "mainnet.helius-rpc.com", |
| #6 | "mainnet.rpc.jito.wtf", |
| #7 | "solana-mainnet", |
| #8 | "mainnet-beta", |
| #9 | ]; |
| #10 | |
| #11 | export interface WhaleActivity { |
| #12 | bias: "long" | "short" | "neutral"; |
| #13 | notional_lamports: number; |
| #14 | description: string; |
| #15 | } |
| #16 | |
| #17 | export function rejectMainnet(rpcUrl: string): void { |
| #18 | if (process.env["MAINNET_OK"] === "1") return; |
| #19 | const lowered = rpcUrl.toLowerCase(); |
| #20 | for (const host of MAINNET_HOSTNAMES) { |
| #21 | if (lowered.includes(host)) { |
| #22 | throw new Error(`[SAFETY] Mainnet RPC URL rejected: "${rpcUrl}". Goblin mode is devnet paper only.`); |
| #23 | } |
| #24 | } |
| #25 | } |
| #26 | |
| #27 | function mulberry32(seed: number): () => number { |
| #28 | return function random() { |
| #29 | seed |= 0; |
| #30 | seed = (seed + 0x6d2b79f5) | 0; |
| #31 | let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); |
| #32 | t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; |
| #33 | return ((t ^ (t >>> 14)) >>> 0) / 4294967296; |
| #34 | }; |
| #35 | } |
| #36 | |
| #37 | export class SynthObserver { |
| #38 | private readonly rand: () => number; |
| #39 | private lastClose: number; |
| #40 | private candles: Candle[] = []; |
| #41 | private readonly windowSize: number; |
| #42 | |
| #43 | constructor(seed = 42, startPrice = 150_000, windowSize = 20) { |
| #44 | this.rand = mulberry32(seed); |
| #45 | this.lastClose = startPrice; |
| #46 | this.windowSize = windowSize; |
| #47 | } |
| #48 | |
| #49 | tick(now = new Date()): { candles: Candle[]; whale_activity: WhaleActivity } { |
| #50 | const move = (this.rand() - 0.48) * 0.03; |
| #51 | const open = this.lastClose; |
| #52 | const close = Math.max(1, Math.round(open * (1 + move))); |
| #53 | const high = Math.round(Math.max(open, close) * (1 + this.rand() * 0.01)); |
| #54 | const low = Math.round(Math.min(open, close) * (1 - this.rand() * 0.01)); |
| #55 | const volume = Math.round(1_000_000 + this.rand() * 9_000_000); |
| #56 | |
| #57 | this.candles.push({ t: now.toISOString(), o: open, h: high, l: low, c: close, v: volume }); |
| #58 | if (this.candles.length > this.windowSize) this.candles.shift(); |
| #59 | this.lastClose = close; |
| #60 | |
| #61 | const whaleRoll = this.rand(); |
| #62 | const bias = whaleRoll > 0.66 ? "long" : whaleRoll < 0.34 ? "short" : "neutral"; |
| #63 | const notional = Math.round(250_000 + this.rand() * 8_000_000); |
| #64 | const description = |
| #65 | bias === "neutral" |
| #66 | ? `no dominant whale impulse; ${notional.toLocaleString()} lamports churned` |
| #67 | : `${bias} whale impulse; ${notional.toLocaleString()} lamports synthetic flow`; |
| #68 | |
| #69 | return { |
| #70 | candles: [...this.candles], |
| #71 | whale_activity: { bias, notional_lamports: notional, description }, |
| #72 | }; |
| #73 | } |
| #74 | } |
| #75 | |
| #76 | export async function observeFromHelius(rpcUrl: string, state: State, windowSize = 20): Promise<Candle[]> { |
| #77 | rejectMainnet(rpcUrl); |
| #78 | console.warn("[observe] Helius adapter not wired; using deterministic synth candles"); |
| #79 | const synth = new SynthObserver(state.tick, 150_000, windowSize); |
| #80 | for (let i = 0; i < Math.min(state.tick, windowSize); i += 1) synth.tick(); |
| #81 | return synth.tick().candles; |
| #82 | } |
| #83 | |
| #84 | export function isStale(candles: Candle[], maxAgeSeconds = 60): boolean { |
| #85 | if (candles.length === 0) return true; |
| #86 | const last = candles[candles.length - 1]!; |
| #87 | const age = (Date.now() - new Date(last.t).getTime()) / 1000; |
| #88 | return age > maxAgeSeconds; |
| #89 | } |
| #90 | |
| #91 |