Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

19 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ArkivLog

npm version license hackathon track

Immutable AI audit trail. Every action your AI agent takes β€” every tool call, every output β€” is written to Arkiv testnet with tamper-proof creator attribution and wallet-based ownership.

Built for ARKIV Γ— PunaTech 2026, track "AI Applications on ARKIV", vertical AI provenance & audit.

  • 🌐 Live demo: coming soon β€” add Vercel URL here
  • πŸŽ₯ Video pitch (ES): coming soon β€” add YouTube link here
  • πŸ“¦ SDK on npm: npm i arkivlog

Quick links: Para el jurado πŸ‡¦πŸ‡· Β· 30-second install Β· What it is Β· Why it matters Β· Architecture Β· Setup Β· Run Β· Demo script Β· Data model Β· SDK


Para el jurado πŸ‡¦πŸ‡·

ArkivLog es una librerΓ­a + dashboard + agente IA de demostraciΓ³n que registra cada acciΓ³n de un agente LLM de forma inmutable en la testnet de Arkiv. Pensado para la vertical AI provenance & audit del track AI Applications on ARKIV.

Por quΓ© importa: los agentes IA hoy actΓΊan en el mundo real (consultan inventario, agendan turnos, cotizan financiaciΓ³n), pero no existe un registro inviolable de quΓ© hicieron, cuΓ‘ndo y para quiΓ©n. Una base de datos comΓΊn la edita cualquiera con permisos de escritura. ArkivLog garantiza tres cosas:

  1. Origen tamper-proof β€” el $creator es la wallet del backend, fijo al momento de crear la entidad, imposible de falsificar.
  2. Propiedad del usuario final β€” el $owner es la wallet del usuario, que controla la retenciΓ³n y puede ejercer el derecho al olvido.
  3. RetenciΓ³n diferenciada por severidad β€” LOW=7d, MEDIUM=30d, HIGH=90d, CRITICAL=365d vΓ­a expiresIn.

QuΓ© entregamos:

  • πŸ“¦ SDK pΓΊblico en npm β€” npm i arkivlog (3 lΓ­neas para integrar)
  • πŸ–₯️ Dashboard SaaS con SIWE + API keys por wallet
  • πŸ€– Demo "concesionaria con asistente IA" donde cada tool-call queda on-chain
  • 🎬 Demo guiado de 90 segundos abajo en este README

Para ver el flow completo paso a paso, leΓ© The 90-second demo script. El resto del README estΓ‘ en inglΓ©s pensando en la audiencia del SDK en npm.


30-second install

The SDK is published on npm. Drop it into any TypeScript backend:

npm install arkivlog
import { init } from "arkivlog";

const logger = init({
  apiKey: process.env.ARKIVLOG_API_KEY!,                  // issued in the ArkivLog dashboard
  endpoint: "https://your-arkivlog.vercel.app/api/logs",  // your deployment
});

// Fire-and-forget. Never blocks your request path.
logger.record({
  eventType: "TOOL_INVOKED",
  actor: sessionId,
  target: "searchInventory",
  severity: "HIGH",
  metadata: { input, output, durationMs: 142 },
});

That's it. The event is signed by ArkivLog's service wallet, owned by your wallet, and written to Arkiv testnet β€” tamper-proof, with severity-based retention.


What it is

ArkivLog is two things in one repo:

  1. An SDK (packages/sdk) that any TypeScript backend can install (npm install arkivlog) to fire-and-forget log structured events.
  2. A SaaS dashboard (apps/web) that receives those events, signs them with a service wallet, and writes them to Arkiv testnet as AuditEvent entities. It also exposes a wallet-aware Provenance Trail UI.

Plus a third app to make the demo concrete:

  1. A consumer demo (apps/demo) β€” a fictitious car dealership with an AI assistant (Gemini 2.5 on Vertex). Every tool the assistant calls is automatically logged via the SDK.

Why it matters

LLM agents act in the world: they search, they query DBs, they schedule, they pay. Today there is no immutable record of what they did, when, on whose behalf, and with what inputs. Logs in a normal database can be modified by anyone with write access. ArkivLog gives that record three guarantees:

  • Tamper-proof origin β€” $creator is the backend's wallet, set at creation, immutable forever. Forging the source is cryptographically impossible.
  • End-user ownership β€” $owner is the user's wallet. They control retention and exercise right-to-be-forgotten.
  • Differentiated retention β€” expiresIn is set by severity (LOW=7d, MEDIUM=30d, HIGH=90d, CRITICAL=365d), reflecting product logic.

Live on Arkiv Braga β€” example transactions

