@zkproofport-app/sdk 0.5.0-beta.3 → 0.5.0-beta.5

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 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.
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. [Circuit Identifiers](#circuit-identifiers) has the full list and what each status means.
7
7
 
8
8
  ## How It Works
9
9
 
@@ -33,7 +33,7 @@ TypeScript SDK for requesting zero-knowledge proofs from the [ZKProofport](https
33
33
  ```
34
34
 
35
35
  1. Your app creates a proof request through the SDK
36
- 2. The SDK asks the relay for a one-time challenge and a tracked request ID. For the Coinbase circuits it also signs that challenge with your wallet signer (EIP-191); the OIDC and mDL circuits need no signature
36
+ 2. The SDK asks the relay for a one-time challenge and a tracked request ID. For the Coinbase circuits it also signs that challenge with your wallet signer (EIP-191); the OIDC circuit needs no signature
37
37
  3. Your app shows the returned deep link as a QR code (desktop) or navigates to it (mobile)
38
38
  4. The user opens the ZKProofport app, which generates the ZK proof on-device
39
39
  5. The proof result flows back through the relay to your app over WebSocket (HTTP polling as fallback)
@@ -90,6 +90,7 @@ if (result.status === 'completed') {
90
90
  publicInputs: result.publicInputs,
91
91
  verifierAddress: result.verifierAddress,
92
92
  chainId: result.chainId,
93
+ signerVariant: result.signerVariant,
93
94
  };
94
95
 
95
96
  const verification = await sdk.verifyResponseOnChain(response);
@@ -99,7 +100,7 @@ if (result.status === 'completed') {
99
100
 
100
101
  ## Circuit Identifiers
101
102
 
102
- Circuit IDs are canonical, lowercase and case-sensitive: each one is the `name` field of that circuit's `Nargo.toml`, verbatim. Hyphenated spellings such as `coinbase-kyc` or `mdl-kr-age` are route and directory names from elsewhere — they are not circuit IDs, and passing one does not fail with a clear error. It produces a nullifier mismatch or a verifier lookup that finds nothing.
103
+ Circuit IDs are canonical, lowercase and case-sensitive: each one is the `name` field of that circuit's `Nargo.toml`, verbatim. Hyphenated spellings such as `coinbase-kyc` or `oidc-domain` are route and directory names from elsewhere — they are not circuit IDs, and passing one does not fail with a clear error. It produces a nullifier mismatch or a verifier lookup that finds nothing.
103
104
 
104
105
  This SDK is the single source of truth for that list. Import the constants rather than typing the strings, and a rename becomes a compile error instead of a runtime surprise:
105
106
 
@@ -126,28 +127,23 @@ import { CIRCUIT_IDS, CIRCUIT_SUPPORT_STATUS } from '@zkproofport-app/sdk/circui
126
127
  | `CIRCUIT_IDS.OIDC_DOMAIN_ATTESTATION` | `oidc_domain_attestation` | **Supported** |
127
128
  | `CIRCUIT_IDS.GIWA_ATTESTATION` | `giwa_attestation` | _Experimental_ |
128
129
  | `CIRCUIT_IDS.DOJANG_PLAYGROUND` | `dojang_playground` | _Experimental_ |
129
- | `CIRCUIT_IDS.MDL_KR_OWNERSHIP` | `mdl_kr_ownership` | Planned |
130
- | `CIRCUIT_IDS.MDL_KR_AGE` | `mdl_kr_age` | Planned |
131
- | `CIRCUIT_IDS.MDL_KR_REGION` | `mdl_kr_region` | Planned |
132
130
 
133
131
  **Supported** means generally available — build a product on it.
134
132
 
135
133
  **Experimental** means you can prove it today and it verifies, but only against a testnet deployment. Inputs, public-input layout and the verifier address can change without a major version bump, and there is no mainnet to point at. Try it; do not ship on it.
136
134
 
137
- **Planned** means the identifier is reserved and the circuit exists, but it is not officially supported yet: availability, inputs and public-input layout can change without a major version bump.
135
+ **Planned** marks an identifier reserved ahead of official support; inputs and public-input layout can change without a major version bump. No circuit has this status today.
138
136
 
139
- Every ID appears in `CircuitType` and in `sdk.getSupportedCircuits()`, planned ones included, so being assignable is not a support guarantee. Gate on the status, not on the type:
137
+ Every ID appears in `CircuitType` and in `sdk.getSupportedCircuits()`, experimental ones included, so being assignable is not a support guarantee. Gate on the status, not on the type:
140
138
 
141
139
  ```typescript
142
140
  import { CIRCUIT_SUPPORT_STATUS, isSupportedCircuitId } from '@zkproofport-app/sdk';
143
141
 
144
142
  CIRCUIT_SUPPORT_STATUS.coinbase_attestation; // 'supported'
145
143
  CIRCUIT_SUPPORT_STATUS.giwa_attestation; // 'experimental'
146
- CIRCUIT_SUPPORT_STATUS.mdl_kr_age; // 'planned'
147
144
 
148
145
  isSupportedCircuitId('oidc_domain_attestation'); // true
149
146
  isSupportedCircuitId('giwa_attestation'); // false — experimental is not supported
150
- isSupportedCircuitId('mdl_kr_age'); // false
151
147
  isSupportedCircuitId('coinbase-kyc'); // false — not an ID at all
152
148
  ```
153
149
 
@@ -163,14 +159,14 @@ isSupportedCircuitId('coinbase-kyc'); // false — not an ID at all
163
159
  | `isCircuitId(value)` | `value is CircuitId` | Narrows an unknown value; `false` for hyphenated names |
164
160
  | `isSupportedCircuitId(value)` | `value is CircuitId` | The same, and additionally requires `supported` |
165
161
  | `getCircuitSupportStatus(id)` | `CircuitSupportStatus` | Status of one ID; throws on an unknown one |
166
- | `CircuitId` | type | Union of the eight IDs. `CircuitType` is an alias of it |
167
- | `CircuitSupportStatus` | type | `'supported' \| 'planned'` |
162
+ | `CircuitId` | type | Union of the five IDs. `CircuitType` is an alias of it |
163
+ | `CircuitSupportStatus` | type | `'supported' \| 'experimental' \| 'planned'` |
168
164
 
169
165
  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.
170
166
 
171
167
  ## Supported Circuits
172
168
 
173
- The circuits below are officially supported. Pass their IDs exactly as written, or use the `CIRCUIT_IDS` constants. `sdk.getSupportedCircuits()` returns every known ID at runtime, planned ones included — see [Circuit Identifiers](#circuit-identifiers).
169
+ The circuits below are officially supported. Pass their IDs exactly as written, or use the `CIRCUIT_IDS` constants. `sdk.getSupportedCircuits()` returns every known ID at runtime, experimental ones included — see [Circuit Identifiers](#circuit-identifiers).
174
170
 
175
171
  ### `coinbase_attestation`
176
172
 
@@ -181,6 +177,7 @@ Proves that a user has completed Coinbase KYC identity verification without reve
181
177
  | `scope` | `string` | Yes | Application-specific identifier (e.g., your domain). Ensures proof uniqueness per app. |
182
178
  | `userAddress` | `string` | No | Address to prove the attestation for. Omit it and the mobile app asks the user to connect a wallet. |
183
179
  | `rawTransaction` | `string` | No | Pre-fetched attestation transaction data. Omit it and the app fetches the attestation itself. |
180
+ | `action` | `TypedAction` | No | One EIP-712 action for the wallet to sign and the proof to carry. Without one the wallet signs the request's signal hash. See [Which circuits take an action](#which-circuits-take-an-action). |
184
181
 
185
182
  ```typescript
186
183
  const relay = await sdk.createRelayRequest('coinbase_attestation', {
@@ -188,6 +185,9 @@ const relay = await sdk.createRelayRequest('coinbase_attestation', {
188
185
  });
189
186
  ```
190
187
 
188
+ Its nullifier is `keccak(keccak(wallet ++ keccak("coinbase_attestation")) ++ scope)`:
189
+ one wallet in one scope has exactly one nullifier.
190
+
191
191
  ### `coinbase_country_attestation`
192
192
 
193
193
  Proves a user's country based on Coinbase verification, supporting inclusion and exclusion checks, without revealing the actual country. Requires a wallet signer.
@@ -267,7 +267,7 @@ const { deepLink } = await sdk.createRelayRequest(
267
267
  **An action is optional here.** Send one
268
268
  and the wallet signs EIP-712 typed data, so the person reads named fields and
269
269
  the proof carries what they approved. Send none and the wallet signs the
270
- request's signal hash, exactly as Coinbase KYC does. The circuit itself
270
+ request's signal hash. Coinbase KYC branches the same way. The circuit itself
271
271
  branches, and it refuses a request that tries to be both: with an action the
272
272
  signal hash must be empty, without one the EIP-712 pair must be.
273
273
 
@@ -288,8 +288,8 @@ await sdk.createRelayRequest(
288
288
  ```
289
289
 
290
290
  Its public inputs are `signal_hash`, `domain_separator`, `action_hash`,
291
- `signer_list_merkle_root`, `scope`, `nullifier` — 64 bytes longer than the Coinbase layout it used to share. Anything holding the
292
- old offsets reads the Merkle root where the nullifier is.
291
+ `signer_list_merkle_root`, `scope`, `nullifier`: Coinbase KYC's order without
292
+ its final `attested_at`.
293
293
 
294
294
  One more difference worth knowing if you count people: its nullifier is
295
295
  `keccak(keccak(wallet ++ keccak("giwa_attestation")) ++ scope)`. The wallet and
@@ -333,76 +333,21 @@ circuit names are special:
333
333
 
334
334
  | What the circuit does with an action | Circuits |
335
335
  |---|---|
336
- | Signs it when present, signs the signal hash when not | `giwa_attestation`, `dojang_playground` |
337
- | Has no action inputs — sending one is refused, not ignored | Coinbase KYC, Coinbase country, OIDC domain, the three mDL circuits |
336
+ | Signs it when present, signs the signal hash when not | `coinbase_attestation`, `giwa_attestation`, `dojang_playground` |
337
+ | Has no action inputs — sending one is refused, not ignored | Coinbase country, OIDC domain |
338
338
 
339
339
  ```typescript
340
340
  import { circuitActionBinding } from '@zkproofport-app/sdk';
341
341
 
342
342
  circuitActionBinding('giwa_attestation'); // 'optional'
343
- circuitActionBinding('coinbase_attestation'); // 'none'
343
+ circuitActionBinding('coinbase_attestation'); // 'optional'
344
+ circuitActionBinding('coinbase_country_attestation'); // 'none'
344
345
  ```
345
346
 
346
347
  Sending an action to a circuit that cannot prove one is an error rather than a
347
348
  silent drop: a dropped action would leave you believing a proof authorized
348
349
  something the circuit never saw.
349
350
 
350
- ## Planned Circuits
351
-
352
- ZKProofport is registered with the Ministry of the Interior and Safety as a Korea Mobile ID integration service (모바일 신분증 연계서비스). The three circuits below are in beta: `CIRCUIT_SUPPORT_STATUS` reports each of them as `planned`, and their input shape and public-input layout can change without a major version bump.
353
-
354
- ### `mdl_kr_ownership`
355
-
356
- Proves the user holds a valid Korean mobile driver's license (모바일 신분증). The license data stays on-device; only the attributes selected by `discloseFlags` are revealed. No wallet signer is required.
357
-
358
- | Field | Type | Required | Description |
359
- |-------|------|----------|-------------|
360
- | `scope` | `string` | Yes | Application-specific identifier for proof uniqueness. Non-empty, 256 characters or fewer, no control characters. |
361
- | `discloseFlags` | `number` | No | Attribute disclosure bitmask, integer 0–15 (`0x01` name, `0x02` birth, `0x04` sex, `0x08` phone). Omit or `0` for a fully anonymous ownership proof. |
362
-
363
- ```typescript
364
- // Anonymous "holds a valid Korean mobile ID" proof
365
- const relay = await sdk.createRelayRequest('mdl_kr_ownership', {
366
- scope: 'myapp.com',
367
- });
368
- ```
369
-
370
- ### `mdl_kr_age`
371
-
372
- Proves the user is at least `ageThreshold` years old according to their Korean mobile driver's license, without revealing the birth date. No wallet signer is required.
373
-
374
- | Field | Type | Required | Description |
375
- |-------|------|----------|-------------|
376
- | `scope` | `string` | Yes | Application-specific identifier (same rules as above) |
377
- | `ageThreshold` | `number` | Yes | Minimum age to prove — integer between 1 and 150 (e.g. `19` for Korean adult verification) |
378
-
379
- ```typescript
380
- const relay = await sdk.createRelayRequest('mdl_kr_age', {
381
- scope: 'myapp.com',
382
- ageThreshold: 19,
383
- });
384
- ```
385
-
386
- ### `mdl_kr_region`
387
-
388
- Proves the user's registered address is in the specified si/do region without revealing the full address. No wallet signer is required.
389
-
390
- | Field | Type | Required | Description |
391
- |-------|------|----------|-------------|
392
- | `scope` | `string` | Yes | Application-specific identifier (same rules as above) |
393
- | `targetRegion` | `string` | Yes | Region (si/do) to prove residency in, in Korean (e.g. `'경기도'`, `'서울특별시'`). Non-empty, 256 characters or fewer, no control characters. |
394
-
395
- ```typescript
396
- const relay = await sdk.createRelayRequest('mdl_kr_region', {
397
- scope: 'myapp.com',
398
- targetRegion: '경기도',
399
- });
400
- ```
401
-
402
- > mDL inputs are checked before the request leaves your process: a missing `scope`, an out-of-range `discloseFlags`, a non-integer `ageThreshold` or an empty `targetRegion` throws locally instead of coming back as a relay error.
403
-
404
- > The mDL circuits do not require `setSigner()` — the proof is bound to the license via an on-device nullifier (`nullifier_value` public input), not a wallet address. Use `sdk.extractNullifier(publicInputs, circuit)` for sybil-resistant user identification.
405
-
406
351
  ## Integration Guide
407
352
 
408
353
  ### Step 1: Initialize
@@ -438,7 +383,7 @@ interface WalletSigner {
438
383
 
439
384
  Any ethers v5/v6 `Signer` is compatible.
440
385
 
441
- > **When you can skip this step:** the OIDC Domain and the three mDL circuits never sign a challenge. Calling `createRelayRequest` for those circuits without a signer works. Calling it for a Coinbase circuit without one throws `Signer not set. Call setSigner() first. Wallet signature is required for this circuit.`
386
+ > **When you can skip this step:** the OIDC Domain circuit never signs a challenge. Calling `createRelayRequest` for it without a signer works. Calling it for a Coinbase circuit without one throws `Signer not set. Call setSigner() first. Wallet signature is required for this circuit.`
442
387
 
443
388
  #### About challenge-signature
444
389
 
@@ -625,7 +570,7 @@ if (ProofportSDK.isMobile()) {
625
570
 
626
571
  ```typescript
627
572
  const result = await sdk.waitForProof(relay.requestId, {
628
- timeoutMs: 300000, // 5 minutes (default)
573
+ timeoutMs: 600000, // 10 minutes (default)
629
574
  onStatusChange: (update) => {
630
575
  console.log('Status:', update.status);
631
576
  },
@@ -658,7 +603,7 @@ const once = await sdk.pollResult(relay.requestId);
658
603
  // Poll until terminal state
659
604
  const result = await sdk.waitForResult(relay.requestId, {
660
605
  intervalMs: 2000,
661
- timeoutMs: 300000,
606
+ timeoutMs: 600000,
662
607
  onStatusChange: (result) => console.log(result.status),
663
608
  });
664
609
  ```
@@ -675,7 +620,7 @@ When you are done, `sdk.disconnect()` closes any open socket.
675
620
  status: 'completed',
676
621
  circuit: 'coinbase_attestation',
677
622
  proof: '0x1f8b0a…', // proof bytes, opaque
678
- publicInputs: ['0x00…2f', '0x00…a1', …], // ordered — 128 entries for this circuit
623
+ publicInputs: ['0x00…2f', '0x00…a1', …], // ordered — 193 entries for this circuit
679
624
  verifierAddress: '0x…', // the verifier the app reports (checked, not trusted)
680
625
  chainId: …, // the network the app reports (checked, not trusted)
681
626
  deepLink: 'zkproofport://proof-request?data=…',
@@ -738,7 +683,7 @@ switch (result.status) {
738
683
 
739
684
  `isTerminalRelayStatus(status)` and `RELAY_TERMINAL_STATUSES` are exported for code that drives `pollResult()` in a loop of its own.
740
685
 
741
- The relay answers `'expired'` before the SDK's default `timeoutMs` (300 000 ms) runs out, so with the defaults you get a result to branch on rather than a thrown timeout. If you pass a much shorter `timeoutMs`, the SDK's own timeout can still fire first and the promise rejects.
686
+ The relay answers `'expired'` before the SDK's default `timeoutMs` (600 000 ms) runs out, so with the defaults you get a result to branch on rather than a thrown timeout. If you pass a much shorter `timeoutMs`, the SDK's own timeout can still fire first and the promise rejects.
742
687
 
743
688
  #### What is not on the result
744
689
 
@@ -772,6 +717,7 @@ The relay answers `'expired'` before the SDK's default `timeoutMs` (300 000 ms)
772
717
  | `publicInputs` | `string[]` | When completed | `result.publicInputs` |
773
718
  | `verifierAddress` | `string` | No | `result.verifierAddress`. Optional. When present it must match the SDK's own verifier for the circuit, or verification is refused |
774
719
  | `chainId` | `number` | No | `result.chainId`. Optional. When present it must match the network the SDK verifies the circuit on, or verification is refused |
720
+ | `signerVariant` | `string` | No | `result.signerVariant`. Copy it unchanged whenever the relay sends it: some circuits are proved by more than one package, each with its own verifier, and this field says which one made the proof (`standard`, `android` or `android_factory`). When it is absent the circuit's standard verifier is used, and a proof that has another package's size is refused; an unrecognised value is refused by name |
775
721
  | `numPublicInputs` | `number` | No | Never delivered by the relay, and never read during verification — `publicInputs.length` is used instead. Omit it |
776
722
  | `timestamp` | `number` | No | Not delivered by the relay. Omit it |
777
723
  | `error` | `string` | On failure | `result.error`. Carried for your own logging; verification ignores it |
@@ -788,9 +734,9 @@ For OIDC domain proofs the SDK first checks what the proof says about the sign-i
788
734
 
789
735
  The SDK also checks that the proof was made with a signing key Google or Microsoft actually publishes. That key is part of the proof's public inputs, so a proof made with a self-generated key would otherwise pass the contract. The SDK fetches the provider's published keys, caches them as the provider's own cache headers allow, and returns `{ valid: false, error }` when the key is not among them or the keys cannot be fetched. Every verify method does this; there is nothing to call separately.
790
736
 
791
- For Coinbase KYC, Coinbase country and GIWA proofs the SDK checks that the attestation's signer belongs to the published signer set: the proof carries the root of the signer list it was made against, and a proof made against any other list is refused with `{ valid: false, error }`. This too runs in every verify method.
737
+ For Coinbase KYC, Coinbase country and GIWA proofs the SDK checks that the attestation's signer belongs to the published signer set: the proof carries the root of the signer list it was made against, and a proof made against any other list is refused with `{ valid: false, error }`. A Coinbase KYC proof also names the time its attestation was confirmed unrevoked; it must be within the last 15 minutes (and not more than 5 minutes ahead of your clock), so verify a Coinbase KYC proof when you receive it rather than re-verifying a stored one later. This too runs in every verify method.
792
738
 
793
- The one limit: a proof made with a key the provider has already withdrawn, which this process never saw while it was published, cannot be checked and is refused. Keys seen while published stay acceptable for 24 hours after they disappear.
739
+ The one limit: a proof made with a key the provider has already withdrawn, which this process never saw while it was published, cannot be checked and is refused. Keys seen while published stay acceptable for 1 hour after they were last listed.
794
740
 
795
741
 
796
742
 
@@ -806,6 +752,7 @@ if (result.status === 'completed') {
806
752
  publicInputs: result.publicInputs,
807
753
  verifierAddress: result.verifierAddress,
808
754
  chainId: result.chainId,
755
+ signerVariant: result.signerVariant,
809
756
  };
810
757
 
811
758
  const verification = await sdk.verifyResponseOnChain(response);
@@ -881,7 +828,7 @@ if (sdk.extractScope(publicInputs, circuit) !== expectedScope) {
881
828
  The nullifier is a deterministic hash over the user's credential and the scope:
882
829
 
883
830
  - **Deterministic** — same user, same scope, same nullifier, every time. That is what makes duplicate detection possible.
884
- - **Privacy-preserving** — the wallet address (Coinbase), the email (OIDC) and the licence (mDL) stay hidden. There is no way back from the nullifier to the person.
831
+ - **Privacy-preserving** — the wallet address (Coinbase) and the email (OIDC) stay hidden. There is no way back from the nullifier to the person.
885
832
  - **Scope-bound** — the same user proving to a different scope produces an unrelated nullifier, so your users cannot be correlated across apps.
886
833
 
887
834
  On your side, it is the primary key of a verified user. Store it once verification succeeds, with a unique constraint, and check it before granting anything:
@@ -929,7 +876,7 @@ import {
929
876
  extractDomainFromPublicInputs,
930
877
  } from '@zkproofport-app/sdk';
931
878
 
932
- // Scope and nullifier work for all six circuits — the layout is picked from the
879
+ // Scope and nullifier work for all five circuits — the layout is picked from the
933
880
  // circuit id, so always pass it:
934
881
  const scope = extractScopeFromPublicInputs(publicInputs, 'coinbase_attestation');
935
882
  const nullifier = extractNullifierFromPublicInputs(publicInputs, 'coinbase_attestation');
@@ -938,15 +885,12 @@ const nullifier = extractNullifierFromPublicInputs(publicInputs, 'coinbase_attes
938
885
  const oidcScope = extractScopeFromPublicInputs(publicInputs, 'oidc_domain_attestation');
939
886
  const oidcNullifier = extractNullifierFromPublicInputs(publicInputs, 'oidc_domain_attestation');
940
887
 
941
- // The three mDL circuits share one layout
942
- const mdlNullifier = extractNullifierFromPublicInputs(publicInputs, 'mdl_kr_age');
943
-
944
888
  // Extract domain from OIDC Domain Attestation
945
889
  const domain = extractDomainFromPublicInputs(publicInputs, 'oidc_domain_attestation');
946
890
  // domain: 'example.com', or null if the circuit doesn't match or inputs are insufficient
947
891
  ```
948
892
 
949
- Omitting the circuit argument falls back to the `coinbase_attestation` layout, which is why the examples above always pass it explicitly.
893
+ The circuit argument is required: the layout differs per circuit, and an omitted or unknown circuit id throws an error naming the known ones.
950
894
 
951
895
  ## Complete Example
952
896
 
@@ -1051,9 +995,6 @@ import type {
1051
995
  OidcDomainInputs,
1052
996
  ActionBoundInputs,
1053
997
  TypedAction,
1054
- MdlKrOwnershipInputs,
1055
- MdlKrAgeInputs,
1056
- MdlKrRegionInputs,
1057
998
  CircuitInputs,
1058
999
  ProofRequest,
1059
1000
  ProofResponse,
@@ -1069,20 +1010,17 @@ import type {
1069
1010
 
1070
1011
  | Type | Description |
1071
1012
  |------|-------------|
1072
- | `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` |
1013
+ | `CircuitId` | Union of the five canonical circuit IDs — `'coinbase_attestation' \| 'coinbase_country_attestation' \| 'oidc_domain_attestation' \| 'giwa_attestation' \| 'dojang_playground'`. Also exported from `@zkproofport-app/sdk/circuits` |
1073
1014
  | `TypedAction` | The EIP-712 action a `giwa_attestation` proof binds to: `{ domain, types, primaryType, message }` — the same object `eth_signTypedData_v4` takes |
1074
- | `CircuitSupportStatus` | `'supported' \| 'planned'` — see [Circuit Identifiers](#circuit-identifiers) |
1015
+ | `CircuitSupportStatus` | `'supported' \| 'experimental' \| 'planned'` — see [Circuit Identifiers](#circuit-identifiers) |
1075
1016
  | `CircuitType` | Alias of `CircuitId`. Kept as the name used throughout `ProofRequest`, `ProofResponse` and the SDK methods |
1076
1017
  | `ProofRequestStatus` | `'pending' \| 'completed' \| 'error' \| 'cancelled'` — the status on a `ProofResponse` |
1077
1018
  | `CoinbaseKycInputs` | Inputs for `coinbase_attestation`: `{ scope, userAddress?, rawTransaction? }` |
1078
1019
  | `CoinbaseCountryInputs` | Inputs for `coinbase_country_attestation`: `{ scope, countryList, isIncluded, userAddress?, rawTransaction? }` |
1079
1020
  | `OidcDomainInputs` | Inputs for `oidc_domain_attestation`: `{ domain, scope, provider? }` |
1080
- | `MdlKrOwnershipInputs` | Inputs for `mdl_kr_ownership`: `{ scope, discloseFlags? }` |
1081
1021
  | `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` |
1082
1022
  | `GiwaAttestationInputs` | Alias of `ActionBoundInputs`, for `giwa_attestation` |
1083
1023
  | `DojangPlaygroundInputs` | Alias of `ActionBoundInputs`, for `dojang_playground` |
1084
- | `MdlKrAgeInputs` | Inputs for `mdl_kr_age`: `{ scope, ageThreshold }` |
1085
- | `MdlKrRegionInputs` | Inputs for `mdl_kr_region`: `{ scope, targetRegion }` |
1086
1024
  | `CircuitInputs` | Union of every input type above (plus an empty-input form for circuits that need nothing from the dApp) |
1087
1025
  | `ProofRequest` | Request object: `requestId`, `circuit`, `inputs`, `createdAt`, plus optional `message`, `dappName`, `dappIcon`, `returnScheme` (a bare scheme such as `mydapp://`), `expiresAt` |
1088
1026
  | `ProofResponse` | What `verifyResponseOnChain()` takes: `requestId`, `circuit`, `status`, and — when completed — `proof`, `publicInputs`, `verifierAddress`, `chainId`; `error` when it failed. `numPublicInputs` and `timestamp` also exist but the relay never fills them, so omit them. Field by field in [Step 6](#step-6-read-the-result) |
@@ -1116,14 +1054,13 @@ import {
1116
1054
  COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT,
1117
1055
  COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT,
1118
1056
  OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT,
1119
- MDL_KR_PUBLIC_INPUT_LAYOUT,
1120
1057
  } from '@zkproofport-app/sdk';
1121
1058
  ```
1122
1059
 
1123
1060
  Two rules hold for every table below:
1124
1061
 
1125
1062
  - **Every number is an index into `publicInputs`, and `_END` is inclusive.** Slice with `slice(START, END + 1)`.
1126
- - **One entry is one field element, not one 32-byte value.** A `bytes32` such as a scope or a nullifier is spread over 32 consecutive entries, each holding a single byte in its low byte: `publicInputs[64] === '0x00…2f'` is the byte `0x2f`. Rebuild it by concatenating those 32 low bytes. The one exception is the OIDC RSA modulus, whose 18 entries each hold a 128-bit limb.
1063
+ - **One entry is one field element, not one 32-byte value.** A `bytes32` such as a scope or a nullifier is spread over 32 consecutive entries, each holding a single byte in its low byte: `publicInputs[128] === '0x00…2f'` is the byte `0x2f`. Rebuild it by concatenating those 32 low bytes. The one exception is the OIDC RSA modulus, whose 18 entries each hold a 128-bit limb.
1127
1064
 
1128
1065
  ```typescript
1129
1066
  const L = COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT;
@@ -1135,14 +1072,17 @@ const toBytes32 = (fields: string[]): string =>
1135
1072
  const signalHash = toBytes32(publicInputs.slice(L.SIGNAL_HASH_START, L.SIGNAL_HASH_END + 1));
1136
1073
  ```
1137
1074
 
1138
- ### `coinbase_attestation` — 128 entries
1075
+ ### `coinbase_attestation` — 193 entries
1139
1076
 
1140
1077
  | Index | Constants | Circuit field | What it is |
1141
1078
  |-------|-----------|---------------|------------|
1142
- | 0–31 | `SIGNAL_HASH_START` / `_END` | `signal_hash` | `bytes32` anti-replay challenge for the request |
1143
- | 32–63 | `MERKLE_ROOT_START` / `_END` | `signer_list_merkle_root` | `bytes32` root of the Coinbase attestation-signer list the proof was checked against |
1144
- | 64–95 | `SCOPE_START` / `_END` | `scope` | `bytes32` — `keccak256` of your scope string. Compare it (Step 8) |
1145
- | 96–127 | `NULLIFIER_START` / `_END` | `nullifier` | `bytes32`, unique per user and scope |
1079
+ | 0–31 | `SIGNAL_HASH_START` / `_END` | `signal_hash` | `bytes32` anti-replay challenge for the request; zero when the wallet signed an EIP-712 action |
1080
+ | 32–63 | `DOMAIN_SEPARATOR_START` / `_END` | `domain_separator` | EIP-712 domain separator of the signed action; zero without one |
1081
+ | 64–95 | `ACTION_HASH_START` / `_END` | `action_hash` | EIP-712 struct hash of the signed action; zero without one |
1082
+ | 96–127 | `MERKLE_ROOT_START` / `_END` | `signer_list_merkle_root` | `bytes32` root of the Coinbase attestation-signer list the proof was checked against |
1083
+ | 128–159 | `SCOPE_START` / `_END` | `scope` | `bytes32` — `keccak256` of your scope string. Compare it (Step 8) |
1084
+ | 160–191 | `NULLIFIER_START` / `_END` | `nullifier` | `bytes32`, unique per wallet and scope |
1085
+ | 192 | `ATTESTED_AT` | `attested_at` | Unix seconds at which the attestation was confirmed unrevoked; checked against your clock by every verify method |
1146
1086
 
1147
1087
  ### `coinbase_country_attestation` — 150 entries
1148
1088
 
@@ -1179,34 +1119,6 @@ Proofs from the earlier 148-entry version of this circuit bind no audience, expi
1179
1119
 
1180
1120
  Index 147 tells you which issuer signed the token, not whether the account belongs to an organisation. Workspace and Microsoft 365 membership is checked on-device only when you set `provider` in the request; if you did not set it, do not read a membership guarantee out of this field.
1181
1121
 
1182
- ### Korea Mobile ID (mDL) — one layout, three circuits
1183
-
1184
- All three mDL circuits share the same first 64 entries and differ after that. Entries are single bytes, except `age_threshold` and `current_year`, which each hold their whole value in one entry.
1185
-
1186
- | Index | Constants | Circuit field | Applies to |
1187
- |-------|-----------|---------------|------------|
1188
- | 0–31 | `SCOPE_START` / `_END` | `scope` | all three |
1189
- | 32–63 | `NULLIFIER_START` / `_END` | `nullifier_value` | all three |
1190
- | 64 | `OWNERSHIP_DISCLOSE_FLAGS` | `disclose_flags` | `mdl_kr_ownership` (97 entries) — the bitmask that was actually proved (`0x01` name, `0x02` birth, `0x04` sex, `0x08` phone) |
1191
- | 65–96 | `OWNERSHIP_OWNER_COMMIT_START` / `_END` | `owner_commit` | `mdl_kr_ownership` — `bytes32` commitment to the disclosed attributes. All zero when `disclose_flags` is `0`, which is the anonymous case |
1192
- | 64 | `AGE_THRESHOLD` | `age_threshold` | `mdl_kr_age` (66 entries) — the age the proof actually clears. Read it back; it is a public input, so confirm it is the threshold you asked for |
1193
- | 65 | `AGE_CURRENT_YEAR` | `current_year` | `mdl_kr_age` — the year the comparison was made in |
1194
- | 64–95 | `REGION_CODE_START` / `_END` | `region_code` | `mdl_kr_region` (96 entries) — `bytes32`, not a readable region name. See below |
1195
-
1196
- `region_code` is `keccak256` over the region name, UTF-8 and zero-padded to 64 bytes. The circuit proves that the region on the licence hashes to this value, but the value itself is a public input — so recompute it from the region you asked for and compare, exactly as you do with the scope:
1197
-
1198
- ```typescript
1199
- import { keccak256 } from 'ethers';
1200
-
1201
- const regionCodeFor = (region: string): string => {
1202
- const padded = new Uint8Array(64);
1203
- padded.set(new TextEncoder().encode(region));
1204
- return keccak256(padded);
1205
- };
1206
-
1207
- // regionCodeFor('경기도') — compare against indices 64–95, rebuilt with toBytes32()
1208
- ```
1209
-
1210
1122
  ## Error Handling
1211
1123
 
1212
1124
  Async SDK methods throw standard `Error` objects. The messages are stable enough to log, not to switch on:
@@ -1220,9 +1132,9 @@ try {
1220
1132
  }
1221
1133
 
1222
1134
  try {
1223
- await sdk.createRelayRequest('mdl_kr_age', { scope: 'app.com', ageThreshold: 0 });
1135
+ await sdk.createRelayRequest('coinbase-kyc', { scope: 'app.com' });
1224
1136
  } catch (err) {
1225
- // "ageThreshold is required and must be an integer between 1 and 150 (circuit: mdl_kr_age)"
1137
+ // "Unknown circuit 'coinbase-kyc'. Expected: coinbase_attestation, ..."
1226
1138
  }
1227
1139
 
1228
1140
  try {
@@ -189,22 +189,6 @@ export declare class ProofportSDK {
189
189
  * falling through to another circuit's builder.
190
190
  */
191
191
  private static readonly REQUEST_BUILDERS;
192
- /**
193
- * @internal
194
- * Creates a Korea Mobile ID (mDL) proof request for any of the three
195
- * mDL predicate circuits (ownership / age / region).
196
- *
197
- * The mobile app collects the license data on-device; the dapp only
198
- * supplies the predicate parameters validated here. No wallet signature
199
- * is required for mDL circuits.
200
- *
201
- * @param circuit - 'mdl_kr_ownership' | 'mdl_kr_age' | 'mdl_kr_region'
202
- * @param inputs - Predicate inputs (see MdlKr*Inputs types)
203
- * @param options - Request configuration options
204
- *
205
- * @throws Error if the predicate inputs fail validation
206
- */
207
- private createMdlKrRequest;
208
192
  /**
209
193
  * @internal
210
194
  * Generates a deep link URL for a proof request.
@@ -509,6 +493,7 @@ export declare class ProofportSDK {
509
493
  */
510
494
  verifyOnChain(circuit: CircuitType, proof: string, publicInputs: string[], providerOrSigner?: any, options?: {
511
495
  request?: OidcRequestContext;
496
+ signerVariant?: string;
512
497
  }): Promise<{
513
498
  valid: boolean;
514
499
  error?: string;
@@ -924,7 +909,7 @@ export declare class ProofportSDK {
924
909
  * @param requestId - The relay-issued request ID
925
910
  * @param options - Polling options
926
911
  * @param options.intervalMs - Polling interval in milliseconds (default: 2000)
927
- * @param options.timeoutMs - Maximum polling time in milliseconds (default: 300000 = 5 min)
912
+ * @param options.timeoutMs - Maximum polling time in milliseconds (default: 600000 = 10 min, `DEFAULT_RELAY_WAIT_MS`)
928
913
  * @param options.onStatusChange - Callback when status changes
929
914
  * @returns Promise resolving to final RelayProofResult
930
915
  * @throws Error if timeout or relay error
@@ -989,7 +974,7 @@ export declare class ProofportSDK {
989
974
  *
990
975
  * @param requestId - The relay-issued request ID
991
976
  * @param options - Configuration options
992
- * @param options.timeoutMs - Maximum wait time in ms (default: 300000 = 5 min)
977
+ * @param options.timeoutMs - Maximum wait time in ms (default: 600000 = 10 min, `DEFAULT_RELAY_WAIT_MS`)
993
978
  * @param options.onStatusChange - Callback for status updates
994
979
  * @returns Promise resolving to final RelayProofResult
995
980
  */
@@ -20,7 +20,7 @@ export declare const RELAY_URLS: Record<SDKEnvironment, string>;
20
20
  * Mirrors the mobile app's `CIRCUIT_NETWORKS` (proofport-app
21
21
  * src/config/contracts.ts), because a proof is checked where the app said its
22
22
  * verifier is: Base for the Coinbase and OIDC circuits, GIWA Sepolia for GIWA
23
- * and Dojang (testnet-only), Base Sepolia for Korea Mobile ID (testnet-only).
23
+ * and Dojang (testnet-only).
24
24
  * The ADDRESS on that chain always comes from core's pinned table, never from
25
25
  * a proof response.
26
26
  */
@@ -31,9 +31,6 @@ export declare const VERIFICATION_CHAINS: {
31
31
  readonly oidc_domain_attestation: 8453;
32
32
  readonly giwa_attestation: 91342;
33
33
  readonly dojang_playground: 91342;
34
- readonly mdl_kr_ownership: 84532;
35
- readonly mdl_kr_age: 84532;
36
- readonly mdl_kr_region: 84532;
37
34
  };
38
35
  readonly staging: {
39
36
  readonly coinbase_attestation: 84532;
@@ -41,9 +38,6 @@ export declare const VERIFICATION_CHAINS: {
41
38
  readonly oidc_domain_attestation: 84532;
42
39
  readonly giwa_attestation: 91342;
43
40
  readonly dojang_playground: 91342;
44
- readonly mdl_kr_ownership: 84532;
45
- readonly mdl_kr_age: 84532;
46
- readonly mdl_kr_region: 84532;
47
41
  };
48
42
  readonly local: {
49
43
  readonly coinbase_attestation: 84532;
@@ -51,9 +45,6 @@ export declare const VERIFICATION_CHAINS: {
51
45
  readonly oidc_domain_attestation: 84532;
52
46
  readonly giwa_attestation: 91342;
53
47
  readonly dojang_playground: 91342;
54
- readonly mdl_kr_ownership: 84532;
55
- readonly mdl_kr_age: 84532;
56
- readonly mdl_kr_region: 84532;
57
48
  };
58
49
  };
59
50
  /**
@@ -92,7 +83,7 @@ export declare const DEEP_LINK_HOSTS: {
92
83
  * ```typescript
93
84
  * const metadata = CIRCUIT_METADATA['coinbase_attestation'];
94
85
  * console.log(metadata.name); // "Coinbase KYC"
95
- * console.log(metadata.publicInputsCount); // 128
86
+ * console.log(metadata.publicInputsCount); // 193
96
87
  * ```
97
88
  */
98
89
  export declare const CIRCUIT_METADATA: Record<CircuitType, {
@@ -132,4 +123,4 @@ export declare const DEFAULT_REQUEST_EXPIRY_MS: number;
132
123
  * ```
133
124
  */
134
125
  export declare const MAX_QR_DATA_SIZE = 2953;
135
- export { VERIFIER_ABI, RPC_ENDPOINTS, COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, MDL_KR_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, } from '@zkproofport-app/core';
126
+ export { VERIFIER_ABI, RPC_ENDPOINTS, COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, } from '@zkproofport-app/core';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Deep Link utilities for ZKProofport SDK
3
3
  */
4
- import type { ProofRequest, ProofResponse, CircuitInputs, DeepLinkComponents } from './types';
4
+ import type { ProofRequest, ProofResponse, DeepLinkComponents } from './types';
5
5
  /**
6
6
  * Generates a unique request ID for proof requests.
7
7
  *
@@ -170,23 +170,6 @@ export declare function parseDeepLink(url: string): DeepLinkComponents | null;
170
170
  * ```
171
171
  */
172
172
  export declare function isProofportDeepLink(url: string, scheme?: string): boolean;
173
- /**
174
- * Validates dapp-supplied inputs for the Korea Mobile ID (mDL) circuits.
175
- * Shared by deep-link request validation and relay request creation so the
176
- * same guards apply on every request path.
177
- *
178
- * Rules:
179
- * - `scope` — required non-empty string, ≤256 chars, no control characters
180
- * - `mdl_kr_ownership.discloseFlags` — optional integer 0–15 (0x0F bitmask)
181
- * - `mdl_kr_age.ageThreshold` — required integer 1–150
182
- * - `mdl_kr_region.targetRegion` — required non-empty string (Korean si/do),
183
- * ≤256 chars, no control characters
184
- *
185
- * @param circuit - One of the three mDL circuit identifiers
186
- * @param inputs - Dapp-supplied circuit inputs
187
- * @returns Error message string, or null when the inputs are valid
188
- */
189
- export declare function validateMdlInputs(circuit: 'mdl_kr_ownership' | 'mdl_kr_age' | 'mdl_kr_region', inputs: CircuitInputs): string | null;
190
173
  /**
191
174
  * Longest accepted `returnScheme` value.
192
175
  * Kept in sync with proofport-relay `src/returnScheme.ts`.
@@ -273,6 +256,22 @@ export declare function validateReturnScheme(value: unknown): string | null;
273
256
  * @internal
274
257
  */
275
258
  export declare function detectReturnScheme(userAgent?: string): string | undefined;
259
+ /**
260
+ * @internal
261
+ * Validates a proof request for completeness and correctness.
262
+ *
263
+ * Performs comprehensive validation including:
264
+ * - Required fields (requestId, circuit, callbackUrl)
265
+ * - Circuit type validity (must be a supported circuit)
266
+ * - Circuit-specific input validation (e.g., userAddress format, countryList structure)
267
+ * - Expiration check (if expiresAt is provided)
268
+ *
269
+ * Note: userAddress is optional for both circuits - the mobile app will prompt
270
+ * for wallet connection if not provided.
271
+ *
272
+ * @param request - Proof request to validate
273
+ * @returns Validation result with `valid` flag and optional `error` message
274
+ */
276
275
  export declare function validateProofRequest(request: ProofRequest): {
277
276
  valid: boolean;
278
277
  error?: string;
package/dist/index.d.ts CHANGED
@@ -27,17 +27,17 @@
27
27
  */
28
28
  export { ProofportSDK, default } from './ProofportSDK';
29
29
  export { extractScopeFromPublicInputs, extractNullifierFromPublicInputs, extractDomainFromPublicInputs, } from './verifier';
30
- export { CIRCUIT_IDS, CIRCUIT_SUPPORT_STATUS, ALL_CIRCUIT_IDS, SUPPORTED_CIRCUIT_IDS, EXPERIMENTAL_CIRCUIT_IDS, PLANNED_CIRCUIT_IDS, isCircuitId, isSupportedCircuitId, getCircuitSupportStatus, CIRCUIT_VK_PATHS, } from './circuits';
30
+ export { CIRCUIT_IDS, CIRCUIT_SUPPORT_STATUS, ALL_CIRCUIT_IDS, SUPPORTED_CIRCUIT_IDS, EXPERIMENTAL_CIRCUIT_IDS, PLANNED_CIRCUIT_IDS, isCircuitId, isSupportedCircuitId, getCircuitSupportStatus, CIRCUIT_VK_PATHS, CIRCUIT_PACKAGES, SIGNER_VARIANTS, isSignerVariant, signerVariantsOf, getCircuitPackage, resolveSignerVariant, evmProofByteLength, } from './circuits';
31
31
  export { verifyProofOffChain } from './offChainVerifier';
32
32
  export type { OffChainVerifyResult, OffChainVerifyOptions, } from './offChainVerifier';
33
- export { PINNED_VERIFIERS, getPinnedVerifier } from './verifier';
33
+ export { PINNED_VERIFIERS, PINNED_VARIANT_VERIFIERS, getPinnedVerifier, getPinnedVariantVerifier } from './verifier';
34
34
  export type { PinnedVerifier } from './verifier';
35
35
  export { checkOidcPolicy, deriveOidcNonceBinding, oidcAudienceHash, OIDC_ALLOWED_AUDIENCES, OIDC_NONCE_DOMAIN, OIDC_NOW_MAX_AGE_SECONDS, OIDC_NOW_MAX_FUTURE_SECONDS, OIDC_V1_PUBLIC_INPUT_COUNT, OIDC_V2_PUBLIC_INPUT_COUNT, } from './verifier';
36
36
  export type { OidcRequestContext, OidcAudience, OidcNonceMode } from './verifier';
37
- export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, MDL_KR_PUBLIC_INPUT_LAYOUT, } from './constants';
38
- export type { CircuitId, CircuitSupportStatus } from './circuits';
37
+ export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, } from './constants';
38
+ export type { CircuitId, CircuitSupportStatus, SignerVariant, CircuitPackage } from './circuits';
39
39
  export { CIRCUIT_ACTION_BINDING, circuitActionBinding, } from './circuits';
40
40
  export type { CircuitActionBinding } from './circuits';
41
- export type { CircuitType, ProofRequestStatus, CoinbaseKycInputs, ActionBoundInputs, GiwaAttestationInputs, DojangPlaygroundInputs, TypedAction, CoinbaseCountryInputs, OidcDomainInputs, MdlKrOwnershipInputs, MdlKrAgeInputs, MdlKrRegionInputs, CircuitInputs, ProofRequest, ProofResponse, QRCodeOptions, VerifierContract, ProofportConfig, ChallengeResponse, WalletSigner, RelayProofRequest, RelayProofResult, RelayProofStatus, RelayTerminalStatus, SDKEnvironment, } from './types';
42
- export { RELAY_TERMINAL_STATUSES, isTerminalRelayStatus } from './relayStatus';
41
+ export type { CircuitType, ProofRequestStatus, CoinbaseKycInputs, ActionBoundInputs, GiwaAttestationInputs, DojangPlaygroundInputs, TypedAction, CoinbaseCountryInputs, OidcDomainInputs, CircuitInputs, ProofRequest, ProofResponse, QRCodeOptions, VerifierContract, ProofportConfig, ChallengeResponse, WalletSigner, RelayProofRequest, RelayProofResult, RelayProofStatus, RelayTerminalStatus, SDKEnvironment, } from './types';
42
+ export { RELAY_TERMINAL_STATUSES, isTerminalRelayStatus, DEFAULT_RELAY_WAIT_MS } from './relayStatus';
43
43
  export { validateTypedAction, parseTypedAction } from './typedAction';