@dorafactory/maci-sdk 0.1.3-pre.21 → 0.1.3-pre.23
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/dist/index.d.ts +7 -2
- package/dist/index.js +1485 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1470 -74
- package/dist/index.mjs.map +1 -1
- package/dist/libs/account/crypto.d.ts +1 -0
- package/dist/libs/account/index.d.ts +35 -0
- package/dist/libs/account/keypair.d.ts +21 -0
- package/dist/libs/account/util.d.ts +25 -0
- package/dist/libs/api/client.d.ts +173 -0
- package/dist/libs/api/index.d.ts +3 -0
- package/dist/libs/const.d.ts +1 -0
- package/dist/libs/crypto/adapter.d.ts +6 -0
- package/dist/libs/crypto/curve.d.ts +3 -0
- package/dist/libs/crypto/index.d.ts +3 -0
- package/dist/libs/crypto/keys.d.ts +8 -1
- package/dist/libs/crypto/rerandomize.d.ts +12 -0
- package/dist/libs/crypto/sign.d.ts +1 -1
- package/dist/libs/cryptography/index.d.ts +0 -0
- package/dist/libs/cryptography/keypair.d.ts +30 -0
- package/dist/libs/cryptography/mnemonics.d.ts +27 -0
- package/dist/libs/cryptography/publickey.d.ts +23 -0
- package/dist/libs/cryptography/signature-scheme.d.ts +1 -0
- package/dist/libs/index.d.ts +1 -0
- package/dist/libs/keypairs/eddsa-poseidon/index.d.ts +2 -0
- package/dist/libs/keypairs/eddsa-poseidon/keypair.d.ts +187 -0
- package/dist/libs/keypairs/eddsa-poseidon/publickey.d.ts +48 -0
- package/dist/maci.d.ts +90 -31
- package/dist/types/index.d.ts +23 -0
- package/dist/utils/base64.d.ts +6 -0
- package/dist/utils/bech32.d.ts +2 -0
- package/dist/utils/decode-address.d.ts +10 -0
- package/dist/utils/fetch.d.ts +1 -0
- package/dist/utils/hex.d.ts +6 -0
- package/dist/utils/index.d.ts +4 -24
- package/dist/utils/validate-address.d.ts +24 -0
- package/dist/voter.d.ts +230 -0
- package/package.json +13 -61
- package/src/index.ts +11 -13
- package/src/libs/account/crypto.ts +7 -0
- package/src/libs/account/index.ts +70 -0
- package/src/libs/account/keypair.ts +29 -0
- package/src/libs/account/util.ts +55 -0
- package/src/libs/api/client.ts +406 -0
- package/src/libs/api/index.ts +3 -0
- package/src/libs/api/types.d.ts +1493 -0
- package/src/libs/const.ts +37 -54
- package/src/libs/crypto/adapter.ts +41 -0
- package/src/libs/crypto/babyjub.ts +4 -7
- package/src/libs/crypto/constants.ts +2 -5
- package/src/libs/crypto/curve.ts +55 -0
- package/src/libs/crypto/hashing.ts +4 -6
- package/src/libs/crypto/index.ts +3 -0
- package/src/libs/crypto/keys.ts +15 -48
- package/src/libs/crypto/rerandomize.ts +77 -0
- package/src/libs/crypto/sign.ts +8 -17
- package/src/libs/crypto/tree.ts +1 -3
- package/src/libs/cryptography/index.ts +0 -0
- package/src/libs/cryptography/keypair.ts +44 -0
- package/src/libs/cryptography/mnemonics.ts +47 -0
- package/src/libs/cryptography/publickey.ts +44 -0
- package/src/libs/cryptography/signature-scheme.ts +1 -0
- package/src/libs/index.ts +1 -0
- package/src/libs/keypairs/eddsa-poseidon/index.ts +6 -0
- package/src/libs/keypairs/eddsa-poseidon/keypair.ts +452 -0
- package/src/libs/keypairs/eddsa-poseidon/publickey.ts +141 -0
- package/src/maci.ts +157 -101
- package/src/types/index.ts +30 -4
- package/src/types/lib.d.ts +7 -0
- package/src/utils/base64.ts +28 -0
- package/src/utils/bech32.ts +29 -0
- package/src/utils/decode-address.ts +35 -0
- package/src/utils/fetch.ts +28 -0
- package/src/utils/hex.ts +23 -0
- package/src/utils/index.ts +15 -79
- package/src/utils/validate-address.ts +82 -0
- package/src/voter.ts +654 -0
package/src/types/index.ts
CHANGED
|
@@ -6,18 +6,18 @@ export type * from '../libs/crypto/types';
|
|
|
6
6
|
|
|
7
7
|
export enum MaciCircuitType {
|
|
8
8
|
IP1V = '0',
|
|
9
|
-
QV = '1'
|
|
9
|
+
QV = '1'
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export enum MaciCertSystemType {
|
|
13
13
|
GROTH16 = 'groth16',
|
|
14
|
-
PLONK = 'plonk'
|
|
14
|
+
PLONK = 'plonk'
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export enum MaciRoundType {
|
|
18
18
|
MACI = '0',
|
|
19
19
|
AMACI = '1',
|
|
20
|
-
ORACLE_MACI = '2'
|
|
20
|
+
ORACLE_MACI = '2'
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export type CertificateEcosystem = 'cosmoshub' | 'doravota';
|
|
@@ -27,7 +27,9 @@ export type ClientParams = {
|
|
|
27
27
|
network: 'mainnet' | 'testnet';
|
|
28
28
|
rpcEndpoint?: string;
|
|
29
29
|
restEndpoint?: string;
|
|
30
|
-
apiEndpoint?: string;
|
|
30
|
+
apiEndpoint?: string; // Indexer GraphQL API endpoint
|
|
31
|
+
saasApiEndpoint?: string; // MACI SaaS API endpoint
|
|
32
|
+
saasApiKey?: string; // MACI SaaS API key
|
|
31
33
|
certificateApiEndpoint?: string;
|
|
32
34
|
registryAddress?: string;
|
|
33
35
|
saasAddress?: string;
|
|
@@ -474,3 +476,27 @@ export type DeactivateMessage = {
|
|
|
474
476
|
maciContractAddress: string;
|
|
475
477
|
maciOperator: string;
|
|
476
478
|
};
|
|
479
|
+
|
|
480
|
+
export type AccountMangerParams = {
|
|
481
|
+
mnemonic?: string;
|
|
482
|
+
secretKey?: string | bigint;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
export type DerivePathParams = {
|
|
486
|
+
accountIndex?: number;
|
|
487
|
+
isExternal?: boolean;
|
|
488
|
+
addressIndex?: number;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
export type VoterClientParams = {
|
|
492
|
+
network: 'mainnet' | 'testnet';
|
|
493
|
+
mnemonic?: string;
|
|
494
|
+
secretKey?: string | bigint;
|
|
495
|
+
registryAddress?: string;
|
|
496
|
+
apiEndpoint?: string; // Indexer GraphQL API endpoint
|
|
497
|
+
restEndpoint?: string; // DoraVota REST API endpoint
|
|
498
|
+
saasApiEndpoint?: string; // MACI SaaS API endpoint
|
|
499
|
+
saasApiKey?: string; // MACI SaaS API key
|
|
500
|
+
customFetch?: typeof fetch;
|
|
501
|
+
defaultOptions?: FetchOptions;
|
|
502
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Copyright (c) Dorafactory, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export function fromBase64(base64String: string): Uint8Array {
|
|
5
|
+
return Uint8Array.from(atob(base64String), char => char.charCodeAt(0));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const CHUNK_SIZE = 8192;
|
|
9
|
+
export function toBase64(bytes: Uint8Array): string {
|
|
10
|
+
// Special-case the simple case for speed's sake.
|
|
11
|
+
if (bytes.length < CHUNK_SIZE) {
|
|
12
|
+
return btoa(String.fromCharCode(...bytes));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let output = '';
|
|
16
|
+
for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
17
|
+
const chunk = bytes.slice(i, i + CHUNK_SIZE);
|
|
18
|
+
output += String.fromCharCode(...chunk);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return btoa(output);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @deprecated use toBase64 instead */
|
|
25
|
+
export const toB64 = toBase64;
|
|
26
|
+
|
|
27
|
+
/** @deprecated use fromBase64 instead */
|
|
28
|
+
export const fromB64 = fromBase64;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { bech32 } from 'bech32';
|
|
2
|
+
|
|
3
|
+
function verifyIsBech32(address: string): Error | undefined {
|
|
4
|
+
try {
|
|
5
|
+
bech32.decode(address);
|
|
6
|
+
} catch (error) {
|
|
7
|
+
return error instanceof Error ? error : new Error('Unknown error');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isValidBech32Address(address: string, prefix: string): boolean {
|
|
14
|
+
// An address is valid if it starts with `dora` and is Bech32 format.
|
|
15
|
+
return address.startsWith(prefix) && verifyIsBech32(address) === undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function convertBech32Prefix(
|
|
19
|
+
address: string,
|
|
20
|
+
newPrefix: string
|
|
21
|
+
): string {
|
|
22
|
+
// Decode the original address
|
|
23
|
+
const decoded = bech32.decode(address);
|
|
24
|
+
|
|
25
|
+
// Encode the address with the new prefix
|
|
26
|
+
const newAddress = bech32.encode(newPrefix, decoded.words);
|
|
27
|
+
|
|
28
|
+
return newAddress;
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert a contract address to Uint256 format.
|
|
5
|
+
* This replicates the exact logic from the Rust code:
|
|
6
|
+
* Use SHA256 hash + reverse bytes order.
|
|
7
|
+
*/
|
|
8
|
+
export function addressToUint256(address: string): bigint {
|
|
9
|
+
// Convert address string to bytes
|
|
10
|
+
const addressBytes = new TextEncoder().encode(address);
|
|
11
|
+
|
|
12
|
+
// Use SHA256 hash to convert the address to a fixed-length 32-byte format
|
|
13
|
+
const hashResult = sha256(addressBytes);
|
|
14
|
+
|
|
15
|
+
// Convert bytes to Uint256 (big-endian) with reversal like Rust code
|
|
16
|
+
const uint256Bytes = new Uint8Array(32);
|
|
17
|
+
for (let i = 0; i < hashResult.length && i < 32; i++) {
|
|
18
|
+
uint256Bytes[31 - i] = hashResult[i]; // Reverse for little-endian to big-endian conversion
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Convert bytes to Uint256 (big-endian)
|
|
22
|
+
let result = 0n;
|
|
23
|
+
for (let i = 0; i < uint256Bytes.length; i++) {
|
|
24
|
+
result = (result << 8n) + BigInt(uint256Bytes[i]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert bigint to hex string.
|
|
32
|
+
*/
|
|
33
|
+
export function bigintToHex(value: bigint): string {
|
|
34
|
+
return '0x' + value.toString(16).padStart(64, '0');
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export async function fetchApi<T>(url: string, retryCount = 3): Promise<T> {
|
|
2
|
+
let lastError: Error | null = null;
|
|
3
|
+
let result: T | null = null;
|
|
4
|
+
|
|
5
|
+
for (let attempt = 0; attempt < retryCount; attempt++) {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(url);
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
result = (await response.json()) as T;
|
|
13
|
+
break;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
lastError = error as Error;
|
|
16
|
+
|
|
17
|
+
if (attempt === retryCount - 1) {
|
|
18
|
+
throw lastError;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await new Promise(resolve =>
|
|
22
|
+
setTimeout(resolve, Math.pow(2, attempt) * 1000)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result as T;
|
|
28
|
+
}
|
package/src/utils/hex.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Dorafactory, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export function fromHex(hexStr: string): Uint8Array {
|
|
5
|
+
const normalized = hexStr.startsWith('0x') ? hexStr.slice(2) : hexStr;
|
|
6
|
+
const padded = normalized.length % 2 === 0 ? normalized : `0${normalized}}`;
|
|
7
|
+
const intArr = padded.match(/.{2}/g)?.map(byte => parseInt(byte, 16)) ?? [];
|
|
8
|
+
|
|
9
|
+
return Uint8Array.from(intArr);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function toHex(bytes: Uint8Array): string {
|
|
13
|
+
return bytes.reduce(
|
|
14
|
+
(str, byte) => str + byte.toString(16).padStart(2, '0'),
|
|
15
|
+
''
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @deprecated use toHex instead */
|
|
20
|
+
export const toHEX = toHex;
|
|
21
|
+
|
|
22
|
+
/** @deprecated use fromHex instead */
|
|
23
|
+
export const fromHEX = fromHex;
|
package/src/utils/index.ts
CHANGED
|
@@ -1,81 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function isValidAddress(address: string): boolean {
|
|
15
|
-
// An address is valid if it starts with `dora` and is Bech32 format.
|
|
16
|
-
return address.startsWith('dora') && verifyIsBech32(address) === undefined;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Converts a hexadecimal string to a decimal string.
|
|
21
|
-
* @param hexString - The hexadecimal string to convert.
|
|
22
|
-
* @returns The decimal string representation of the input.
|
|
23
|
-
*/
|
|
24
|
-
export function hexToDecimalString(hexString: string) {
|
|
25
|
-
const decimalString = BigInt('0x' + hexString).toString(10);
|
|
26
|
-
return decimalString;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Converts a hexadecimal string to a BigInt.
|
|
31
|
-
* @param hexString - The hexadecimal string to convert.
|
|
32
|
-
* @returns The BigInt representation of the input.
|
|
1
|
+
// Copyright (c) Dorafactory, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* BCS implementation {@see https://github.com/diem/bcs } for JavaScript.
|
|
6
|
+
* Intended to be used for Move applications; supports both NodeJS and browser.
|
|
7
|
+
*
|
|
8
|
+
* For more details and examples {@see README.md }.
|
|
9
|
+
*
|
|
10
|
+
* @module bcs
|
|
11
|
+
* @property {BcsReader}
|
|
33
12
|
*/
|
|
34
|
-
export function hexToBigInt(hexString: string) {
|
|
35
|
-
return BigInt('0x' + hexString);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function padWithZerosIfNeeded(inputString: string) {
|
|
39
|
-
if (inputString.length === 64) {
|
|
40
|
-
return inputString;
|
|
41
|
-
} else if (inputString.length < 64) {
|
|
42
|
-
const zerosToAdd = 64 - inputString.length;
|
|
43
|
-
const zeroPadding = '0'.repeat(zerosToAdd);
|
|
44
|
-
return zeroPadding + inputString;
|
|
45
|
-
}
|
|
46
|
-
throw new Error('Invalid input string length');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Parses a public key string into its x and y coordinates.
|
|
51
|
-
* @param publickKey - The public key string to parse (128 characters long).
|
|
52
|
-
* @returns An object containing the x and y coordinates as decimal strings.
|
|
53
|
-
*/
|
|
54
|
-
export function decompressPublicKey(compressedPubkey: string) {
|
|
55
|
-
const x = compressedPubkey.slice(0, 64);
|
|
56
|
-
const y = compressedPubkey.slice(64);
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
x: hexToDecimalString(x),
|
|
60
|
-
y: hexToDecimalString(y),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function compressPublicKey(decompressedPubkey: any[]) {
|
|
65
|
-
const x = decompressedPubkey[0];
|
|
66
|
-
const y = decompressedPubkey[1];
|
|
67
|
-
const compressedPubkey =
|
|
68
|
-
padWithZerosIfNeeded(x.toString(16)) + padWithZerosIfNeeded(y.toString(16));
|
|
69
|
-
return compressedPubkey;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function transformPubkey(oldPubkey: string) {
|
|
73
|
-
const x = oldPubkey.slice(0, 64);
|
|
74
|
-
const y = oldPubkey.slice(64);
|
|
75
13
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return packedPubkey;
|
|
81
|
-
}
|
|
14
|
+
export { fromB64, fromBase64, toB64, toBase64 } from './base64';
|
|
15
|
+
export { fromHex, fromHEX, toHex, toHEX } from './hex';
|
|
16
|
+
export { addressToUint256, bigintToHex } from './decode-address';
|
|
17
|
+
export * from './validate-address';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { bech32 } from 'bech32';
|
|
2
|
+
import { packPubKey, PubKey } from 'src/libs/crypto';
|
|
3
|
+
|
|
4
|
+
function verifyIsBech32(address: string): Error | undefined {
|
|
5
|
+
try {
|
|
6
|
+
bech32.decode(address);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
return error instanceof Error ? error : new Error('Unknown error');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isValidAddress(address: string): boolean {
|
|
15
|
+
// An address is valid if it starts with `dora` and is Bech32 format.
|
|
16
|
+
return address.startsWith('dora') && verifyIsBech32(address) === undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Converts a hexadecimal string to a decimal string.
|
|
21
|
+
* @param hexString - The hexadecimal string to convert.
|
|
22
|
+
* @returns The decimal string representation of the input.
|
|
23
|
+
*/
|
|
24
|
+
export function hexToDecimalString(hexString: string) {
|
|
25
|
+
const decimalString = BigInt('0x' + hexString).toString(10);
|
|
26
|
+
return decimalString;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Converts a hexadecimal string to a BigInt.
|
|
31
|
+
* @param hexString - The hexadecimal string to convert.
|
|
32
|
+
* @returns The BigInt representation of the input.
|
|
33
|
+
*/
|
|
34
|
+
export function hexToBigInt(hexString: string) {
|
|
35
|
+
return BigInt('0x' + hexString);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function padWithZerosIfNeeded(inputString: string) {
|
|
39
|
+
if (inputString.length === 64) {
|
|
40
|
+
return inputString;
|
|
41
|
+
} else if (inputString.length < 64) {
|
|
42
|
+
const zerosToAdd = 64 - inputString.length;
|
|
43
|
+
const zeroPadding = '0'.repeat(zerosToAdd);
|
|
44
|
+
return zeroPadding + inputString;
|
|
45
|
+
}
|
|
46
|
+
throw new Error('Invalid input string length');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parses a public key string into its x and y coordinates.
|
|
51
|
+
* @param publickKey - The public key string to parse (128 characters long).
|
|
52
|
+
* @returns An object containing the x and y coordinates as decimal strings.
|
|
53
|
+
*/
|
|
54
|
+
export function decompressPublicKey(compressedPubkey: string) {
|
|
55
|
+
const x = compressedPubkey.slice(0, 64);
|
|
56
|
+
const y = compressedPubkey.slice(64);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
x: hexToDecimalString(x),
|
|
60
|
+
y: hexToDecimalString(y),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function compressPublicKey(decompressedPubkey: any[]) {
|
|
65
|
+
const x = decompressedPubkey[0];
|
|
66
|
+
const y = decompressedPubkey[1];
|
|
67
|
+
const compressedPubkey =
|
|
68
|
+
padWithZerosIfNeeded(x.toString(16)) +
|
|
69
|
+
padWithZerosIfNeeded(y.toString(16));
|
|
70
|
+
return compressedPubkey;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function transformPubkey(oldPubkey: string) {
|
|
74
|
+
const x = oldPubkey.slice(0, 64);
|
|
75
|
+
const y = oldPubkey.slice(64);
|
|
76
|
+
|
|
77
|
+
const pubkey: PubKey = [hexToBigInt(x), hexToBigInt(y)];
|
|
78
|
+
console.log(pubkey);
|
|
79
|
+
console.log([hexToDecimalString(x), hexToDecimalString(y)]);
|
|
80
|
+
const packedPubkey = packPubKey(pubkey);
|
|
81
|
+
return packedPubkey;
|
|
82
|
+
}
|