@logrix/sdk 0.3.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 ADDED
@@ -0,0 +1,231 @@
1
+ # @logrix/sdk
2
+
3
+ Official CLI and AssemblyScript runtime library for [Logrix](https://github.com/Vishal-770/Logrix) blockchain indexers.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@logrix/sdk)](https://www.npmjs.com/package/@logrix/sdk)
6
+
7
+ ---
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ npx @logrix/sdk init my-indexer
13
+ cd my-indexer
14
+ npm install
15
+ npm run codegen
16
+ npm run validate
17
+ npm run build
18
+ npm test
19
+ ```
20
+
21
+ ---
22
+
23
+ ## CLI Commands
24
+
25
+ ### `logrix init [name]`
26
+
27
+ Scaffolds a new indexer project. Run interactively or pass flags for CI/CD automation.
28
+
29
+ Options:
30
+ - `-y, --yes` -- non-interactive mode using sensible defaults
31
+ - `--network <name>` -- network preset (`arbitrum-one`, `ethereum`, `base`, `polygon`, `sepolia`)
32
+ - `--rpc <url>` -- custom RPC endpoint
33
+ - `--contract-name <name>` -- contract name
34
+ - `--address <address>` -- target contract 0x address
35
+ - `--start-block <block>` -- initial indexing block
36
+
37
+ Creates:
38
+ ```
39
+ my-indexer/
40
+ logrix.yaml # indexer manifest: network, contracts, ABIs, WASM path
41
+ schema.graphql # entity definitions
42
+ abis/
43
+ TokenContract.json # contract ABI
44
+ src/
45
+ mapping.ts # handler logic
46
+ package.json
47
+ tsconfig.json
48
+ .gitignore
49
+ README.md
50
+ ```
51
+
52
+ ---
53
+
54
+ ### `logrix add contract <name>`
55
+
56
+ Adds an additional smart contract to an existing `logrix.yaml` project for multi-contract indexing.
57
+
58
+ ```bash
59
+ # Add with manual ABI path
60
+ npx logrix add contract SecondaryToken \
61
+ --address 0x1111111111111111111111111111111111111111 \
62
+ --abi ./my-abis/SecondaryToken.json \
63
+ --start-block 1000000
64
+
65
+ # Add with automatic ABI fetch from Etherscan / Sourcify
66
+ npx logrix add contract DaiToken \
67
+ --address 0x6b175474e89094c44da98b954eedeac495271d0f \
68
+ --fetch-abi
69
+ ```
70
+
71
+ Options:
72
+ - `--address <address>` -- contract address
73
+ - `--abi <path>` -- path to local ABI JSON
74
+ - `--fetch-abi` -- auto-fetch verified contract ABI from Etherscan / Sourcify
75
+ - `--etherscan-key <key>` -- optional Etherscan API key for rate limits
76
+ - `--start-block <block>` -- initial indexing block
77
+
78
+ ---
79
+
80
+ ### `logrix validate`
81
+
82
+ Validates your `logrix.yaml`, contract addresses, ABI files, `schema.graphql` entities, and handler mappings before compilation.
83
+
84
+ ```bash
85
+ npx logrix validate
86
+ ```
87
+
88
+ ---
89
+
90
+ ### `logrix codegen`
91
+
92
+ Parses `logrix.yaml` dataSources, ABI files, and `schema.graphql` to generate typed AssemblyScript bindings.
93
+
94
+ ```bash
95
+ npx logrix codegen
96
+ ```
97
+
98
+ Outputs:
99
+ - `src/generated/events.ts` -- typed event and parameter classes (namespaced per contract), multi-contract address constants, and router helpers
100
+ - `src/generated/schema.ts` -- entity classes with static `.load(id)`, `.remove(id)`, and `.save()`, handling `@derivedFrom` relationships
101
+
102
+ ---
103
+
104
+ ### `logrix build`
105
+
106
+ Compiles your AssemblyScript handler into an optimized WebAssembly module (`build/mapping.wasm`).
107
+
108
+ ```bash
109
+ npx logrix build
110
+ ```
111
+
112
+ Options:
113
+ - `--entry <file>` -- handler entry point (default: `src/mapping.ts`)
114
+ - `--out <file>` -- output WASM binary path (default: `build/mapping.wasm`)
115
+ - `--debug` -- compile without optimizations for debugging
116
+
117
+ ---
118
+
119
+ ### `logrix export-values`
120
+
121
+ Reads `logrix.yaml`, `schema.graphql`, and `build/mapping.wasm` to produce a ready-to-deploy `indexer-values.yaml` for Helm.
122
+
123
+ ```bash
124
+ npx logrix export-values
125
+ ```
126
+
127
+ Options:
128
+ - `--out <file>` -- output values file (default: `indexer-values.yaml`)
129
+ - `--chain-id <id>` -- override chain ID
130
+ - `--rpc <url>` -- override RPC URL
131
+
132
+ Deploy directly to Kubernetes:
133
+ ```bash
134
+ helm install logrix oci://ghcr.io/vishal-770/charts/logrix \
135
+ -f cluster-infra.yaml \
136
+ -f indexer-values.yaml
137
+ ```
138
+
139
+ ---
140
+
141
+ ### `logrix test`
142
+
143
+ Runs local test validation across configuration, code generation, and WASM compilation, and executes any unit tests in `tests/`.
144
+
145
+ ```bash
146
+ npx logrix test
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Runtime API
152
+
153
+ Imported via `import { ... } from "@logrix/sdk"` in AssemblyScript handlers:
154
+
155
+ | Export | Description |
156
+ |--------|-------------|
157
+ | `store.get(entity, id)` | Load raw entity JSON string from persistent store |
158
+ | `store.set(entity, id, data)` | Write entity to persistent store and stage for DB |
159
+ | `store.remove(entity, id)` | Mark entity as deleted and stage soft-delete |
160
+ | `Entity.load(id)` | Auto-generated on each `@entity` class; loads existing entity or returns `null` |
161
+ | `Entity.remove(id)` | Auto-generated on each `@entity` class; removes entity by ID |
162
+ | `entity.save()` | Saves and persists entity state |
163
+ | `BigInt` | EVM 256-bit safe arithmetic: `.plus()`, `.minus()`, `.times()`, `.div()`, `.equals()`, `.gt()`, `.lt()` |
164
+ | `Address` | Address wrapper: `.fromString()`, `.toHexString()`, `.equals()` |
165
+ | `Bytes` | Hex byte wrapper: `.fromHexString()`, `.fromUTF8()`, `.toHexString()`, `.toByteArray()`, `.length` |
166
+ | `crypto.keccak256(data)` | Computes standard EVM Keccak-256 hash |
167
+ | `formatUnits(val, decimals)` | Formats raw BigInt value into human-readable decimal string (default: 18) |
168
+ | `log` | Structured logging: `log.info()`, `log.warning()`, `log.error()`, `log.debug()` |
169
+ | `allocate(size)` | Guest memory allocator (must be re-exported by `mapping.ts`) |
170
+
171
+ ---
172
+
173
+ ## Writing Handlers (`src/mapping.ts`)
174
+
175
+ ```typescript
176
+ import { EventLog, log, BigInt, Address, crypto, formatUnits, allocate } from "@logrix/sdk";
177
+ import { TransferEvent } from "./generated/events";
178
+ import { Transfer } from "./generated/schema";
179
+
180
+ export { allocate } from "@logrix/sdk";
181
+
182
+ export function handle_event(ptr: i32, len: i32): i32 {
183
+ const bytes = new Uint8Array(len);
184
+ for (let i = 0; i < len; i++) {
185
+ bytes[i] = load<u8>(ptr + i);
186
+ }
187
+ const raw = String.UTF8.decode(bytes.buffer);
188
+
189
+ if (raw.includes("Transfer")) {
190
+ return handleTransfer(raw);
191
+ }
192
+ return 0;
193
+ }
194
+
195
+ function handleTransfer(raw: string): i32 {
196
+ const event = new TransferEvent(new EventLog());
197
+
198
+ const entityId = event.transactionHash + "-" + event.logIndex.toString();
199
+
200
+ // Load existing entity or instantiate a new one
201
+ let entity = Transfer.load(entityId);
202
+ if (!entity) {
203
+ entity = new Transfer(entityId);
204
+ }
205
+
206
+ entity.fromAddress = event.params.from.toHexString();
207
+ entity.toAddress = event.params.to.toHexString();
208
+ entity.amount = event.params.value; // Typed BigInt
209
+ entity.save();
210
+
211
+ log.info("Indexed transfer: " + entityId);
212
+ return 0;
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Deployment
219
+
220
+ 1. Use `@logrix/sdk` to build and export your indexer:
221
+ ```bash
222
+ logrix codegen && logrix build && logrix export-values
223
+ ```
224
+ 2. Deploy via Helm to your Kubernetes cluster:
225
+ ```bash
226
+ helm install logrix oci://ghcr.io/vishal-770/charts/logrix \
227
+ -f cluster-infra.yaml \
228
+ -f indexer-values.yaml
229
+ ```
230
+
231
+ Refer to the [Logrix Helm Chart](https://github.com/Vishal-770/Logrix) for full Helm deployment options.
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.addCommand = addCommand;
37
+ const commander_1 = require("commander");
38
+ const prompts_1 = require("@inquirer/prompts");
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const yaml_1 = require("yaml");
42
+ const templates_1 = require("../templates");
43
+ /**
44
+ * Fetch ABI from Etherscan (mainnet) or Sourcify as fallback.
45
+ * Returns the parsed ABI array, or null if both fail.
46
+ */
47
+ async function fetchAbi(address, etherscanKey) {
48
+ const addr = address.toLowerCase();
49
+ // Try Etherscan first if API key provided
50
+ if (etherscanKey) {
51
+ try {
52
+ const url = `https://api.etherscan.io/api?module=contract&action=getabi&address=${addr}&apikey=${etherscanKey}`;
53
+ const resp = await fetch(url);
54
+ const json = await resp.json();
55
+ if (json.status === "1" && json.result) {
56
+ const parsed = JSON.parse(json.result);
57
+ if (Array.isArray(parsed) && parsed.length > 0) {
58
+ console.log(` Fetched ABI from Etherscan for ${address}`);
59
+ return parsed;
60
+ }
61
+ }
62
+ }
63
+ catch {
64
+ // fall through
65
+ }
66
+ }
67
+ // Try Sourcify (no key required)
68
+ try {
69
+ // Sourcify returns verified contracts at chainId 1 (mainnet)
70
+ const url = `https://repo.sourcify.dev/contracts/full_match/1/${addr}/metadata.json`;
71
+ const resp = await fetch(url);
72
+ if (resp.ok) {
73
+ const json = await resp.json();
74
+ const abi = json?.output?.abi;
75
+ if (Array.isArray(abi) && abi.length > 0) {
76
+ console.log(` Fetched ABI from Sourcify for ${address}`);
77
+ return abi;
78
+ }
79
+ }
80
+ }
81
+ catch {
82
+ // fall through
83
+ }
84
+ return null;
85
+ }
86
+ function addCommand() {
87
+ const cmd = new commander_1.Command("add");
88
+ cmd
89
+ .description("Add components to an existing Logrix indexer project")
90
+ .argument("<type>", "Type of component to add (e.g. contract)")
91
+ .argument("[name]", "Name of the component")
92
+ .option("--address <address>", "Contract address")
93
+ .option("--abi <path>", "Path to ABI JSON file")
94
+ .option("--fetch-abi", "Auto-fetch ABI from Etherscan / Sourcify")
95
+ .option("--etherscan-key <key>", "Etherscan API key for ABI fetch (optional)")
96
+ .option("--start-block <block>", "Starting block number", "0")
97
+ .action(async (type, nameArg, opts) => {
98
+ const projectDir = process.cwd();
99
+ const configPath = path.join(projectDir, "logrix.yaml");
100
+ if (!fs.existsSync(configPath)) {
101
+ console.error("Error: logrix.yaml not found. Please run this command from the project root.");
102
+ process.exit(1);
103
+ }
104
+ if (type !== "contract") {
105
+ console.error(`Error: Unknown component type '${type}'. Supported types: contract`);
106
+ process.exit(1);
107
+ }
108
+ let config;
109
+ try {
110
+ config = (0, yaml_1.parse)(fs.readFileSync(configPath, "utf-8"));
111
+ }
112
+ catch (e) {
113
+ console.error(`Error parsing logrix.yaml: ${e.message}`);
114
+ process.exit(1);
115
+ }
116
+ if (!config.dataSources) {
117
+ config.dataSources = [];
118
+ }
119
+ // 1. Resolve contract name
120
+ let contractName = nameArg;
121
+ if (!contractName) {
122
+ if (process.stdin.isTTY) {
123
+ contractName = await (0, prompts_1.input)({
124
+ message: "Smart Contract Name:",
125
+ default: "SecondaryContract",
126
+ validate: (v) => /^[A-Za-z][A-Za-z0-9]*$/.test(v.trim()) ||
127
+ "Must be alphanumeric, starting with a letter",
128
+ });
129
+ }
130
+ else {
131
+ contractName = "SecondaryContract";
132
+ }
133
+ }
134
+ contractName = contractName.trim();
135
+ // Check for collision
136
+ const exists = config.dataSources.some((ds) => ds.name === contractName);
137
+ if (exists) {
138
+ console.error(`Error: A contract named '${contractName}' is already defined in logrix.yaml.`);
139
+ process.exit(1);
140
+ }
141
+ // 2. Resolve contract address
142
+ let contractAddress = opts.address;
143
+ if (!contractAddress) {
144
+ if (process.stdin.isTTY) {
145
+ contractAddress = await (0, prompts_1.input)({
146
+ message: "Target Contract Address:",
147
+ default: "0x0000000000000000000000000000000000000000",
148
+ validate: (v) => /^0x[0-9a-fA-F]{40}$/.test(v.trim()) || "Must be a valid 0x hex address",
149
+ });
150
+ }
151
+ else {
152
+ contractAddress = "0x0000000000000000000000000000000000000000";
153
+ }
154
+ }
155
+ contractAddress = contractAddress.trim();
156
+ // 3. Resolve start block
157
+ let startBlockStr = opts.startBlock;
158
+ if (!opts.address && process.stdin.isTTY) {
159
+ startBlockStr = await (0, prompts_1.input)({
160
+ message: "Start Block:",
161
+ default: "0",
162
+ validate: (v) => /^\d+$/.test(v.trim()) || "Must be a non-negative integer",
163
+ });
164
+ }
165
+ const startBlock = parseInt(startBlockStr.trim(), 10);
166
+ // 4. Resolve ABI
167
+ const abisDir = path.join(projectDir, "abis");
168
+ fs.mkdirSync(abisDir, { recursive: true });
169
+ const targetAbiFile = path.join(abisDir, `${contractName}.json`);
170
+ if (opts.abi) {
171
+ // Explicit local file
172
+ const sourceAbi = path.resolve(projectDir, opts.abi);
173
+ if (!fs.existsSync(sourceAbi)) {
174
+ console.error(`Error: Specified ABI file not found: ${opts.abi}`);
175
+ process.exit(1);
176
+ }
177
+ fs.copyFileSync(sourceAbi, targetAbiFile);
178
+ console.log(` Copied ABI from ${opts.abi} to abis/${contractName}.json`);
179
+ }
180
+ else if (opts.fetchAbi) {
181
+ // Auto-fetch from Etherscan / Sourcify
182
+ console.log(` Fetching ABI for ${contractAddress} ...`);
183
+ const abi = await fetchAbi(contractAddress, opts.etherscanKey);
184
+ if (abi) {
185
+ fs.writeFileSync(targetAbiFile, JSON.stringify(abi, null, 2));
186
+ console.log(` Saved fetched ABI to abis/${contractName}.json`);
187
+ }
188
+ else {
189
+ console.warn(` Warning: Could not fetch ABI for ${contractAddress}. Using ERC-20 placeholder.`);
190
+ fs.writeFileSync(targetAbiFile, JSON.stringify(templates_1.ERC20_ABI, null, 2));
191
+ }
192
+ }
193
+ else if (!fs.existsSync(targetAbiFile)) {
194
+ // Default placeholder
195
+ fs.writeFileSync(targetAbiFile, JSON.stringify(templates_1.ERC20_ABI, null, 2));
196
+ console.log(` Created default ERC-20 placeholder ABI at abis/${contractName}.json`);
197
+ }
198
+ // 5. Append new dataSource to logrix.yaml
199
+ const networkName = config.network?.name || "mainnet";
200
+ const newDataSource = {
201
+ kind: "ethereum/contract",
202
+ name: contractName,
203
+ network: networkName,
204
+ source: {
205
+ address: contractAddress,
206
+ abi: contractName,
207
+ startBlock: startBlock,
208
+ },
209
+ mapping: {
210
+ kind: "wasm/assemblyscript",
211
+ file: "./build/mapping.wasm",
212
+ abis: [
213
+ {
214
+ name: contractName,
215
+ file: `./abis/${contractName}.json`,
216
+ },
217
+ ],
218
+ eventHandlers: [
219
+ {
220
+ event: "Transfer(address indexed,address indexed,uint256)",
221
+ handler: `handle${contractName}Transfer`,
222
+ },
223
+ ],
224
+ },
225
+ };
226
+ config.dataSources.push(newDataSource);
227
+ fs.writeFileSync(configPath, (0, yaml_1.stringify)(config, { indent: 2 }));
228
+ console.log(`\nSuccessfully added contract '${contractName}' to logrix.yaml!`);
229
+ console.log("Next steps:");
230
+ console.log(` 1. Review / replace ABI at: abis/${contractName}.json`);
231
+ console.log(" 2. Run: logrix codegen");
232
+ console.log(" 3. Run: logrix build\n");
233
+ });
234
+ return cmd;
235
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildCommand = buildCommand;
37
+ const commander_1 = require("commander");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const child_process_1 = require("child_process");
41
+ const yaml_1 = require("yaml");
42
+ function buildCommand() {
43
+ const cmd = new commander_1.Command("build");
44
+ cmd
45
+ .description("Compile AssemblyScript handler to WASM using asc")
46
+ .option("--entry <file>", "Handler entry point", "src/mapping.ts")
47
+ .option("--out <file>", "Output WASM path", "build/mapping.wasm")
48
+ .option("--debug", "Build without optimization (for debugging)")
49
+ .action((opts) => {
50
+ const projectDir = process.cwd();
51
+ // Resolve entry and output paths
52
+ const entryFile = path.resolve(projectDir, opts.entry);
53
+ const outFile = path.resolve(projectDir, opts.out);
54
+ if (!fs.existsSync(entryFile)) {
55
+ console.error(`Error: entry file not found: ${opts.entry}`);
56
+ process.exit(1);
57
+ }
58
+ // Ensure output directory exists
59
+ fs.mkdirSync(path.dirname(outFile), { recursive: true });
60
+ // Prefer logrix.yaml mapping.file if present
61
+ const configPath = path.join(projectDir, "logrix.yaml");
62
+ let resolvedEntry = entryFile;
63
+ if (fs.existsSync(configPath)) {
64
+ try {
65
+ const config = (0, yaml_1.parse)(fs.readFileSync(configPath, "utf-8"));
66
+ const mappingFile = config?.dataSources?.[0]?.mapping?.file;
67
+ if (mappingFile) {
68
+ const fromConfig = path.resolve(projectDir, mappingFile);
69
+ if (fs.existsSync(fromConfig)) {
70
+ resolvedEntry = fromConfig;
71
+ }
72
+ }
73
+ }
74
+ catch {
75
+ // fallback to opts.entry
76
+ }
77
+ }
78
+ // Locate asc: prefer local node_modules, fall back to global
79
+ let ascBin = path.join(projectDir, "node_modules", ".bin", "asc");
80
+ if (!fs.existsSync(ascBin)) {
81
+ ascBin = "asc"; // global
82
+ }
83
+ const optimizeFlag = opts.debug ? "" : "--optimize";
84
+ const cmd = [
85
+ ascBin,
86
+ resolvedEntry,
87
+ "--config", "tsconfig.json",
88
+ "-o", outFile,
89
+ optimizeFlag,
90
+ "--exportRuntime",
91
+ ]
92
+ .filter(Boolean)
93
+ .join(" ");
94
+ console.log(`Running: ${cmd}\n`);
95
+ try {
96
+ (0, child_process_1.execSync)(cmd, { cwd: projectDir, stdio: "inherit" });
97
+ const stat = fs.statSync(outFile);
98
+ console.log(`\nBuild complete: ${path.relative(projectDir, outFile)} (${stat.size} bytes)`);
99
+ }
100
+ catch {
101
+ console.error("\nBuild failed.");
102
+ process.exit(1);
103
+ }
104
+ });
105
+ return cmd;
106
+ }