@hexora/address-guard 1.0.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.
@@ -0,0 +1,3 @@
1
+ import type { CheckAddressParams, CheckResult } from "./types.js";
2
+ export declare function checkAddress(params: CheckAddressParams): Promise<CheckResult>;
3
+ //# sourceMappingURL=checkAddress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkAddress.d.ts","sourceRoot":"","sources":["../src/checkAddress.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAOlE,wBAAsB,YAAY,CAChC,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,WAAW,CAAC,CAkGtB"}
@@ -0,0 +1,79 @@
1
+ import { detectProvider, validateAddress, findMostSimilar, DefaultHistoryProvider, txCache, } from "@hexora/core";
2
+ import { analyzeInputAddress, analyzeUserHistory } from "./detector.js";
3
+ import { buildResult, buildErrorResult } from "./scorer.js";
4
+ const DEFAULT_HISTORY_LIMIT = 20;
5
+ const DEFAULT_SIMILARITY_THRESHOLD = 85;
6
+ export async function checkAddress(params) {
7
+ const { userAddress, inputAddress, provider, historyProvider, historyLimit = DEFAULT_HISTORY_LIMIT, similarityThreshold = DEFAULT_SIMILARITY_THRESHOLD, dustThreshold, apiKeys = {}, } = params;
8
+ // Step 1: detect provider, resolve chain
9
+ let chain;
10
+ try {
11
+ chain = await detectProvider(provider).chainId();
12
+ }
13
+ catch (err) {
14
+ const e = err;
15
+ return buildErrorResult(e.hexoraCode ?? "unknown", e.message ?? "Failed to detect provider");
16
+ }
17
+ // Step 2: validate addresses
18
+ if (!validateAddress(userAddress, chain))
19
+ return buildErrorResult("invalid_address", `userAddress "${userAddress}" is not valid for chain "${chain}"`, chain);
20
+ if (!validateAddress(inputAddress, chain))
21
+ return buildErrorResult("invalid_address", `inputAddress "${inputAddress}" is not valid for chain "${chain}"`, chain);
22
+ // Step 3: fetch history (in-memory cache, 5 min TTL)
23
+ const fetcher = historyProvider ?? new DefaultHistoryProvider(apiKeys);
24
+ let userHistory = [];
25
+ let inputAddrHistory = [];
26
+ try {
27
+ const cached = txCache.get(userAddress, chain);
28
+ const [userTxs, inputTxs] = await Promise.all([
29
+ cached
30
+ ? Promise.resolve(cached)
31
+ : fetcher.getTransactions(userAddress, chain, Math.min(historyLimit, 50)),
32
+ fetcher.getTransactions(inputAddress, chain, 50),
33
+ ]);
34
+ if (!cached)
35
+ txCache.set(userAddress, chain, userTxs);
36
+ userHistory = userTxs;
37
+ inputAddrHistory = inputTxs;
38
+ }
39
+ catch {
40
+ /* continue with empty history */
41
+ }
42
+ // Step 4: similarity check
43
+ const known = extractKnownAddresses(userHistory, userAddress);
44
+ const match = findMostSimilar(inputAddress, known, similarityThreshold);
45
+ // Step 5: analyze user history for injected poison
46
+ const histAnalysis = analyzeUserHistory(inputAddress, userHistory, userAddress);
47
+ // Step 6: analyze input address on-chain behavior
48
+ const inputAnalysis = analyzeInputAddress(inputAddress, inputAddrHistory, dustThreshold);
49
+ // Step 7: combine signals
50
+ return buildResult({
51
+ chain,
52
+ userAddress,
53
+ inputAddress,
54
+ historyScanned: userHistory.length,
55
+ similarityScore: match?.similarityScore ?? 0,
56
+ matchedAddress: match?.address ?? null,
57
+ zeroValueFound: histAnalysis.zeroValueFound,
58
+ batchPoisonFound: histAnalysis.batchPoisonFound,
59
+ dustFound: histAnalysis.dustFound,
60
+ transferFromFound: histAnalysis.transferFromFound,
61
+ inputAddrDetection: {
62
+ detected: inputAnalysis.detected,
63
+ reason: inputAnalysis.reason,
64
+ confidence: inputAnalysis.confidence,
65
+ },
66
+ });
67
+ }
68
+ function extractKnownAddresses(history, userAddress) {
69
+ const lower = userAddress.toLowerCase();
70
+ const seen = new Set();
71
+ for (const tx of history) {
72
+ if (tx.from === lower && tx.to)
73
+ seen.add(tx.to);
74
+ if (tx.to === lower && tx.from && !tx.isZeroValue)
75
+ seen.add(tx.from);
76
+ }
77
+ return Array.from(seen);
78
+ }
79
+ //# sourceMappingURL=checkAddress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkAddress.js","sourceRoot":"","sources":["../src/checkAddress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { NormalizedTransaction, ScamReason } from "@hexora/core";
2
+ export declare const DEFAULT_DUST_THRESHOLD = 10000000000000n;
3
+ export interface DetectionResult {
4
+ detected: boolean;
5
+ reason: ScamReason | null;
6
+ confidence: number;
7
+ evidence: string[];
8
+ }
9
+ export declare function analyzeInputAddress(inputAddress: string, inputAddrHistory: NormalizedTransaction[], dustThreshold?: bigint): DetectionResult;
10
+ export declare function analyzeUserHistory(inputAddress: string, userHistory: NormalizedTransaction[], userAddress: string): {
11
+ zeroValueFound: boolean;
12
+ batchPoisonFound: boolean;
13
+ dustFound: boolean;
14
+ transferFromFound: boolean;
15
+ evidence: string[];
16
+ };
17
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEtE,eAAO,MAAM,sBAAsB,kBAAsB,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,qBAAqB,EAAE,EACzC,aAAa,GAAE,MAA+B,GAC7C,eAAe,CA2DjB;AAED,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,qBAAqB,EAAE,EACpC,WAAW,EAAE,MAAM,GAClB;IACD,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAqCA"}
@@ -0,0 +1,78 @@
1
+ export const DEFAULT_DUST_THRESHOLD = 10000000000000n; // 0.00001 ETH in wei
2
+ export function analyzeInputAddress(inputAddress, inputAddrHistory, dustThreshold = DEFAULT_DUST_THRESHOLD) {
3
+ const evidence = [];
4
+ let maxConfidence = 0;
5
+ let reason = null;
6
+ const addr = inputAddress.toLowerCase();
7
+ const batchTxs = inputAddrHistory.filter((tx) => tx.isBatchPoison);
8
+ if (batchTxs.length > 0) {
9
+ evidence.push(`Address executed ${batchTxs.length} batch poisoning transaction(s) with zero-value token transfers`);
10
+ maxConfidence = Math.max(maxConfidence, 98);
11
+ reason = "batch_poisoning";
12
+ }
13
+ const zeroSent = inputAddrHistory.filter((tx) => tx.isZeroValue && tx.from === addr);
14
+ if (zeroSent.length >= 2) {
15
+ evidence.push(`Address sent ${zeroSent.length} zero-value token transfers`);
16
+ maxConfidence = Math.max(maxConfidence, 90);
17
+ if (!reason)
18
+ reason = "zero_value_transfer";
19
+ }
20
+ const dustSent = inputAddrHistory.filter((tx) => tx.from === addr &&
21
+ !tx.isZeroValue &&
22
+ tx.value > 0n &&
23
+ tx.value < dustThreshold);
24
+ const uniqueTargets = new Set(dustSent.map((tx) => tx.to)).size;
25
+ if (uniqueTargets >= 5) {
26
+ evidence.push(`Address sent dust to ${uniqueTargets} unique addresses`);
27
+ maxConfidence = Math.max(maxConfidence, 85);
28
+ if (!reason)
29
+ reason = "dust_attack";
30
+ }
31
+ if (inputAddrHistory.length > 0) {
32
+ const firstTx = Math.min(...inputAddrHistory.map((tx) => tx.timestamp));
33
+ const ageDays = (Date.now() / 1000 - firstTx) / 86400;
34
+ const onlyOut = inputAddrHistory.every((tx) => tx.from === addr || tx.isBatchPoison);
35
+ if (ageDays < 30 && onlyOut && inputAddrHistory.length >= 3) {
36
+ evidence.push(`Address is ${Math.round(ageDays)} days old with only outgoing activity`);
37
+ maxConfidence = Math.max(maxConfidence, 75);
38
+ if (!reason)
39
+ reason = "new_suspicious_address";
40
+ }
41
+ }
42
+ return {
43
+ detected: evidence.length > 0,
44
+ reason,
45
+ confidence: maxConfidence,
46
+ evidence,
47
+ };
48
+ }
49
+ export function analyzeUserHistory(inputAddress, userHistory, userAddress) {
50
+ const evidence = [];
51
+ const inputLower = inputAddress.toLowerCase();
52
+ const userLower = userAddress.toLowerCase();
53
+ const fromInput = userHistory.filter((tx) => tx.from === inputLower);
54
+ const zeroValueFound = fromInput.some((tx) => tx.isZeroValue);
55
+ const batchPoisonFound = fromInput.some((tx) => tx.isBatchPoison);
56
+ const dustFound = fromInput.some((tx) => !tx.isZeroValue && tx.value > 0n && tx.value < DEFAULT_DUST_THRESHOLD);
57
+ const transferFromFound = userHistory.some((tx) => tx.from === userLower &&
58
+ tx.to === inputLower &&
59
+ tx.isZeroValue &&
60
+ tx.contractAddress !== undefined &&
61
+ tx.contractAddress !== userLower);
62
+ if (zeroValueFound)
63
+ evidence.push(`Incoming zero-value transfer from ${inputAddress}`);
64
+ if (batchPoisonFound)
65
+ evidence.push(`Batch poisoning transaction from ${inputAddress}`);
66
+ if (dustFound)
67
+ evidence.push(`Dust transaction received from ${inputAddress}`);
68
+ if (transferFromFound)
69
+ evidence.push(`transferFrom spoofing detected involving ${inputAddress}`);
70
+ return {
71
+ zeroValueFound,
72
+ batchPoisonFound,
73
+ dustFound,
74
+ transferFromFound,
75
+ evidence,
76
+ };
77
+ }
78
+ //# sourceMappingURL=detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { checkAddress } from "./checkAddress.js";
2
+ export { DEFAULT_DUST_THRESHOLD } from "./detector.js";
3
+ export type { CheckAddressParams, CheckResult, CheckDetails } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEvD,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { checkAddress } from "./checkAddress.js";
2
+ export { DEFAULT_DUST_THRESHOLD } from "./detector.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { ChainId, ScamReason } from "@hexora/core";
2
+ import type { CheckResult } from "./types.js";
3
+ export interface ScorerInput {
4
+ chain: ChainId;
5
+ userAddress: string;
6
+ inputAddress: string;
7
+ historyScanned: number;
8
+ similarityScore: number;
9
+ matchedAddress: string | null;
10
+ zeroValueFound: boolean;
11
+ batchPoisonFound: boolean;
12
+ dustFound: boolean;
13
+ transferFromFound: boolean;
14
+ inputAddrDetection: {
15
+ detected: boolean;
16
+ reason: ScamReason | null;
17
+ confidence: number;
18
+ };
19
+ }
20
+ export declare function buildResult(input: ScorerInput): CheckResult;
21
+ export declare function buildErrorResult(code: string, message: string, chain?: ChainId): CheckResult;
22
+ //# sourceMappingURL=scorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.d.ts","sourceRoot":"","sources":["../src/scorer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAyB,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE;QAClB,QAAQ,EAAE,OAAO,CAAC;QAClB,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAoD3D;AAUD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,OAAoB,GAC1B,WAAW,CAmBb"}
package/dist/scorer.js ADDED
@@ -0,0 +1,79 @@
1
+ export function buildResult(input) {
2
+ const signals = [];
3
+ if (input.batchPoisonFound)
4
+ signals.push({ reason: "batch_poisoning", weight: 100 });
5
+ if (input.transferFromFound)
6
+ signals.push({ reason: "transferfrom_spoofing", weight: 95 });
7
+ if (input.zeroValueFound)
8
+ signals.push({ reason: "zero_value_transfer", weight: 90 });
9
+ if (input.inputAddrDetection.detected &&
10
+ input.inputAddrDetection.reason !== null)
11
+ signals.push({
12
+ reason: input.inputAddrDetection.reason,
13
+ weight: input.inputAddrDetection.confidence,
14
+ });
15
+ if (input.similarityScore >= 85)
16
+ signals.push({
17
+ reason: "address_poisoning",
18
+ weight: input.similarityScore,
19
+ });
20
+ if (input.dustFound)
21
+ signals.push({ reason: "dust_attack", weight: 70 });
22
+ signals.sort((a, b) => b.weight - a.weight);
23
+ const top = signals[0];
24
+ const riskScore = top?.weight ?? 0;
25
+ const confidence = signals.length > 1
26
+ ? Math.min(100, riskScore + signals.length * 3)
27
+ : riskScore;
28
+ const details = {
29
+ chain: input.chain,
30
+ userAddress: input.userAddress,
31
+ inputAddress: input.inputAddress,
32
+ historyScanned: input.historyScanned,
33
+ poisonTxFound: input.batchPoisonFound || input.transferFromFound,
34
+ zeroValueFound: input.zeroValueFound,
35
+ dustFound: input.dustFound,
36
+ };
37
+ return {
38
+ scam: top !== undefined && riskScore >= 70,
39
+ reason: top?.reason ?? null,
40
+ riskLevel: toRiskLevel(riskScore, signals.length),
41
+ similarityScore: input.similarityScore,
42
+ confidence,
43
+ matchedAddress: input.matchedAddress,
44
+ details,
45
+ error: null,
46
+ };
47
+ }
48
+ function toRiskLevel(score, count) {
49
+ if (score === 0)
50
+ return "none";
51
+ if (score < 50)
52
+ return "low";
53
+ if (score < 70)
54
+ return "medium";
55
+ if (score >= 90 || (score >= 80 && count >= 2))
56
+ return "critical";
57
+ return "high";
58
+ }
59
+ export function buildErrorResult(code, message, chain = "ethereum") {
60
+ return {
61
+ scam: false,
62
+ reason: null,
63
+ riskLevel: "none",
64
+ similarityScore: 0,
65
+ confidence: 0,
66
+ matchedAddress: null,
67
+ details: {
68
+ chain,
69
+ userAddress: "",
70
+ inputAddress: "",
71
+ historyScanned: 0,
72
+ poisonTxFound: false,
73
+ zeroValueFound: false,
74
+ dustFound: false,
75
+ },
76
+ error: { code: code, message },
77
+ };
78
+ }
79
+ //# sourceMappingURL=scorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.js","sourceRoot":"","sources":["../src/scorer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { ChainId, RiskLevel, ScamReason, CheckError, HistoryProvider, RawProvider } from "@hexora/core";
2
+ export interface CheckAddressParams {
3
+ userAddress: string;
4
+ inputAddress: string;
5
+ provider: RawProvider;
6
+ historyProvider?: HistoryProvider;
7
+ historyLimit?: number;
8
+ similarityThreshold?: number;
9
+ dustThreshold?: bigint;
10
+ apiKeys?: {
11
+ etherscan?: string;
12
+ bscscan?: string;
13
+ polygonscan?: string;
14
+ };
15
+ }
16
+ export interface CheckDetails {
17
+ chain: ChainId;
18
+ userAddress: string;
19
+ inputAddress: string;
20
+ historyScanned: number;
21
+ poisonTxFound: boolean;
22
+ zeroValueFound: boolean;
23
+ dustFound: boolean;
24
+ }
25
+ export interface CheckResult {
26
+ scam: boolean;
27
+ reason: ScamReason | null;
28
+ riskLevel: RiskLevel;
29
+ similarityScore: number;
30
+ confidence: number;
31
+ matchedAddress: string | null;
32
+ details: CheckDetails;
33
+ error: CheckError | null;
34
+ }
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,UAAU,EACV,UAAU,EACV,eAAe,EACf,WAAW,EACZ,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@hexora/address-guard",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@hexora/core": "1.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.5.4",
22
+ "vitest": "^1.6.0"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "test": "vitest run",
30
+ "lint": "eslint src",
31
+ "dev": "tsc --watch"
32
+ }
33
+ }