@mtnts/contract-client 0.0.6

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,6 @@
1
+ node_modules
2
+ dist
3
+ coverage
4
+ pnpm-lock.yaml
5
+ yarn.lock
6
+ package-lock.json
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@mtnts/contract-client",
3
+ "type": "module",
4
+ "main": "./dist/index.cjs",
5
+ "module": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "lint-staged": {
8
+ "*.{ts,js,json,md}": "prettier --write"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "dev": "tsup --watch",
13
+ "format": "prettier . --write",
14
+ "check-format": "prettier . --check",
15
+ "release": "pnpm build && npm version patch --no-git-tag-version && npm publish --access public"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^25.9.3",
19
+ "globby": "^16.2.0",
20
+ "husky": "^9.1.7",
21
+ "lint-staged": "^17.0.7",
22
+ "prettier": "^3.0.0",
23
+ "tsup": "^8.0.0",
24
+ "typescript": "^5.0.0"
25
+ },
26
+ "version": "0.0.6"
27
+ }
@@ -0,0 +1,2 @@
1
+ allowBuilds:
2
+ esbuild: true
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ semi: true,
3
+ singleQuote: false,
4
+ tabWidth: 2,
5
+ trailingComma: "all",
6
+ printWidth: 100,
7
+ arrowParens: "always",
8
+ };
@@ -0,0 +1,81 @@
1
+ import { globby } from "globby";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { abiToTs, getStructString } from "./io-to-ts";
5
+ import {
6
+ ask,
7
+ contractsTemplate,
8
+ createEventsName,
9
+ createMethodsName,
10
+ normalizePath,
11
+ writePrettyFile,
12
+ } from "./utils";
13
+ import { AbiItem } from "src/types";
14
+
15
+ type Abis = [string, AbiItem[]][];
16
+
17
+ function createGlobalDeclaration(content: string[] | string) {
18
+ const finalContent = Array.isArray(content) ? content.join("\n") : content;
19
+ return ` export {}
20
+ declare global {
21
+ ${finalContent}
22
+ }`;
23
+ }
24
+
25
+ function defineMethods(abiName: string, items: AbiItem[]) {
26
+ const content = items
27
+ .map((item) => {
28
+ const inputs = abiToTs(item.inputs, "in");
29
+ const outputs = abiToTs(item.outputs!, "out");
30
+ return `\n${item.name}: [${inputs},${outputs}]`;
31
+ })
32
+ .join("");
33
+ return createGlobalDeclaration(`type ${createMethodsName(abiName)} = {${content}}`);
34
+ }
35
+
36
+ function defineEvents(abiName: string, items: AbiItem[]) {
37
+ const content = items
38
+ .map((item) => {
39
+ return `\n${item.name}: ${abiToTs(item.inputs!, "out")}`;
40
+ })
41
+ .join("");
42
+ return createGlobalDeclaration(`type ${createEventsName(abiName)} = {${content}}`);
43
+ }
44
+
45
+ function defineAllContracts(abis: Abis) {
46
+ const content = abis.map(([name]) => {
47
+ return `\n${name}: ${createMethodsName(name)}`;
48
+ });
49
+ const allMehodsString = `type ContractMethods = RegistryHandlers<{${content}}, ContractOptions>`;
50
+ return createGlobalDeclaration(contractsTemplate + allMehodsString);
51
+ }
52
+
53
+ export async function generateBlockchainTypes() {
54
+ const rootPath = process.cwd();
55
+ const _input = await ask("type relative path: ");
56
+ const input = _input ? _input + "/" : "";
57
+ const outDir = path.resolve(rootPath, `src/${input}@bc-types`);
58
+
59
+ const filePaths = await globby(`src/${input}abis/*.abi.json`, {
60
+ cwd: rootPath,
61
+ absolute: true,
62
+ });
63
+
64
+ const abis = filePaths.map((filePath: string) => [
65
+ normalizePath(filePath),
66
+ JSON.parse(fs.readFileSync(filePath, "utf-8")),
67
+ ]) as Abis;
68
+
69
+ abis.forEach(async ([name, abiArray]) => {
70
+ const dir = path.resolve(outDir, name);
71
+ const methods = abiArray.filter((i) => i.type === "function");
72
+ const events = abiArray.filter((i) => i.type === "event");
73
+
74
+ writePrettyFile(`${name}.methods.d.ts`, defineMethods(name, methods), dir);
75
+ writePrettyFile(`${name}.events.d.ts`, defineEvents(name, events), dir);
76
+ });
77
+
78
+ const structsString = createGlobalDeclaration(getStructString());
79
+ writePrettyFile(`structs.d.ts`, structsString, outDir);
80
+ writePrettyFile(`contracts.d.ts`, defineAllContracts(abis), outDir);
81
+ }
@@ -0,0 +1,70 @@
1
+ const structs = new Map<string, string>();
2
+
3
+ function solidityPrimitiveToTs(type: string): string {
4
+ if (["string", "address"].includes(type) || type.startsWith("byte")) return "string";
5
+ if (type.startsWith("uint")) return "number";
6
+ if (type === "bool") return "boolean";
7
+ throw new Error(`[abiTypeToTsType] Not handle type: ${type}`);
8
+ }
9
+
10
+ function normalizeStructName(internalType?: string) {
11
+ if (!internalType) {
12
+ return "UnknownStruct";
13
+ }
14
+
15
+ const rawName = internalType.split(" ")[1] ?? "UnknownStruct";
16
+
17
+ return `BC${rawName.replace("[]", "").replaceAll(".", "")}`;
18
+ }
19
+
20
+ function buildTsObjectType(ios: any[], ioType: string): string {
21
+ if (!ios?.length) {
22
+ return ioType === "in" ? "null" : "void";
23
+ }
24
+
25
+ if (ios.length === 1 && ios[0].name === "" && ioType === "out") {
26
+ return abiTypeToTsType(ios[0], ioType);
27
+ }
28
+
29
+ const fields = ios
30
+ .map((item) => `${item.name || '""'}: ${abiTypeToTsType(item, ioType)}`)
31
+ .join("; ");
32
+
33
+ return `{ ${fields} }`;
34
+ }
35
+
36
+ function getOrCreateStructType(item: any, ioType: string): string {
37
+ const structName = normalizeStructName(item.internalType);
38
+
39
+ if (!structs.has(structName)) {
40
+ const body = buildTsObjectType(item.components ?? [], ioType);
41
+
42
+ structs.set(structName, `type ${structName} = ${body}`);
43
+ }
44
+
45
+ return structName;
46
+ }
47
+
48
+ function abiTypeToTsType(item: any, ioType: string): string {
49
+ const isArray = item.type.endsWith("[]");
50
+
51
+ if (item.type.includes("tuple")) {
52
+ const structName = getOrCreateStructType(item, ioType);
53
+
54
+ return isArray ? `${structName}[]` : structName;
55
+ }
56
+
57
+ const baseType = isArray ? item.type.slice(0, -2) : item.type;
58
+
59
+ const tsType = solidityPrimitiveToTs(baseType);
60
+
61
+ return isArray ? `${tsType}[]` : tsType;
62
+ }
63
+
64
+ export function abiToTs(ios: any[], ioType: string) {
65
+ return buildTsObjectType(ios, ioType);
66
+ }
67
+
68
+ export function getStructString() {
69
+ return [...structs.values()].join("\n");
70
+ }
@@ -0,0 +1,69 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import prettier from "prettier";
4
+ import readline from "readline";
5
+
6
+ export function ask(label: string): Promise<string> {
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+
12
+ return new Promise((resolve) => {
13
+ rl.question(label, (answer) => {
14
+ rl.close();
15
+
16
+ const rs = answer.trim();
17
+
18
+ resolve(rs);
19
+ });
20
+ });
21
+ }
22
+
23
+ export function capitalize(input: string) {
24
+ return input.charAt(0).toUpperCase() + input.slice(1);
25
+ }
26
+
27
+ export function createMethodsName(name: string) {
28
+ return `${capitalize(name)}Mehods`;
29
+ }
30
+
31
+ export function createEventsName(name: string) {
32
+ return `${capitalize(name)}Events`;
33
+ }
34
+
35
+ export function normalizePath(input: string) {
36
+ return path.basename(input, ".abi.json").split(/[-.]/g).join("");
37
+ }
38
+
39
+ export async function writePrettyFile(fileName: string, content: string, outDir: string) {
40
+ fs.mkdirSync(outDir, {
41
+ recursive: true,
42
+ });
43
+ const outFile = path.join(outDir, fileName);
44
+ const config = await prettier.resolveConfig(outFile);
45
+ const formatted = await prettier.format(content, {
46
+ ...config,
47
+ filepath: outFile,
48
+ });
49
+ fs.writeFileSync(outFile, formatted, "utf-8");
50
+ }
51
+
52
+ export const contractsTemplate = ` type HandlerDefinition<Input = unknown, Output = unknown> = [Input, Output];
53
+
54
+ type RegistrySchema = Record<string, Record<string, HandlerDefinition<unknown, unknown>>>;
55
+
56
+ type RegistryHandlers<TSchema extends RegistrySchema, TOptions = unknown> = {
57
+ [TGroup in keyof TSchema]: {
58
+ [TKey in keyof TSchema[TGroup]]: (
59
+ input: TSchema[TGroup][TKey][0],
60
+ options?: TOptions,
61
+ ) => Promise<TSchema[TGroup][TKey][1]>;
62
+ };
63
+ };
64
+ type ContractOptions = {
65
+ from?: string
66
+ to?: string
67
+ gas?: string | number
68
+ }
69
+ `;
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ console.log("hello lib");
package/src/types.ts ADDED
@@ -0,0 +1,20 @@
1
+ export interface AbiIO {
2
+ indexed?: boolean;
3
+ internalType?: string;
4
+ name?: string;
5
+ type: string;
6
+ components?: AbiIO[];
7
+ [key: string]: any;
8
+ }
9
+
10
+ export interface AbiItem {
11
+ inputs: AbiIO[];
12
+ type: string;
13
+ name?: string;
14
+ outputs?: AbiIO[];
15
+ stateMutability?: string;
16
+ anonymous?: boolean;
17
+ indexed?: boolean;
18
+ internalType?: string;
19
+ [key: string]: any;
20
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2021",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "declaration": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "outDir": "dist",
11
+ "baseUrl": "."
12
+ },
13
+ "include": ["src"]
14
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig([
4
+ {
5
+ entry: ["src/index.ts"],
6
+ format: ["esm", "cjs"],
7
+ dts: true,
8
+ sourcemap: true,
9
+ clean: true,
10
+ minify: false,
11
+ target: "es2020",
12
+ outDir: "dist",
13
+ },
14
+ {
15
+ entry: {
16
+ cli: "src/cli/index.ts",
17
+ },
18
+ format: ["cjs"],
19
+ platform: "node",
20
+ external: ["fast-glob", "prettier"],
21
+ banner: {
22
+ js: "#!/usr/bin/env node",
23
+ },
24
+ tsconfig: "./tsconfig.json", // Buộc tsup đọc tsconfig
25
+ },
26
+ ]);