File size: 3,752 Bytes
c404bdc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// @snapkitty/ledge β€” JavaScript/TypeScript interface
// Crypto: Rust (ledge-open/src/lib.rs) compiled to WASM via wasm-pack
// This file is a typed wrapper. Zero crypto runs here.
//
// Build WASM first:
//   cd ledge-open && wasm-pack build --features wasm --target bundler --out-dir pkg

export interface SealedEvent {
  index:        number
  seal:         string
  previousSeal: string
  payload:      Record<string, unknown>
  timestampMs:  number
}

export interface VerifyResult {
  valid:       boolean
  failures:    number[]
  merkleRoot:  string
  eventCount:  number
  error?:      string
}

// ── WASM module interface ─────────────────────────────────────────────────────

interface LedgeWasm {
  ledge_genesis():                                              string
  ledge_seal(prev: string, payload: string, ts: bigint, idx: bigint): string
  ledge_verify(events_json: string):                            string
  ledge_merkle_root(seals_json: string):                        string
}

// ── In-memory chain (state lives in JS, crypto lives in Rust) ─────────────────

export class LedgeChain {
  private events: SealedEvent[] = []
  private head:   string
  private genesis: string
  private wasm:   LedgeWasm

  constructor(wasm: LedgeWasm) {
    this.wasm    = wasm
    this.genesis = wasm.ledge_genesis()
    this.head    = this.genesis
  }

  seal(payload: Record<string, unknown>, timestampMs = Date.now()): SealedEvent {
    const index       = this.events.length
    const payloadJson = JSON.stringify(payload)
    const seal        = this.wasm.ledge_seal(
      this.head,
      payloadJson,
      BigInt(timestampMs),
      BigInt(index),
    )

    const event: SealedEvent = {
      index,
      seal,
      previousSeal: this.head,
      payload,
      timestampMs,
    }

    this.head = seal
    this.events.push(event)
    return event
  }

  verify(): VerifyResult {
    const result = JSON.parse(this.wasm.ledge_verify(JSON.stringify(this.events)))
    return result as VerifyResult
  }

  merkleRoot(): string {
    const seals = this.events.map(e => e.seal)
    return this.wasm.ledge_merkle_root(JSON.stringify(seals))
  }

  getEvents(): SealedEvent[] {
    return [...this.events]
  }

  getGenesis(): string {
    return this.genesis
  }
}

// ── Factory ────────────────────────────────────────────────────────────────────

/**

 * Load the WASM module and return a factory for creating chains.

 *

 * Browser / bundler:

 *   import { initLedge } from '@snapkitty/ledge'

 *   const { createChain } = await initLedge()

 *   const chain = createChain()

 *

 * Node.js (after wasm-pack --target nodejs):

 *   const wasm = require('../pkg/ledge')

 *   const { initLedge } = require('@snapkitty/ledge')

 *   const { createChain } = await initLedge(wasm)

 */
export async function initLedge(wasmModule?: LedgeWasm): Promise<{
  createChain: () => LedgeChain
  genesis:     () => string
}> {
  let wasm: LedgeWasm

  if (wasmModule) {
    wasm = wasmModule
  } else {
    // Dynamic import β€” bundler resolves to pkg/ledge_bg.wasm
    const mod = await import('../pkg/ledge.js' as string) as LedgeWasm & { default?: () => Promise<void> }
    if (mod.default) await mod.default()
    wasm = mod
  }

  return {
    createChain: () => new LedgeChain(wasm),
    genesis:     () => wasm.ledge_genesis(),
  }
}