Real entities written by ArkivLog to the Braga testnet. Click the entity key to open the block explorer.

Entity Type $creator $owner Block Link
0x787d8bba…6a0f81 Project 0x8274…a838 0x8274…a838 1049395 open in explorer
0xb0d4b89c…10c3e0 AuditEvent Β· TOOL_INVOKED Β· searchInventory Β· LOW 0x8274…a838 0x8274…a838 1050514 open in explorer

That second row is a real tool-call from the demo: the Vertex agent invoked searchInventory({ onlyAvailable: true }) and the SDK fired record(). The full payload (5 vehicles returned by the tool) is in the entity's payload, the indexed attrs carry actor, eventType, severity, timestamp, and projectKey (FK to the Project entity above). $creator is the ArkivLog service wallet β€” immutable. $owner is the end-user wallet, who can drop the entity at any time.

To list every entity written under the arkivlog-punatech26-v1 namespace yourself, run pnpm list from the repo root.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  apps/demo  (port 3200)      β”‚         β”‚  apps/web  (port 3100)       β”‚
β”‚  Concesionaria Demo          β”‚         β”‚  ArkivLog SaaS               β”‚
β”‚                              β”‚         β”‚                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  POST   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Vertex AI agent        │──┼─────────┼─▢│ POST /api/logs         β”‚  β”‚
β”‚  β”‚ (Gemini 2.5 + tools)   β”‚  β”‚  /api/  β”‚  β”‚ - validates API key    β”‚  β”‚
β”‚  β”‚                        β”‚  β”‚  logs   β”‚  β”‚ - createEntity (Arkiv) β”‚  β”‚
β”‚  β”‚ tools/                 β”‚  β”‚         β”‚  β”‚ - changeOwnership      β”‚  β”‚
β”‚  β”‚  β”œβ”€ searchInventory    β”‚  β”‚         β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”‚  β”œβ”€ getVehicleDetails  β”‚  β”‚         β”‚                              β”‚
β”‚  β”‚  β”œβ”€ scheduleTestDrive  β”‚  β”‚         β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  └─ getFinancingQuote  β”‚  β”‚         β”‚  β”‚ GET  /api/logs         β”‚  β”‚
β”‚  β”‚   (each fires SDK)     β”‚  β”‚         β”‚  β”‚ - filters: severity,   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚         β”‚  β”‚   eventType, actor,    β”‚  β”‚
β”‚                              β”‚         β”‚  β”‚   since, owner         β”‚  β”‚
β”‚  Chat UI (useChat)           β”‚         β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚                              β”‚
                                         β”‚  /dashboard                  β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚  - wagmi wallet connect      β”‚
β”‚  packages/sdk  (arkivlog)    β”‚         β”‚  - Provenance table          β”‚
β”‚  - init({apiKey, endpoint})  β”‚         β”‚  - filters + entity expand   β”‚
β”‚  - record() fire-and-forget  β”‚         β”‚  - $owner / $creator links   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                        β”‚
                                                        β”‚ @arkiv-network/sdk
                                                        β–Ό
                                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                         β”‚   Arkiv Braga testnet        β”‚
                                         β”‚   chain id 60138453102       β”‚
                                         β”‚   Entity AuditEvent          β”‚
                                         β”‚   - attributes (indexed)     β”‚
                                         β”‚   - payload (JSON)           β”‚
                                         β”‚   - $creator (immutable)     β”‚
                                         β”‚   - $owner   (user wallet)   β”‚
                                         β”‚   - expiresIn by severity    β”‚
                                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Repo layout

arkivlog/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ web/          Next.js β€” SaaS dashboard + API routes (port 3100)
β”‚   └── demo/         Next.js β€” Concesionaria Demo with Vertex agent (port 3200)
β”œβ”€β”€ packages/
β”‚   └── sdk/          arkivlog β€” TypeScript SDK published as `arkivlog`
└── README.md

Workspace tooling: pnpm 10, Node 20+, Next.js 16, TypeScript 5.


Setup

1. Clone and install

git clone <repo>
cd arkivlog
pnpm install

2. Wallet for Arkiv testnet

You need one wallet for the backend's service signer. It will be $creator on every entity ArkivLog writes.

  1. Install MetaMask.
  2. Add the Arkiv Braga Testnet network:
    • RPC: https://braga.hoodi.arkiv.network/rpc
    • Chain ID: 60138453102
    • Symbol: GLM
    • Explorer: https://explorer.braga.hoodi.arkiv.network/
  3. Fund it from the faucet: https://braga.hoodi.arkiv.network/faucet/
  4. Export the private key of that account (Account details β†’ Show private key).

