veriq-client 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -3
- package/dist/bin/veriq.js +823 -0
- package/dist/chunk-AYDF3IFZ.js +2 -0
- package/dist/chunk-AYDF3IFZ.js.map +1 -0
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2565 -0
- package/dist/index.d.ts +2565 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +2 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +132 -0
- package/dist/react/index.d.ts +132 -0
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -0
- package/dist/styles.css +240 -0
- package/package.json +76 -6
package/README.md
CHANGED
|
@@ -1,3 +1,214 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<h1 align="center">Veriq SDK</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center"><strong>Verified Ethereum reads, with a receipt for every answer.</strong></p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/veriq-client"><img alt="npm" src="https://img.shields.io/npm/v/veriq-client?color=6B62B5&label=npm"></a>
|
|
7
|
+
<img alt="types included" src="https://img.shields.io/badge/types-included-9C95D6">
|
|
8
|
+
<a href="https://bundlephobia.com/package/veriq-client"><img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/veriq-client?color=2F9E6E&label=min%2Bgzip"></a>
|
|
9
|
+
<img alt="license" src="https://img.shields.io/badge/license-MIT-2A2440">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
`veriq-client` talks to **Veriq**, a local engine on `http://127.0.0.1:8547` that answers Ethereum JSON-RPC on Sepolia. For balance and storage reads, the engine checks a Merkle proof against a **finalized state root from its own Helios light client** and returns the proven value plus a **Trust Receipt**. The SDK never verifies proofs itself: it talks to the engine, validates every receipt's shape with Zod, and makes the result easy to use.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
- **Proofs, not promises.** Every value comes back with the block, state root, provider and proof mechanism that produced it.
|
|
19
|
+
- **Fails closed.** When a guarantee can't be met you get a typed error with a one-line hint, never a weaker answer.
|
|
20
|
+
- **Drops into what you have.** A viem transport, React hooks and components, and a CLI. About 8 kB min+gzip.
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
flowchart LR
|
|
24
|
+
App["Your app"] --> SDK["veriq-client"]
|
|
25
|
+
SDK -->|"JSON-RPC (localhost)"| Engine["Veriq engine<br/>127.0.0.1:8547"]
|
|
26
|
+
Helios["Helios light client"] -->|"finalized state root"| Engine
|
|
27
|
+
Engine -->|"eth_getProof"| P1["RPC provider A<br/>(untrusted)"]
|
|
28
|
+
Engine -->|"eth_getProof"| P2["RPC provider B<br/>(untrusted)"]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
**1. Install**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm add veriq-client zod
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**2. Start the engine** (from the Veriq repo)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export VERIQ_PROVIDERS="a=<sepolia-rpc-1>,b=<sepolia-rpc-2>"
|
|
43
|
+
export VERIQ_CONFIG_DIR=backend/config
|
|
44
|
+
cargo run --manifest-path backend/Cargo.toml -p veriq-server
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**3. Read**
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createVeriq } from 'veriq-client'
|
|
51
|
+
|
|
52
|
+
const veriq = createVeriq()
|
|
53
|
+
const { value, verified } = await veriq.readBalance({ address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' })
|
|
54
|
+
console.log(value, verified ? 'verified' : 'NOT verified') // 2000000000000000000n verified
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Core API
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const veriq = createVeriq({ url?, policy?, fetch?, timeoutMs?, debug? })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Method | Returns | Notes |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `readBalance({ address, policy? })` | `VerifiedResult<bigint>` | `veriq_readBalance`. Value in wei. |
|
|
66
|
+
| `readStorage({ address, slot, policy? })` | `` VerifiedResult<`0x${string}`> `` | `veriq_readStorage`. 32-byte hex. |
|
|
67
|
+
| `getReceipt(id)` | `TrustReceipt` | `veriq_getReceipt` |
|
|
68
|
+
| `getReceiptRecord(id)` | `ReceiptRecord` | `GET /receipts/:id`, including value, attempts and error |
|
|
69
|
+
| `listReceipts(limit?)` | `ReceiptRecord[]` | `GET /receipts?limit=`, newest first |
|
|
70
|
+
| `health()` | `EngineHealth` | `GET /health`: light client and providers |
|
|
71
|
+
| `providers()` | `ProviderHealth[]` | `veriq_getProviderHealth` |
|
|
72
|
+
| `capabilities()` | `Capabilities` | `veriq_getCapabilities` |
|
|
73
|
+
| `transport(opts?)` | viem `Transport` | See below |
|
|
74
|
+
| `withPolicy(policy)` | `VeriqClient` | New, immutable client |
|
|
75
|
+
| `request(method, params?)` | `unknown` | Raw JSON-RPC to the engine |
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
type VerifiedResult<T> = {
|
|
79
|
+
value: T
|
|
80
|
+
receipt: TrustReceipt // mirrors backend/crates/core/src/receipt.rs
|
|
81
|
+
verified: boolean // receipt.verification.status === 'verified'
|
|
82
|
+
attempts: Attempt[] // every provider tried, rejected ones included
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Before anything is sent, the SDK checks addresses (0x + 40 hex), slots (0x + 1–64 hex) and policy fields, and throws `InvalidRequestError` without making a network call. It does **not** block `latest` + `required`. The engine answers that with `POLICY_UNSATISFIABLE`, so your UI can show the real reason.
|
|
87
|
+
|
|
88
|
+
The SDK uses only the global `fetch`, so it works in Node 18+, Bun, Deno and browsers. It never logs addresses or URLs, and writes nothing to the console unless `debug: true`.
|
|
89
|
+
|
|
90
|
+
## Policies & presets
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
type Policy = {
|
|
94
|
+
verification: 'required' | 'preferred' | 'disabled' // VerificationPolicy in policy.rs
|
|
95
|
+
privacy: 'required' | 'preferred' | 'off'
|
|
96
|
+
consistency: 'single' | 'compare'
|
|
97
|
+
blockPolicy: 'finalized' | 'latest' // BlockPolicy in policy.rs
|
|
98
|
+
timeoutMs: number // one deadline for all attempts
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Preset | What it does |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `verifiedFinalized` (default) | Only proven answers, pinned to finalized blocks, 8 s deadline |
|
|
105
|
+
| `verifiedCompare` | Same, and a second provider at the same block (`agree` / `divergent`) |
|
|
106
|
+
| `privateVerified` | Verified, private transport only; `PRIVACY_UNAVAILABLE` rather than sending direct |
|
|
107
|
+
| `devUnverified` | `verification: 'preferred'` for local dev without a synced light client |
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { presets } from 'veriq-client'
|
|
111
|
+
const strict = veriq.withPolicy(presets.verifiedCompare)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## viem transport
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { createPublicClient } from 'viem'
|
|
118
|
+
import { sepolia } from 'viem/chains'
|
|
119
|
+
|
|
120
|
+
const transport = veriq.transport({ onReceipt: (r) => console.log('proven at', r.block.number) })
|
|
121
|
+
const client = createPublicClient({ chain: sepolia, transport })
|
|
122
|
+
|
|
123
|
+
const balance = await client.getBalance({ address, blockTag: 'finalized' })
|
|
124
|
+
transport.lastReceipt?.verification.status // 'verified'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`eth_getBalance` and `eth_getStorageAt` go through the verified path. Every other method is forwarded to the engine unchanged, and unsupported ones come back as `UnsupportedVerificationError`. In strict mode (the default when verification is `required`), block tags other than `"finalized"` throw before anything is sent. viem defaults to `latest`, so pass `blockTag: 'finalized'`. viem is an optional peer: the transport imports only its types.
|
|
128
|
+
|
|
129
|
+
## React components
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { VeriqProvider, VerifiedValue, EngineStatus } from 'veriq-client/react'
|
|
133
|
+
import 'veriq-client/styles.css'
|
|
134
|
+
|
|
135
|
+
<VeriqProvider client={veriq}>
|
|
136
|
+
<EngineStatus />
|
|
137
|
+
<VerifiedValue address="0x…" />
|
|
138
|
+
</VeriqProvider>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| Component / hook | What it is |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `<ReceiptBadge status \| error size? />` | Status pill with a dot: verified, unverified, unsupported, error |
|
|
144
|
+
| `<VerifiedValue address slot? format? />` | Value in the heading style plus badge, a shimmer while loading, name and hint on error; click for the receipt |
|
|
145
|
+
| `<ReceiptCard receipt value? attempts? error? />` | The full receipt: block, hashes (copy on click), root source, attempts, raw JSON, download |
|
|
146
|
+
| `<EngineStatus health? />` | "● Engine online · 127.0.0.1:8547" / "● Engine offline", plus an amber DEV MODE pill |
|
|
147
|
+
| `useVerifiedBalance(address, { policy?, refreshInterval? })` | `{ data, error, isLoading, refresh }` |
|
|
148
|
+
| `useVerifiedStorage(address, slot, opts)` · `useVeriqHealth({ refreshInterval = 5000 })` · `useReceipt(id)` | Same shape |
|
|
149
|
+
|
|
150
|
+
Hooks share a small built-in cache keyed by method and params, revalidate on window focus, and poll when you set `refreshInterval`. No data library is needed.
|
|
151
|
+
|
|
152
|
+
The styles are plain CSS with `vq-` class names. They read the Veriq dashboard's tokens (`--vq-ink`, `--vq-border`, `--vq-radius-card`, …) and fall back to the same values, so the components look identical inside the dashboard and work in any React app. Motion is CSS only (180 ms fades, a verified-dot pulse, skeleton shimmer) and turns off under `prefers-reduced-motion`.
|
|
153
|
+
|
|
154
|
+
## CLI
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx veriq-client read 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
158
|
+
npx veriq-client read 0xCONTRACT --slot 0x0
|
|
159
|
+
npx veriq-client health
|
|
160
|
+
# flags: --url <engine> --json --policy verifiedFinalized|verifiedCompare|devUnverified
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
╭─ Veriq · Trust Receipt ────────────────────────╮
|
|
165
|
+
│ ● VERIFIED 0.4213 ETH │
|
|
166
|
+
│ block #7,412,903 0x9a3f…c21e │
|
|
167
|
+
│ stateRoot 0x51be…07aa │
|
|
168
|
+
│ root local light client (Helios) │
|
|
169
|
+
│ provider alchemy · 2 attempts · 184 ms │
|
|
170
|
+
│ rejected infura-bad INVALID_PROOF │
|
|
171
|
+
╰────────────────────────────────────────────────╯
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The box uses the dashboard palette as 24-bit colour. Output is plain text when `NO_COLOR` is set or stdout isn't a TTY. Errors print the error name, code and hint, and exit with code 1.
|
|
175
|
+
|
|
176
|
+
## Errors
|
|
177
|
+
|
|
178
|
+
Every error extends `VeriqError { code, codeName, hint, data?, receiptId? }`. `String(err)` gives `VeriqError[-32014 POLICY_UNSATISFIABLE]: … — hint`. Use `isVeriqError(e)` or `instanceof`.
|
|
179
|
+
|
|
180
|
+
| Code | Class | Meaning | Hint |
|
|
181
|
+
|---:|---|---|---|
|
|
182
|
+
| -32010 | `InvalidProofError` | Every attempted proof failed verification | Add an honest provider or wait for cooldowns |
|
|
183
|
+
| -32011 | `RootUnavailableError` | No trusted root yet | The light client hasn't synced a finalized root yet. Wait ~1 min or check /health. |
|
|
184
|
+
| -32012 | `UnsupportedVerificationError` | Method or block tag can't be proven | Use blockTag 'finalized' or verification 'preferred' |
|
|
185
|
+
| -32013 | `PrivacyUnavailableError` | No healthy private provider; nothing sent | Use privacy 'preferred' or 'off' |
|
|
186
|
+
| -32014 | `PolicyUnsatisfiableError` | Contradictory policy | latest + verification=required can't be proven. Use blockPolicy 'finalized'. |
|
|
187
|
+
| -32015 | `ProviderTimeoutError` | Deadline ran out (also used for client-side timeouts) | Raise timeoutMs or check provider health |
|
|
188
|
+
| -32016 | `AllProvidersFailedError` | Every eligible provider failed | Check `veriq health` for breaker states |
|
|
189
|
+
| -32602 | `InvalidRequestError` | Malformed address, slot or policy | Thrown before any network call |
|
|
190
|
+
| -1 | `EngineUnreachableError` | Network failure | No Veriq engine at http://127.0.0.1:8547. Start it with: cargo run -p veriq-server |
|
|
191
|
+
| -2 | `VeriqSchemaError` | Engine answer doesn't match the schema | `issues` lists paths like `receipt.block.stateRoot: expected 0x-hex, got undefined` |
|
|
192
|
+
|
|
193
|
+
## Security model & honest limits
|
|
194
|
+
|
|
195
|
+
- **Finalized only.** A `verified` receipt means the value was proven against a finalized Sepolia state root that the engine's Helios light client authenticated from a pinned checkpoint. `latest` can't be verified and is refused under `required`.
|
|
196
|
+
- **The SDK trusts the local engine.** It validates the receipt's shape, not the proof. Run the engine on your own machine. Anything that can impersonate `127.0.0.1:8547` can lie to the SDK.
|
|
197
|
+
- **What is verified:** balances, and storage slots (account proof plus storage proof, including exclusion proofs for zero values).
|
|
198
|
+
- **What isn't:** every other method is `unsupported` or forwarded unverified, as the policy allows. Agreement between providers is recorded, never treated as proof.
|
|
199
|
+
- **Dev mode is unverified.** `rootSource: 'untrusted-rpc'` receipts come from an RPC-supplied root. `<EngineStatus>` shows an amber DEV MODE pill.
|
|
200
|
+
- **Privacy:** with `transport: direct`, providers still see the address you query and your IP. Veriq protects integrity, not privacy.
|
|
201
|
+
|
|
202
|
+
## FAQ
|
|
203
|
+
|
|
204
|
+
**Does the SDK verify Merkle proofs in the browser?** No. The local engine does. `verifierLocation` in the receipt says where verification happened.
|
|
205
|
+
|
|
206
|
+
**Why does `client.getBalance()` throw `UnsupportedVerificationError`?** viem sends `latest` by default. Pass `blockTag: 'finalized'`, or create the transport with `{ strict: false }`.
|
|
207
|
+
|
|
208
|
+
**Can I use it without React or viem?** Yes. The core imports neither. Both are optional peers.
|
|
209
|
+
|
|
210
|
+
**Where does the receipt shape come from?** `backend/crates/core/src/receipt.rs`. `src/schema.ts` mirrors it field for field, and a test asserts the keys match.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
Examples live in [`examples/`](examples) and run in the test suite against a mocked engine.
|