@zkproofport-app/sdk 0.3.3 → 0.4.1

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@zkproofport-app/sdk)](https://www.npmjs.com/package/@zkproofport-app/sdk)
4
4
  [![license](https://img.shields.io/npm/l/@zkproofport-app/sdk)](./LICENSE)
5
5
 
6
- TypeScript SDK for requesting zero-knowledge proofs from the [ZKProofport](https://zkproofport.com) mobile app and verifying them on-chain. Three circuits are officially supported today — Coinbase KYC, Coinbase country attestation, and OIDC email-domain attestation (Google, Microsoft 365). Two more prove today against testnet deployments and are marked experimental: Arc eligibility and GIWA attestation. The three Korean mobile ID (mDL) identifiers are reserved and planned. [Circuit Identifiers](#circuit-identifiers) has the full list and what each status means.
6
+ TypeScript SDK for requesting zero-knowledge proofs from the [ZKProofport](https://zkproofport.com) mobile app and verifying them on-chain. Three circuits are officially supported today — Coinbase KYC, Coinbase country attestation, and OIDC email-domain attestation (Google, Microsoft 365). Two more prove today against a testnet deployment and are marked experimental: GIWA attestation and Dojang Playground. The three Korean mobile ID (mDL) identifiers are reserved and planned. [Circuit Identifiers](#circuit-identifiers) has the full list and what each status means.
7
7
 
8
8
  ## How It Works
9
9
 
@@ -124,8 +124,8 @@ import { CIRCUIT_IDS, CIRCUIT_SUPPORT_STATUS } from '@zkproofport-app/sdk/circui
124
124
  | `CIRCUIT_IDS.COINBASE_ATTESTATION` | `coinbase_attestation` | **Supported** |
125
125
  | `CIRCUIT_IDS.COINBASE_COUNTRY_ATTESTATION` | `coinbase_country_attestation` | **Supported** |
126
126
  | `CIRCUIT_IDS.OIDC_DOMAIN_ATTESTATION` | `oidc_domain_attestation` | **Supported** |
127
- | `CIRCUIT_IDS.ARC_ELIGIBILITY` | `arc_eligibility` | _Experimental_ |
128
127
  | `CIRCUIT_IDS.GIWA_ATTESTATION` | `giwa_attestation` | _Experimental_ |
128
+ | `CIRCUIT_IDS.DOJANG_PLAYGROUND` | `dojang_playground` | _Experimental_ |
129
129
  | `CIRCUIT_IDS.MDL_KR_OWNERSHIP` | `mdl_kr_ownership` | Planned |
130
130
  | `CIRCUIT_IDS.MDL_KR_AGE` | `mdl_kr_age` | Planned |
131
131
  | `CIRCUIT_IDS.MDL_KR_REGION` | `mdl_kr_region` | Planned |
@@ -142,11 +142,11 @@ Every ID appears in `CircuitType` and in `sdk.getSupportedCircuits()`, planned o
142
142
  import { CIRCUIT_SUPPORT_STATUS, isSupportedCircuitId } from '@zkproofport-app/sdk';
143
143
 
144
144
  CIRCUIT_SUPPORT_STATUS.coinbase_attestation; // 'supported'
145
- CIRCUIT_SUPPORT_STATUS.arc_eligibility; // 'experimental'
145
+ CIRCUIT_SUPPORT_STATUS.giwa_attestation; // 'experimental'
146
146
  CIRCUIT_SUPPORT_STATUS.mdl_kr_age; // 'planned'
147
147
 
148
148
  isSupportedCircuitId('oidc_domain_attestation'); // true
149
- isSupportedCircuitId('arc_eligibility'); // false — experimental is not supported
149
+ isSupportedCircuitId('giwa_attestation'); // false — experimental is not supported
150
150
  isSupportedCircuitId('mdl_kr_age'); // false
151
151
  isSupportedCircuitId('coinbase-kyc'); // false — not an ID at all
152
152
  ```
@@ -163,7 +163,7 @@ isSupportedCircuitId('coinbase-kyc'); // false — not an ID at all
163
163
  | `isCircuitId(value)` | `value is CircuitId` | Narrows an unknown value; `false` for hyphenated names |
164
164
  | `isSupportedCircuitId(value)` | `value is CircuitId` | The same, and additionally requires `supported` |
165
165
  | `getCircuitSupportStatus(id)` | `CircuitSupportStatus` | Status of one ID; throws on an unknown one |
166
- | `CircuitId` | type | Union of the seven IDs. `CircuitType` is an alias of it |
166
+ | `CircuitId` | type | Union of the eight IDs. `CircuitType` is an alias of it |
167
167
  | `CircuitSupportStatus` | type | `'supported' \| 'planned'` |
168
168
 
169
169
  Both objects and all three arrays are frozen. `ALL_CIRCUIT_IDS`, `SUPPORTED_CIRCUIT_IDS` and `PLANNED_CIRCUIT_IDS` are derived from `CIRCUIT_SUPPORT_STATUS` at load time, so they cannot disagree with it.
@@ -251,75 +251,6 @@ const relay = await sdk.createRelayRequest('oidc_domain_attestation', {
251
251
 
252
252
  ## Experimental Circuits
253
253
 
254
- ### `arc_eligibility`
255
-
256
- Proves the same Coinbase attestation as `coinbase_attestation`, with one thing
257
- changed: the wallet signs a named EIP-712 action instead of an opaque signal
258
- hash. The proof's public inputs then carry the action's domain separator and
259
- struct hash, so a verifier checks **which** action was authorised — not merely
260
- that somebody eligible signed something.
261
-
262
- That matters once an agent moves money. `personal_sign` over 32 opaque bytes
263
- shows a person a hex string, so they approve without seeing what they approve;
264
- a typed action is rendered field by field in the wallet.
265
-
266
- ```typescript
267
- import { CIRCUIT_IDS, validateTypedAction } from '@zkproofport-app/sdk';
268
-
269
- const action = {
270
- domain: {
271
- name: 'MyVault',
272
- version: '1',
273
- chainId: 5042002, // Arc Testnet
274
- verifyingContract: '0xYourContract',
275
- },
276
- types: {
277
- Deposit: [
278
- { name: 'amount', type: 'uint256' },
279
- { name: 'nonce', type: 'uint256' },
280
- ],
281
- },
282
- primaryType: 'Deposit',
283
- message: { amount: '1000000', nonce: '1' },
284
- };
285
-
286
- // Optional: catch a malformed action before sending it. The circuit hashes the
287
- // action without reading it, so a broken one produces a valid proof of the
288
- // wrong thing rather than failing.
289
- const problem = validateTypedAction(action);
290
- if (problem) throw new Error(problem);
291
-
292
- const relay = await sdk.createRelayRequest(CIRCUIT_IDS.ARC_ELIGIBILITY, {
293
- scope: 'myapp.com',
294
- action,
295
- });
296
- ```
297
-
298
- `action` is optional, as of 2026-09-22. Send one and the wallet signs the
299
- typed data; send none and it personal_signs the request's signal hash, exactly
300
- as Coinbase KYC does. The circuit branches, and it refuses a request that
301
- tries to be both: with an action `signal_hash` must be empty, without one the
302
- EIP-712 pair must be.
303
-
304
- It used to be required, and not by choice: the circuit had no second path, so
305
- a request without an action produced a proof attempt that failed inside the
306
- prover. `CIRCUIT_ACTION_BINDING` now reports `'optional'` for it.
307
-
308
- One more thing worth knowing if you count people: its nullifier is
309
- `keccak(keccak(wallet ++ keccak("arc_eligibility")) ++ scope)` -- the wallet
310
- and a constant compiled into the circuit. It used to come from `signal_hash`,
311
- which nothing constrains and, in action mode, nobody signs.
312
-
313
- **Testnet only.** The verifier is deployed on Arc Testnet (chain `5042002`) and
314
- nowhere else. Circle has not published a mainnet chain id, so there is nothing
315
- to point a production build at. The mobile app keeps the whole Arc network
316
- behind Developer Mode for the same reason.
317
-
318
- The public inputs are `signal_hash`, `domain_separator`, `action_hash`,
319
- `signer_list_merkle_root`, `scope`, `nullifier` — a different order from every
320
- other circuit, which is why `extractScopeFromPublicInputs` and
321
- `extractNullifierFromPublicInputs` require the circuit id and refuse to guess.
322
-
323
254
  ### `giwa_attestation`
324
255
 
325
256
  The same attestation shape as Coinbase KYC, against GIWA's attester on GIWA
@@ -333,7 +264,7 @@ const { deepLink } = await sdk.createRelayRequest(
333
264
  );
334
265
  ```
335
266
 
336
- **An action is optional here, as it is for Arc.** Send one
267
+ **An action is optional here.** Send one
337
268
  and the wallet signs EIP-712 typed data, so the person reads named fields and
338
269
  the proof carries what they approved. Send none and the wallet signs the
339
270
  request's signal hash, exactly as Coinbase KYC does. The circuit itself
@@ -357,8 +288,7 @@ await sdk.createRelayRequest(
357
288
  ```
358
289
 
359
290
  Its public inputs are `signal_hash`, `domain_separator`, `action_hash`,
360
- `signer_list_merkle_root`, `scope`, `nullifier` — the same order as Arc, and
361
- 64 bytes longer than the Coinbase layout it used to share. Anything holding the
291
+ `signer_list_merkle_root`, `scope`, `nullifier` — 64 bytes longer than the Coinbase layout it used to share. Anything holding the
362
292
  old offsets reads the Merkle root where the nullifier is.
363
293
 
364
294
  One more difference worth knowing if you count people: its nullifier is
@@ -370,6 +300,33 @@ one nullifier and there is no input a prover can vary.
370
300
  nowhere else, so the mobile app keeps it behind Developer Mode. A mainnet
371
301
  deployment is planned; until it exists, do not ship on this.
372
302
 
303
+ ### `dojang_playground`
304
+
305
+ Proves that the wallet signing the request is the wallet that issued itself a
306
+ Dojang through the GIWA Playground: the same key signed a `payAndIssueEAS()`
307
+ call to the Playground faucet. The app finds that transaction itself, so a
308
+ request needs a scope and nothing else.
309
+
310
+ ```typescript
311
+ const { deepLink } = await sdk.createRelayRequest(
312
+ CIRCUIT_IDS.DOJANG_PLAYGROUND,
313
+ { scope: 'myapp.com' },
314
+ { dappName: 'My App' },
315
+ );
316
+ ```
317
+
318
+ An action is optional, exactly as for `giwa_attestation`: send one and the
319
+ wallet signs EIP-712 typed data, send none and it signs the signal hash.
320
+
321
+ Its public inputs are `signal_hash`, `domain_separator`, `action_hash`,
322
+ `scope`, `nullifier` (160 fields). There is no issuer list, so there is no
323
+ Merkle root, and scope and nullifier sit 32 fields earlier than in
324
+ `giwa_attestation`. Read them with `extractScopeFromPublicInputs(inputs,
325
+ 'dojang_playground')`, never with another circuit's id. The nullifier is
326
+ `keccak(keccak(wallet ++ keccak("dojang_playground")) ++ scope)`.
327
+
328
+ Testnet only, like `giwa_attestation`.
329
+
373
330
  ### Which circuits take an action
374
331
 
375
332
  `CIRCUIT_ACTION_BINDING` is the one table, so you do not have to learn which
@@ -377,13 +334,12 @@ circuit names are special:
377
334
 
378
335
  | What the circuit does with an action | Circuits |
379
336
  |---|---|
380
- | Signs it when present, signs the signal hash when not | `arc_eligibility`, `giwa_attestation` |
337
+ | Signs it when present, signs the signal hash when not | `giwa_attestation`, `dojang_playground` |
381
338
  | Has no action inputs — sending one is refused, not ignored | Coinbase KYC, Coinbase country, OIDC domain, the three mDL circuits |
382
339
 
383
340
  ```typescript
384
341
  import { circuitActionBinding } from '@zkproofport-app/sdk';
385
342
 
386
- circuitActionBinding('arc_eligibility'); // 'optional'
387
343
  circuitActionBinding('giwa_attestation'); // 'optional'
388
344
  circuitActionBinding('coinbase_attestation'); // 'none'
389
345
  ```
@@ -1097,8 +1053,8 @@ import type {
1097
1053
 
1098
1054
  | Type | Description |
1099
1055
  |------|-------------|
1100
- | `CircuitId` | Union of the eight canonical circuit IDs — `'coinbase_attestation' \| 'coinbase_country_attestation' \| 'oidc_domain_attestation' \| 'arc_eligibility' \| 'giwa_attestation' \| 'mdl_kr_ownership' \| 'mdl_kr_age' \| 'mdl_kr_region'`. Also exported from `@zkproofport-app/sdk/circuits` |
1101
- | `TypedAction` | The EIP-712 action an `arc_eligibility` proof binds to: `{ domain, types, primaryType, message }` — the same object `eth_signTypedData_v4` takes |
1056
+ | `CircuitId` | Union of the eight canonical circuit IDs — `'coinbase_attestation' \| 'coinbase_country_attestation' \| 'oidc_domain_attestation' \| 'giwa_attestation' \| 'dojang_playground' \| 'mdl_kr_ownership' \| 'mdl_kr_age' \| 'mdl_kr_region'`. Also exported from `@zkproofport-app/sdk/circuits` |
1057
+ | `TypedAction` | The EIP-712 action a `giwa_attestation` proof binds to: `{ domain, types, primaryType, message }` — the same object `eth_signTypedData_v4` takes |
1102
1058
  | `CircuitSupportStatus` | `'supported' \| 'planned'` — see [Circuit Identifiers](#circuit-identifiers) |
1103
1059
  | `CircuitType` | Alias of `CircuitId`. Kept as the name used throughout `ProofRequest`, `ProofResponse` and the SDK methods |
1104
1060
  | `ProofRequestStatus` | `'pending' \| 'completed' \| 'error' \| 'cancelled'` — the status on a `ProofResponse` |
@@ -1106,9 +1062,9 @@ import type {
1106
1062
  | `CoinbaseCountryInputs` | Inputs for `coinbase_country_attestation`: `{ scope, countryList, isIncluded, userAddress?, rawTransaction? }` |
1107
1063
  | `OidcDomainInputs` | Inputs for `oidc_domain_attestation`: `{ domain, scope, provider? }` |
1108
1064
  | `MdlKrOwnershipInputs` | Inputs for `mdl_kr_ownership`: `{ scope, discloseFlags? }` |
1109
- | `ActionBoundInputs` | Inputs for the circuits that can bind an EIP-712 action: `{ scope, action?, userAddress?, rawTransaction? }`. Whether the action is required is the circuit's property, in `CIRCUIT_ACTION_BINDING` — required for `arc_eligibility`, optional for `giwa_attestation` |
1110
- | `ArcEligibilityInputs` | Deprecated alias of `ActionBoundInputs`. It declared `action` as required, which was true of the circuit and not of the shape |
1065
+ | `ActionBoundInputs` | Inputs for the circuits that can bind an EIP-712 action: `{ scope, action?, userAddress?, rawTransaction? }`. Whether the action is required is the circuit's property, in `CIRCUIT_ACTION_BINDING` — optional for `giwa_attestation` and `dojang_playground` |
1111
1066
  | `GiwaAttestationInputs` | Alias of `ActionBoundInputs`, for `giwa_attestation` |
1067
+ | `DojangPlaygroundInputs` | Alias of `ActionBoundInputs`, for `dojang_playground` |
1112
1068
  | `MdlKrAgeInputs` | Inputs for `mdl_kr_age`: `{ scope, ageThreshold }` |
1113
1069
  | `MdlKrRegionInputs` | Inputs for `mdl_kr_region`: `{ scope, targetRegion }` |
1114
1070
  | `CircuitInputs` | Union of every input type above (plus an empty-input form for circuits that need nothing from the dApp) |
@@ -53,10 +53,10 @@ export declare const CIRCUIT_IDS: Readonly<{
53
53
  readonly COINBASE_COUNTRY_ATTESTATION: "coinbase_country_attestation";
54
54
  /** OIDC email-domain attestation. Officially supported. */
55
55
  readonly OIDC_DOMAIN_ATTESTATION: "oidc_domain_attestation";
56
- /** Arc eligibility: exact EIP-712 action authorization. Experimental on Arc Testnet. */
57
- readonly ARC_ELIGIBILITY: "arc_eligibility";
58
56
  /** GIWA attestation. Planned — not officially supported yet. */
59
57
  readonly GIWA_ATTESTATION: "giwa_attestation";
58
+ /** Dojang Playground on GIWA Sepolia. Experimental — testnet only. */
59
+ readonly DOJANG_PLAYGROUND: "dojang_playground";
60
60
  /** Korea Mobile ID ownership. Planned — not officially supported yet. */
61
61
  readonly MDL_KR_OWNERSHIP: "mdl_kr_ownership";
62
62
  /** Korea Mobile ID age threshold. Planned — not officially supported yet. */
@@ -131,12 +131,11 @@ export declare const SUPPORTED_CIRCUIT_IDS: readonly CircuitId[];
131
131
  /**
132
132
  * Circuits you can prove today, on a testnet, with everything subject to change.
133
133
  *
134
- * `arc_eligibility` is the reason this status exists. Calling it `planned`
135
- * would be wrong — the circuit compiles, the verifier is deployed and a proof
136
- * verifies against it — and calling it `supported` would be worse, because the
137
- * only deployment is Arc Testnet, Circle has not published a mainnet chain id,
138
- * and the public-input layout is still open. Neither existing word was true, so
139
- * the honest move was a third word rather than picking the closer lie.
134
+ * `giwa_attestation` is in this state. Calling it `planned` would be wrong —
135
+ * the circuit compiles, the verifier is deployed and a proof verifies against
136
+ * it — and calling it `supported` would be worse, because the only deployment
137
+ * is a testnet (GIWA Sepolia). Neither existing word was true, so the honest
138
+ * move was a third word rather than picking the closer lie.
140
139
  */
141
140
  export declare const EXPERIMENTAL_CIRCUIT_IDS: readonly CircuitId[];
142
141
  export declare const PLANNED_CIRCUIT_IDS: readonly CircuitId[];
@@ -193,7 +192,7 @@ export declare function getCircuitSupportStatus(circuit: CircuitId): CircuitSupp
193
192
  *
194
193
  * ONE TABLE, because the answer was becoming a chain of `circuit === '...'`
195
194
  * tests. It was asked in the deep-link validator and again in
196
- * `createRelayRequest`, both spelling out `arc_eligibility`, and a second
195
+ * `createRelayRequest`, both spelling out one circuit id, and a second
197
196
  * action-capable circuit would have made each of them a two-branch chain that
198
197
  * ends on whatever the last test happens to be.
199
198
  *
@@ -212,8 +211,8 @@ export declare const CIRCUIT_ACTION_BINDING: {
212
211
  coinbase_attestation: "none";
213
212
  coinbase_country_attestation: "none";
214
213
  oidc_domain_attestation: "none";
215
- arc_eligibility: "optional";
216
214
  giwa_attestation: "optional";
215
+ dojang_playground: "optional";
217
216
  mdl_kr_ownership: "none";
218
217
  mdl_kr_age: "none";
219
218
  mdl_kr_region: "none";
@@ -228,10 +227,10 @@ export declare function circuitActionBinding(circuit: string): CircuitActionBind
228
227
  export declare const CIRCUIT_NEEDS_WALLET_SIGNATURE: {
229
228
  coinbase_attestation: true;
230
229
  coinbase_country_attestation: true;
231
- arc_eligibility: true;
232
230
  oidc_domain_attestation: false;
233
231
  mdl_kr_ownership: false;
234
232
  mdl_kr_age: false;
235
233
  mdl_kr_region: false;
236
234
  giwa_attestation: true;
235
+ dojang_playground: true;
237
236
  };
@@ -44,10 +44,10 @@ const CIRCUIT_IDS = Object.freeze({
44
44
  COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',
45
45
  /** OIDC email-domain attestation. Officially supported. */
46
46
  OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',
47
- /** Arc eligibility: exact EIP-712 action authorization. Experimental on Arc Testnet. */
48
- ARC_ELIGIBILITY: 'arc_eligibility',
49
47
  /** GIWA attestation. Planned — not officially supported yet. */
50
48
  GIWA_ATTESTATION: 'giwa_attestation',
49
+ /** Dojang Playground on GIWA Sepolia. Experimental — testnet only. */
50
+ DOJANG_PLAYGROUND: 'dojang_playground',
51
51
  /** Korea Mobile ID ownership. Planned — not officially supported yet. */
52
52
  MDL_KR_OWNERSHIP: 'mdl_kr_ownership',
53
53
  /** Korea Mobile ID age threshold. Planned — not officially supported yet. */
@@ -74,12 +74,14 @@ const CIRCUIT_SUPPORT_STATUS = Object.freeze({
74
74
  coinbase_attestation: 'supported',
75
75
  coinbase_country_attestation: 'supported',
76
76
  oidc_domain_attestation: 'supported',
77
- arc_eligibility: 'experimental',
78
77
  // Experimental, not planned: the circuit is built, its verifier is
79
78
  // deployed on GIWA Sepolia and the demo requests it. What keeps it out of
80
79
  // 'supported' is that it is testnet-only, so the app offers it behind
81
80
  // Developer Mode and reads its files from main rather than a release tag.
82
81
  giwa_attestation: 'experimental',
82
+ // Same footing as GIWA: built, verifier deployed on GIWA Sepolia, a real
83
+ // proof verifies there. Testnet only.
84
+ dojang_playground: 'experimental',
83
85
  mdl_kr_ownership: 'planned',
84
86
  mdl_kr_age: 'planned',
85
87
  mdl_kr_region: 'planned',
@@ -108,8 +110,8 @@ const CIRCUIT_VK_PATHS = Object.freeze({
108
110
  coinbase_attestation: 'coinbase-attestation/target/vk/vk',
109
111
  coinbase_country_attestation: 'coinbase-country-attestation/target/vk/vk',
110
112
  oidc_domain_attestation: 'oidc-domain-attestation/target/vk/vk',
111
- arc_eligibility: 'arc-eligibility/target/vk/vk',
112
113
  giwa_attestation: 'giwa-attestation/target/vk/vk',
114
+ dojang_playground: 'dojang-playground/target/vk/vk',
113
115
  mdl_kr_ownership: 'mdl/kr-ownership/target/vk/vk',
114
116
  mdl_kr_age: 'mdl/kr-age/target/vk/vk',
115
117
  mdl_kr_region: 'mdl/kr-region/target/vk/vk',
@@ -137,12 +139,11 @@ const SUPPORTED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCU
137
139
  /**
138
140
  * Circuits you can prove today, on a testnet, with everything subject to change.
139
141
  *
140
- * `arc_eligibility` is the reason this status exists. Calling it `planned`
141
- * would be wrong — the circuit compiles, the verifier is deployed and a proof
142
- * verifies against it — and calling it `supported` would be worse, because the
143
- * only deployment is Arc Testnet, Circle has not published a mainnet chain id,
144
- * and the public-input layout is still open. Neither existing word was true, so
145
- * the honest move was a third word rather than picking the closer lie.
142
+ * `giwa_attestation` is in this state. Calling it `planned` would be wrong —
143
+ * the circuit compiles, the verifier is deployed and a proof verifies against
144
+ * it — and calling it `supported` would be worse, because the only deployment
145
+ * is a testnet (GIWA Sepolia). Neither existing word was true, so the honest
146
+ * move was a third word rather than picking the closer lie.
146
147
  */
147
148
  const EXPERIMENTAL_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter(id => CIRCUIT_SUPPORT_STATUS[id] === 'experimental'));
148
149
  const PLANNED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned'));
@@ -196,13 +197,11 @@ const CIRCUIT_ACTION_BINDING = {
196
197
  coinbase_country_attestation: 'none',
197
198
  // The signature lives inside the identity token; no wallet, no action.
198
199
  oidc_domain_attestation: 'none',
199
- // Both branch in-circuit: with an action the wallet signs the typed data,
200
- // without one it personal_signs the request's signal hash. Arc read
201
- // 'required' until 2026-09-22 because its circuit had no second path -- a
202
- // request without an action built a vector 64 bytes short and died inside
203
- // the prover. The circuit gained the branch; the table follows it.
204
- arc_eligibility: 'optional',
200
+ // Branches in-circuit: with an action the wallet signs the typed data,
201
+ // without one it personal_signs the request's signal hash.
205
202
  giwa_attestation: 'optional',
203
+ // Same branch as GIWA; see dojang-playground/src/main.nr.
204
+ dojang_playground: 'optional',
206
205
  mdl_kr_ownership: 'none',
207
206
  mdl_kr_age: 'none',
208
207
  mdl_kr_region: 'none',
@@ -232,20 +231,20 @@ function circuitActionBinding(circuit) {
232
231
  const CIRCUIT_NEEDS_WALLET_SIGNATURE = {
233
232
  coinbase_attestation: true,
234
233
  coinbase_country_attestation: true,
235
- // Same Coinbase-attested wallet as above; what changes is that it signs an
236
- // EIP-712 action rather than signal_hash.
237
- arc_eligibility: true,
238
234
  // The signature is inside the OIDC identity token, checked by the prover.
239
235
  oidc_domain_attestation: false,
240
236
  // Web2 (OmniOne CX) flows with no wallet binding at all.
241
237
  mdl_kr_ownership: false,
242
238
  mdl_kr_age: false,
243
239
  mdl_kr_region: false,
244
- // A GIWA-attested wallet signing an EIP-712 action, the same shape as Arc.
240
+ // A GIWA-attested wallet signing an EIP-712 action (or its signal hash).
245
241
  // This read `false` while the circuit had always verified a wallet signature
246
242
  // -- it sat under the Web2 comment above and was never revisited. A request
247
243
  // for it would have reached the relay with no signer.
248
244
  giwa_attestation: true,
245
+ // The wallet signs the request, and the same key must have signed its
246
+ // Playground faucet transaction.
247
+ dojang_playground: true,
249
248
  };
250
249
 
251
250
  export { ALL_CIRCUIT_IDS, CIRCUIT_ACTION_BINDING, CIRCUIT_IDS, CIRCUIT_NEEDS_WALLET_SIGNATURE, CIRCUIT_SUPPORT_STATUS, CIRCUIT_VK_PATHS, EXPERIMENTAL_CIRCUIT_IDS, PLANNED_CIRCUIT_IDS, SUPPORTED_CIRCUIT_IDS, circuitActionBinding, getCircuitSupportStatus, isCircuitId, isSupportedCircuitId };
@@ -1 +1 @@
1
- {"version":3,"file":"circuits.esm.js","sources":["../src/circuits.ts"],"sourcesContent":["/**\n * Canonical ZKProofport circuit identifiers.\n *\n * This module is the single source of truth for circuit IDs. Every other layer\n * — the mobile app, the demo, the relay, server-side provers — reads its list\n * from here instead of keeping a private copy that silently drifts.\n *\n * It has **no imports and no dependencies**, so it can be pulled in on its own:\n *\n * ```typescript\n * import { CIRCUIT_IDS, isCircuitId } from '@zkproofport-app/sdk/circuits';\n * ```\n *\n * The same names are also re-exported from the package root\n * (`@zkproofport-app/sdk`) for callers that already import the SDK.\n *\n * ## Identifiers are canonical and case-sensitive\n *\n * Each ID is the circuit's `name` in its `Nargo.toml`, verbatim. The format is\n * lowercase with underscores. Hyphenated spellings (`coinbase-kyc`,\n * `mdl-kr-age`) are directory or UI route names in other repositories — they\n * are **not** circuit IDs, and passing one produces a nullifier mismatch or a\n * failed on-chain lookup rather than a clear error.\n */\n\n/**\n * Whether a circuit is officially supported today or still on the roadmap.\n *\n * - `supported` — generally available. Safe to build a product on.\n * - `planned` — the identifier is reserved and the circuit exists, but it is\n * not officially supported yet. Availability, inputs and public-input layout\n * may change without a major version bump.\n */\nexport type CircuitSupportStatus = 'supported' | 'experimental' | 'planned';\n\n/**\n * Canonical circuit identifiers, keyed by a stable constant name.\n *\n * Prefer these constants over string literals so a rename is a compile error\n * rather than a runtime surprise.\n *\n * @example\n * ```typescript\n * import { CIRCUIT_IDS } from '@zkproofport-app/sdk/circuits';\n *\n * const relay = await sdk.createRelayRequest(CIRCUIT_IDS.COINBASE_ATTESTATION, {\n * scope: 'myapp.com',\n * });\n * ```\n */\nexport const CIRCUIT_IDS = Object.freeze({\n /** Coinbase KYC attestation. Officially supported. */\n COINBASE_ATTESTATION: 'coinbase_attestation',\n /** Coinbase country attestation (inclusion / exclusion). Officially supported. */\n COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',\n /** OIDC email-domain attestation. Officially supported. */\n OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',\n /** Arc eligibility: exact EIP-712 action authorization. Experimental on Arc Testnet. */\n ARC_ELIGIBILITY: 'arc_eligibility',\n /** GIWA attestation. Planned — not officially supported yet. */\n GIWA_ATTESTATION: 'giwa_attestation',\n /** Korea Mobile ID ownership. Planned — not officially supported yet. */\n MDL_KR_OWNERSHIP: 'mdl_kr_ownership',\n /** Korea Mobile ID age threshold. Planned — not officially supported yet. */\n MDL_KR_AGE: 'mdl_kr_age',\n /** Korea Mobile ID si/do region. Planned — not officially supported yet. */\n MDL_KR_REGION: 'mdl_kr_region',\n} as const);\n\n/**\n * Union of every canonical circuit identifier.\n *\n * `CircuitType` (exported from the package root) is an alias of this type — the\n * two names refer to the same union, and this module is where it is defined.\n */\nexport type CircuitId = (typeof CIRCUIT_IDS)[keyof typeof CIRCUIT_IDS];\n\n/**\n * Support status for every circuit.\n *\n * The record is exhaustive over {@link CircuitId}: adding an identifier without\n * giving it a status is a compile error, so this list cannot drift from\n * {@link CIRCUIT_IDS}.\n *\n * @example\n * ```typescript\n * import { CIRCUIT_SUPPORT_STATUS } from '@zkproofport-app/sdk/circuits';\n *\n * CIRCUIT_SUPPORT_STATUS.coinbase_attestation; // 'supported'\n * CIRCUIT_SUPPORT_STATUS.mdl_kr_age; // 'planned'\n * ```\n */\nexport const CIRCUIT_SUPPORT_STATUS: Readonly<Record<CircuitId, CircuitSupportStatus>> =\n Object.freeze({\n coinbase_attestation: 'supported',\n coinbase_country_attestation: 'supported',\n oidc_domain_attestation: 'supported',\n arc_eligibility: 'experimental',\n // Experimental, not planned: the circuit is built, its verifier is\n // deployed on GIWA Sepolia and the demo requests it. What keeps it out of\n // 'supported' is that it is testnet-only, so the app offers it behind\n // Developer Mode and reads its files from main rather than a release tag.\n giwa_attestation: 'experimental',\n mdl_kr_ownership: 'planned',\n mdl_kr_age: 'planned',\n mdl_kr_region: 'planned',\n } as const);\n\n/**\n * Where each circuit's verification key FILE lives inside the\n * `zkproofport/circuits` repository, relative to its root.\n *\n * Off-chain verification needs the verification key and nothing else — no\n * circuit bytecode, no witness. The key is public by nature: it is what anyone\n * checking a proof must already have, and the Solidity verifiers deployed\n * on-chain embed the same key.\n *\n * NOTE THE TRAILING `/vk` TWICE. In that repository `target/vk` is a DIRECTORY\n * holding `vk`, `vk_hash` and `SHA256SUMS`; the key itself is `target/vk/vk`.\n * These paths were first copied here from the mobile app, which keeps the\n * directory and the filename as two fields and joins them — so taking only the\n * directory produced a URL that answers 404, and the first browser to try\n * off-chain verification said \"Could not fetch the verification key (HTTP 404)\".\n *\n * Exhaustive over {@link CircuitId} on purpose. A new circuit whose key path is\n * not listed here fails to compile rather than failing at verification time,\n * where the only symptom would be a proof that cannot be checked.\n */\nexport const CIRCUIT_VK_PATHS: Readonly<Record<CircuitId, string>> = Object.freeze({\n coinbase_attestation: 'coinbase-attestation/target/vk/vk',\n coinbase_country_attestation: 'coinbase-country-attestation/target/vk/vk',\n oidc_domain_attestation: 'oidc-domain-attestation/target/vk/vk',\n arc_eligibility: 'arc-eligibility/target/vk/vk',\n giwa_attestation: 'giwa-attestation/target/vk/vk',\n mdl_kr_ownership: 'mdl/kr-ownership/target/vk/vk',\n mdl_kr_age: 'mdl/kr-age/target/vk/vk',\n mdl_kr_region: 'mdl/kr-region/target/vk/vk',\n} as const);\n\n/**\n * Every canonical circuit identifier, in declaration order.\n *\n * Includes circuits that are still `planned`. Use {@link SUPPORTED_CIRCUIT_IDS}\n * when you only want the ones that are officially supported today.\n */\nexport const ALL_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n Object.values(CIRCUIT_IDS) as CircuitId[]\n);\n\n/**\n * Circuits that are officially supported today: the two Coinbase circuits and\n * OIDC domain attestation.\n *\n * Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.\n */\nexport const SUPPORTED_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'supported')\n);\n\n/**\n * Circuits whose identifiers are reserved but which are not officially\n * supported yet: GIWA attestation and the three Korea Mobile ID circuits.\n *\n * Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.\n */\n/**\n * Circuits you can prove today, on a testnet, with everything subject to change.\n *\n * `arc_eligibility` is the reason this status exists. Calling it `planned`\n * would be wrong — the circuit compiles, the verifier is deployed and a proof\n * verifies against it — and calling it `supported` would be worse, because the\n * only deployment is Arc Testnet, Circle has not published a mainnet chain id,\n * and the public-input layout is still open. Neither existing word was true, so\n * the honest move was a third word rather than picking the closer lie.\n */\nexport const EXPERIMENTAL_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n ALL_CIRCUIT_IDS.filter(id => CIRCUIT_SUPPORT_STATUS[id] === 'experimental'),\n);\n\nexport const PLANNED_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned')\n);\n\n/**\n * Narrows an unknown value to a canonical circuit identifier.\n *\n * Returns `false` for hyphenated route names such as `'coinbase-kyc'`, which\n * are not circuit IDs.\n *\n * @example\n * ```typescript\n * isCircuitId('coinbase_attestation'); // true\n * isCircuitId('coinbase-kyc'); // false\n * ```\n */\nexport function isCircuitId(value: unknown): value is CircuitId {\n return typeof value === 'string' && (ALL_CIRCUIT_IDS as readonly string[]).includes(value);\n}\n\n/**\n * Narrows an unknown value to a circuit identifier that is officially supported\n * today. Planned circuits return `false`.\n *\n * @example\n * ```typescript\n * isSupportedCircuitId('oidc_domain_attestation'); // true\n * isSupportedCircuitId('mdl_kr_age'); // false — planned\n * ```\n */\nexport function isSupportedCircuitId(value: unknown): value is CircuitId {\n return isCircuitId(value) && CIRCUIT_SUPPORT_STATUS[value] === 'supported';\n}\n\n/**\n * Returns the support status of a circuit.\n *\n * @param circuit - A canonical circuit identifier.\n * @throws Error when the value is not a canonical circuit identifier.\n *\n * @example\n * ```typescript\n * getCircuitSupportStatus('giwa_attestation'); // 'planned'\n * ```\n */\nexport function getCircuitSupportStatus(circuit: CircuitId): CircuitSupportStatus {\n if (!isCircuitId(circuit)) {\n throw new Error(\n `Unknown circuit '${String(circuit)}'. Expected one of: ${ALL_CIRCUIT_IDS.join(', ')}`\n );\n }\n return CIRCUIT_SUPPORT_STATUS[circuit];\n}\n\n/**\n * Whether a circuit's relay request must carry a wallet signature.\n *\n * The SDK checks this to fail fast — \"Signer not set\" locally beats a 401 from\n * the relay two round trips later, with nothing saying which circuit needed\n * what. The relay declares its OWN copy on purpose and does not import this\n * one: it is a security boundary, and an auth policy that a published package\n * can change is not a policy. A test in the relay asserts the two agree.\n *\n * `satisfies` and not an annotation, so a circuit added above is a compile\n * error here until somebody decides which it is.\n */\n/**\n * Whether a circuit's proof can bind an EIP-712 action, and whether it must.\n *\n * ONE TABLE, because the answer was becoming a chain of `circuit === '...'`\n * tests. It was asked in the deep-link validator and again in\n * `createRelayRequest`, both spelling out `arc_eligibility`, and a second\n * action-capable circuit would have made each of them a two-branch chain that\n * ends on whatever the last test happens to be.\n *\n * 'required' the circuit proves nothing without an action -- the wallet\n * signs the typed data and there is no other message.\n * 'optional' the circuit signs the action when one is supplied and\n * personal_signs its signal hash when one is not. The circuit\n * itself branches; see giwa-attestation/src/main.nr.\n * 'none' the circuit has no action inputs. An action sent anyway is a\n * caller misunderstanding, and is refused rather than dropped:\n * silently ignoring it would let a dapp believe a proof\n * authorized something it never mentioned.\n */\nexport type CircuitActionBinding = 'none' | 'required' | 'optional';\n\nexport const CIRCUIT_ACTION_BINDING = {\n coinbase_attestation: 'none',\n coinbase_country_attestation: 'none',\n // The signature lives inside the identity token; no wallet, no action.\n oidc_domain_attestation: 'none',\n // Both branch in-circuit: with an action the wallet signs the typed data,\n // without one it personal_signs the request's signal hash. Arc read\n // 'required' until 2026-09-22 because its circuit had no second path -- a\n // request without an action built a vector 64 bytes short and died inside\n // the prover. The circuit gained the branch; the table follows it.\n arc_eligibility: 'optional',\n giwa_attestation: 'optional',\n mdl_kr_ownership: 'none',\n mdl_kr_age: 'none',\n mdl_kr_region: 'none',\n} satisfies Record<CircuitId, CircuitActionBinding>;\n\n/*\n * The union is declared above rather than derived from the table's values.\n *\n * Deriving it narrowed the type to whatever the table happens to hold today --\n * 'none' | 'optional' -- and TypeScript then called every `=== 'required'`\n * check impossible. The publish typecheck caught it; `rollup` and `vitest` did\n * not. 'required' is a state a circuit can legitimately be in (an EIP-712 path\n * and nothing else), so the union says so and the table is checked against it.\n */\n\n/**\n * How this circuit treats an action, or an error naming the id that is not one.\n *\n * No default: a circuit nobody has classified must not inherit 'none', which\n * would silently drop an action a dapp meant to bind.\n */\nexport function circuitActionBinding(circuit: string): CircuitActionBinding {\n const binding = (CIRCUIT_ACTION_BINDING as Record<string, CircuitActionBinding | undefined>)[circuit];\n if (!binding) {\n throw new Error(\n `Unknown circuit '${circuit}'. Expected: ${ALL_CIRCUIT_IDS.join(', ')}`,\n );\n }\n return binding;\n}\n\nexport const CIRCUIT_NEEDS_WALLET_SIGNATURE = {\n coinbase_attestation: true,\n coinbase_country_attestation: true,\n // Same Coinbase-attested wallet as above; what changes is that it signs an\n // EIP-712 action rather than signal_hash.\n arc_eligibility: true,\n // The signature is inside the OIDC identity token, checked by the prover.\n oidc_domain_attestation: false,\n // Web2 (OmniOne CX) flows with no wallet binding at all.\n mdl_kr_ownership: false,\n mdl_kr_age: false,\n mdl_kr_region: false,\n // A GIWA-attested wallet signing an EIP-712 action, the same shape as Arc.\n // This read `false` while the circuit had always verified a wallet signature\n // -- it sat under the Web2 comment above and was never revisited. A request\n // for it would have reached the relay with no signer.\n giwa_attestation: true,\n} satisfies Record<CircuitId, boolean>;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"circuits.esm.js","sources":["../src/circuits.ts"],"sourcesContent":["/**\n * Canonical ZKProofport circuit identifiers.\n *\n * This module is the single source of truth for circuit IDs. Every other layer\n * — the mobile app, the demo, the relay, server-side provers — reads its list\n * from here instead of keeping a private copy that silently drifts.\n *\n * It has **no imports and no dependencies**, so it can be pulled in on its own:\n *\n * ```typescript\n * import { CIRCUIT_IDS, isCircuitId } from '@zkproofport-app/sdk/circuits';\n * ```\n *\n * The same names are also re-exported from the package root\n * (`@zkproofport-app/sdk`) for callers that already import the SDK.\n *\n * ## Identifiers are canonical and case-sensitive\n *\n * Each ID is the circuit's `name` in its `Nargo.toml`, verbatim. The format is\n * lowercase with underscores. Hyphenated spellings (`coinbase-kyc`,\n * `mdl-kr-age`) are directory or UI route names in other repositories — they\n * are **not** circuit IDs, and passing one produces a nullifier mismatch or a\n * failed on-chain lookup rather than a clear error.\n */\n\n/**\n * Whether a circuit is officially supported today or still on the roadmap.\n *\n * - `supported` — generally available. Safe to build a product on.\n * - `planned` — the identifier is reserved and the circuit exists, but it is\n * not officially supported yet. Availability, inputs and public-input layout\n * may change without a major version bump.\n */\nexport type CircuitSupportStatus = 'supported' | 'experimental' | 'planned';\n\n/**\n * Canonical circuit identifiers, keyed by a stable constant name.\n *\n * Prefer these constants over string literals so a rename is a compile error\n * rather than a runtime surprise.\n *\n * @example\n * ```typescript\n * import { CIRCUIT_IDS } from '@zkproofport-app/sdk/circuits';\n *\n * const relay = await sdk.createRelayRequest(CIRCUIT_IDS.COINBASE_ATTESTATION, {\n * scope: 'myapp.com',\n * });\n * ```\n */\nexport const CIRCUIT_IDS = Object.freeze({\n /** Coinbase KYC attestation. Officially supported. */\n COINBASE_ATTESTATION: 'coinbase_attestation',\n /** Coinbase country attestation (inclusion / exclusion). Officially supported. */\n COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',\n /** OIDC email-domain attestation. Officially supported. */\n OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',\n /** GIWA attestation. Planned — not officially supported yet. */\n GIWA_ATTESTATION: 'giwa_attestation',\n /** Dojang Playground on GIWA Sepolia. Experimental — testnet only. */\n DOJANG_PLAYGROUND: 'dojang_playground',\n /** Korea Mobile ID ownership. Planned — not officially supported yet. */\n MDL_KR_OWNERSHIP: 'mdl_kr_ownership',\n /** Korea Mobile ID age threshold. Planned — not officially supported yet. */\n MDL_KR_AGE: 'mdl_kr_age',\n /** Korea Mobile ID si/do region. Planned — not officially supported yet. */\n MDL_KR_REGION: 'mdl_kr_region',\n} as const);\n\n/**\n * Union of every canonical circuit identifier.\n *\n * `CircuitType` (exported from the package root) is an alias of this type — the\n * two names refer to the same union, and this module is where it is defined.\n */\nexport type CircuitId = (typeof CIRCUIT_IDS)[keyof typeof CIRCUIT_IDS];\n\n/**\n * Support status for every circuit.\n *\n * The record is exhaustive over {@link CircuitId}: adding an identifier without\n * giving it a status is a compile error, so this list cannot drift from\n * {@link CIRCUIT_IDS}.\n *\n * @example\n * ```typescript\n * import { CIRCUIT_SUPPORT_STATUS } from '@zkproofport-app/sdk/circuits';\n *\n * CIRCUIT_SUPPORT_STATUS.coinbase_attestation; // 'supported'\n * CIRCUIT_SUPPORT_STATUS.mdl_kr_age; // 'planned'\n * ```\n */\nexport const CIRCUIT_SUPPORT_STATUS: Readonly<Record<CircuitId, CircuitSupportStatus>> =\n Object.freeze({\n coinbase_attestation: 'supported',\n coinbase_country_attestation: 'supported',\n oidc_domain_attestation: 'supported',\n // Experimental, not planned: the circuit is built, its verifier is\n // deployed on GIWA Sepolia and the demo requests it. What keeps it out of\n // 'supported' is that it is testnet-only, so the app offers it behind\n // Developer Mode and reads its files from main rather than a release tag.\n giwa_attestation: 'experimental',\n // Same footing as GIWA: built, verifier deployed on GIWA Sepolia, a real\n // proof verifies there. Testnet only.\n dojang_playground: 'experimental',\n mdl_kr_ownership: 'planned',\n mdl_kr_age: 'planned',\n mdl_kr_region: 'planned',\n } as const);\n\n/**\n * Where each circuit's verification key FILE lives inside the\n * `zkproofport/circuits` repository, relative to its root.\n *\n * Off-chain verification needs the verification key and nothing else — no\n * circuit bytecode, no witness. The key is public by nature: it is what anyone\n * checking a proof must already have, and the Solidity verifiers deployed\n * on-chain embed the same key.\n *\n * NOTE THE TRAILING `/vk` TWICE. In that repository `target/vk` is a DIRECTORY\n * holding `vk`, `vk_hash` and `SHA256SUMS`; the key itself is `target/vk/vk`.\n * These paths were first copied here from the mobile app, which keeps the\n * directory and the filename as two fields and joins them — so taking only the\n * directory produced a URL that answers 404, and the first browser to try\n * off-chain verification said \"Could not fetch the verification key (HTTP 404)\".\n *\n * Exhaustive over {@link CircuitId} on purpose. A new circuit whose key path is\n * not listed here fails to compile rather than failing at verification time,\n * where the only symptom would be a proof that cannot be checked.\n */\nexport const CIRCUIT_VK_PATHS: Readonly<Record<CircuitId, string>> = Object.freeze({\n coinbase_attestation: 'coinbase-attestation/target/vk/vk',\n coinbase_country_attestation: 'coinbase-country-attestation/target/vk/vk',\n oidc_domain_attestation: 'oidc-domain-attestation/target/vk/vk',\n giwa_attestation: 'giwa-attestation/target/vk/vk',\n dojang_playground: 'dojang-playground/target/vk/vk',\n mdl_kr_ownership: 'mdl/kr-ownership/target/vk/vk',\n mdl_kr_age: 'mdl/kr-age/target/vk/vk',\n mdl_kr_region: 'mdl/kr-region/target/vk/vk',\n} as const);\n\n/**\n * Every canonical circuit identifier, in declaration order.\n *\n * Includes circuits that are still `planned`. Use {@link SUPPORTED_CIRCUIT_IDS}\n * when you only want the ones that are officially supported today.\n */\nexport const ALL_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n Object.values(CIRCUIT_IDS) as CircuitId[]\n);\n\n/**\n * Circuits that are officially supported today: the two Coinbase circuits and\n * OIDC domain attestation.\n *\n * Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.\n */\nexport const SUPPORTED_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'supported')\n);\n\n/**\n * Circuits whose identifiers are reserved but which are not officially\n * supported yet: GIWA attestation and the three Korea Mobile ID circuits.\n *\n * Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.\n */\n/**\n * Circuits you can prove today, on a testnet, with everything subject to change.\n *\n * `giwa_attestation` is in this state. Calling it `planned` would be wrong —\n * the circuit compiles, the verifier is deployed and a proof verifies against\n * it — and calling it `supported` would be worse, because the only deployment\n * is a testnet (GIWA Sepolia). Neither existing word was true, so the honest\n * move was a third word rather than picking the closer lie.\n */\nexport const EXPERIMENTAL_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n ALL_CIRCUIT_IDS.filter(id => CIRCUIT_SUPPORT_STATUS[id] === 'experimental'),\n);\n\nexport const PLANNED_CIRCUIT_IDS: readonly CircuitId[] = Object.freeze(\n ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned')\n);\n\n/**\n * Narrows an unknown value to a canonical circuit identifier.\n *\n * Returns `false` for hyphenated route names such as `'coinbase-kyc'`, which\n * are not circuit IDs.\n *\n * @example\n * ```typescript\n * isCircuitId('coinbase_attestation'); // true\n * isCircuitId('coinbase-kyc'); // false\n * ```\n */\nexport function isCircuitId(value: unknown): value is CircuitId {\n return typeof value === 'string' && (ALL_CIRCUIT_IDS as readonly string[]).includes(value);\n}\n\n/**\n * Narrows an unknown value to a circuit identifier that is officially supported\n * today. Planned circuits return `false`.\n *\n * @example\n * ```typescript\n * isSupportedCircuitId('oidc_domain_attestation'); // true\n * isSupportedCircuitId('mdl_kr_age'); // false — planned\n * ```\n */\nexport function isSupportedCircuitId(value: unknown): value is CircuitId {\n return isCircuitId(value) && CIRCUIT_SUPPORT_STATUS[value] === 'supported';\n}\n\n/**\n * Returns the support status of a circuit.\n *\n * @param circuit - A canonical circuit identifier.\n * @throws Error when the value is not a canonical circuit identifier.\n *\n * @example\n * ```typescript\n * getCircuitSupportStatus('giwa_attestation'); // 'planned'\n * ```\n */\nexport function getCircuitSupportStatus(circuit: CircuitId): CircuitSupportStatus {\n if (!isCircuitId(circuit)) {\n throw new Error(\n `Unknown circuit '${String(circuit)}'. Expected one of: ${ALL_CIRCUIT_IDS.join(', ')}`\n );\n }\n return CIRCUIT_SUPPORT_STATUS[circuit];\n}\n\n/**\n * Whether a circuit's relay request must carry a wallet signature.\n *\n * The SDK checks this to fail fast — \"Signer not set\" locally beats a 401 from\n * the relay two round trips later, with nothing saying which circuit needed\n * what. The relay declares its OWN copy on purpose and does not import this\n * one: it is a security boundary, and an auth policy that a published package\n * can change is not a policy. A test in the relay asserts the two agree.\n *\n * `satisfies` and not an annotation, so a circuit added above is a compile\n * error here until somebody decides which it is.\n */\n/**\n * Whether a circuit's proof can bind an EIP-712 action, and whether it must.\n *\n * ONE TABLE, because the answer was becoming a chain of `circuit === '...'`\n * tests. It was asked in the deep-link validator and again in\n * `createRelayRequest`, both spelling out one circuit id, and a second\n * action-capable circuit would have made each of them a two-branch chain that\n * ends on whatever the last test happens to be.\n *\n * 'required' the circuit proves nothing without an action -- the wallet\n * signs the typed data and there is no other message.\n * 'optional' the circuit signs the action when one is supplied and\n * personal_signs its signal hash when one is not. The circuit\n * itself branches; see giwa-attestation/src/main.nr.\n * 'none' the circuit has no action inputs. An action sent anyway is a\n * caller misunderstanding, and is refused rather than dropped:\n * silently ignoring it would let a dapp believe a proof\n * authorized something it never mentioned.\n */\nexport type CircuitActionBinding = 'none' | 'required' | 'optional';\n\nexport const CIRCUIT_ACTION_BINDING = {\n coinbase_attestation: 'none',\n coinbase_country_attestation: 'none',\n // The signature lives inside the identity token; no wallet, no action.\n oidc_domain_attestation: 'none',\n // Branches in-circuit: with an action the wallet signs the typed data,\n // without one it personal_signs the request's signal hash.\n giwa_attestation: 'optional',\n // Same branch as GIWA; see dojang-playground/src/main.nr.\n dojang_playground: 'optional',\n mdl_kr_ownership: 'none',\n mdl_kr_age: 'none',\n mdl_kr_region: 'none',\n} satisfies Record<CircuitId, CircuitActionBinding>;\n\n/*\n * The union is declared above rather than derived from the table's values.\n *\n * Deriving it narrowed the type to whatever the table happens to hold today --\n * 'none' | 'optional' -- and TypeScript then called every `=== 'required'`\n * check impossible. The publish typecheck caught it; `rollup` and `vitest` did\n * not. 'required' is a state a circuit can legitimately be in (an EIP-712 path\n * and nothing else), so the union says so and the table is checked against it.\n */\n\n/**\n * How this circuit treats an action, or an error naming the id that is not one.\n *\n * No default: a circuit nobody has classified must not inherit 'none', which\n * would silently drop an action a dapp meant to bind.\n */\nexport function circuitActionBinding(circuit: string): CircuitActionBinding {\n const binding = (CIRCUIT_ACTION_BINDING as Record<string, CircuitActionBinding | undefined>)[circuit];\n if (!binding) {\n throw new Error(\n `Unknown circuit '${circuit}'. Expected: ${ALL_CIRCUIT_IDS.join(', ')}`,\n );\n }\n return binding;\n}\n\nexport const CIRCUIT_NEEDS_WALLET_SIGNATURE = {\n coinbase_attestation: true,\n coinbase_country_attestation: true,\n // The signature is inside the OIDC identity token, checked by the prover.\n oidc_domain_attestation: false,\n // Web2 (OmniOne CX) flows with no wallet binding at all.\n mdl_kr_ownership: false,\n mdl_kr_age: false,\n mdl_kr_region: false,\n // A GIWA-attested wallet signing an EIP-712 action (or its signal hash).\n // This read `false` while the circuit had always verified a wallet signature\n // -- it sat under the Web2 comment above and was never revisited. A request\n // for it would have reached the relay with no signer.\n giwa_attestation: true,\n // The wallet signs the request, and the same key must have signed its\n // Playground faucet transaction.\n dojang_playground: true,\n} satisfies Record<CircuitId, boolean>;\n"],"names":[],"mappings":"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"}
package/dist/circuits.js CHANGED
@@ -46,10 +46,10 @@ const CIRCUIT_IDS = Object.freeze({
46
46
  COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',
47
47
  /** OIDC email-domain attestation. Officially supported. */
48
48
  OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',
49
- /** Arc eligibility: exact EIP-712 action authorization. Experimental on Arc Testnet. */
50
- ARC_ELIGIBILITY: 'arc_eligibility',
51
49
  /** GIWA attestation. Planned — not officially supported yet. */
52
50
  GIWA_ATTESTATION: 'giwa_attestation',
51
+ /** Dojang Playground on GIWA Sepolia. Experimental — testnet only. */
52
+ DOJANG_PLAYGROUND: 'dojang_playground',
53
53
  /** Korea Mobile ID ownership. Planned — not officially supported yet. */
54
54
  MDL_KR_OWNERSHIP: 'mdl_kr_ownership',
55
55
  /** Korea Mobile ID age threshold. Planned — not officially supported yet. */
@@ -76,12 +76,14 @@ const CIRCUIT_SUPPORT_STATUS = Object.freeze({
76
76
  coinbase_attestation: 'supported',
77
77
  coinbase_country_attestation: 'supported',
78
78
  oidc_domain_attestation: 'supported',
79
- arc_eligibility: 'experimental',
80
79
  // Experimental, not planned: the circuit is built, its verifier is
81
80
  // deployed on GIWA Sepolia and the demo requests it. What keeps it out of
82
81
  // 'supported' is that it is testnet-only, so the app offers it behind
83
82
  // Developer Mode and reads its files from main rather than a release tag.
84
83
  giwa_attestation: 'experimental',
84
+ // Same footing as GIWA: built, verifier deployed on GIWA Sepolia, a real
85
+ // proof verifies there. Testnet only.
86
+ dojang_playground: 'experimental',
85
87
  mdl_kr_ownership: 'planned',
86
88
  mdl_kr_age: 'planned',
87
89
  mdl_kr_region: 'planned',
@@ -110,8 +112,8 @@ const CIRCUIT_VK_PATHS = Object.freeze({
110
112
  coinbase_attestation: 'coinbase-attestation/target/vk/vk',
111
113
  coinbase_country_attestation: 'coinbase-country-attestation/target/vk/vk',
112
114
  oidc_domain_attestation: 'oidc-domain-attestation/target/vk/vk',
113
- arc_eligibility: 'arc-eligibility/target/vk/vk',
114
115
  giwa_attestation: 'giwa-attestation/target/vk/vk',
116
+ dojang_playground: 'dojang-playground/target/vk/vk',
115
117
  mdl_kr_ownership: 'mdl/kr-ownership/target/vk/vk',
116
118
  mdl_kr_age: 'mdl/kr-age/target/vk/vk',
117
119
  mdl_kr_region: 'mdl/kr-region/target/vk/vk',
@@ -139,12 +141,11 @@ const SUPPORTED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCU
139
141
  /**
140
142
  * Circuits you can prove today, on a testnet, with everything subject to change.
141
143
  *
142
- * `arc_eligibility` is the reason this status exists. Calling it `planned`
143
- * would be wrong — the circuit compiles, the verifier is deployed and a proof
144
- * verifies against it — and calling it `supported` would be worse, because the
145
- * only deployment is Arc Testnet, Circle has not published a mainnet chain id,
146
- * and the public-input layout is still open. Neither existing word was true, so
147
- * the honest move was a third word rather than picking the closer lie.
144
+ * `giwa_attestation` is in this state. Calling it `planned` would be wrong —
145
+ * the circuit compiles, the verifier is deployed and a proof verifies against
146
+ * it — and calling it `supported` would be worse, because the only deployment
147
+ * is a testnet (GIWA Sepolia). Neither existing word was true, so the honest
148
+ * move was a third word rather than picking the closer lie.
148
149
  */
149
150
  const EXPERIMENTAL_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter(id => CIRCUIT_SUPPORT_STATUS[id] === 'experimental'));
150
151
  const PLANNED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned'));
@@ -198,13 +199,11 @@ const CIRCUIT_ACTION_BINDING = {
198
199
  coinbase_country_attestation: 'none',
199
200
  // The signature lives inside the identity token; no wallet, no action.
200
201
  oidc_domain_attestation: 'none',
201
- // Both branch in-circuit: with an action the wallet signs the typed data,
202
- // without one it personal_signs the request's signal hash. Arc read
203
- // 'required' until 2026-09-22 because its circuit had no second path -- a
204
- // request without an action built a vector 64 bytes short and died inside
205
- // the prover. The circuit gained the branch; the table follows it.
206
- arc_eligibility: 'optional',
202
+ // Branches in-circuit: with an action the wallet signs the typed data,
203
+ // without one it personal_signs the request's signal hash.
207
204
  giwa_attestation: 'optional',
205
+ // Same branch as GIWA; see dojang-playground/src/main.nr.
206
+ dojang_playground: 'optional',
208
207
  mdl_kr_ownership: 'none',
209
208
  mdl_kr_age: 'none',
210
209
  mdl_kr_region: 'none',
@@ -234,20 +233,20 @@ function circuitActionBinding(circuit) {
234
233
  const CIRCUIT_NEEDS_WALLET_SIGNATURE = {
235
234
  coinbase_attestation: true,
236
235
  coinbase_country_attestation: true,
237
- // Same Coinbase-attested wallet as above; what changes is that it signs an
238
- // EIP-712 action rather than signal_hash.
239
- arc_eligibility: true,
240
236
  // The signature is inside the OIDC identity token, checked by the prover.
241
237
  oidc_domain_attestation: false,
242
238
  // Web2 (OmniOne CX) flows with no wallet binding at all.
243
239
  mdl_kr_ownership: false,
244
240
  mdl_kr_age: false,
245
241
  mdl_kr_region: false,
246
- // A GIWA-attested wallet signing an EIP-712 action, the same shape as Arc.
242
+ // A GIWA-attested wallet signing an EIP-712 action (or its signal hash).
247
243
  // This read `false` while the circuit had always verified a wallet signature
248
244
  // -- it sat under the Web2 comment above and was never revisited. A request
249
245
  // for it would have reached the relay with no signer.
250
246
  giwa_attestation: true,
247
+ // The wallet signs the request, and the same key must have signed its
248
+ // Playground faucet transaction.
249
+ dojang_playground: true,
251
250
  };
252
251
 
253
252
  exports.ALL_CIRCUIT_IDS = ALL_CIRCUIT_IDS;