3. Configure apps/web/.env

Copy apps/web/env.example to apps/web/.env and fill in:

ARKIV_SERVICE_PRIVATE_KEY=0x<your_private_key>
SESSION_SECRET=<long_random_string_for_signing_JWTs>
SCRIPTS_TEST_OWNER=0x<any_address_for_test_scripts>

ARKIVLOG_API_KEY is no longer a single shared key β€” it's issued per wallet from the dashboard (see step 5).

4. Configure apps/demo/.env

Copy apps/demo/env.example to apps/demo/.env and fill in (leave ARKIVLOG_API_KEY empty for now β€” step 5 fills it):

ARKIVLOG_ENDPOINT=http://localhost:3100/api/logs
ARKIVLOG_API_KEY=ak_<will_be_set_after_step_5>

VERTEX_PROJECT_ID=<your_gcp_project>
VERTEX_LOCATION=us-central1
VERTEX_CLIENT_EMAIL=<service-account>@<project>.iam.gserviceaccount.com
VERTEX_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
MODEL=gemini-2.5-flash

Tip: VERTEX_PRIVATE_KEY from a Google service account JSON usually has literal \n separators. Keep them β€” the code unescapes them.


Run

# Terminal 1 β€” SaaS (dashboard + API)
pnpm dev:web         # http://localhost:3100

5. Sign in and issue your API key

  1. Open http://localhost:3100/dashboard.
  2. Click Sign in with MetaMask. MetaMask pops up with a SIWE message β€” read it and Sign.
  3. The dashboard creates a Project entity on Arkiv (takes ~5-10s the first time) and shows your API key (ak_…).
  4. Copy the key into apps/demo/.env as ARKIVLOG_API_KEY.

6. Start the demo

# Terminal 2 β€” Demo (concesionaria with AI assistant)
pnpm dev:demo        # http://localhost:3200
  • Open http://localhost:3200 and chat with the assistant. Try "ΒΏQuΓ© Toyotas tenΓ©s bajo USD 30.000?".
  • Switch back to http://localhost:3100/dashboard β€” your logs are now there, with full payload, and they're privacy-scoped to your wallet. No one else (not even with the right query) can read them via the API.

The 90-second demo script

This is the story the judges should see:

  1. "Esta es la SaaS de ArkivLog. Hago Sign-In con MetaMask." β†’ click Sign in with MetaMask in /dashboard. MetaMask popup β†’ Sign. The dashboard creates the Project entity on-chain and shows my API key.
  2. "Copio esta key, la pego en mi backend β€” acΓ‘ la concesionaria que ya integrΓ³ el SDK." β†’ paste into apps/demo/.env (already done off-camera), open apps/demo.
  3. "Charlo con el asistente IA." β†’ user message. Tool indicator πŸ”§ searchInventory appears, then assistant response.
  4. "Cada acciΓ³n quedΓ³ firmada en Arkiv. Y solo yo la veo." β†’ switch to dashboard tab. New row appeared. Expand β†’ full payload. Click entityKey β†’ Braga explorer shows the on-chain tx.
  5. "Si abro este dashboard SIN sesiΓ³n iniciada, no veo nada. La privacidad no es una feature β€” es la arquitectura." β†’ optional: sign out and refresh to demonstrate empty state.
  6. "$creator es nuestra wallet de servicio, inmutable. $owner es tu wallet β€” vos controlΓ‘s la retenciΓ³n y podΓ©s borrar. Pero nadie puede falsificar lo que ya quedΓ³ escrito."

That's it. The story sells itself.


Data model

We store two related entity types, both carrying the PROJECT_ATTRIBUTE = { key: "project", value: "arkivlog-punatech26-v1" }.

Project (parent)

Field Type Purpose
attrs entityType=project, ownerWallet, name indexed
payload { name, description, ownerWallet } JSON
expiresIn 365 days parent retention

AuditEvent (child)

Field Type Purpose
attrs entityType=auditEvent, eventType, actor, severity, timestamp (numeric), projectKey (FK), optional target indexed for queries
payload { eventType, actor, target, severity, metadata } JSON
$creator service wallet tamper-proof
$owner end-user wallet control
expiresIn severity-based: LOW=7d, MEDIUM=30d, HIGH=90d, CRITICAL=365d differentiated retention

projectKey acts as a foreign key β€” the standard Arkiv pattern for relationships.

Sample queries

// All HIGH-severity events from a given actor in the last hour
publicArkiv
  .buildQuery()
  .where(eq("project", "arkivlog-punatech26-v1"))
  .where(eq("entityType", "auditEvent"))
  .where(eq("severity", "HIGH"))
  .where(eq("actor", "session-abc"))
  .where(gt("timestamp", Date.now() - 3600_000))
  .withPayload(true)
  .limit(50)
  .fetch();

