/** * GridStamp — Spatial Proof-of-Presence for Autonomous Robots * * Nobody else unifies spatial memory + payment verification + anti-spoofing. * - Niantic has maps but no payments * - NVIDIA has rendering but no memory persistence * - OpenMind has robot payments but no spatial proof * - FOAM/Auki has proof-of-location but no payments * * GridStamp sits at the intersection. * * API: * agent.see() — Capture + process current view * agent.remember() — Store spatial context to memory * agent.navigate() — Plan path to target * agent.verifySpatial() — Prove robot is at claimed location * agent.settle() — Payment with spatial proof requirement */ // Re-export all public types export type { Vec3, Quaternion, Pose, Mat4, AABB, CameraIntrinsics, StereoConfig, CameraFrame, DepthMap, CameraConfig, GaussianSplat, SplatScene, RenderedView, ShortTermEntry, EpisodicMemory, LongTermMemory, ConsolidationEvent, PlaceCell, GridCell, SpatialCode, Waypoint, Path, SpatialMetrics, VerificationThresholds, SpatialProof, ProofChainVerification, SpatialSettlement, ThreatDetection, FrameIntegrity, GridStampConfig, GridStampAgent, } from './types/index.js'; export { CameraType, MemoryTier, PathAlgorithm, ReferenceFrame, SettlementStatus, ThreatType, ThreatSeverity, } from './types/index.js'; // Re-export modules export * from './perception/index.js'; export * from './memory/index.js'; export type { ElephantMemoryConfig } from './memory/elephant-memory.js'; export * from './navigation/index.js'; export * from './verification/index.js'; export * from './antispoofing/index.js'; export * from './gamification/index.js'; export * as remoteidModule from './remoteid/index.js'; export * from './evidence/index.js'; // ============================================================ // REMOTE ID / ASTM F3411 NAMESPACE // ============================================================ import { signRemoteId as _signRemoteId, verifyRemoteId as _verifyRemoteId, buildMerkleRoot as _buildMerkleRoot, loadOrCreateRemoteIdKey as _loadOrCreateRemoteIdKey, generateRemoteIdKey as _generateRemoteIdKey, verifyInclusionProof as _verifyInclusionProof, } from './remoteid/index.js'; /** * Public namespace for ASTM F3411 Remote ID signing + COSE Merkle batching. * * Uses the persisted Ed25519 identity key at `${GRIDSTAMP_PERSIST_DIR}/remoteid-ed25519.key` * (auto-created on first use). See src/remoteid/f3411.ts for the key lifecycle. * * Usage: * import { remoteid } from 'gridstamp'; * const key = remoteid.loadKey(); * const log = remoteid.sign({ kind: 'operator_id', operatorIdType: 0, operatorId: 'FA12345678' }, key); * const ok = remoteid.verify(log, key.publicKey); * const batch = remoteid.batchRoot([log]); */ export const remoteid = { sign: _signRemoteId, verify: _verifyRemoteId, batchRoot: _buildMerkleRoot, loadKey: _loadOrCreateRemoteIdKey, generateKey: _generateRemoteIdKey, verifyInclusion: _verifyInclusionProof, }; // Re-export key utilities export { hmacSign, hmacVerify, signFrame, verifyFrame, generateNonce, sha256, deriveKey, } from './utils/crypto.js'; export { vec3Distance, vec3Add, vec3Sub, vec3Scale, vec3Normalize, egoToAllo, alloToEgo, stereoDepth, gaussian3D, meanAbsoluteError, poseToMat4, quatRotateVec3, quatSlerp, } from './utils/math.js'; // ============================================================ // PAYMENT RAIL AUTO-WIRING (via @mnemopay/sdk) // ============================================================ import { StripeRail, PaystackRail, LightningRail, MockRail, } from '@mnemopay/sdk'; import type { PaymentRail } from '@mnemopay/sdk'; // Re-export payment rails so GridStamp consumers can construct them directly export { StripeRail, PaystackRail, LightningRail, MockRail }; export type { PaymentRail }; /** * Auto-detect payment rail from environment variables. * Returns a PaymentRail instance from @mnemopay/sdk, or undefined * (which means createAgent will fall back to MockRail). * * Reads: * MNEMOPAY_PAYMENT_RAIL — "stripe", "paystack", "lightning", or "mock" * STRIPE_SECRET_KEY / STRIPE_CURRENCY * PAYSTACK_SECRET_KEY / PAYSTACK_CURRENCY * LIGHTNING_LND_URL / LIGHTNING_MACAROON / LIGHTNING_BTC_PRICE */ export function detectPaymentRail(): PaymentRail | undefined { const railName = (process.env.MNEMOPAY_PAYMENT_RAIL || '').toLowerCase(); switch (railName) { case 'stripe': { const key = process.env.STRIPE_SECRET_KEY; if (!key) throw new Error('STRIPE_SECRET_KEY required when MNEMOPAY_PAYMENT_RAIL=stripe'); return new StripeRail(key, process.env.STRIPE_CURRENCY || 'usd'); } case 'paystack': { const key = process.env.PAYSTACK_SECRET_KEY; if (!key) throw new Error('PAYSTACK_SECRET_KEY required when MNEMOPAY_PAYMENT_RAIL=paystack'); return new PaystackRail(key, { currency: (process.env.PAYSTACK_CURRENCY as any) || 'NGN', }); } case 'lightning': { const lndUrl = process.env.LIGHTNING_LND_URL; const macaroon = process.env.LIGHTNING_MACAROON; if (!lndUrl) throw new Error('LIGHTNING_LND_URL required when MNEMOPAY_PAYMENT_RAIL=lightning'); if (!macaroon) throw new Error('LIGHTNING_MACAROON required when MNEMOPAY_PAYMENT_RAIL=lightning'); const btcPrice = Number(process.env.LIGHTNING_BTC_PRICE) || 60000; return new LightningRail(lndUrl, macaroon, btcPrice); } case 'mock': return new MockRail(); default: // No env var set or unrecognized value — auto-detect from available keys if (process.env.STRIPE_SECRET_KEY) { return new StripeRail( process.env.STRIPE_SECRET_KEY, process.env.STRIPE_CURRENCY || 'usd', ); } if (process.env.PAYSTACK_SECRET_KEY) { return new PaystackRail(process.env.PAYSTACK_SECRET_KEY, { currency: (process.env.PAYSTACK_CURRENCY as any) || 'NGN', }); } // No keys found — return undefined (createAgent will use MockRail) return undefined; } } // ============================================================ // AGENT FACTORY // ============================================================ import type { GridStampConfig, GridStampAgent as IGridStampAgent, CameraFrame, EpisodicMemory, Path, SpatialProof, SpatialSettlement, SpatialCode, Pose, Vec3, PathAlgorithm, VerificationThresholds, } from './types/index.js'; import type { CameraDriver } from './perception/index.js'; import { FrameCapture } from './perception/index.js'; import { ShortTermMemory, MidTermMemory, LongTermMemory as LongTermMemoryStore, MemoryConsolidator, ElephantMemory, } from './memory/index.js'; import { PlaceCellPopulation, GridCellSystem, computeSpatialCode } from './memory/index.js'; import { OccupancyGrid, planPath } from './navigation/index.js'; import { generateSpatialProof, createSettlement, } from './verification/index.js'; import { FrameIntegrityChecker, CanarySystem } from './antispoofing/index.js'; import { deriveKey } from './utils/crypto.js'; /** * Create a GridStamp agent * * This is the main entry point. Pass a config + camera driver, * get back an agent with see/remember/navigate/verify/settle methods. */ export function createAgent( config: GridStampConfig, primaryDriver: CameraDriver, ): IGridStampAgent { // Validate config if (!config.robotId) throw new Error('robotId is required'); if (!config.hmacSecret || config.hmacSecret.length < 32) { throw new Error('hmacSecret must be at least 32 characters'); } // Auto-wire payment rail from env vars when not explicitly provided. // Falls back to MockRail for backwards compatibility. const resolvedRail: PaymentRail | undefined = config.paymentRail ?? detectPaymentRail() ?? new MockRail(); // Derive separate keys for each subsystem (key separation) const frameKey = deriveKey(config.hmacSecret, 'frame-signing'); const memoryKey = deriveKey(config.hmacSecret, 'memory-signing'); const proofKey = config.hmacSecret; // proof uses master key // Initialize subsystems const frameCapture = new FrameCapture( primaryDriver, config.cameras[0]!, frameKey, ); const shortTerm = new ShortTermMemory( 900, config.memoryConfig?.shortTermTTL ?? 30_000, ); const midTerm = new MidTermMemory( config.memoryConfig?.midTermMaxEntries ?? 1000, ); const longTermStore = new LongTermMemoryStore(memoryKey); const consolidator = new MemoryConsolidator(shortTerm, midTerm, longTermStore); const placeCells = new PlaceCellPopulation(); const gridCells = new GridCellSystem(); const integrityChecker = new FrameIntegrityChecker(frameKey); const canaries = new CanarySystem(memoryKey); // Initialize elephant memory bridge (persistent behavioral history via MnemoPay) const elephant = config.elephantMemory ? new ElephantMemory({ robotId: config.robotId, mnemopay: config.elephantMemory, defaultTags: ['gridstamp', 'robot', config.robotId], }) : null; let lastFrame: CameraFrame | undefined; let initialized = false; const agent: IGridStampAgent = { async see(): Promise { if (!initialized) { await frameCapture.initialize(); initialized = true; } const frame = await frameCapture.capture(); // Run integrity check on every frame (fail-closed) const integrity = integrityChecker.check(frame); if (!integrityChecker.isSafe(integrity)) { const criticalThreats = integrity.threats .filter(t => t.severity === 'critical') .map(t => t.details) .join('; '); throw new Error(`Frame rejected by anti-spoofing: ${criticalThreats}`); } // Store in short-term memory (empty splats for now — 3DGS integration point) shortTerm.add(frame, []); // Auto-generate place cell at frame position if we have pose if (frame.pose) { const cell = (await import('./memory/place-cells.js')).createPlaceCell( frame.pose.position, ); placeCells.add(cell); } lastFrame = frame; return frame; }, async remember(tags?: string[]): Promise { const consolidated = consolidator.consolidateToMidTerm(tags ?? []); if (!consolidated) { // Even if not enough for full consolidation, store what we have const entries = shortTerm.getAll(); if (entries.length === 0) { throw new Error('No frames in short-term memory to remember'); } // Force store const allSplats = entries.flatMap(e => e.splats); const scene = { id: (await import('./utils/crypto.js')).generateNonce(16), splats: allSplats, count: allSplats.length, boundingBox: { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 }, }, createdAt: Date.now(), }; const location = lastFrame?.pose?.position ?? { x: 0, y: 0, z: 0 }; return midTerm.store(scene, location, tags ?? []); } // Persist consolidation event to elephant memory if (elephant) { elephant.onConsolidation(consolidated).catch(() => {}); } // Get the most recent mid-term memory const memories = midTerm.findNear( lastFrame?.pose?.position ?? { x: 0, y: 0, z: 0 }, 100, ); return memories[0]!; }, async navigate(target: Vec3, options?: { algorithm?: PathAlgorithm }): Promise { const start = lastFrame?.pose?.position ?? { x: 0, y: 0, z: 0 }; const bounds = { min: { x: Math.min(start.x, target.x) - 10, y: Math.min(start.y, target.y) - 10, z: Math.min(start.z, target.z) - 2, }, max: { x: Math.max(start.x, target.x) + 10, y: Math.max(start.y, target.y) + 10, z: Math.max(start.z, target.z) + 2, }, }; const grid = new OccupancyGrid(bounds); const algorithm = options?.algorithm ?? config.navigationConfig?.defaultAlgorithm ?? 'a-star' as PathAlgorithm; const path = planPath(start, target, grid, bounds, algorithm); if (!path) throw new Error('No path found to target'); return path; }, async verifySpatial(claimedPose?: Pose): Promise { if (!lastFrame) throw new Error('No frame captured. Call see() first.'); const pose = claimedPose ?? lastFrame.pose; if (!pose) throw new Error('No pose available. Provide claimedPose or ensure camera provides pose.'); // In production, this would render from long-term memory 3DGS // For now, use the last frame as "expected" (self-verification) const expectedRender = { rgb: lastFrame.rgb, depth: lastFrame.depth ?? new Float32Array(0), width: lastFrame.width, height: lastFrame.height, pose, renderTimeMs: 0, }; const proof = await generateSpatialProof( config.robotId, pose, lastFrame, expectedRender, 'pending-merkle-root', // would come from long-term memory [], proofKey, config.verificationThresholds as VerificationThresholds | undefined, ); // Persist verification result to elephant memory if (elephant) { elephant.onVerification(proof.passed, proof.metrics.ssim).catch(() => {}); } return proof; }, async settle(params: { amount: number; currency: string; payeeId: string; spatialProof: boolean; }): Promise { if (!params.spatialProof) { throw new Error('GridStamp requires spatialProof=true. Use MnemoPay directly for non-spatial payments.'); } const proof = await agent.verifySpatial(); const settlement = createSettlement(proof, params.amount, params.currency, params.payeeId, proofKey); // Move money via the resolved payment rail after spatial proof verification. // MockRail keeps backwards compatibility (local-only settlement). if (resolvedRail && settlement.status === 'verified') { const hold = await resolvedRail.createHold( params.amount, `GridStamp spatial settlement: ${config.robotId} → ${params.payeeId}`, config.robotId, ); const capture = await resolvedRail.capturePayment(hold.externalId, params.amount); (settlement as any).externalPaymentId = capture.externalId; (settlement as any).paymentRail = resolvedRail.name; (settlement as any).paymentStatus = capture.status; } // Persist settlement to elephant memory if (elephant) { elephant.onSettlement(params.amount, params.currency, params.payeeId, settlement.status).catch(() => {}); } return settlement; }, getSpatialCode(): SpatialCode { const position = lastFrame?.pose?.position ?? { x: 0, y: 0, z: 0 }; return computeSpatialCode(position, placeCells, gridCells); }, getMemoryStats() { const shortEntries = shortTerm.getAll(); return { shortTerm: { count: shortTerm.count, oldestMs: shortEntries.length > 0 ? Date.now() - shortEntries[0]!.timestamp : 0, }, midTerm: { count: midTerm.count, totalSplats: midTerm.getTotalSplatCount(), }, longTerm: { count: longTermStore.totalEntries, rooms: longTermStore.roomCount, }, }; }, async shutdown(): Promise { await frameCapture.shutdown(); integrityChecker.reset(); }, }; return agent; }