@zkproofport-ai/sdk 0.2.17 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +120 -154
  2. package/dist/actionApproval.d.ts +51 -0
  3. package/dist/actionApproval.d.ts.map +1 -0
  4. package/dist/actionApproval.js +216 -0
  5. package/dist/actionApproval.js.map +1 -0
  6. package/dist/attestation.d.ts +6 -1
  7. package/dist/attestation.d.ts.map +1 -1
  8. package/dist/attestation.js +41 -4
  9. package/dist/attestation.js.map +1 -1
  10. package/dist/cdp.d.ts +3 -3
  11. package/dist/circuits.d.ts +1 -3
  12. package/dist/circuits.d.ts.map +1 -1
  13. package/dist/circuits.js +0 -4
  14. package/dist/circuits.js.map +1 -1
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +0 -10
  17. package/dist/constants.js.map +1 -1
  18. package/dist/extract.d.ts.map +1 -1
  19. package/dist/extract.js +5 -7
  20. package/dist/extract.js.map +1 -1
  21. package/dist/flow.d.ts +2 -2
  22. package/dist/flow.d.ts.map +1 -1
  23. package/dist/flow.js +42 -30
  24. package/dist/flow.js.map +1 -1
  25. package/dist/index.d.ts +3 -4
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -12
  28. package/dist/index.js.map +1 -1
  29. package/dist/inputs.d.ts +4 -3
  30. package/dist/inputs.d.ts.map +1 -1
  31. package/dist/inputs.js +4 -7
  32. package/dist/inputs.js.map +1 -1
  33. package/dist/payment.d.ts +7 -9
  34. package/dist/payment.d.ts.map +1 -1
  35. package/dist/payment.js +20 -43
  36. package/dist/payment.js.map +1 -1
  37. package/dist/typedAction.js +1 -1
  38. package/dist/typedAction.js.map +1 -1
  39. package/dist/types.d.ts +12 -11
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/types.js +0 -2
  42. package/dist/types.js.map +1 -1
  43. package/dist/usdc.d.ts +6 -10
  44. package/dist/usdc.d.ts.map +1 -1
  45. package/dist/usdc.js +6 -11
  46. package/dist/usdc.js.map +1 -1
  47. package/dist/wallets.d.ts +8 -34
  48. package/dist/wallets.d.ts.map +1 -1
  49. package/dist/wallets.js +10 -87
  50. package/dist/wallets.js.map +1 -1
  51. package/package.json +2 -12
  52. package/dist/arcWallet.d.ts +0 -50
  53. package/dist/arcWallet.d.ts.map +0 -1
  54. package/dist/arcWallet.js +0 -155
  55. package/dist/arcWallet.js.map +0 -1
  56. package/dist/delegation.d.ts +0 -107
  57. package/dist/delegation.d.ts.map +0 -1
  58. package/dist/delegation.js +0 -126
  59. package/dist/delegation.js.map +0 -1
  60. package/dist/nanopayment.d.ts +0 -71
  61. package/dist/nanopayment.d.ts.map +0 -1
  62. package/dist/nanopayment.js +0 -77
  63. package/dist/nanopayment.js.map +0 -1
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # @zkproofport-ai/sdk
2
2
 
3
- Client SDK for ZKProofport zero-knowledge proofs, including experimental exact-action authorization on Arc Testnet.
3
+ Client SDK for ZKProofport zero-knowledge proofs, including experimental exact-action authorization with `giwa_attestation` on GIWA Sepolia.
4
4
 
5
- The shared `hashTypedAction` helper is available from **SDK 0.2.12**. Arc/GIWA optional actions require **SDK 0.2.14 or later**; MCP stepwise input preparation requires **MCP 0.2.15 or later**. Release Please owns the versions. Confirm the resolved npm versions before using this path.
5
+ The shared `hashTypedAction` helper is available from **SDK 0.2.12**. GIWA optional actions require **SDK 0.2.14 or later**; MCP stepwise input preparation requires **MCP 0.2.15 or later**. Release Please owns the versions. Confirm the resolved npm versions before using this path.
6
+
7
+ Human action approval requires SDK **0.3.0 or later** and a compatible AI service with the approval API enabled. For the CLI/MCP flow, use MCP **0.3.0 or later** as well. Earlier releases that support action hashing do not implement this approval lifecycle. Action requests now pause for human approval; handle `ActionApprovalRequiredError` and resume as shown below.
6
8
 
7
9
  ## Overview
8
10
 
@@ -15,7 +17,7 @@ ZKProofport has a TEE-based proving architecture. Enclave encryption and hardwar
15
17
  A service tells you what it charges. Ask for a proof and the answer is either
16
18
  the proof or a `402` listing the chains it takes USDC on — you pick one.
17
19
 
18
- For supported x402 offers, the payer signs an authorization and the payment rail settles it. Arc Gateway nanopayments spend an already funded Gateway balance; its initial deposit is an on-chain transaction requiring gas. A USDC wallet balance alone is insufficient for nanopayments. Wallet, chain and signing-domain compatibility must match the actual offer.
20
+ For supported x402 offers, the payer signs an authorization and the payment rail settles it. Wallet, chain and signing-domain compatibility must match the actual offer.
19
21
 
20
22
  Supported payment-wallet adapters:
21
23
 
@@ -24,9 +26,7 @@ import {
24
26
  generateProof,
25
27
  walletFromPrivateKey, // a raw key
26
28
  walletFromCdp, // a Coinbase CDP server wallet
27
- walletFromCircle, // a Circle developer-controlled wallet
28
- walletFromArcAgent, // an existing Circle Agent Wallet through Circle CLI
29
- walletFor, // walletFor('arc') selects the same adapter from environment
29
+ walletFor, // walletFor('key' | 'cdp') builds one from environment
30
30
  } from '@zkproofport-ai/sdk';
