@zkproofport-app/sdk 0.3.1 → 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.
- package/README.md +69 -58
- package/dist/circuits.d.ts +42 -10
- package/dist/circuits.esm.js +47 -25
- package/dist/circuits.esm.js.map +1 -1
- package/dist/circuits.js +48 -24
- package/dist/circuits.js.map +1 -1
- package/dist/circuits.mjs +47 -25
- package/dist/circuits.mjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +112 -76
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +113 -75
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -76
- package/dist/index.mjs.map +1 -1
- package/dist/typedAction.d.ts +6 -1
- package/dist/typedAction.esm.js +6 -1
- package/dist/typedAction.esm.js.map +1 -1
- package/dist/typedAction.js +6 -1
- package/dist/typedAction.js.map +1 -1
- package/dist/typedAction.mjs +6 -1
- package/dist/typedAction.mjs.map +1 -1
- package/dist/types.d.ts +20 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -84,28 +84,24 @@ const CIRCUIT_METADATA = {
|
|
|
84
84
|
publicInputsCount: 148,
|
|
85
85
|
publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier', 'provider'],
|
|
86
86
|
},
|
|
87
|
-
arc_eligibility: {
|
|
88
|
-
name: 'Arc Eligibility',
|
|
89
|
-
description: 'Prove eligibility and authorize one EIP-712 action, for an Arc gate',
|
|
90
|
-
// 6 x `pub [u8; 32]` in arc-eligibility/src/main.nr, flattened to bytes.
|
|
91
|
-
// Read off the compiled ABI rather than counted: signal_hash 0-31,
|
|
92
|
-
// domain_separator 32-63, action_hash 64-95, signer_list_merkle_root
|
|
93
|
-
// 96-127, scope 128-159, nullifier 160-191.
|
|
94
|
-
publicInputsCount: 192,
|
|
95
|
-
publicInputNames: ['signal_hash', 'domain_separator', 'action_hash', 'signer_list_merkle_root'],
|
|
96
|
-
},
|
|
97
87
|
// Planned circuit — the identifier is reserved and the circuit exists, but it
|
|
98
88
|
// is not officially supported yet. See CIRCUIT_SUPPORT_STATUS in ./circuits.
|
|
99
|
-
// Forked from coinbase_attestation
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
89
|
+
// Forked from coinbase_attestation, then given the EIP-712 pair and a
|
|
90
|
+
// branch: with an action the wallet signs the typed data and signal_hash is
|
|
91
|
+
// zero; without one it signs signal_hash and the pair is zero. Read off the
|
|
92
|
+
// compiled ABI (6 x [u8; 32] = 192): signal_hash 0-31, domain_separator
|
|
93
|
+
// 32-63, action_hash 64-95, signer_list_merkle_root 96-127, scope 128-159,
|
|
94
|
+
// nullifier 160-191.
|
|
95
|
+
//
|
|
96
|
+
// signal_hash is NOT part of this circuit's nullifier: a nullifier derived
|
|
97
|
+
// from a public input nobody signs and nothing constrains would let one
|
|
98
|
+
// wallet mint as many nullifiers as it likes; here the secret is the address
|
|
99
|
+
// plus a constant compiled into the circuit.
|
|
104
100
|
giwa_attestation: {
|
|
105
101
|
name: 'GIWA Attestation',
|
|
106
|
-
description: 'Prove GIWA identity verification (planned — not officially supported yet)',
|
|
107
|
-
publicInputsCount:
|
|
108
|
-
publicInputNames: ['signal_hash', 'signer_list_merkle_root', 'scope', 'nullifier'],
|
|
102
|
+
description: 'Prove GIWA identity verification, optionally authorizing one EIP-712 action (planned — not officially supported yet)',
|
|
103
|
+
publicInputsCount: 192,
|
|
104
|
+
publicInputNames: ['signal_hash', 'domain_separator', 'action_hash', 'signer_list_merkle_root', 'scope', 'nullifier'],
|
|
109
105
|
},
|
|
110
106
|
// Korea Mobile ID (mDL) circuits. Planned — not officially supported yet.
|
|
111
107
|
// Counts are byte-flattened field totals from the compiled circuit ABIs:
|
|
@@ -172,11 +168,6 @@ const RPC_ENDPOINTS = {
|
|
|
172
168
|
84532: 'https://sepolia.base.org', // Base Sepolia
|
|
173
169
|
8453: 'https://mainnet.base.org', // Base Mainnet
|
|
174
170
|
91342: 'https://sepolia-rpc.giwa.io', // GIWA Sepolia
|
|
175
|
-
// Arc Testnet. The arc_eligibility verifier is deployed here and nowhere
|
|
176
|
-
// else, and this line was missing on 2026-09-12 — the circuit shipped, a
|
|
177
|
-
// phone produced a valid proof, and on-chain verification answered "No RPC
|
|
178
|
-
// endpoint configured for chain 5042002", which reads like the proof's fault.
|
|
179
|
-
5042002: 'https://rpc.testnet.arc.io', // Arc Testnet
|
|
180
171
|
};
|
|
181
172
|
/**
|
|
182
173
|
* Default proof request expiration time in milliseconds.
|
|
@@ -384,8 +375,6 @@ const CIRCUIT_IDS = Object.freeze({
|
|
|
384
375
|
COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',
|
|
385
376
|
/** OIDC email-domain attestation. Officially supported. */
|
|
386
377
|
OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',
|
|
387
|
-
/** Arc eligibility: one EIP-712 action, authorized. Officially supported. */
|
|
388
|
-
ARC_ELIGIBILITY: 'arc_eligibility',
|
|
389
378
|
/** GIWA attestation. Planned — not officially supported yet. */
|
|
390
379
|
GIWA_ATTESTATION: 'giwa_attestation',
|
|
391
380
|
/** Korea Mobile ID ownership. Planned — not officially supported yet. */
|
|
@@ -414,8 +403,11 @@ const CIRCUIT_SUPPORT_STATUS = Object.freeze({
|
|
|
414
403
|
coinbase_attestation: 'supported',
|
|
415
404
|
coinbase_country_attestation: 'supported',
|
|
416
405
|
oidc_domain_attestation: 'supported',
|
|
417
|
-
|
|
418
|
-
|
|
406
|
+
// Experimental, not planned: the circuit is built, its verifier is
|
|
407
|
+
// deployed on GIWA Sepolia and the demo requests it. What keeps it out of
|
|
408
|
+
// 'supported' is that it is testnet-only, so the app offers it behind
|
|
409
|
+
// Developer Mode and reads its files from main rather than a release tag.
|
|
410
|
+
giwa_attestation: 'experimental',
|
|
419
411
|
mdl_kr_ownership: 'planned',
|
|
420
412
|
mdl_kr_age: 'planned',
|
|
421
413
|
mdl_kr_region: 'planned',
|
|
@@ -444,7 +436,6 @@ const CIRCUIT_VK_PATHS = Object.freeze({
|
|
|
444
436
|
coinbase_attestation: 'coinbase-attestation/target/vk/vk',
|
|
445
437
|
coinbase_country_attestation: 'coinbase-country-attestation/target/vk/vk',
|
|
446
438
|
oidc_domain_attestation: 'oidc-domain-attestation/target/vk/vk',
|
|
447
|
-
arc_eligibility: 'arc-eligibility/target/vk/vk',
|
|
448
439
|
giwa_attestation: 'giwa-attestation/target/vk/vk',
|
|
449
440
|
mdl_kr_ownership: 'mdl/kr-ownership/target/vk/vk',
|
|
450
441
|
mdl_kr_age: 'mdl/kr-age/target/vk/vk',
|
|
@@ -473,12 +464,11 @@ const SUPPORTED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCU
|
|
|
473
464
|
/**
|
|
474
465
|
* Circuits you can prove today, on a testnet, with everything subject to change.
|
|
475
466
|
*
|
|
476
|
-
* `
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
* the honest move was a third word rather than picking the closer lie.
|
|
467
|
+
* `giwa_attestation` is in this state. Calling it `planned` would be wrong —
|
|
468
|
+
* the circuit compiles, the verifier is deployed and a proof verifies against
|
|
469
|
+
* it — and calling it `supported` would be worse, because the only deployment
|
|
470
|
+
* is a testnet (GIWA Sepolia). Neither existing word was true, so the honest
|
|
471
|
+
* move was a third word rather than picking the closer lie.
|
|
482
472
|
*/
|
|
483
473
|
const EXPERIMENTAL_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter(id => CIRCUIT_SUPPORT_STATUS[id] === 'experimental'));
|
|
484
474
|
const PLANNED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned'));
|
|
@@ -527,44 +517,72 @@ function getCircuitSupportStatus(circuit) {
|
|
|
527
517
|
}
|
|
528
518
|
return CIRCUIT_SUPPORT_STATUS[circuit];
|
|
529
519
|
}
|
|
530
|
-
|
|
531
|
-
|
|
520
|
+
const CIRCUIT_ACTION_BINDING = {
|
|
521
|
+
coinbase_attestation: 'none',
|
|
522
|
+
coinbase_country_attestation: 'none',
|
|
523
|
+
// The signature lives inside the identity token; no wallet, no action.
|
|
524
|
+
oidc_domain_attestation: 'none',
|
|
525
|
+
// Branches in-circuit: with an action the wallet signs the typed data,
|
|
526
|
+
// without one it personal_signs the request's signal hash.
|
|
527
|
+
giwa_attestation: 'optional',
|
|
528
|
+
mdl_kr_ownership: 'none',
|
|
529
|
+
mdl_kr_age: 'none',
|
|
530
|
+
mdl_kr_region: 'none',
|
|
531
|
+
};
|
|
532
|
+
/*
|
|
533
|
+
* The union is declared above rather than derived from the table's values.
|
|
532
534
|
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
535
|
+
* Deriving it narrowed the type to whatever the table happens to hold today --
|
|
536
|
+
* 'none' | 'optional' -- and TypeScript then called every `=== 'required'`
|
|
537
|
+
* check impossible. The publish typecheck caught it; `rollup` and `vitest` did
|
|
538
|
+
* not. 'required' is a state a circuit can legitimately be in (an EIP-712 path
|
|
539
|
+
* and nothing else), so the union says so and the table is checked against it.
|
|
540
|
+
*/
|
|
541
|
+
/**
|
|
542
|
+
* How this circuit treats an action, or an error naming the id that is not one.
|
|
538
543
|
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
544
|
+
* No default: a circuit nobody has classified must not inherit 'none', which
|
|
545
|
+
* would silently drop an action a dapp meant to bind.
|
|
541
546
|
*/
|
|
547
|
+
function circuitActionBinding(circuit) {
|
|
548
|
+
const binding = CIRCUIT_ACTION_BINDING[circuit];
|
|
549
|
+
if (!binding) {
|
|
550
|
+
throw new Error(`Unknown circuit '${circuit}'. Expected: ${ALL_CIRCUIT_IDS.join(', ')}`);
|
|
551
|
+
}
|
|
552
|
+
return binding;
|
|
553
|
+
}
|
|
542
554
|
const CIRCUIT_NEEDS_WALLET_SIGNATURE = {
|
|
543
555
|
coinbase_attestation: true,
|
|
544
556
|
coinbase_country_attestation: true,
|
|
545
|
-
// Same Coinbase-attested wallet as above; what changes is that it signs an
|
|
546
|
-
// EIP-712 action rather than signal_hash.
|
|
547
|
-
arc_eligibility: true,
|
|
548
557
|
// The signature is inside the OIDC identity token, checked by the prover.
|
|
549
558
|
oidc_domain_attestation: false,
|
|
550
559
|
// Web2 (OmniOne CX) flows with no wallet binding at all.
|
|
551
560
|
mdl_kr_ownership: false,
|
|
552
561
|
mdl_kr_age: false,
|
|
553
562
|
mdl_kr_region: false,
|
|
554
|
-
|
|
563
|
+
// A GIWA-attested wallet signing an EIP-712 action (or its signal hash).
|
|
564
|
+
// This read `false` while the circuit had always verified a wallet signature
|
|
565
|
+
// -- it sat under the Web2 comment above and was never revisited. A request
|
|
566
|
+
// for it would have reached the relay with no signer.
|
|
567
|
+
giwa_attestation: true,
|
|
555
568
|
};
|
|
556
569
|
|
|
557
570
|
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
558
571
|
/**
|
|
559
572
|
* The reason the value is not a usable action, or `null` when it is one.
|
|
560
573
|
*
|
|
574
|
+
* Structural validation only, not application authorization policy. Consumers
|
|
575
|
+
* (e.g. EligibilityGate) must independently enforce the expected chain, verifying
|
|
576
|
+
* contract, actor, action parameters, nonce and deadline as applicable.
|
|
577
|
+
* EIP-712 signs only fields declared in types, not extra message properties.
|
|
578
|
+
*
|
|
561
579
|
* A string rather than a boolean because every caller shows it to somebody —
|
|
562
580
|
* a dapp developer reading a validation error, or a person looking at the
|
|
563
581
|
* mobile app's demo screen.
|
|
564
582
|
*/
|
|
565
583
|
function validateTypedAction(value) {
|
|
566
584
|
if (value === undefined || value === null) {
|
|
567
|
-
return 'action is required
|
|
585
|
+
return 'action is required. The circuit proves that a wallet authorised ONE EIP-712 action; there is nothing to prove without it.';
|
|
568
586
|
}
|
|
569
587
|
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
570
588
|
return 'action must be an object.';
|
|
@@ -1120,8 +1138,7 @@ function validateProofRequest(request) {
|
|
|
1120
1138
|
return { valid: false, error: 'Missing circuit type' };
|
|
1121
1139
|
}
|
|
1122
1140
|
// The list is the SDK's own, not a copy. The copy that used to sit here
|
|
1123
|
-
//
|
|
1124
|
-
// `arc_eligibility` — so a link for either was refused as an invalid type
|
|
1141
|
+
// had missed circuits, so a link for one was refused as an invalid type
|
|
1125
1142
|
// while every other layer accepted it.
|
|
1126
1143
|
if (!isCircuitId(request.circuit)) {
|
|
1127
1144
|
return {
|
|
@@ -1176,14 +1193,24 @@ function validateProofRequest(request) {
|
|
|
1176
1193
|
return { valid: false, error: mdlError };
|
|
1177
1194
|
}
|
|
1178
1195
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1196
|
+
// The action, decided by the circuit's own entry rather than by naming a
|
|
1197
|
+
// circuit here, so every action-capable circuit is checked the same way.
|
|
1198
|
+
{
|
|
1199
|
+
const action = request.inputs?.action;
|
|
1200
|
+
const binding = circuitActionBinding(request.circuit);
|
|
1201
|
+
if (binding === 'none') {
|
|
1202
|
+
if (action !== undefined) {
|
|
1203
|
+
return {
|
|
1204
|
+
valid: false,
|
|
1205
|
+
error: `${request.circuit} has no action inputs, so an action sent with it would not be proved. Refusing rather than dropping it.`,
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1183
1208
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1209
|
+
else if (binding === 'required' || action !== undefined) {
|
|
1210
|
+
const actionError = validateTypedAction(action);
|
|
1211
|
+
if (actionError) {
|
|
1212
|
+
return { valid: false, error: actionError };
|
|
1213
|
+
}
|
|
1187
1214
|
}
|
|
1188
1215
|
}
|
|
1189
1216
|
// Check expiry
|
|
@@ -1459,7 +1486,7 @@ function getVerifierContract(providerOrSigner, verifier) {
|
|
|
1459
1486
|
function getDefaultProvider(chainId) {
|
|
1460
1487
|
const rpcUrl = RPC_ENDPOINTS[chainId];
|
|
1461
1488
|
if (!rpcUrl) {
|
|
1462
|
-
throw new Error(`No RPC endpoint configured for chain ${chainId}`);
|
|
1489
|
+
throw new Error(`No RPC endpoint configured for chain ${chainId}. Known chains: ${Object.keys(RPC_ENDPOINTS).join(', ')}`);
|
|
1463
1490
|
}
|
|
1464
1491
|
return createJsonRpcProvider(rpcUrl);
|
|
1465
1492
|
}
|
|
@@ -1608,7 +1635,7 @@ function getVerifierChainId(circuit, customVerifier) {
|
|
|
1608
1635
|
*
|
|
1609
1636
|
* It replaced an `if / else if / else` chain whose final `else` handed every
|
|
1610
1637
|
* unrecognised circuit — and every call that named no circuit at all —
|
|
1611
|
-
* Coinbase's offsets. For `
|
|
1638
|
+
* Coinbase's offsets. For `giwa_attestation` that is not a near miss: its
|
|
1612
1639
|
* public inputs are signal_hash, domain_separator, action_hash, merkle root,
|
|
1613
1640
|
* scope, nullifier, so the nullifier slot under Coinbase's offsets holds the
|
|
1614
1641
|
* MERKLE ROOT, a value identical for every user. Duplicate detection built on
|
|
@@ -1617,14 +1644,14 @@ function getVerifierChainId(circuit, customVerifier) {
|
|
|
1617
1644
|
const PUBLIC_INPUT_OFFSETS = Object.freeze({
|
|
1618
1645
|
// signal_hash, signer_list_merkle_root, scope, nullifier
|
|
1619
1646
|
coinbase_attestation: { scope: [64, 95], nullifier: [96, 127] },
|
|
1620
|
-
//
|
|
1621
|
-
|
|
1647
|
+
// No longer Coinbase's layout. The GIWA circuit gained the EIP-712 pair, so
|
|
1648
|
+
// it reads: signal_hash, domain_separator, action_hash, merkle root, then
|
|
1649
|
+
// scope and nullifier 64 bytes further along than Coinbase.
|
|
1650
|
+
giwa_attestation: { scope: [128, 159], nullifier: [160, 191] },
|
|
1622
1651
|
// country_list, country_list_length and is_included sit before scope.
|
|
1623
1652
|
coinbase_country_attestation: { scope: [86, 117], nullifier: [118, 149] },
|
|
1624
1653
|
// pubkey_modulus_limbs and the bounded domain sit before scope.
|
|
1625
1654
|
oidc_domain_attestation: { scope: [83, 114], nullifier: [115, 146] },
|
|
1626
|
-
// signal_hash, domain_separator, action_hash, merkle root, then the pair.
|
|
1627
|
-
arc_eligibility: { scope: [128, 159], nullifier: [160, 191] },
|
|
1628
1655
|
// The Korea mDL circuits open with the pair; signal_hash is commented out.
|
|
1629
1656
|
mdl_kr_ownership: { scope: [0, 31], nullifier: [32, 63] },
|
|
1630
1657
|
mdl_kr_age: { scope: [0, 31], nullifier: [32, 63] },
|
|
@@ -3005,9 +3032,9 @@ class ProofportSDK {
|
|
|
3005
3032
|
* ```
|
|
3006
3033
|
*/
|
|
3007
3034
|
// Which circuits need a wallet signature, from the one record in
|
|
3008
|
-
// src/circuits.ts.
|
|
3009
|
-
//
|
|
3010
|
-
//
|
|
3035
|
+
// src/circuits.ts. A typed-out list here once missed a circuit, so a dapp
|
|
3036
|
+
// calling createRelayRequest() for it with no signer got no local error and
|
|
3037
|
+
// a relay 401 two round trips later.
|
|
3011
3038
|
static needsWalletSignature(circuit) {
|
|
3012
3039
|
return CIRCUIT_NEEDS_WALLET_SIGNATURE[circuit] === true;
|
|
3013
3040
|
}
|
|
@@ -3027,15 +3054,24 @@ class ProofportSDK {
|
|
|
3027
3054
|
throw new Error(`${mdlError} (circuit: ${circuit})`);
|
|
3028
3055
|
}
|
|
3029
3056
|
}
|
|
3030
|
-
//
|
|
3031
|
-
//
|
|
3032
|
-
//
|
|
3033
|
-
//
|
|
3034
|
-
//
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3057
|
+
// The action, per the circuit's entry in CIRCUIT_ACTION_BINDING. Checked
|
|
3058
|
+
// here, before a challenge is burned, because the alternative is a refusal
|
|
3059
|
+
// from inside the mobile app -- a screen the dapp developer never sees,
|
|
3060
|
+
// reached after the person has already scanned a QR code.
|
|
3061
|
+
//
|
|
3062
|
+
// The circuit id belongs in the table, not in the check.
|
|
3063
|
+
{
|
|
3064
|
+
const action = inputs.action;
|
|
3065
|
+
const binding = circuitActionBinding(circuit);
|
|
3066
|
+
if (binding === 'none' && action !== undefined) {
|
|
3067
|
+
throw new Error(`${circuit} has no action inputs, so an action sent with it would not be proved. ` +
|
|
3068
|
+
'Remove it, or use a circuit that binds one.');
|
|
3069
|
+
}
|
|
3070
|
+
if (binding === 'required' || (binding === 'optional' && action !== undefined)) {
|
|
3071
|
+
const actionError = validateTypedAction(action);
|
|
3072
|
+
if (actionError) {
|
|
3073
|
+
throw new Error(`${actionError} (circuit: ${circuit})`);
|
|
3074
|
+
}
|
|
3039
3075
|
}
|
|
3040
3076
|
}
|
|
3041
3077
|
// Return target: validated before we burn a challenge, so a typo surfaces
|
|
@@ -3364,6 +3400,7 @@ class ProofportSDK {
|
|
|
3364
3400
|
}
|
|
3365
3401
|
|
|
3366
3402
|
exports.ALL_CIRCUIT_IDS = ALL_CIRCUIT_IDS;
|
|
3403
|
+
exports.CIRCUIT_ACTION_BINDING = CIRCUIT_ACTION_BINDING;
|
|
3367
3404
|
exports.CIRCUIT_IDS = CIRCUIT_IDS;
|
|
3368
3405
|
exports.CIRCUIT_SUPPORT_STATUS = CIRCUIT_SUPPORT_STATUS;
|
|
3369
3406
|
exports.CIRCUIT_VK_PATHS = CIRCUIT_VK_PATHS;
|
|
@@ -3375,6 +3412,7 @@ exports.OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT = OIDC_DOMAIN_ATTESTATION_PU
|
|
|
3375
3412
|
exports.PLANNED_CIRCUIT_IDS = PLANNED_CIRCUIT_IDS;
|
|
3376
3413
|
exports.ProofportSDK = ProofportSDK;
|
|
3377
3414
|
exports.SUPPORTED_CIRCUIT_IDS = SUPPORTED_CIRCUIT_IDS;
|
|
3415
|
+
exports.circuitActionBinding = circuitActionBinding;
|
|
3378
3416
|
exports.default = ProofportSDK;
|
|
3379
3417
|
exports.extractDomainFromPublicInputs = extractDomainFromPublicInputs;
|
|
3380
3418
|
exports.extractNullifierFromPublicInputs = extractNullifierFromPublicInputs;
|