// Only events owned by my wallet (right-to-be-forgotten requires this)
publicArkiv
  .buildQuery()
  .where(eq("project", "arkivlog-punatech26-v1"))
  .ownedBy("0xMyWallet")
  .fetch();

// Only events whose origin is the official ArkivLog backend (anti-spoofing)
publicArkiv
  .buildQuery()
  .where(eq("project", "arkivlog-punatech26-v1"))
  .createdBy(SERVICE_WALLET_ADDRESS)
  .fetch();

SDK usage

The SDK lives at packages/sdk and is published on npm as arkivlog β€” anyone can install and integrate it today:

npm install arkivlog
import { init } from "arkivlog";

const logger = init({
  apiKey: process.env.ARKIVLOG_API_KEY!,
  endpoint: process.env.ARKIVLOG_ENDPOINT ?? "http://localhost:3100/api/logs",
  onError: (err) => console.error("arkivlog:", err), // optional
});

logger.record({
  eventType: "TOOL_INVOKED",
  actor: sessionId,
  target: "searchInventory",
  severity: "LOW",
  metadata: { input, output, durationMs },
});

record() is fire-and-forget β€” it returns immediately and never blocks the request path. Errors are surfaced via the optional onError callback.

API surface

Export Type Purpose
init(config) (ArkivLogConfig) => ArkivLogger Create a logger bound to an API key + endpoint
logger.record(event) (AuditEventInput) => void Send one event. Non-blocking, no return value.
Severity "LOW" | "MEDIUM" | "HIGH" | "CRITICAL" Drives retention on Arkiv

Zero runtime dependencies. ESM only. Targets Node 20+ and any modern bundler.


Scripts

Command What it does
pnpm dev:web run the SaaS (port 3100)
pnpm dev:demo run the demo (port 3200)
pnpm smoke direct test against Arkiv: creates a Project + 3 AuditEvents + 3 queries
pnpm list pretty-print every entity ArkivLog has written under its namespace
pnpm --filter web run sdk-smoke end-to-end test: SDK β†’ endpoint β†’ Arkiv

Design decisions worth knowing

  • Two transactions per log (createEntity + changeOwnership). Arkiv doesn't accept $owner as a parameter on creation β€” the signer is the implicit owner. To set the user as $owner, we have to transfer. We do it sequentially in the API; the SDK is fire-and-forget so this latency is hidden.
  • changeOwnership is skipped when ownerWallet equals the service wallet β€” saves a tx in the trivial case.
  • transpilePackages: ["arkivlog"] is set in both apps' next.config.ts so we never have to rebuild the SDK during development.
  • Numeric attributes use real numbers, not strings β€” required for Arkiv range queries (gt, lt).
  • Queries chain .where() calls instead of passing arrays β€” array-form predicates cause context cancelled errors from the Arkiv RPC (confirmed against Braga testnet).
  • Severity-based expiresIn demonstrates intentional, differentiated retention β€” a Level-5 signal per the rubric.

Known limitations

  • The keystore (apps/web/data/keys.json) is a local JSON file β€” fine for a single-node hackathon demo, but production would back it with a real DB (Postgres) and rotate keys.
  • The wallet abstraction in the dashboard uses injected (MetaMask only) to avoid a WalletConnect projectId for the demo. Production should use getDefaultConfig from RainbowKit with full wallet support.
  • Arkiv Braga testnet RPC is sometimes flaky (context cancelled errors). Production retries with exponential backoff are not implemented; the smoke scripts simply retry on failure.
  • The chat history in apps/demo is in-memory per browser session. There is no persistence layer for chats themselves (the audit trail in Arkiv is the persistent record).
  • Nonces are stored in-memory; if the SaaS process restarts mid-login, the user has to click Sign-In again. A real deployment would persist them in Redis.

Tech stack

Concern Choice
Monorepo pnpm workspaces
Apps Next.js 16 (App Router, Turbopack)
SDK TypeScript, ESM, tsup for bundling
Arkiv @arkiv-network/sdk 0.6.8, Braga testnet
AI ai 6.x + @ai-sdk/google-vertex + Gemini 2.5 Flash
Wallet wagmi + viem + injected connector
Auth SIWE (EIP-4361) via siwe + JWT cookies via jose
Persistence JSON keystore on disk + Arkiv for the audit trail itself
Tests Smoke scripts (no test framework β€” hackathon scope)
Styling Tailwind CSS 4

License

MIT β€” see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages