@mysten/sui 2.22.0 → 2.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/bcs/bcs.d.mts +6 -6
- package/dist/bcs/index.d.mts +20 -20
- package/dist/client/address-balance-transaction-expiration.mjs +22 -0
- package/dist/client/address-balance-transaction-expiration.mjs.map +1 -0
- package/dist/cryptography/signature.d.mts +6 -6
- package/dist/graphql/core.d.mts.map +1 -1
- package/dist/graphql/core.mjs +11 -1
- package/dist/graphql/core.mjs.map +1 -1
- package/dist/graphql/generated/queries.mjs +3 -0
- package/dist/graphql/generated/queries.mjs.map +1 -1
- package/dist/grpc/core.d.mts.map +1 -1
- package/dist/grpc/core.mjs +13 -2
- package/dist/grpc/core.mjs.map +1 -1
- package/dist/grpc/proto/sui/forking/v1alpha/forking_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/ledger_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/move_package_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/name_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/state_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/subscription_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/transaction_execution_service.client.d.mts +4 -4
- package/dist/transactions/Transaction.d.mts +9 -9
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/dist/zklogin/address.mjs +2 -2
- package/dist/zklogin/address.mjs.map +1 -1
- package/dist/zklogin/bcs.d.mts +14 -14
- package/dist/zklogin/bcs.d.mts.map +1 -1
- package/dist/zklogin/poseidon.mjs +5 -2
- package/dist/zklogin/poseidon.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client/address-balance-transaction-expiration.ts +53 -0
- package/src/graphql/core.ts +13 -3
- package/src/graphql/generated/queries.ts +4 -1
- package/src/graphql/queries/transactions.graphql +3 -0
- package/src/grpc/core.ts +14 -3
- package/src/version.ts +1 -1
- package/src/zklogin/address.ts +2 -2
- package/src/zklogin/poseidon.ts +6 -4
package/dist/zklogin/bcs.d.mts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as _mysten_bcs810 from "@mysten/bcs";
|
|
2
2
|
import { InferBcsInput } from "@mysten/bcs";
|
|
3
3
|
|
|
4
4
|
//#region src/zklogin/bcs.d.ts
|
|
5
|
-
declare const zkLoginSignature:
|
|
6
|
-
inputs:
|
|
7
|
-
proofPoints:
|
|
8
|
-
a:
|
|
5
|
+
declare const zkLoginSignature: _mysten_bcs810.BcsStruct<{
|
|
6
|
+
inputs: _mysten_bcs810.BcsStruct<{
|
|
7
|
+
proofPoints: _mysten_bcs810.BcsStruct<{
|
|
8
|
+
a: _mysten_bcs810.BcsType<string[], Iterable<string> & {
|
|
9
9
|
length: number;
|
|
10
10
|
}, string>;
|
|
11
|
-
b:
|
|
11
|
+
b: _mysten_bcs810.BcsType<string[][], Iterable<Iterable<string> & {
|
|
12
12
|
length: number;
|
|
13
13
|
}> & {
|
|
14
14
|
length: number;
|
|
15
15
|
}, string>;
|
|
16
|
-
c:
|
|
16
|
+
c: _mysten_bcs810.BcsType<string[], Iterable<string> & {
|
|
17
17
|
length: number;
|
|
18
18
|
}, string>;
|
|
19
19
|
}, string>;
|
|
20
|
-
issBase64Details:
|
|
21
|
-
value:
|
|
22
|
-
indexMod4:
|
|
20
|
+
issBase64Details: _mysten_bcs810.BcsStruct<{
|
|
21
|
+
value: _mysten_bcs810.BcsType<string, string, "string">;
|
|
22
|
+
indexMod4: _mysten_bcs810.BcsType<number, number, "u8">;
|
|
23
23
|
}, string>;
|
|
24
|
-
headerBase64:
|
|
25
|
-
addressSeed:
|
|
24
|
+
headerBase64: _mysten_bcs810.BcsType<string, string, "string">;
|
|
25
|
+
addressSeed: _mysten_bcs810.BcsType<string, string, "string">;
|
|
26
26
|
}, string>;
|
|
27
|
-
maxEpoch:
|
|
28
|
-
userSignature:
|
|
27
|
+
maxEpoch: _mysten_bcs810.BcsType<string, string | number | bigint, "u64">;
|
|
28
|
+
userSignature: _mysten_bcs810.BcsType<Uint8Array<ArrayBufferLike>, Iterable<number>, "vector<u8>">;
|
|
29
29
|
}, string>;
|
|
30
30
|
type ZkLoginSignature = InferBcsInput<typeof zkLoginSignature>;
|
|
31
31
|
type ZkLoginSignatureInputs = ZkLoginSignature['inputs'];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bcs.d.mts","names":[],"sources":["../../src/zklogin/bcs.ts"],"mappings":";;;;cAMa,gBAAA,
|
|
1
|
+
{"version":3,"file":"bcs.d.mts","names":[],"sources":["../../src/zklogin/bcs.ts"],"mappings":";;;;cAMa,gBAAA,iBAAgB,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;KAkBjB,gBAAA,GAAmB,aAAA,QAAqB,gBAAA;AAAA,KACxC,sBAAA,GAAyB,gBAAA"}
|
|
@@ -27,8 +27,11 @@ function poseidonHash(inputs) {
|
|
|
27
27
|
});
|
|
28
28
|
const hashFN = poseidonNumToHashFN[inputs.length - 1];
|
|
29
29
|
if (hashFN) return hashFN(inputs);
|
|
30
|
-
else if (inputs.length
|
|
31
|
-
|
|
30
|
+
else if (inputs.length > 16 && inputs.length <= 64) {
|
|
31
|
+
const chunkHashes = [];
|
|
32
|
+
for (let i = 0; i < inputs.length; i += 16) chunkHashes.push(poseidonHash(inputs.slice(i, i + 16)));
|
|
33
|
+
return poseidonHash(chunkHashes);
|
|
34
|
+
} else throw new Error(`Yet to implement: Unable to hash a vector of length ${inputs.length}`);
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"poseidon.mjs","names":[],"sources":["../../src/zklogin/poseidon.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n\tposeidon1,\n\tposeidon2,\n\tposeidon3,\n\tposeidon4,\n\tposeidon5,\n\tposeidon6,\n\tposeidon7,\n\tposeidon8,\n\tposeidon9,\n\tposeidon10,\n\tposeidon11,\n\tposeidon12,\n\tposeidon13,\n\tposeidon14,\n\tposeidon15,\n\tposeidon16,\n} from 'poseidon-lite';\n\nconst poseidonNumToHashFN = [\n\tposeidon1,\n\tposeidon2,\n\tposeidon3,\n\tposeidon4,\n\tposeidon5,\n\tposeidon6,\n\tposeidon7,\n\tposeidon8,\n\tposeidon9,\n\tposeidon10,\n\tposeidon11,\n\tposeidon12,\n\tposeidon13,\n\tposeidon14,\n\tposeidon15,\n\tposeidon16,\n];\n\nexport const BN254_FIELD_SIZE =\n\t21888242871839275222246405745257275088548364400416034343698204186575808495617n;\n\nexport function poseidonHash(inputs: (number | bigint | string)[]): bigint {\n\tinputs.forEach((x) => {\n\t\tconst b = BigInt(x);\n\t\tif (b < 0 || b >= BN254_FIELD_SIZE) {\n\t\t\tthrow new Error(`Element ${b} not in the BN254 field`);\n\t\t}\n\t});\n\n\tconst hashFN = poseidonNumToHashFN[inputs.length - 1];\n\n\tif (hashFN) {\n\t\treturn hashFN(inputs);\n\t} else if (inputs.length <=
|
|
1
|
+
{"version":3,"file":"poseidon.mjs","names":[],"sources":["../../src/zklogin/poseidon.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n\tposeidon1,\n\tposeidon2,\n\tposeidon3,\n\tposeidon4,\n\tposeidon5,\n\tposeidon6,\n\tposeidon7,\n\tposeidon8,\n\tposeidon9,\n\tposeidon10,\n\tposeidon11,\n\tposeidon12,\n\tposeidon13,\n\tposeidon14,\n\tposeidon15,\n\tposeidon16,\n} from 'poseidon-lite';\n\nconst poseidonNumToHashFN = [\n\tposeidon1,\n\tposeidon2,\n\tposeidon3,\n\tposeidon4,\n\tposeidon5,\n\tposeidon6,\n\tposeidon7,\n\tposeidon8,\n\tposeidon9,\n\tposeidon10,\n\tposeidon11,\n\tposeidon12,\n\tposeidon13,\n\tposeidon14,\n\tposeidon15,\n\tposeidon16,\n];\n\nexport const BN254_FIELD_SIZE =\n\t21888242871839275222246405745257275088548364400416034343698204186575808495617n;\n\nexport function poseidonHash(inputs: (number | bigint | string)[]): bigint {\n\tinputs.forEach((x) => {\n\t\tconst b = BigInt(x);\n\t\tif (b < 0 || b >= BN254_FIELD_SIZE) {\n\t\t\tthrow new Error(`Element ${b} not in the BN254 field`);\n\t\t}\n\t});\n\n\tconst hashFN = poseidonNumToHashFN[inputs.length - 1];\n\n\tif (hashFN) {\n\t\treturn hashFN(inputs);\n\t} else if (inputs.length > 16 && inputs.length <= 64) {\n\t\tconst chunkHashes = [];\n\t\tfor (let i = 0; i < inputs.length; i += 16) {\n\t\t\tchunkHashes.push(poseidonHash(inputs.slice(i, i + 16)));\n\t\t}\n\t\treturn poseidonHash(chunkHashes);\n\t} else {\n\t\tthrow new Error(`Yet to implement: Unable to hash a vector of length ${inputs.length}`);\n\t}\n}\n"],"mappings":";;;AAsBA,MAAM,sBAAsB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAED,MAAa,mBACZ;AAED,SAAgB,aAAa,QAA8C;AAC1E,QAAO,SAAS,MAAM;EACrB,MAAM,IAAI,OAAO,EAAE;AACnB,MAAI,IAAI,KAAK,KAAK,iBACjB,OAAM,IAAI,MAAM,WAAW,EAAE,yBAAyB;GAEtD;CAEF,MAAM,SAAS,oBAAoB,OAAO,SAAS;AAEnD,KAAI,OACH,QAAO,OAAO,OAAO;UACX,OAAO,SAAS,MAAM,OAAO,UAAU,IAAI;EACrD,MAAM,cAAc,EAAE;AACtB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GACvC,aAAY,KAAK,aAAa,OAAO,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC;AAExD,SAAO,aAAa,YAAY;OAEhC,OAAM,IAAI,MAAM,uDAAuD,OAAO,SAAS"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { TransactionDataBuilder } from '../transactions/TransactionData.js';
|
|
5
|
+
import type { TransactionData } from '../transactions/data/internal.js';
|
|
6
|
+
import type { ClientWithCoreApi } from './core.js';
|
|
7
|
+
|
|
8
|
+
export async function setAddressBalanceTransactionExpirationFromSimulatedEpoch({
|
|
9
|
+
transactionData,
|
|
10
|
+
client,
|
|
11
|
+
epoch,
|
|
12
|
+
originalTransactionData,
|
|
13
|
+
isTransactionKindOnly,
|
|
14
|
+
doGasSelection,
|
|
15
|
+
}: {
|
|
16
|
+
transactionData: TransactionDataBuilder;
|
|
17
|
+
client: ClientWithCoreApi;
|
|
18
|
+
epoch: string | number | bigint | null | undefined;
|
|
19
|
+
originalTransactionData: TransactionData;
|
|
20
|
+
isTransactionKindOnly: boolean;
|
|
21
|
+
doGasSelection: boolean;
|
|
22
|
+
}) {
|
|
23
|
+
if (
|
|
24
|
+
isTransactionKindOnly ||
|
|
25
|
+
doGasSelection ||
|
|
26
|
+
originalTransactionData.expiration ||
|
|
27
|
+
originalTransactionData.gasData.payment?.length !== 0
|
|
28
|
+
) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (transactionData.expiration && transactionData.expiration.$kind !== 'None') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const [{ chainIdentifier }, systemStateResult] = await Promise.all([
|
|
37
|
+
client.core.getChainIdentifier(),
|
|
38
|
+
epoch == null ? client.core.getCurrentSystemState() : null,
|
|
39
|
+
]);
|
|
40
|
+
const currentEpoch = BigInt(epoch ?? systemStateResult!.systemState.epoch);
|
|
41
|
+
|
|
42
|
+
transactionData.expiration = {
|
|
43
|
+
$kind: 'ValidDuring',
|
|
44
|
+
ValidDuring: {
|
|
45
|
+
minEpoch: String(currentEpoch),
|
|
46
|
+
maxEpoch: String(currentEpoch + 1n),
|
|
47
|
+
minTimestamp: null,
|
|
48
|
+
maxTimestamp: null,
|
|
49
|
+
chain: chainIdentifier,
|
|
50
|
+
nonce: (Math.random() * 0x100000000) >>> 0,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
package/src/graphql/core.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
transactionToGrpcJson,
|
|
45
45
|
grpcTransactionToTransactionData,
|
|
46
46
|
} from '../client/transaction-resolver.js';
|
|
47
|
+
import { setAddressBalanceTransactionExpirationFromSimulatedEpoch } from '../client/address-balance-transaction-expiration.js';
|
|
47
48
|
import { BalanceChange as BalanceChangeType } from '../grpc/proto/sui/rpc/v2/balance_change.js';
|
|
48
49
|
import { TransactionEffects as TransactionEffectsType } from '../grpc/proto/sui/rpc/v2/effects.js';
|
|
49
50
|
import { Transaction as GrpcTransactionType } from '../grpc/proto/sui/rpc/v2/transaction.js';
|
|
@@ -732,14 +733,15 @@ export class GraphQLCoreClient extends CoreClient {
|
|
|
732
733
|
}
|
|
733
734
|
const grpcTransaction = transactionDataToGrpcTransaction(snapshot);
|
|
734
735
|
const transactionJson = GrpcTransactionType.toJson(grpcTransaction);
|
|
736
|
+
const doGasSelection =
|
|
737
|
+
!options.onlyTransactionKind &&
|
|
738
|
+
(snapshot.gasData.budget == null || snapshot.gasData.payment == null);
|
|
735
739
|
|
|
736
740
|
const { data, errors } = await graphqlClient.query({
|
|
737
741
|
query: ResolveTransactionDocument,
|
|
738
742
|
variables: {
|
|
739
743
|
transaction: transactionJson,
|
|
740
|
-
doGasSelection
|
|
741
|
-
!options.onlyTransactionKind &&
|
|
742
|
-
(snapshot.gasData.budget == null || snapshot.gasData.payment == null),
|
|
744
|
+
doGasSelection,
|
|
743
745
|
checksEnabled: !options.onlyTransactionKind,
|
|
744
746
|
},
|
|
745
747
|
});
|
|
@@ -780,6 +782,14 @@ export class GraphQLCoreClient extends CoreClient {
|
|
|
780
782
|
} else {
|
|
781
783
|
transactionData.applyResolvedData(resolved);
|
|
782
784
|
}
|
|
785
|
+
await setAddressBalanceTransactionExpirationFromSimulatedEpoch({
|
|
786
|
+
transactionData,
|
|
787
|
+
client: graphqlClient,
|
|
788
|
+
epoch: transactionEffects?.epoch?.epochId,
|
|
789
|
+
originalTransactionData: snapshot,
|
|
790
|
+
isTransactionKindOnly: !!options.onlyTransactionKind,
|
|
791
|
+
doGasSelection,
|
|
792
|
+
});
|
|
783
793
|
|
|
784
794
|
return await next();
|
|
785
795
|
};
|
|
@@ -338,7 +338,7 @@ export type ResolveTransactionQueryVariables = Exact<{
|
|
|
338
338
|
}>;
|
|
339
339
|
|
|
340
340
|
|
|
341
|
-
export type ResolveTransactionQuery = { simulateTransaction: { effects: { transaction: { transactionBcs: string | null, effects: { status: ExecutionStatus | null, executionError: { message: string, abortCode: string | null, identifier: string | null, constant: string | null, sourceLineNumber: number | null, instructionOffset: number | null, module: { name: string, package: { address: string } | null } | null, function: { name: string } | null } | null } | null } | null } | null } };
|
|
341
|
+
export type ResolveTransactionQuery = { simulateTransaction: { effects: { transaction: { transactionBcs: string | null, effects: { status: ExecutionStatus | null, epoch: { epochId: number } | null, executionError: { message: string, abortCode: string | null, identifier: string | null, constant: string | null, sourceLineNumber: number | null, instructionOffset: number | null, module: { name: string, package: { address: string } | null } | null, function: { name: string } | null } | null } | null } | null } | null } };
|
|
342
342
|
|
|
343
343
|
export type VerifyZkLoginSignatureQueryVariables = Exact<{
|
|
344
344
|
bytes: string;
|
|
@@ -1153,6 +1153,9 @@ export const ResolveTransactionDocument = new TypedDocumentString(`
|
|
|
1153
1153
|
transaction {
|
|
1154
1154
|
transactionBcs
|
|
1155
1155
|
effects {
|
|
1156
|
+
epoch {
|
|
1157
|
+
epochId
|
|
1158
|
+
}
|
|
1156
1159
|
status
|
|
1157
1160
|
executionError {
|
|
1158
1161
|
message
|
package/src/grpc/core.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
transactionToGrpcTransaction,
|
|
44
44
|
grpcTransactionToTransactionData,
|
|
45
45
|
} from '../client/transaction-resolver.js';
|
|
46
|
+
import { setAddressBalanceTransactionExpirationFromSimulatedEpoch } from '../client/address-balance-transaction-expiration.js';
|
|
46
47
|
import { Value } from './proto/google/protobuf/struct.js';
|
|
47
48
|
import { ExecutedTransaction } from './proto/sui/rpc/v2/executed_transaction.js';
|
|
48
49
|
import {
|
|
@@ -720,14 +721,15 @@ export class GrpcCoreClient extends CoreClient {
|
|
|
720
721
|
snapshot.sender = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
721
722
|
}
|
|
722
723
|
const grpcTransaction = transactionDataToGrpcTransaction(snapshot);
|
|
724
|
+
const doGasSelection =
|
|
725
|
+
!options.onlyTransactionKind &&
|
|
726
|
+
(snapshot.gasData.budget == null || snapshot.gasData.payment == null);
|
|
723
727
|
|
|
724
728
|
let response;
|
|
725
729
|
try {
|
|
726
730
|
const result = await client.transactionExecutionService.simulateTransaction({
|
|
727
731
|
transaction: grpcTransaction,
|
|
728
|
-
doGasSelection
|
|
729
|
-
!options.onlyTransactionKind &&
|
|
730
|
-
(snapshot.gasData.budget == null || snapshot.gasData.payment == null),
|
|
732
|
+
doGasSelection,
|
|
731
733
|
// Kind-only txns are never executed directly and do not have sender, so skip validation checks.
|
|
732
734
|
checks: options.onlyTransactionKind
|
|
733
735
|
? SimulateTransactionRequest_TransactionChecks.DISABLED
|
|
@@ -739,6 +741,7 @@ export class GrpcCoreClient extends CoreClient {
|
|
|
739
741
|
'transaction.transaction.expiration',
|
|
740
742
|
'transaction.transaction.kind',
|
|
741
743
|
'transaction.effects.status',
|
|
744
|
+
'transaction.effects.epoch',
|
|
742
745
|
],
|
|
743
746
|
},
|
|
744
747
|
});
|
|
@@ -770,6 +773,14 @@ export class GrpcCoreClient extends CoreClient {
|
|
|
770
773
|
}
|
|
771
774
|
|
|
772
775
|
applyGrpcResolvedTransaction(transactionData, response.transaction.transaction, options);
|
|
776
|
+
await setAddressBalanceTransactionExpirationFromSimulatedEpoch({
|
|
777
|
+
transactionData,
|
|
778
|
+
client,
|
|
779
|
+
epoch: response.transaction.effects?.epoch,
|
|
780
|
+
originalTransactionData: snapshot,
|
|
781
|
+
isTransactionKindOnly: !!options.onlyTransactionKind,
|
|
782
|
+
doGasSelection,
|
|
783
|
+
});
|
|
773
784
|
|
|
774
785
|
return await next();
|
|
775
786
|
};
|
package/src/version.ts
CHANGED
package/src/zklogin/address.ts
CHANGED
|
@@ -40,8 +40,8 @@ export function computeZkLoginAddressFromSeed(
|
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export const MAX_HEADER_LEN_B64 =
|
|
44
|
-
export const MAX_PADDED_UNSIGNED_JWT_LEN = 64 *
|
|
43
|
+
export const MAX_HEADER_LEN_B64 = 279;
|
|
44
|
+
export const MAX_PADDED_UNSIGNED_JWT_LEN = 64 * 30;
|
|
45
45
|
|
|
46
46
|
export function lengthChecks(jwt: string) {
|
|
47
47
|
const [header, payload] = jwt.split('.');
|
package/src/zklogin/poseidon.ts
CHANGED
|
@@ -54,10 +54,12 @@ export function poseidonHash(inputs: (number | bigint | string)[]): bigint {
|
|
|
54
54
|
|
|
55
55
|
if (hashFN) {
|
|
56
56
|
return hashFN(inputs);
|
|
57
|
-
} else if (inputs.length <=
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
} else if (inputs.length > 16 && inputs.length <= 64) {
|
|
58
|
+
const chunkHashes = [];
|
|
59
|
+
for (let i = 0; i < inputs.length; i += 16) {
|
|
60
|
+
chunkHashes.push(poseidonHash(inputs.slice(i, i + 16)));
|
|
61
|
+
}
|
|
62
|
+
return poseidonHash(chunkHashes);
|
|
61
63
|
} else {
|
|
62
64
|
throw new Error(`Yet to implement: Unable to hash a vector of length ${inputs.length}`);
|
|
63
65
|
}
|