zk-credits 0.2.5 → 0.2.8
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 +53 -26
- package/circuits/base-sepolia-v2.tar.gz +0 -0
- package/dist/artifact-release.d.ts +2 -1
- package/dist/artifact-release.js +63 -18
- package/dist/base-registered-trial.d.ts +6 -2
- package/dist/base-registered-trial.js +18 -2
- package/dist/base-sidecar.d.ts +2 -0
- package/dist/base-sidecar.js +13 -1
- package/dist/cli.js +152 -40
- package/dist/codex-profile.js +1 -1
- package/dist/gateway-trial-status.d.ts +13 -0
- package/dist/gateway-trial-status.js +70 -0
- package/dist/local-x402-agent.d.ts +34 -0
- package/dist/local-x402-agent.js +130 -0
- package/dist/responses-bridge.js +5 -3
- package/dist/setup-config.d.ts +1 -1
- package/dist/sidecar.d.ts +2 -0
- package/dist/sidecar.js +8 -0
- package/dist/zk-credits.js +699 -375
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Loopback sidecar that attaches a hash-pinned BN254 Groth16 proof to each
|
|
4
4
|
coding-agent LLM request through the experimental x402 `zk-prepaid` scheme.
|
|
5
|
-
Built for the invite-only
|
|
6
|
-
|
|
7
|
-
audited.
|
|
5
|
+
Built for the invite-only experimental Base Sepolia pilot. Product calls use
|
|
6
|
+
founder-provisioned test credits and have no product payment step; the circuit
|
|
7
|
+
is not independently audited. External research participants in slots A and C
|
|
8
|
+
receive $25 for a 30-minute session regardless of setup success. That payment
|
|
9
|
+
is not product revenue or willingness-to-pay evidence.
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
|
-
npm install --global zk-credits@0.2.
|
|
12
|
+
npm install --global zk-credits@0.2.8
|
|
11
13
|
```
|
|
12
14
|
|
|
13
15
|
## Pinned pilot versions
|
|
@@ -17,17 +19,18 @@ not mix versions between the sidecar, the adapter package, and the gateway:
|
|
|
17
19
|
|
|
18
20
|
| Package | Version |
|
|
19
21
|
| --- | --- |
|
|
20
|
-
| `zk-credits` (this sidecar) | `0.2.
|
|
22
|
+
| `zk-credits` (this sidecar) | `0.2.8` |
|
|
21
23
|
| `@zk-credits/x402-zk-prepaid` (adapter) | `0.1.0` |
|
|
22
24
|
| `@zk-credits/shared` | `0.1.0` |
|
|
23
25
|
|
|
24
|
-
The
|
|
25
|
-
`POST /v1/chat/completions` path below, includes the loopback Responses
|
|
26
|
-
for Codex
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
The slot A Base Sepolia trial version is `0.2.8`. It uses the non-streaming
|
|
27
|
+
`POST /v1/chat/completions` path below, includes the narrow loopback Responses
|
|
28
|
+
bridge for supported Codex text and function-tool requests, and includes the
|
|
29
|
+
pinned proving bundle in the package. It also
|
|
30
|
+
synchronizes V2 Base events and checks the gateway known root before local
|
|
31
|
+
setup completes. The packaged archive is checked against the immutable release
|
|
32
|
+
digest pinned by `circuits/manifest.json`. Version 0.2.8 must be published by
|
|
33
|
+
the owner before inviting a slot A participant.
|
|
31
34
|
|
|
32
35
|
Verify the shipped artifacts before your first prove. The package's
|
|
33
36
|
`circuits/manifest.json` fixes the SHA-256 of the frozen
|
|
@@ -57,14 +60,16 @@ still receives the class. A rejected request consumes no credit.
|
|
|
57
60
|
|
|
58
61
|
## First run
|
|
59
62
|
|
|
60
|
-
1. Redeem
|
|
61
|
-
|
|
62
|
-
2.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
1. Redeem the founder-prepared invite and download the encrypted V2 credential
|
|
64
|
+
export. Slot A needs no access to the proving-bundle repository.
|
|
65
|
+
2. Give Codex the single setup instruction in the
|
|
66
|
+
[first-use guide](../../docs/onboarding/base-zk-credits-codex-first-use.md).
|
|
67
|
+
Codex installs this pinned release, verifies its packaged bundle, and runs setup.
|
|
68
|
+
The human signs in if needed, enters the recovery password at the hidden
|
|
69
|
+
prompt, gives consent, and chooses their own task. No product payment is
|
|
70
|
+
involved. The sidecar needs no GitHub token to install proving material.
|
|
71
|
+
3. The setup command sets the credential, gateway, and Base Sepolia RPC
|
|
72
|
+
endpoint:
|
|
68
73
|
|
|
69
74
|
```bash
|
|
70
75
|
export ZK_CREDITS_CREDENTIAL_PATH="$HOME/Downloads/zk-credits-credential.json"
|
|
@@ -73,10 +78,10 @@ still receives the class. A rejected request consumes no credit.
|
|
|
73
78
|
```
|
|
74
79
|
|
|
75
80
|
`zk-credits setup codex` searches the standard local folders first. If it
|
|
76
|
-
finds no valid bundle, it
|
|
77
|
-
|
|
81
|
+
finds no valid bundle, it reads the packaged archive, verifies its pinned
|
|
82
|
+
archive digest and all three file digests,
|
|
78
83
|
then atomically installs the bundle under `~/.zk-credits/artifacts` with
|
|
79
|
-
owner-only permissions.
|
|
84
|
+
owner-only permissions. Any mismatch stops setup.
|
|
80
85
|
It syncs public Base events from the pinned V2 bond and deployment block,
|
|
81
86
|
validates that the resulting witness resolves the activated credential,
|
|
82
87
|
and asks the gateway's public `POST /v1/root-known` endpoint whether the
|
|
@@ -99,7 +104,7 @@ still receives the class. A rejected request consumes no credit.
|
|
|
99
104
|
leaves, and the local slot stays reusable. The gateway never serves a path
|
|
100
105
|
for a named leaf or commitment.
|
|
101
106
|
|
|
102
|
-
|
|
107
|
+
4. Start setup. It verifies the credential, pinned artifact hashes, and
|
|
103
108
|
witness locally, then prompts for the backup password without echo:
|
|
104
109
|
|
|
105
110
|
```bash
|
|
@@ -107,7 +112,9 @@ still receives the class. A rejected request consumes no credit.
|
|
|
107
112
|
```
|
|
108
113
|
|
|
109
114
|
No gateway admin token is required. Then run `zk-credits codex` to open
|
|
110
|
-
Codex with the local sidecar.
|
|
115
|
+
Codex with the local sidecar. A/C first-use measurement targets five
|
|
116
|
+
minutes of active human work; unattended download, proving, and provider
|
|
117
|
+
waits are measured separately. More than five minutes is a friction finding.
|
|
111
118
|
|
|
112
119
|
The sidecar binds `127.0.0.1:3210` only. It does not modify `~/.cline` or
|
|
113
120
|
`~/.codex`.
|
|
@@ -198,11 +205,20 @@ zk-credits cline [cline arguments...]
|
|
|
198
205
|
zk-credits setup codex [--model <model>]
|
|
199
206
|
zk-credits codex [codex arguments...]
|
|
200
207
|
zk-credits status
|
|
201
|
-
zk-credits serve [--port <port>]
|
|
208
|
+
zk-credits serve [--port <port>] [--internal-trial-one-proof]
|
|
209
|
+
zk-credits x402-agent
|
|
210
|
+
zk-credits founder-demo-agent
|
|
202
211
|
zk-credits trial-registered-adapter
|
|
203
212
|
eval "$(zk-credits env)"
|
|
204
213
|
```
|
|
205
214
|
|
|
215
|
+
`serve --internal-trial-one-proof` is an opt-in maintainer mode for the
|
|
216
|
+
internal Base Sepolia trial. During that process lifetime, one authenticated,
|
|
217
|
+
valid spend request may reach the prepaid transport and the proof factory may
|
|
218
|
+
attempt one proof. A later valid request receives HTTP 409 with
|
|
219
|
+
`internal_trial_limit_reached`; ordinary `serve` behavior is unchanged without
|
|
220
|
+
the flag.
|
|
221
|
+
|
|
206
222
|
`trial-registered-adapter` is a maintainer-only internal trial command. It
|
|
207
223
|
requires the locally recovered credential and pinned proving bundle, prompts
|
|
208
224
|
for the credential password and gateway admin token without echo when they are
|
|
@@ -220,6 +236,17 @@ Codex SDK:
|
|
|
220
236
|
import { buildCodexSdkOptions, buildCodexThreadOptions } from 'zk-credits/codex';
|
|
221
237
|
```
|
|
222
238
|
|
|
239
|
+
`zk-credits/x402` exports `createLocalX402Agent` for an operator who is
|
|
240
|
+
integrating the adapter into their own x402 agent. It loads the same local
|
|
241
|
+
credential, pinned proof artifacts, public witness, proof engine, and slot
|
|
242
|
+
ledger used by the sidecar. The request-aware client binds the actual method,
|
|
243
|
+
URL, and body into each spend; its metrics endpoint exposes authenticated
|
|
244
|
+
aggregate counters on loopback. See
|
|
245
|
+
[`docs/onboarding/base-zk-credits-x402-agent.md`](../../docs/onboarding/base-zk-credits-x402-agent.md)
|
|
246
|
+
for integration and measured-call steps. The included `zk-credits x402-agent`
|
|
247
|
+
command is a small task-running starter for the founder demo and local
|
|
248
|
+
rehearsal; external slot C uses their own agent.
|
|
249
|
+
|
|
223
250
|
The credential secret is decrypted only in local memory. The sidecar cache
|
|
224
251
|
contains public Base event leaves and never stores the secret, prompt, response,
|
|
225
252
|
proof, account, or wallet.
|
|
Binary file
|
|
@@ -5,6 +5,7 @@ export interface AcquirePinnedBundleOptions {
|
|
|
5
5
|
manifest: CircuitManifest;
|
|
6
6
|
targetDirectory: string;
|
|
7
7
|
runGithubCommand?: GithubCommand;
|
|
8
|
+
bundledArchivePath?: string;
|
|
8
9
|
}
|
|
9
|
-
/**
|
|
10
|
+
/** Installs the pinned package archive, or fetches the release for legacy/test callers. */
|
|
10
11
|
export declare function acquirePinnedArtifactBundle(options: AcquirePinnedBundleOptions): Promise<string>;
|
package/dist/artifact-release.js
CHANGED
|
@@ -5,7 +5,8 @@ import { gunzipSync } from 'node:zlib';
|
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { chmod, lstat, mkdir, open, readFile, rename, rm, } from 'node:fs/promises';
|
|
7
7
|
import { dirname, join, resolve } from 'node:path';
|
|
8
|
-
import {
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { ArtifactBundleError, resolvePinnedArtifactBundle } from './artifact-bundle.js';
|
|
9
10
|
const execFile = promisify(nodeExecFile);
|
|
10
11
|
const MAX_COMPRESSED_BYTES = 20 * 1024 * 1024;
|
|
11
12
|
const MAX_EXPANDED_BYTES = 32 * 1024 * 1024;
|
|
@@ -126,14 +127,15 @@ function pinnedAsset(release, raw) {
|
|
|
126
127
|
}
|
|
127
128
|
const record = value;
|
|
128
129
|
const assets = Array.isArray(record.assets) ? record.assets : [];
|
|
129
|
-
const
|
|
130
|
+
const matchingAssets = assets.filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
130
131
|
&& entry.name === release.assetName);
|
|
132
|
+
const asset = matchingAssets[0];
|
|
131
133
|
if (record.id !== release.releaseId
|
|
132
134
|
|| record.tag_name !== release.tag
|
|
133
135
|
|| record.draft !== false
|
|
134
136
|
|| record.immutable !== true
|
|
135
|
-
||
|
|
136
|
-
||
|
|
137
|
+
|| matchingAssets.length !== 1
|
|
138
|
+
|| asset.digest !== 'sha256:' + release.sha256) {
|
|
137
139
|
throw new Error('Pinned proving release identity or immutability check failed');
|
|
138
140
|
}
|
|
139
141
|
}
|
|
@@ -185,13 +187,15 @@ async function replaceDirectoryAtomically(stage, target) {
|
|
|
185
187
|
await rm(stage, { recursive: true, force: true });
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
|
-
/**
|
|
190
|
+
/** Installs the pinned package archive, or fetches the release for legacy/test callers. */
|
|
189
191
|
export async function acquirePinnedArtifactBundle(options) {
|
|
190
192
|
const release = options.manifest.release;
|
|
191
193
|
if (!release)
|
|
192
194
|
throw new Error('This sidecar manifest has no pinned artifact release');
|
|
193
195
|
const names = new Set(options.manifest.artifacts.map((artifact) => artifact.file));
|
|
194
196
|
const command = options.runGithubCommand ?? defaultGithubCommand;
|
|
197
|
+
const bundledArchivePath = options.bundledArchivePath
|
|
198
|
+
?? (!options.runGithubCommand ? fileURLToPath(new URL('../circuits/base-sepolia-v2.tar.gz', import.meta.url)) : undefined);
|
|
195
199
|
const parent = dirname(resolve(options.targetDirectory));
|
|
196
200
|
const tempDirectory = join(parent, '.base-sepolia-download-' + randomUUID());
|
|
197
201
|
const stage = join(parent, '.base-sepolia-stage-' + randomUUID());
|
|
@@ -199,16 +203,49 @@ export async function acquirePinnedArtifactBundle(options) {
|
|
|
199
203
|
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
200
204
|
await mkdir(tempDirectory, { recursive: false, mode: 0o700 });
|
|
201
205
|
await mkdir(stage, { recursive: false, mode: 0o700 });
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
if (!bundledArchivePath) {
|
|
207
|
+
try {
|
|
208
|
+
await command(['auth', 'status']);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
throw new Error('GitHub CLI authentication is required; run gh auth login locally before downloading the pinned proving bundle.');
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const repositoryJson = await command(['api', `repos/${release.repository}`]);
|
|
215
|
+
const repository = JSON.parse(repositoryJson);
|
|
216
|
+
if (repository.full_name !== release.repository)
|
|
217
|
+
throw new Error('Repository identity mismatch');
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
throw new Error(`Pinned proving bundle requires read access to ${release.repository}; ask the founder to grant access to your signed-in GitHub account.`);
|
|
221
|
+
}
|
|
222
|
+
let releaseJson;
|
|
223
|
+
try {
|
|
224
|
+
releaseJson = await command(['api', `repos/${release.repository}/releases/${release.releaseId}`]);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
throw new Error('Pinned proving release metadata could not be read; ask the founder to verify the published release with an authorized account. A 404 does not distinguish a missing release from insufficient access.');
|
|
228
|
+
}
|
|
229
|
+
pinnedAsset(release, releaseJson);
|
|
230
|
+
try {
|
|
231
|
+
await command([
|
|
232
|
+
'release', 'download', release.tag,
|
|
233
|
+
'--repo', release.repository,
|
|
234
|
+
'--dir', tempDirectory,
|
|
235
|
+
'--pattern', release.assetName,
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
throw new Error('Pinned proving bundle download failed; verify gh access and the published release asset.');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const archivePath = bundledArchivePath ?? join(tempDirectory, release.assetName);
|
|
243
|
+
const archiveInfo = await lstat(archivePath).catch((error) => {
|
|
244
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
245
|
+
throw new Error('Pinned proving bundle archive is missing or too large');
|
|
246
|
+
}
|
|
247
|
+
throw error;
|
|
248
|
+
});
|
|
212
249
|
if (archiveInfo.isSymbolicLink() || !archiveInfo.isFile() || archiveInfo.size > MAX_COMPRESSED_BYTES) {
|
|
213
250
|
throw new Error('Pinned proving bundle archive is missing or too large');
|
|
214
251
|
}
|
|
@@ -226,15 +263,23 @@ export async function acquirePinnedArtifactBundle(options) {
|
|
|
226
263
|
for (const [name, bytes] of files)
|
|
227
264
|
await writeBundleFile(stage, name, bytes);
|
|
228
265
|
await chmod(stage, 0o700);
|
|
229
|
-
|
|
266
|
+
try {
|
|
267
|
+
await resolvePinnedArtifactBundle({ artifactDirectory: stage, manifest: options.manifest });
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
if (error instanceof ArtifactBundleError && error.category === 'artifact_hash_mismatch') {
|
|
271
|
+
throw new Error(`Pinned proving bundle file SHA-256 check failed: ${error.message}`, { cause: error });
|
|
272
|
+
}
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
230
275
|
await replaceDirectoryAtomically(stage, resolve(options.targetDirectory));
|
|
231
276
|
await resolvePinnedArtifactBundle({ artifactDirectory: resolve(options.targetDirectory), manifest: options.manifest });
|
|
232
277
|
return resolve(options.targetDirectory);
|
|
233
278
|
}
|
|
234
279
|
catch (error) {
|
|
235
|
-
if (error instanceof Error && /^(Pinned proving|This sidecar manifest|Local proving bundle)/u.test(error.message))
|
|
280
|
+
if (error instanceof Error && /^(Pinned proving|This sidecar manifest|Local proving bundle|GitHub CLI authentication)/u.test(error.message))
|
|
236
281
|
throw error;
|
|
237
|
-
throw new Error('Pinned proving bundle acquisition failed; verify
|
|
282
|
+
throw new Error('Pinned proving bundle acquisition failed; verify the packaged archive and manifest pins.');
|
|
238
283
|
}
|
|
239
284
|
finally {
|
|
240
285
|
await rm(tempDirectory, { recursive: true, force: true });
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One-shot local transport for
|
|
2
|
+
* One-shot local transport for the founder's task-running x402 demo agent.
|
|
3
3
|
*
|
|
4
4
|
* @x402/core creates scheme payloads from PaymentRequired and does not pass
|
|
5
5
|
* HTTP method/body context to a scheme client. This harness captures that
|
|
6
6
|
* request context locally, then binds it into the same Base proof factory
|
|
7
7
|
* before invoking the registered `zk-prepaid` scheme. It is intended only for
|
|
8
|
-
* the internal Base Sepolia
|
|
8
|
+
* the internal Base Sepolia founder demo, not as a general HTTP client.
|
|
9
9
|
*
|
|
10
10
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
11
11
|
*/
|
|
@@ -28,6 +28,10 @@ export interface BaseRegisteredTrialOptions extends BasePrepaidClientOptions {
|
|
|
28
28
|
body: string;
|
|
29
29
|
headers?: HeadersInit;
|
|
30
30
|
fetch?: typeof fetch;
|
|
31
|
+
/** The response can be shown locally to the operator; it is never retained by metrics. */
|
|
32
|
+
onResponse?: (response: Response) => void | Promise<void>;
|
|
31
33
|
}
|
|
34
|
+
/** A bounded, single-task request for the founder's internal x402 demo. */
|
|
35
|
+
export declare function buildBaseRegisteredTrialRequestBody(task?: string): string;
|
|
32
36
|
/** Runs one POST to the pilot chat route through a directly registered adapter. */
|
|
33
37
|
export declare function runBaseRegisteredTrialExchange(options: BaseRegisteredTrialOptions): Promise<BaseRegisteredTrialResult>;
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One-shot local transport for
|
|
2
|
+
* One-shot local transport for the founder's task-running x402 demo agent.
|
|
3
3
|
*
|
|
4
4
|
* @x402/core creates scheme payloads from PaymentRequired and does not pass
|
|
5
5
|
* HTTP method/body context to a scheme client. This harness captures that
|
|
6
6
|
* request context locally, then binds it into the same Base proof factory
|
|
7
7
|
* before invoking the registered `zk-prepaid` scheme. It is intended only for
|
|
8
|
-
* the internal Base Sepolia
|
|
8
|
+
* the internal Base Sepolia founder demo, not as a general HTTP client.
|
|
9
9
|
*
|
|
10
10
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
11
11
|
*/
|
|
12
12
|
import { x402Client } from '@x402/core/client';
|
|
13
13
|
import { BASE_SEPOLIA_NETWORK, decodeHeader, encodeHeader, lifecycleFailure, lifecycleStage, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PAYMENT_SIGNATURE_HEADER, registerZkPrepaidClient, ZK_PREPAID_SCHEME, } from '@zk-credits/x402-zk-prepaid';
|
|
14
14
|
import { createBasePaymentFactory } from './base-sidecar.js';
|
|
15
|
+
/** A bounded, single-task request for the founder's internal x402 demo. */
|
|
16
|
+
export function buildBaseRegisteredTrialRequestBody(task = 'Reply with exactly: internal trial complete.') {
|
|
17
|
+
if (typeof task !== 'string' || task.trim().length === 0 || task.length > 4_000) {
|
|
18
|
+
throw new Error('Task must contain between 1 and 4000 characters');
|
|
19
|
+
}
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
model: 'deepseek/deepseek-v4-flash',
|
|
22
|
+
messages: [{ role: 'user', content: task }],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
15
25
|
function observe(options, event) {
|
|
16
26
|
try {
|
|
17
27
|
options.lifecycle?.(event);
|
|
@@ -163,6 +173,12 @@ export async function runBaseRegisteredTrialExchange(options) {
|
|
|
163
173
|
}
|
|
164
174
|
result.responseSucceeded = true;
|
|
165
175
|
observe(options, lifecycleStage('exchange_succeeded'));
|
|
176
|
+
try {
|
|
177
|
+
await options.onResponse?.(paidResponse.clone());
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Displaying a completed response is local convenience, not settlement.
|
|
181
|
+
}
|
|
166
182
|
result.committedSlotDelta = ledger.snapshot().committed - startingCommittedSlots;
|
|
167
183
|
return result;
|
|
168
184
|
}
|
package/dist/base-sidecar.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ export interface BasePrepaidClientOptions {
|
|
|
50
50
|
credential: CreditCredential;
|
|
51
51
|
witnessProvider: BaseWitnessProvider;
|
|
52
52
|
prove: BaseProofGenerator;
|
|
53
|
+
/** Limits this local client to one proof attempt for a single internal trial. */
|
|
54
|
+
internalTrialOneProof?: boolean;
|
|
53
55
|
fetch?: typeof fetch;
|
|
54
56
|
/**
|
|
55
57
|
* Durable local slot ledger. A slot is provisional until the proof passes
|
package/dist/base-sidecar.js
CHANGED
|
@@ -16,6 +16,12 @@ const TREE_DEPTH = 20;
|
|
|
16
16
|
const FIELD_ORDER = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
17
17
|
const REPLAY_TTL_MS = 24 * 60 * 60 * 1000;
|
|
18
18
|
const MAX_PENDING_PAYMENTS = 64;
|
|
19
|
+
const PAYMENT_PROOF_FIELDS = ['pi_a', 'pi_b', 'pi_c'];
|
|
20
|
+
function proofForPayment(proof) {
|
|
21
|
+
return Object.fromEntries(PAYMENT_PROOF_FIELDS
|
|
22
|
+
.filter((field) => Object.prototype.hasOwnProperty.call(proof, field))
|
|
23
|
+
.map((field) => [field, proof[field]]));
|
|
24
|
+
}
|
|
19
25
|
function field(value, label) {
|
|
20
26
|
if (!/^\d+$/u.test(value))
|
|
21
27
|
throw new Error(`Invalid ${label}`);
|
|
@@ -63,6 +69,7 @@ function requestBodyBytes(body) {
|
|
|
63
69
|
/** Creates the shared local proof factory used by both client transports. */
|
|
64
70
|
export function createBasePaymentFactory(options) {
|
|
65
71
|
const secret = secretFromBase64Url(options.credential.secret);
|
|
72
|
+
let internalTrialProofAttempted = false;
|
|
66
73
|
return async ({ method, url, body, requirements }) => {
|
|
67
74
|
if (requirements.extra.deploymentDomain !== options.credential.deploymentDomain) {
|
|
68
75
|
throw new Error('Credential deployment domain does not match the gateway challenge');
|
|
@@ -98,6 +105,11 @@ export function createBasePaymentFactory(options) {
|
|
|
98
105
|
nullifier,
|
|
99
106
|
share,
|
|
100
107
|
];
|
|
108
|
+
if (options.internalTrialOneProof) {
|
|
109
|
+
if (internalTrialProofAttempted)
|
|
110
|
+
throw new Error('internal_trial_limit_reached');
|
|
111
|
+
internalTrialProofAttempted = true;
|
|
112
|
+
}
|
|
101
113
|
const proof = await options.prove({
|
|
102
114
|
secret: secretToField(secret),
|
|
103
115
|
tier_id: String(options.credential.tierId),
|
|
@@ -118,7 +130,7 @@ export function createBasePaymentFactory(options) {
|
|
|
118
130
|
await slotLedger.commit(selectedSlot);
|
|
119
131
|
const payment = buildPaymentPayload({
|
|
120
132
|
requirements,
|
|
121
|
-
proof: proof.proof,
|
|
133
|
+
proof: proofForPayment(proof.proof),
|
|
122
134
|
publicSignals: [...proof.publicSignals],
|
|
123
135
|
nonce,
|
|
124
136
|
responseKey: responseKeys.publicKey,
|