31
31
 
32
32
  const proof = await generateProof(
@@ -62,14 +62,13 @@ never quietly pays on a different one.
62
62
  From the command line:
63
63
 
64
64
  ```bash
65
- zkproofport-prove coinbase_kyc --pay-with circle --pay-on <chain>
65
+ zkproofport-prove coinbase_kyc --pay-with key --pay-on <chain>
66
66
  zkproofport-prove coinbase_kyc --pay-with cdp
67
67
  ```
68
68
 
69
69
  `--pay-with cdp` needs `CDP_API_KEY_ID`, `CDP_API_KEY_SECRET` and
70
- `CDP_WALLET_SECRET`; `--pay-with circle` needs `CIRCLE_API_KEY`,
71
- `CIRCLE_ENTITY_SECRET` and `CIRCLE_WALLET_ID`; `--pay-with key` needs
72
- `PAYMENT_PRIVATE_KEY`. For the existing Circle Agent Wallet, explicitly select `--pay-with arc`; Circle CLI owns the login, and `ARC_AGENT_WALLET` selects its wallet. With exactly one key/CDP/developer-controlled wallet configured you can omit
70
+ `CDP_WALLET_SECRET`; `--pay-with key` needs `PAYMENT_PRIVATE_KEY`. These are
71
+ the only two payment wallets. With exactly one of them configured you can omit
73
72
  the flag. With more than one, naming it is required — a wallet is money, and
74
73
  guessing which account it leaves is not a default worth having.
75
74
 
@@ -77,7 +76,7 @@ guessing which account it leaves is not a default worth having.
77
76
 
78
77
  When the selected endpoint supplies a supported attested `teePublicKey`, the SDK encrypts inputs with X25519 ECDH + AES-256-GCM. The host relays ciphertext to the enclave; Nitro mode rejects plaintext inputs. Validate the returned attestation against the required trust policy.
79
78
 
80
- An endpoint without that key receives readable circuit inputs over HTTPS and provides no hardware-attestation guarantee. The current Arc staging demo uses that mode. HTTPS transport encryption does not make the prover a blind relay. A valid public proof can hide the credential-holder address from the relying dApp without establishing enclave execution. Only claim hardware-attested proving when the actual attestation supports it.
79
+ An endpoint without that key receives readable circuit inputs over HTTPS and provides no hardware-attestation guarantee. The current GCP Cloud Run deployments use that mode. HTTPS transport encryption does not make the prover a blind relay. A valid public proof can hide the credential-holder address from the relying dApp without establishing enclave execution. Only claim hardware-attested proving when the actual attestation supports it.
81
80
 
82
81
  ## Installation
83
82
 
@@ -85,7 +84,7 @@ An endpoint without that key receives readable circuit inputs over HTTPS and pro
85
84
  npm install @zkproofport-ai/sdk@latest ethers
86
85
  ```
87
86
 
88
- Use **SDK 0.2.14 or later** and **MCP 0.2.15 or later** for both Arc/GIWA signature modes, including stepwise preparation. Install from npm and check the resolved version. Release Please manages package versions and the release workflow publishes them; repository source changes alone do not update `@latest`.
87
+ Use **SDK 0.2.14 or later** and **MCP 0.2.15 or later** for both GIWA signature modes, including stepwise preparation. Install from npm and check the resolved version. Release Please manages package versions and the release workflow publishes them; repository source changes alone do not update `@latest`.
89
88
 
90
89
  ## Prerequisites
91
90
 
@@ -93,15 +92,15 @@ Use **SDK 0.2.14 or later** and **MCP 0.2.15 or later** for both Arc/GIWA signat
93
92
 
94
93
  1. **Coinbase account with KYC verification** — Complete identity verification on [Coinbase](https://www.coinbase.com/)
95
94
  2. **Coinbase KYC EAS attestation on Base** — Obtain an attestation via [Coinbase Verifications](https://www.coinbase.com/onchain-verify). This creates an on-chain EAS attestation on Base linked to your wallet address.
96
- 3. **Credential signer** (required) — A local signer for the wallet holding the EAS attestation. The CLI uses `ATTESTATION_KEY`; programmatic callers can supply a compatible `ProofportSigner`. Keep key material outside model context.
95
+ 3. **Credential signer for proofs without an action** — A local signer for the wallet holding the EAS attestation. The CLI uses `ATTESTATION_KEY`; programmatic callers can supply a compatible `ProofportSigner`. A GIWA action instead requires the credential holder to connect a wallet and sign through the approval page. Keep key material outside model context.
97
96
 
98
97
  **For OIDC circuits** (`oidc_domain`): No wallet or attestation needed — just a JWT `id_token` from your OIDC provider.
99
98
 
100
- **For GIWA** (`giwa_attestation`): an attestation from GIWA's attester on **GIWA Sepolia**, and a local signer for that wallet. It is not a Coinbase attestation — one wallet is rarely attested by both, so the key you pass must be the wallet attested on the chain the circuit reads.
99
+ **For GIWA** (`giwa_attestation`): an attestation from GIWA's attester on **GIWA Sepolia**, and control of that wallet. Without an action, supply its local signer; with an action, the person connects that wallet on the approval page. The attestation must belong to the wallet signing the proof request on the chain the circuit reads.
101
100
 
102
- **For the circuits that can bind an action** (`arc_eligibility`, `giwa_attestation`): the attestation above for whichever one you ask for. What differs is what the wallet signs, and the action is **optional** — see below.
101
+ **For the circuit that can bind an action** (`giwa_attestation`): the GIWA attestation above. What differs is what the wallet signs, and the action is **optional** — see below.
103
102
 
104
- **One wallet has a different nullifier per circuit, and for the circuits above it does not depend on what you signed.** `arc_eligibility` and `giwa_attestation` derive it from your address, the circuit's own identifier and the scope, so the same wallet in the same scope gives the same nullifier whether or not you bind an action. The Coinbase circuits derive theirs from the signed challenge instead. Nothing to configure — the SDK picks the rule the circuit expects, and refuses a circuit it has no rule for rather than guessing one. Requires **0.2.14 or later**; earlier versions sent the Coinbase rule for every circuit, which a GIWA proof rejects as a nullifier mismatch.
103
+ **One wallet has a different nullifier per circuit, and for `giwa_attestation` it does not depend on what you signed.** `giwa_attestation` derives it from your address, the circuit's own identifier and the scope, so the same wallet in the same scope gives the same nullifier whether or not you bind an action. The Coinbase circuits derive theirs from the signed challenge instead. Nothing to configure — the SDK picks the rule the circuit expects, and refuses a circuit it has no rule for rather than guessing one. Requires **0.2.14 or later**; earlier versions sent the Coinbase rule for every circuit, which a GIWA proof rejects as a nullifier mismatch.
105
104
 
106
105
  ## Quick Start
107
106
 
@@ -126,16 +125,15 @@ console.log('Valid:', verification.valid);
126
125
 
127
126
  > **Experimental. Testnet only.**
128
127
  >
129
- > Two circuits can bind an action, both on testnets and neither generally available:
128
+ > One circuit can bind an action, on a testnet and not generally available:
130
129
  >
131
130
  > | Circuit | Chain | Verifier |
132
131
  > |---|---|---|
133
- > | `arc_eligibility` | Arc Testnet, 5042002 | `0x2aEB66292f631ceb6225ffA2439B1f2b4b15e44a` |
134
132
  > | `giwa_attestation` | GIWA Sepolia, 91342 | `0x5Da234546874304F8c51BBEed00fC632938211c1` |
135
133
  >
136
- > The Ledger House EligibilityGate on Arc is `0xD0F3eE648386B59B484157332E736388Fcc41F47`. Public-input layouts and deployments may change; do not put either in front of real funds. Generally available: `coinbase_kyc`, `coinbase_country`, `oidc_domain`.
134
+ > Public-input layouts and deployments may change; do not put it in front of real funds. Generally available: `coinbase_kyc`, `coinbase_country`, `oidc_domain`.
137
135
  >
138
- > **The action is optional on both.** Send one and the wallet signs typed data; send none and it signs the request's signal hash exactly as `coinbase_kyc` does. The circuits branch on this, and refuse a request that tries to be both. Until 2026-09-22 `arc_eligibility` required an action — its circuit had no second path — and `giwa_attestation` could not take one at all.
136
+ > **The action is optional.** Send one and the SDK pauses for a human to connect a wallet and sign the exact typed data. Send none and the supplied credential signer signs the request's signal hash as before. Supplying `ATTESTATION_KEY` does not bypass action approval.
139
137
 
140
138
  ### The problem it solves
141
139
 
@@ -154,22 +152,25 @@ wallet and scope no matter what the proof is later used for, so:
154
152
 
155
153
  ### What changes
156
154
 
157
- Pass an `action` and the wallet signs an [EIP-712](https://eips.ethereum.org/EIPS/eip-712)
158
- typed structure instead. You define the structure — this SDK keeps no registry
159
- of action shapes and never invents one.
155
+ Pass an `action` and a human wallet signs an [EIP-712](https://eips.ethereum.org/EIPS/eip-712)
156
+ typed structure through the AI service's approval page. You define the structure;
157
+ the SDK does not invent action fields. No proof challenge or payment starts while
158
+ approval is pending. The agent's payment wallet remains separately configured.
160
159
 
161
160
  ```typescript
162
- const result = await generateProof(
163
- config,
164
- { attestation: attestationSigner },
165
- {
166
- circuit: 'arc_eligibility',
161
+ import {
162
+ ActionApprovalRequiredError, generateProof, getActionApprovalStatus,
163
+ type ActionApproval, type ClientConfig, type PaymentWallet, type ProofParams,
164
+ } from '@zkproofport-ai/sdk';
165
+
166
+ const params: ProofParams = {
167
+ circuit: 'giwa_attestation',
167
168
  scope: 'my-app',
168
169
  action: {
169
170
  domain: {
170
171
  name: 'MyVault',
171
172
  version: '1',
172
- chainId: 5042002,
173
+ chainId: 91342,
173
174
  verifyingContract: vaultAddress, // binds the signature to ONE contract
174
175
  },
175
176
  types: {
@@ -180,19 +181,60 @@ const result = await generateProof(
180
181
  ],
181
182
  },
182
183
  primaryType: 'Deposit',
183
- message: { amount: 100_000n, nonce: 1n, expiry: deadline },
184
+ message: { amount: '100000', nonce: '1', expiry: String(deadline) },
184
185
  },
185
- },
186
- );
186
+ };
187
+
188
+ // For a CLI or backend with an appropriate request lifetime. An MCP tool should
189
+ // return the URL promptly and resume in a later call instead of waiting here.
190
+ async function waitForHumanApproval(config: ClientConfig, approval: ActionApproval) {
191
+ while (Date.now() < Date.parse(approval.expiresAt)) {
192
+ const status = await getActionApprovalStatus(config, approval);
193
+ if (status.status === 'approved') return;
194
+ if (status.status !== 'pending') throw new Error(`Action approval is ${status.status}`);
195
+ await new Promise(resolve => setTimeout(resolve, 2000));
196
+ }
197
+ throw new Error('Action approval expired');
198
+ }
199
+
200
+ async function generateWithHumanApproval(config: ClientConfig, payment: PaymentWallet, params: ProofParams) {
201
+ try {
202
+ return await generateProof(config, { payment }, params);
203
+ } catch (error) {
204
+ if (!(error instanceof ActionApprovalRequiredError)) throw error;
205
+ const approval = error.approval;
206
+ console.error('Review and sign:', approval.approvalUrl);
207
+ await waitForHumanApproval(config, approval);
208
+ // Consume once. Do not retry this call automatically after a failure.
209
+ return generateProof(config, { payment }, { ...params, actionApproval: approval });
210
+ }
211
+ }
212
+
213
+ const result = await generateWithHumanApproval(config, payment, params);
187
214
  ```
188
215
 
189
- Ask for `giwa_attestation` instead and the same call works against GIWA's
190
- attestation, with the domain naming chain `91342`. A wallet refuses typed data
191
- whose domain names a chain it is not on, so the chain id follows the circuit.
216
+ `ActionApprovalRequiredError` is a pause, not a failed proof. Its `.approval`
217
+ handle contains `{approvalId, approvalUrl, requesterToken, expiresAt}`. Keep
218
+ `requesterToken` in private application storage; only show the approval URL to
219
+ the person. Status replies contain no signature. `resolveActionApproval`
220
+ checks the original circuit, scope, full action and recovered signer before
221
+ preparing inputs. Rejected, expired and consumed sessions cannot proceed.
222
+
223
+ The session expires after ten minutes and can be consumed once. If a response
224
+ is lost after consumption, inspect the previous proof/payment outcome before
225
+ requesting fresh approval; never automatically pay again. Session expiry does
226
+ not expire the EIP-712 signature on chain. The relying contract must enforce
227
+ the action's own nonce and deadline. This flow supports EOA credential wallets;
228
+ contract-wallet signature verification is outside the current circuit.
229
+
230
+ `giwa_attestation` uses the credential wallet's GIWA Sepolia attestation. The
231
+ action domain names the relying contract's execution chain;
232
+ it is independent of the chain used for attestation lookup or proof payment.
233
+ The approval page asks the wallet to use the action's declared chain.
192
234
 
193
235
  The wallet renders `amount`, `nonce` and `expiry` by name for the person to
194
236
  read. Two 32-byte hashes reach the circuit — the EIP-712 domain separator and
195
- the hash of the action — and the circuit proves the KYC'd key signed them. It
237
+ the hash of the action — and the circuit proves the attested key signed them. It
196
238
  never learns what the action was, so nothing about vaults or USDC is baked into
197
239
  it: a grant of authority or an agreement in prose works the same way.
198
240
 
@@ -232,65 +274,29 @@ one byte each:
232
274
  | `scope` | 128–159 |
233
275
  | `nullifier` | 160–191 |
234
276
 
235
- `signal_hash` is still present, and nobody signs it: the nullifier derives from
236
- it, exactly as on the other Coinbase circuits. Deriving the nullifier from the
237
- signed action instead would give a different nullifier per action and destroy
238
- the one-per-person property it exists for.
277
+ `signal_hash` is zero when an action is bound. The nullifier derives from the
278
+ wallet, the circuit id and the scope, not from the signed action: deriving it
279
+ from the action would give a different nullifier per action and destroy the
280
+ one-per-person property it exists for.
239
281
 
240
282
  ### Omitting `action`
241
283
 
242
- `arc_eligibility` requires `action`; omitting it is rejected. Other circuits reject `action`; Coinbase uses `personal_sign`, while OIDC uses its JWT flow without wallet signing. The application must verify the exact target, operational wallet, amount, nonce and deadline as well as the proof.
284
+ `giwa_attestation` accepts an omitted action and retains its local `personal_sign` path. Coinbase circuits reject `action` and retain their existing signing path; OIDC rejects `action` and uses its JWT flow without wallet signing. For action proofs, the application must verify the exact target, actor, amount, nonce and deadline as well as the proof.
243
285
 
244
- ## Arc Circle Agent Wallet path — EXPERIMENTAL
286
+ ### Approving the exact payment
245
287
 
246
- Use the existing Circle CLI login and selected Arc Agent Wallet; do not create or switch wallets as part of proof generation. Install the CLI if needed, then let the user complete any missing login locally. Load A’s `ATTESTATION_KEY` only through the trusted local client environment or secret store. Keep A’s key and address out of the dApp service, model context, UI, logs, Git and npm package contents. Keep Circle login secrets local too.
247
-
248
- ```bash
249
- npm install -g @circle-fin/cli
250
- circle wallet list --chain ARC-TESTNET --type agent
251
- # Set ARC_AGENT_WALLET to the user's existing, approved wallet B address.
252
- circle gateway balance --address "$ARC_AGENT_WALLET" --chain ARC-TESTNET --output json
253
- # Only when the user has approved this funding amount:
254
- circle gateway deposit --amount 0.1 --address "$ARC_AGENT_WALLET" --chain ARC-TESTNET --method direct
255
- ```
256
-
257
- For the live Gateway batched offer, SDK `signPayment` uses `@circle-fin/x402-batching` and the Circle CLI wallet adapter. It signs the Gateway authorization against the offer’s Gateway signing domain and spends the existing funded Gateway balance. The payment adapter uses the agent smart-wallet address for direct USDC payments (`arc-testnet`, verified through ERC-1271) and Circle's backing EOA for Gateway authorization (`arc-testnet-nano`). `wallet.address` is the on-chain owner; `wallet.gatewayAddress` identifies the separate Gateway depositor, and `signPayment` selects the correct owner for the chosen offer. The operational smart-wallet address remains Wallet B for the action and stake; do not substitute the backing EOA into the action's `delegate` field. `walletFromArcAgent({address: process.env.ARC_AGENT_WALLET, chain: 'ARC-TESTNET'})` and `walletFor('arc')` provide the same payment path.
258
-
259
- After the user reviews the action and the live x402 offer, save those exact approved objects locally as `approved-action.json` and `approved-payment.json`:
260
-
261
- ```typescript
262
- import { readFile } from 'node:fs/promises';
263
- import {
264
- createConfig, fromPrivateKey, generateProof, verifyProof, walletFor,
265
- type ProofParams, type ApprovedPayment,
266
- } from '@zkproofport-ai/sdk';
267
-
268
- const config = createConfig({baseUrl: 'https://stg-ai.zkproofport.app'});
269
- const action: NonNullable<ProofParams['action']> = JSON.parse(await readFile('approved-action.json', 'utf8'));
270
- const approvedPayment: ApprovedPayment = JSON.parse(await readFile('approved-payment.json', 'utf8'));
271
- const result = await generateProof(config, {
272
- attestation: fromPrivateKey(process.env.ATTESTATION_KEY!), // local credential holder A
273
- payment: await walletFor('arc'), // existing Circle wallet B
274
- }, {
275
- circuit: 'arc_eligibility', scope: 'ledger-house', action,
276
- payOn: 'arc-testnet-nano', maxPayment: '0.001', approvedPayment,
277
- });
278
- const verification = await verifyProof(result);
279
- if (!verification.valid) throw new Error('Arc verifier rejected the proof');
280
- ```
281
-
282
- `approvedPayment` pins the selected live offer's `network`, `scheme`, `amount`, `asset`, `payTo`, and signing domain `extra: {name, version, verifyingContract}`. For direct `exact` EIP-3009 payments (Base, Ethereum and Arc), set the approved `extra.verifyingContract` to the offer's `asset`: the standard offer may omit `extra.verifyingContract` because the token is the signing contract. For Circle Gateway (`GatewayWalletBatched`, version `1`), copy the required `extra.verifyingContract` from the offer; never substitute the token address. Missing or conflicting domain information and unsupported transfer methods such as Permit2 are rejected. `amount` is an integer string in USDC base units (`"1000"` is 0.001 USDC); `maxPayment` is a decimal USDC string (`"0.001"`). A changed fee, recipient, asset, network or signing domain is rejected before payment signing. Approve the actual offer; do not manufacture its recipient or domain from a documentation example.
288
+ Pass `maxPayment` and `approvedPayment` in `ProofParams`. `approvedPayment` pins the selected live offer's `network`, `scheme`, `amount`, `asset`, `payTo`, and signing domain `extra: {name, version, verifyingContract}`. For `exact` EIP-3009 payments (Base and Ethereum), set the approved `extra.verifyingContract` to the offer's `asset`: the standard offer may omit `extra.verifyingContract` because the token is the signing contract. Missing or conflicting domain information, Circle Gateway batched domains and unsupported transfer methods such as Permit2 are rejected. `amount` is an integer string in USDC base units (`"1000"` is 0.001 USDC); `maxPayment` is a decimal USDC string (`"0.001"`). A changed fee, recipient, asset, network or signing domain is rejected before payment signing. Approve the actual offer; do not manufacture its recipient or domain from a documentation example.
283
289
 
284
290
  ### Action object: caller-owned types, keys and values
285
291
 
286
- Both `generateProof`/`generate_proof` and Arc input preparation use this complete `TypedAction` shape:
292
+ Both `generateProof`/`generate_proof` and stepwise input preparation use this complete `TypedAction` shape:
287
293
 
288
294
  | Field | Type | Meaning |
289
295
  |---|---|---|
290
296
  | `domain` | object | EIP-712 signing context; all four fields below are required. |
291
297
  | `domain.name` | non-empty string | Application/domain name agreed with the verifier. |
292
298
  | `domain.version` | non-empty string | Signing-domain version. |
293
- | `domain.chainId` | integer number | Target chain ID, such as `5042002`. |
299
+ | `domain.chainId` | integer number | Target chain ID, such as `91342`. |
294
300
  | `domain.verifyingContract` | string | 20-byte `0x` contract address bound by the signature. |
295
301
  | `types` | object | Map from caller-chosen struct names to arrays of field declarations. Omit `EIP712Domain`; ethers derives it from `domain`. |
296
302
  | `types[structName]` | array of objects | Ordered fields; each declaration has `name` and `type`. Include referenced nested structs. |
@@ -299,51 +305,10 @@ Both `generateProof`/`generate_proof` and Arc input preparation use this complet
299
305
  | `primaryType` | non-empty string | The declared top-level struct; it must equal the single root inferred by the encoder. |
300
306
  | `message` | object | Values matching the declared field types, including nested objects and arrays. |
301
307
 
302
- No SDK action registry restricts struct names, field names or business values. For example, `Instruction` can contain `terms: Terms` and `labels: string[]`, where `Terms` contains `quantity: uint256` and `memo: string`. The circuit binds their hashes; each relying application decides which structures it can execute. Declare every approval-critical field in `types`: extra message keys absent from the schema are not signed. Invalid shape, missing declared fields (including nested structs/arrays), non-boolean `bool` values, malformed field declarations/encoded values, ambiguous roots and a mismatched `primaryType` are rejected before local signing. Field names and type declarations must be non-empty strings.
308
+ No SDK action registry restricts business-specific struct names, field names or values. For example, `Instruction` can contain `terms: Terms` and `labels: string[]`, where `Terms` contains `quantity: uint256` and `memo: string`. The circuit binds their hashes; each relying application decides which structures it can execute. Declare every approval-critical field in `types`; the approval API rejects extra unsigned message keys. Invalid shape, missing declared fields (including nested structs/arrays), non-boolean `bool` values, malformed field declarations/encoded values, ambiguous roots and a mismatched `primaryType` are rejected before human signing. The API also bounds request size, nesting, array length and declaration count.
303
309
 
304
310
  Use decimal **integer strings** for JSON integer message values, especially `uint256` (for example `"10000000"`); JSON cannot represent `bigint`, and large JavaScript numbers can lose precision. Addresses/bytes are `0x` strings, booleans remain JSON booleans, and nested values follow their declared types. `domain.chainId` remains an integer JSON number.
305
311
 
306
- ### Full 10-USDC Ledger House Gate action
307
-
308
- This valid JSON shows the Gate's exact five-field schema. The example delegate is a public placeholder for Wallet B: replace it with the approved operational smart-wallet address, choose a future approved Unix-seconds deadline and a fresh nonce, then review the whole object. Never include the private credential-holder A's address or key in it.
309
-
310
- ```json
311
- {
312
- "domain": {
313
- "name": "Ledger House Staking",
314
- "version": "1",
315
- "chainId": 5042002,
316
- "verifyingContract": "0xD0F3eE648386B59B484157332E736388Fcc41F47"
317
- },
318
- "types": {
319
- "CredentialDelegation": [
320
- { "name": "delegate", "type": "address" },
321
- { "name": "action", "type": "string" },
322
- { "name": "amount", "type": "uint256" },
323
- { "name": "expiresAt", "type": "uint256" },
324
- { "name": "nonce", "type": "string" }
325
- ]
326
- },
327
- "primaryType": "CredentialDelegation",
328
- "message": {
329
- "delegate": "0x0000000000000000000000000000000000000001",
330
- "action": "stake",
331
- "amount": "10000000",
332
- "expiresAt": "2000000000",
333
- "nonce": "replace-with-fresh-user-approved-nonce"
334
- }
335
- }
336
- ```
337
-
338
- `"10000000"` means **10 USDC** (6 decimals). The proof fee is separate: **0.001 USDC**, or `"1000"` base units. `delegate` and `CredentialDelegation` are established wire names for exact-action authorization: A authorizes this operation by B; B does not inherit A's identity or KYC credential. User approval covers the exact action and live proof-payment terms, followed by separate approval of the actual stake transaction.
339
-
340
- The exported SDK `buildDelegationAction` helper is a legacy generic four-field schema (`delegate`, `action`, `expiresAt`, `nonce`). It **does not include `amount` and is not compatible with this staking Gate**. Its existing wire hash remains unchanged. Build the explicit five-field `TypedAction` above for the Gate; arbitrary custom actions remain valid SDK inputs but require a verifier that understands them.
341
-
342
-
343
- Generating and verifying a proof does not submit a stake. After a separate stake approval, the operational wallet calls the dApp gate, which verifies the proof and enforces its policy, exact action, unused nonce and deadline atomically. Check the receipt before reporting a position change.
344
-
345
- The MCP `gateway_balance` and `deposit_to_gateway` helpers currently require `PAYMENT_PRIVATE_KEY`; they do not operate the Circle Agent Wallet. Use the Circle CLI commands above for that wallet. SDK `gatewayBalance` / `ensureGatewayBalance` likewise accept the private-key `NanopaymentWallet` shape. This path documents the supported Arc Testnet offer, not a guarantee for every wallet on every chain.
346
-
347
312
  ## Configuration
348
313
 
349
314
  ```typescript
@@ -441,58 +406,62 @@ if (verification.valid) {
441
406
 
442
407
  ## Step-by-Step API
443
408
 
444
- For Arc, validate/hash **before** local signing. `prepareInputs` needs the camelCase hashes to recover A's public key from the EIP-712 digest; it does not add those hashes to its returned object. Attach the snake_case fields for submission. `signal_hash` is still computed internally for nullifier derivation and is not the Arc signing message.
409
+ For GIWA actions, obtain human approval before preparing inputs. `prepareInputs` needs the camelCase hashes to recover A's public key from the approved EIP-712 digest; it does not add those hashes to its returned object. Attach the snake_case fields for submission. `signal_hash` is still computed internally for nullifier derivation and is not the action signing message. Low-level witness APIs accept signatures but cannot establish who operated the signing wallet.
445
410
 
446
411
  Prepared inputs are **private witness data**: they include A's public key, signature and attestation data. Keep them inside trusted local code and send them only to the chosen prover (encrypted when a supported TEE key is provided). Do not forward them to the dApp, model, UI or logs. The `generateProof` public result is the boundary for sharing a proof; step callbacks and `proofport://config` can contain sensitive signer data and must not be forwarded wholesale.
447
412
 
448
413
  ```typescript
449
414
  import { readFile } from 'node:fs/promises';
450
415
  import {
451
- createConfig, fromPrivateKey, hashTypedAction, prepareInputs,
416
+ createConfig, createActionApproval, resolveActionApproval, hashTypedAction, prepareInputs,
452
417
  requestChallenge, signPayment, submitProof, submitEncryptedProof,
453
- encryptForTee, walletFor, CIRCUIT_IDS,
418
+ encryptForTee, walletFromEnv, CIRCUIT_IDS,
454
419
  type TypedAction, type ApprovedPayment,
455
420
  } from '@zkproofport-ai/sdk';
456
421
 
457
422
  const config = createConfig({ baseUrl: 'https://stg-ai.zkproofport.app' });
458
423
  const action: TypedAction = JSON.parse(await readFile('approved-action.json', 'utf8'));
459
424
  const approvedPayment: ApprovedPayment = JSON.parse(await readFile('approved-payment.json', 'utf8'));
460
- const signer = fromPrivateKey(process.env.ATTESTATION_KEY!); // local A only
461
- const scope = 'ledger-house';
425
+ const scope = 'myapp:membership';
462
426
 
463
- // 1. Validate the exact user-approved action, then sign it locally.
427
+ // 1. Create an immutable request, then let the person connect and sign.
464
428
  const { domainSeparator, actionHash } = hashTypedAction(action);
465
- const userSignature = await signer.signTypedData(action.domain, action.types, action.message);
429
+ const request = { circuit: 'giwa_attestation' as const, scope, action };
430
+ const approval = await createActionApproval(config, request);
431
+ console.error('Review and sign:', approval.approvalUrl);
432
+ await waitForHumanApproval(config, approval); // bounded helper from the example above
433
+ const approved = await resolveActionApproval(config, request, approval);
466
434
 
467
- // 2. Recover from the typed digest and build the private witness locally.
435
+ // 2. The signature has been verified and consumed. Build the private witness.
468
436
  const prepared = await prepareInputs(config, {
469
- circuitId: CIRCUIT_IDS.ARC_ELIGIBILITY,
470
- userAddress: await signer.getAddress(), userSignature, scope,
437
+ circuitId: CIRCUIT_IDS.GIWA_ATTESTATION,
438
+ userAddress: approved.address, userSignature: approved.signature, scope,
471
439
  domainSeparator, actionHash,
472
440
  });
473
441
  const inputs = { ...prepared, domain_separator: domainSeparator, action_hash: actionHash };
474
442
 
475
443
  // 3. Fetch a fresh nonce/offer/TEE key without sending the witness yet.
476
- const challenge = await requestChallenge(config, 'arc_eligibility');
444
+ const challenge = await requestChallenge(config, 'giwa_attestation');
477
445
  const paymentHeaders = challenge.requiresPayment
478
- ? (await signPayment(challenge, await walletFor('arc'), {
479
- network: 'arc-testnet-nano', maxPayment: '0.001', approvedPayment,
446
+ ? (await signPayment(challenge, await walletFromEnv(), {
447
+ network: approvedPayment.network, maxPayment: '0.001', approvedPayment,
480
448
  })).headers
481
449
  : undefined;
482
450
 
483
451
  // 4. Submit with the same nonce and approved payment; preserve TEE encryption.
484
452
  const result = challenge.teePublicKey
485
453
  ? await submitEncryptedProof(config, {
486
- circuit: 'arc_eligibility', nonce: challenge.nonce, paymentHeaders,
454
+ circuit: 'giwa_attestation', nonce: challenge.nonce, paymentHeaders,
487
455
  encryptedPayload: encryptForTee(
488
- JSON.stringify({ circuitId: CIRCUIT_IDS.ARC_ELIGIBILITY, inputs }),
456
+ JSON.stringify({ circuitId: CIRCUIT_IDS.GIWA_ATTESTATION, inputs }),
489
457
  challenge.teePublicKey.publicKey,
490
458
  ),
491
459
  })
492
460
  : await submitProof(config, {
493
- circuit: 'arc_eligibility', inputs, nonce: challenge.nonce, paymentHeaders,
461
+ circuit: 'giwa_attestation', inputs, nonce: challenge.nonce, paymentHeaders,
494
462
  });
495
- // Share only the public result, verify it, then separately approve the stake.
463
+ // Share only the public result and verify it. Any application transaction
464
+ // that relies on the proof needs its own approval.
496
465
  ```
497
466
 
498
467
  `hashTypedAction(action)` returns `{domainSeparator: string, actionHash: string}` as 32-byte `0x` hashes. For Coinbase KYC/country, sign `computeSignalHash(address, scope, circuitId)` with `signMessage`, call `prepareInputs` without action hashes, then use the same challenge/payment/submission sequence. OIDC uses `prepareOidcPayload` and no attestation-wallet signature. The all-in-one `generateProof` already handles these distinctions.
@@ -616,8 +585,8 @@ The proof contains 148 public input fields (32 bytes each):
616
585
  **Circuit Types:**
617
586
 
618
587
  ```typescript
619
- type CircuitName = 'coinbase_kyc' | 'coinbase_country' | 'oidc_domain' | 'arc_eligibility';
620
- type CircuitId = 'coinbase_attestation' | 'coinbase_country_attestation' | 'oidc_domain_attestation' | 'arc_eligibility';
588
+ type CircuitName = 'coinbase_kyc' | 'coinbase_country' | 'oidc_domain' | 'giwa_attestation';
589
+ type CircuitId = 'coinbase_attestation' | 'coinbase_country_attestation' | 'oidc_domain_attestation' | 'giwa_attestation';
621
590
  ```
622
591
 
623
592
  **Configuration:**
@@ -650,7 +619,8 @@ function fromSigner(signer: ethers.Signer): ProofportSigner;
650
619
  ```typescript
651
620
  interface ProofParams {
652
621
  circuit: CircuitName;
653
- action?: { domain: { name: string; version: string; chainId: number; verifyingContract: string }; types: Record<string, Array<{name: string; type: string}>>; primaryType: string; message: Record<string, unknown> }; // optional for arc_eligibility and giwa_attestation
622
+ action?: { domain: { name: string; version: string; chainId: number; verifyingContract: string }; types: Record<string, Array<{name: string; type: string}>>; primaryType: string; message: Record<string, unknown> }; // optional for giwa_attestation
623
+ actionApproval?: ActionApproval; // private handle; resume the original action after human approval
654
624
  payOn?: string;
655
625
  maxPayment?: string; // decimal USDC
656
626
  approvedPayment?: ApprovedPayment;
@@ -760,21 +730,17 @@ npm run test:e2e:published -- --sdk-version 0.2.16 --mcp-version 0.2.16
760
730
  ```
761
731
 
762
732
  It installs those exact npm versions outside the workspace, resolves SDK imports
763
- and the MCP process to that installation, and removes it afterwards. The runner
764
- also installs and verifies Circle CLI 1.1.4, automatically putting it on the test
765
- subprocess PATH without changing the global installation. Optional CDP/x402 and
766
- Circle developer wallet dependencies are installed at tested exact versions,
733
+ and the MCP process to that installation, and removes it afterwards. Optional CDP/x402
734
+ wallet dependencies are installed at tested exact versions,
767
735
  and their imports are verified before paid tests start. A normal
768
736
  workspace test run exercises local packages and is not published-package evidence.
769
737
  The full suite creates paid testnet proofs. It requires the attestation and payer
770
738
  credentials loaded by `tests/setup.ts`; GIWA uses `GIWA_ATTESTATION_KEY`, and OIDC
771
739
  uses `E2E_OIDC_JWT` or an authenticated gcloud account. The payment matrix checks
772
- Base Sepolia, Arc immediate settlement, Ethereum Sepolia, and Arc Gateway nano
773
- separately, so each needs a funded payer on its selected network. Optional
774
- `E2E_PAYER_KEY_<NETWORK>` overrides use uppercase names with underscores, for
775
- example `E2E_PAYER_KEY_ETHEREUM_SEPOLIA`. `E2E_ARC_AGENT_ADDRESS` selects an
776
- existing Circle Agent Wallet for both Arc cases; the supported Circle CLI,
777
- on-chain USDC for immediate settlement, and Gateway USDC for nano are required. The runner never deposits or transfers funds.
740
+ Base Sepolia and Ethereum Sepolia separately, so each needs a funded payer on its
741
+ selected network. Optional `E2E_PAYER_KEY_<NETWORK>` overrides use uppercase names
742
+ with underscores, for example `E2E_PAYER_KEY_ETHEREUM_SEPOLIA`. The runner never
743
+ transfers funds.
778
744
 
779
745
  For a non-paying package/discovery check, append
780
- `-t 'should list all 5 circuits'`. This checks discovery only, not proof generation.
746
+ `-t 'should list all 4 circuits'`. This checks discovery only, not proof generation.
@@ -0,0 +1,51 @@
1
+ import type { CircuitName, ClientConfig, TypedAction } from './types.js';
2
+ /** Requester capability. Keep this handle private; show only approvalUrl to the person. */
3
+ export interface ActionApproval {
4
+ approvalId: string;
5
+ approvalUrl: string;
6
+ requesterToken: string;
7
+ /** Fixed session expiry, as an ISO timestamp. This is not an on-chain signature deadline. */
8
+ expiresAt: string;
9
+ }
10
+ export interface ActionApprovalRequest {
11
+ circuit: CircuitName;
12
+ scope: string;
13
+ action: TypedAction;
14
+ expectedSigner?: string;
15
+ }
16
+ export type ActionApprovalState = 'pending' | 'approved' | 'rejected' | 'consumed' | 'expired';
17
+ export interface ActionApprovalStatus extends ActionApprovalRequest {
18
+ approvalId: string;
19
+ status: ActionApprovalState;
20
+ expiresAt: string;
21
+ address?: string;
22
+ }
23
+ export type ActionApprovalErrorCode = 'ACTION_APPROVAL_INVALID' | 'ACTION_APPROVAL_REJECTED' | 'ACTION_APPROVAL_CONSUMED' | 'ACTION_APPROVAL_EXPIRED' | 'ACTION_APPROVAL_UNAVAILABLE';
24
+ export declare class ActionApprovalError extends Error {
25
+ readonly code: ActionApprovalErrorCode;
26
+ constructor(code: ActionApprovalErrorCode, message: string);
27
+ }
28
+ /** A resumable pause before proof work or payment, not a failed proof. */
29
+ export declare class ActionApprovalRequiredError extends Error {
30
+ readonly code = "ACTION_APPROVAL_REQUIRED";
31
+ readonly approval: ActionApproval;
32
+ constructor(approval: ActionApproval);
33
+ }
34
+ /** Create a pending session; this does not sign, prepare a proof, or pay. */
35
+ export declare function createActionApproval(config: ClientConfig, request: ActionApprovalRequest): Promise<ActionApproval>;
36
+ /** Status never returns a raw signature or a requester credential. */
37
+ export declare function getActionApprovalStatus(config: ClientConfig, approval: ActionApproval): Promise<ActionApprovalStatus>;
38
+ /**
39
+ * Consume once and cryptographically check the returned signature against the
40
+ * caller's frozen action. A lost response must be inspected, never auto-retried.
41
+ */
42
+ export declare function consumeActionApproval(config: ClientConfig, request: ActionApprovalRequest, approval: ActionApproval): Promise<{
43
+ address: string;
44
+ signature: string;
45
+ }>;
46
+ /** Resume an approved action, or yield a handle for human review before any proof work. */
47
+ export declare function resolveActionApproval(config: ClientConfig, request: ActionApprovalRequest, approval?: ActionApproval): Promise<{
48
+ address: string;
49
+ signature: string;
50
+ }>;
51
+ //# sourceMappingURL=actionApproval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionApproval.d.ts","sourceRoot":"","sources":["../src/actionApproval.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzE,2FAA2F;AAC3F,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAC/F,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,uBAAuB,GAC/B,yBAAyB,GACzB,0BAA0B,GAC1B,0BAA0B,GAC1B,yBAAyB,GACzB,6BAA6B,CAAC;AAElC,qBAAa,mBAAoB,SAAQ,KAAK;aAChB,IAAI,EAAE,uBAAuB;gBAA7B,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM;CAI3E;AAED,0EAA0E;AAC1E,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,SAAiB,QAAQ,EAAE,cAAc,CAAC;gBAE9B,QAAQ,EAAE,cAAc;CAMrC;AAiGD,6EAA6E;AAC7E,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC,CAOxH;AAED,sEAAsE;AACtE,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAa3H;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAgB3K;AAED,2FAA2F;AAC3F,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,qBAAqB,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAiB5K"}