diff --git a/README.md b/README.md index 4d18487..3cfffbe 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ import {createMulticallRequest, multicall} from 'starknet_multicall' const calls = [ createMulticallRequest('', , '', [, ]), -] +] as const const [result1] = await multicall(calls, , ) ``` diff --git a/eslint.config.js b/eslint.config.js index 13586a6..c0c4683 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -21,7 +21,7 @@ import pluginPromise from "eslint-plugin-promise"; import * as pluginRegexp from "eslint-plugin-regexp"; import pluginSecurity from "eslint-plugin-security"; import pluginSimpleImportSort from "eslint-plugin-simple-import-sort"; -import pluginSonarjs from "eslint-plugin-sonarjs"; +// import pluginSonarjs from "eslint-plugin-sonarjs"; import pluginVitest from "eslint-plugin-vitest"; import globals from "globals"; // eslint-disable-next-line import-x/no-unresolved -- import-x error @@ -79,67 +79,67 @@ const applyToVitest = createApplyTo( //------------------------------------------------------------------------------ const coreConfigs = [ - ...applyToAll("core/eslint-recommended", eslint.configs.recommended), - ...applyToAll("core/security", pluginSecurity.configs.recommended), - ...applyToAll("core/promise", pluginPromise.configs["flat/recommended"]), - ...applyToAll("core/import-x", ...compat.config(pluginImportX.configs.recommended)), - ...applyToAll("core/no-use-extend-native", pluginNoUseExtendNative.configs.recommended), - ...applyToAll("core/eslint-comments", { + ...applyToAll('core/eslint-recommended', eslint.configs.recommended), + ...applyToAll('core/security', pluginSecurity.configs.recommended), + ...applyToAll('core/promise', pluginPromise.configs['flat/recommended']), + ...applyToAll('core/import-x', ...compat.config(pluginImportX.configs.recommended)), + ...applyToAll('core/no-use-extend-native', pluginNoUseExtendNative.configs.recommended), + ...applyToAll('core/eslint-comments', { ...pluginEslintComments.configs.recommended, // workaround for https://github.com/eslint-community/eslint-plugin-eslint-comments/issues/215 plugins: { - "@eslint-community/eslint-comments": pluginEslintComments, + '@eslint-community/eslint-comments': pluginEslintComments, }, }), - ...applyToAll("core/regexp", pluginRegexp.configs["flat/recommended"]), + ...applyToAll('core/regexp', pluginRegexp.configs['flat/recommended']), - ...applyToAll("core/depend", pluginDepend.configs["flat/recommended"]), - ...applyToAll("core/sonarjs", pluginSonarjs.configs.recommended), // drop this if using SonarQube or SonarCloud in favor of the IDE extension - ...applyToAll("core/no-relative-import-paths", { + ...applyToAll('core/depend', pluginDepend.configs['flat/recommended']), + // ...applyToAll("core/sonarjs", pluginSonarjs.configs.recommended), // drop this if using SonarQube or SonarCloud in favor of the IDE extension + ...applyToAll('core/no-relative-import-paths', { plugins: { - "no-relative-import-paths": pluginNoRelativeImportPaths, + 'no-relative-import-paths': pluginNoRelativeImportPaths, }, rules: { - "no-relative-import-paths/no-relative-import-paths": [ - "warn", - { allowSameFolder: true, rootDir: "src", prefix: "@" }, + 'no-relative-import-paths/no-relative-import-paths': [ + 'warn', + {allowSameFolder: true, rootDir: 'src', prefix: '@'}, ], }, }), - ...applyToAll("core/simple-import-sort", { + ...applyToAll('core/simple-import-sort', { plugins: { - "simple-import-sort": pluginSimpleImportSort, + 'simple-import-sort': pluginSimpleImportSort, }, rules: { - "sort-imports": "off", - "simple-import-sort/imports": "error", - "simple-import-sort/exports": "error", + 'sort-imports': 'off', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', }, }), - ...applyToAll("core/no-barrel-files", { + ...applyToAll('core/no-barrel-files', { plugins: { - "no-barrel-files": pluginNoBarrelFiles, // switch to eslint-plugin-barrel-files? + 'no-barrel-files': pluginNoBarrelFiles, // switch to eslint-plugin-barrel-files? }, rules: { - "no-barrel-files/no-barrel-files": "error", + 'no-barrel-files/no-barrel-files': 'error', }, }), - ...applyToAll("core/no-secrets", { + ...applyToAll('core/no-secrets', { plugins: { - "no-secrets": pluginNoSecrets, + 'no-secrets': pluginNoSecrets, }, rules: { - "no-secrets/no-secrets": [ - "error", + 'no-secrets/no-secrets': [ + 'error', { tolerance: 4.5, }, ], }, }), - ...applyToAll("core/exception-handling", { + ...applyToAll('core/exception-handling', { plugins: { - "exception-handling": pluginExceptionHandling, + 'exception-handling': pluginExceptionHandling, }, rules: { // 'exception-handling/no-unhandled': 'error', @@ -148,7 +148,7 @@ const coreConfigs = [ // 'plugin:jsdoc/recommended-typescript', // TODO: To be added later // 'plugin:unicorn/recommended', // TODO: To be added later // 'plugin:isaacscript/recommended' // TODO: To be added later -]; +] const jsonConfigs = [ ...applyToJson("json/json", pluginJsonc.configs["flat/recommended-with-json"]), @@ -262,20 +262,20 @@ const testConfigs = [ const config = tsEslint.config( pluginGitignore({ root: true, - files: [".gitignore"], + files: ['.gitignore'], strict: false, }), { - ignores: ["public/*", "**/*.gen.ts", "vitest.config.ts.timestamp*", "src/abis/**/*"], + ignores: ['public/*', '**/*.gen.ts', 'vitest.config.ts.timestamp*', 'src/abis/**/*'], }, ...coreConfigs, ...jsonConfigs, ...typescriptConfigs, ...testConfigs, - ...applyToAll("core", { + ...applyToAll('core', { languageOptions: { - sourceType: "module", - ecmaVersion: "latest", + sourceType: 'module', + ecmaVersion: 'latest', parserOptions: { ecmaFeatures: { impliedStrict: true, @@ -291,9 +291,9 @@ const config = tsEslint.config( }, }, rules: { - "import-x/no-unresolved": "error", - "import-x/order": "off", - "import-x/namespace": "off", + 'import-x/no-unresolved': 'error', + 'import-x/order': 'off', + 'import-x/namespace': 'off', // 'unicorn/better-regex': 'warn', // 'unicorn/filename-case': [ // 'error', @@ -304,21 +304,21 @@ const config = tsEslint.config( // } // } // ], - "@eslint-community/eslint-comments/require-description": [ - "error", - { ignore: ["eslint-enable"] }, + '@eslint-community/eslint-comments/require-description': [ + 'error', + {ignore: ['eslint-enable']}, ], - "sonarjs/no-duplicate-string": "warn", - "promise/always-return": ["warn", { ignoreLastCallback: true }], - "promise/no-callback-in-promise": [ - "warn", + // "sonarjs/no-duplicate-string": "warn", + 'promise/always-return': ['warn', {ignoreLastCallback: true}], + 'promise/no-callback-in-promise': [ + 'warn', { - exceptions: ["process.nextTick", "setImmediate", "setTimeout"], + exceptions: ['process.nextTick', 'setImmediate', 'setTimeout'], }, ], }, }), - ...applyToAll("prettier", pluginPrettierRecommended) // Always the last -); + ...applyToAll('prettier', pluginPrettierRecommended), // Always the last +) export default config; diff --git a/package.json b/package.json index b1709bc..ba27975 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "starknet_multicall", - "version": "1.0.3", + "version": "1.0.4", "type": "module", "sideEffects": false, "moduleSideEffects": false, @@ -48,13 +48,13 @@ "packageManager": "pnpm@9.14.4", "dependencies": { "abi-wan-kanabi": "^2.2.3", - "starknet": "^6.20.3" + "starknet": "^6.20.3", + "tiny-invariant": "^1.3.3" }, "peerDependencies": { "abi-wan-kanabi": "^2.2.3" }, "devDependencies": { - "jiti": "^2.4.1", "@commitlint/cli": "^19.6.0", "@eslint-community/eslint-plugin-eslint-comments": "^4.4.1", "@eslint-react/eslint-plugin": "^1.17.2", @@ -96,6 +96,7 @@ "eslint-plugin-vitest": "^0.5.4", "globals": "^15.13.0", "husky": "^9.1.7", + "jiti": "^2.4.1", "lint-staged": "^15.2.10", "prettier": "^3.4.1", "tsup": "^8.3.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03144b4..05aa0c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: starknet: specifier: ^6.20.3 version: 6.20.3 + tiny-invariant: + specifier: ^1.3.3 + version: 1.3.3 devDependencies: '@commitlint/cli': specifier: ^19.6.0 @@ -4223,6 +4226,9 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -9148,6 +9154,8 @@ snapshots: through@2.3.8: {} + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@0.3.1: {} diff --git a/src/multicall.ts b/src/multicall.ts index 2dc5e8b..b8400bf 100644 --- a/src/multicall.ts +++ b/src/multicall.ts @@ -12,6 +12,7 @@ import { type ProviderInterface, type RawArgs, } from 'starknet' +import invariant from 'tiny-invariant' import MulticallABI from '../artifacts/MulticallABI' @@ -26,12 +27,11 @@ export type ContractMethodArgs< export function createMulticallRequest< ContractAbi extends Abi, Method extends ExtractAbiFunctionNames, - Args extends ContractMethodArgs, >( address: string, abi: ContractAbi, method: Method, - args?: Args, + args?: ContractMethodArgs, ): { abi: ContractAbi contractAddress: string @@ -56,11 +56,11 @@ export interface CallAndAbi { calldata: bigint[] } -export type MulticallResult = Promise<{ +export type MulticallResult = Promise<{ [k in keyof Ts]: FunctionRet }> -export async function multicall( +export async function multicall( calls: Ts, multicallAddress: string, providerOrAccount: ProviderInterface | AccountInterface, @@ -79,12 +79,15 @@ export async function multicall( }), ) + const result = results[1] + invariant(Array.isArray(result), 'Multicall failed, response is not an array') + // @ts-expect-error -- too complex - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call -- its safe - return results[1].map((result, index) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-member-access -- guranteed - const callData = new CallData(calls[index]!.abi) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-member-access -- guranteed - return callData.parse(calls[index]!.entrypoint, result as unknown as string[]) + return result.map((result, index) => { + const call = calls[index] + invariant(call, 'Multicall failed, call is undefined') + + const callData = new CallData(call.abi) + return callData.parse(call.entrypoint, result as string[]) }) }