@sparkdotfi/abi-cli 0.0.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.
Files changed (53) hide show
  1. package/README.md +66 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +11 -0
  5. package/dist/common.d.ts +7 -0
  6. package/dist/common.d.ts.map +1 -0
  7. package/dist/common.js +5 -0
  8. package/dist/config/Config.d.ts +19 -0
  9. package/dist/config/Config.d.ts.map +1 -0
  10. package/dist/config/Config.js +32 -0
  11. package/dist/config/schema.d.ts +135 -0
  12. package/dist/config/schema.d.ts.map +1 -0
  13. package/dist/config/schema.js +48 -0
  14. package/dist/fetch/AbiFetcher.d.ts +29 -0
  15. package/dist/fetch/AbiFetcher.d.ts.map +1 -0
  16. package/dist/fetch/AbiFetcher.js +63 -0
  17. package/dist/fetch/BlockchainClientRepository.d.ts +29 -0
  18. package/dist/fetch/BlockchainClientRepository.d.ts.map +1 -0
  19. package/dist/fetch/BlockchainClientRepository.js +51 -0
  20. package/dist/fetch/fetchAbis.d.ts +8 -0
  21. package/dist/fetch/fetchAbis.d.ts.map +1 -0
  22. package/dist/fetch/fetchAbis.js +38 -0
  23. package/dist/generate/dataProcessing.d.ts +8 -0
  24. package/dist/generate/dataProcessing.d.ts.map +1 -0
  25. package/dist/generate/dataProcessing.js +25 -0
  26. package/dist/generate/formatOutput.d.ts +2 -0
  27. package/dist/generate/formatOutput.d.ts.map +1 -0
  28. package/dist/generate/formatOutput.js +19 -0
  29. package/dist/generate/generate.d.ts +11 -0
  30. package/dist/generate/generate.d.ts.map +1 -0
  31. package/dist/generate/generate.js +56 -0
  32. package/dist/index.d.ts +5 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +4 -0
  35. package/dist/main.d.ts +4 -0
  36. package/dist/main.d.ts.map +1 -0
  37. package/dist/main.js +11 -0
  38. package/dist/services/ProgressBar.d.ts +19 -0
  39. package/dist/services/ProgressBar.d.ts.map +1 -0
  40. package/dist/services/ProgressBar.js +35 -0
  41. package/dist/test/loadModuleFromString.d.ts +2 -0
  42. package/dist/test/loadModuleFromString.d.ts.map +1 -0
  43. package/dist/test/loadModuleFromString.js +14 -0
  44. package/dist/test/mockConfig.d.ts +5 -0
  45. package/dist/test/mockConfig.d.ts.map +1 -0
  46. package/dist/test/mockConfig.js +15 -0
  47. package/dist/types.d.ts +27 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +1 -0
  50. package/dist/verify/verify.d.ts +13 -0
  51. package/dist/verify/verify.d.ts.map +1 -0
  52. package/dist/verify/verify.js +35 -0
  53. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @sparkdotfi/abi-cli
2
+
3
+ CLI tool for automatically fetching and generating ABIs from blockchain contracts across multiple networks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @sparkdotfi/abi-cli
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ Create `abi-cli.config.ts` in your project root:
14
+
15
+ ```typescript
16
+ import { GlobalConfig } from '@sparkdotfi/abi-cli'
17
+ import { getEnv } from '@sparkdotfi/common-nodejs/env'
18
+
19
+ const env = getEnv()
20
+
21
+ const config: GlobalConfig = {
22
+ out: 'out',
23
+ keys: {
24
+ alchemyApiKey: env.string('ALCHEMY_API_KEY'),
25
+ drpcApiKey: env.string('DRPC_API_KEY'),
26
+ etherscanApiKey: env.string('ETHERSCAN_API_KEY'),
27
+ },
28
+ contracts: {
29
+ USDC: {
30
+ mainnet: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
31
+ base: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
32
+ arbitrum: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
33
+ optimism: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',
34
+ unichain: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
35
+ },
36
+ },
37
+ }
38
+
39
+ export default config
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```bash
45
+ # Set environment variables
46
+ export ALCHEMY_API_KEY="your_key"
47
+ export DRPC_API_KEY="your_key"
48
+ export ETHERSCAN_API_KEY="your_key"
49
+
50
+ # Run CLI
51
+ npx @sparkdotfi/abi-cli
52
+
53
+ # Or add `abi-cli` command to your package.json
54
+ {
55
+ "scripts": {
56
+ "generate": "abi-cli"
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Features
62
+
63
+ - Fetches ABIs from multiple networks
64
+ - Supports proxy contract resolution
65
+ - Verifies ABI compatability between many networks
66
+ - Generates TypeScript files
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,11 @@
1
+ import { NodeContext, NodeRuntime } from '@effect/platform-node';
2
+ import { Effect, Layer } from 'effect';
3
+ import { configLayer } from './config/Config';
4
+ import { abiFetcherLayer } from './fetch/AbiFetcher';
5
+ import { blockchainClientRepositoryLayer } from './fetch/BlockchainClientRepository';
6
+ import { main } from './main';
7
+ import { progressBarLayer } from './services/ProgressBar';
8
+ const blockchainClientRepository = Layer.provide(blockchainClientRepositoryLayer, configLayer);
9
+ const abiFetcher = Layer.provide(abiFetcherLayer, [blockchainClientRepository, configLayer]);
10
+ const mainDeps = Layer.mergeAll(configLayer, blockchainClientRepository, abiFetcher, NodeContext.layer, progressBarLayer);
11
+ main.pipe(Effect.provide(mainDeps), Effect.withSpan('cli'), NodeRuntime.runMain);
@@ -0,0 +1,7 @@
1
+ import { Hash256 } from '@sparkdotfi/common-universal';
2
+ export interface DefaultError {
3
+ cause?: unknown;
4
+ message: string;
5
+ }
6
+ export declare function calcHash(data: string): Hash256;
7
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAGtD,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C"}
package/dist/common.js ADDED
@@ -0,0 +1,5 @@
1
+ import { Hash256 } from '@sparkdotfi/common-universal';
2
+ import { keccak256, toHex } from 'viem';
3
+ export function calcHash(data) {
4
+ return Hash256(keccak256(toHex(data)));
5
+ }
@@ -0,0 +1,19 @@
1
+ import { Context, Effect, Layer } from 'effect';
2
+ import { ParseError } from 'effect/ParseResult';
3
+ import { DefaultError } from '../common';
4
+ import { GlobalConfig } from './schema';
5
+ declare const ConfigParseError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
6
+ readonly _tag: "ConfigParseError";
7
+ } & Readonly<A>;
8
+ export declare class ConfigParseError extends ConfigParseError_base<DefaultError> {
9
+ }
10
+ export interface CliConfigService {
11
+ readonly getConfig: () => Effect.Effect<GlobalConfig>;
12
+ }
13
+ declare const CliConfig_base: Context.TagClass<CliConfig, "CliConfig", CliConfigService>;
14
+ export declare class CliConfig extends CliConfig_base {
15
+ }
16
+ export declare function configLayerFromConfig(cfg: GlobalConfig): Layer.Layer<CliConfig, ParseError>;
17
+ export declare const configLayer: Layer.Layer<CliConfig, ConfigParseError | ParseError, never>;
18
+ export {};
19
+ //# sourceMappingURL=Config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAQ,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,YAAY,EAAgB,MAAM,UAAU,CAAA;;;;AAErD,qBAAa,gBAAiB,SAAQ,sBAAqC,YAAY,CAAC;CAAG;AAE3F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;CACtD;;AASD,qBAAa,SAAU,SAAQ,cAAuD;CAAG;AAEzF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,UAAU,CAAC,CAE3F;AAED,eAAO,MAAM,WAAW,8DASmB,CAAA"}
@@ -0,0 +1,32 @@
1
+ import path from 'node:path';
2
+ import { Context, Data, Effect, Layer, Schema } from 'effect';
3
+ import { configSchema } from './schema';
4
+ export class ConfigParseError extends Data.TaggedError('ConfigParseError') {
5
+ }
6
+ const make = (config) => Effect.gen(function* () {
7
+ return {
8
+ getConfig: () => Effect.succeed(config),
9
+ };
10
+ }).pipe(Effect.withSpan('make'));
11
+ export class CliConfig extends Context.Tag('CliConfig')() {
12
+ }
13
+ export function configLayerFromConfig(cfg) {
14
+ return Layer.effect(CliConfig, make(cfg));
15
+ }
16
+ export const configLayer = Layer.effect(CliConfig, Effect.gen(function* () {
17
+ const config = yield* loadRawConfigFromFile;
18
+ const decodedConfig = yield* Schema.decodeUnknownEither(configSchema)(config);
19
+ yield* Effect.logInfo('Config loaded');
20
+ return yield* make(decodedConfig);
21
+ }).pipe(Effect.withSpan('configLayer')));
22
+ const loadRawConfigFromFile = Effect.tryPromise({
23
+ try: async () => {
24
+ const rootDir = process.cwd();
25
+ const config = await import(path.join(rootDir, 'abi-cli.config.ts'));
26
+ return config.default;
27
+ },
28
+ catch: (error) => new ConfigParseError({
29
+ message: 'Failed to load config',
30
+ cause: error,
31
+ }),
32
+ }).pipe(Effect.withSpan('loadRawConfigFromFile'));
@@ -0,0 +1,135 @@
1
+ import { Schema } from 'effect';
2
+ declare const addressesSchema: Schema.SchemaClass<{
3
+ readonly mainnet?: (`0x${string}` & {
4
+ readonly __TAG__: "CheckedAddress";
5
+ }) | undefined;
6
+ readonly base?: (`0x${string}` & {
7
+ readonly __TAG__: "CheckedAddress";
8
+ }) | undefined;
9
+ readonly gnosis?: (`0x${string}` & {
10
+ readonly __TAG__: "CheckedAddress";
11
+ }) | undefined;
12
+ readonly arbitrum?: (`0x${string}` & {
13
+ readonly __TAG__: "CheckedAddress";
14
+ }) | undefined;
15
+ readonly optimism?: (`0x${string}` & {
16
+ readonly __TAG__: "CheckedAddress";
17
+ }) | undefined;
18
+ readonly worldchain?: (`0x${string}` & {
19
+ readonly __TAG__: "CheckedAddress";
20
+ }) | undefined;
21
+ readonly unichain?: (`0x${string}` & {
22
+ readonly __TAG__: "CheckedAddress";
23
+ }) | undefined;
24
+ }, {
25
+ readonly [x: string]: string | undefined;
26
+ }, never>;
27
+ export declare const configSchema: Schema.Struct<{
28
+ out: typeof Schema.String;
29
+ secrets: Schema.Struct<{
30
+ alchemyApiKey: typeof Schema.String;
31
+ drpcApiKey: typeof Schema.String;
32
+ etherscanApiKey: typeof Schema.String;
33
+ }>;
34
+ eoa: Schema.PropertySignature<":", {
35
+ readonly [x: string]: {
36
+ readonly mainnet?: (`0x${string}` & {
37
+ readonly __TAG__: "CheckedAddress";
38
+ }) | undefined;
39
+ readonly base?: (`0x${string}` & {
40
+ readonly __TAG__: "CheckedAddress";
41
+ }) | undefined;
42
+ readonly gnosis?: (`0x${string}` & {
43
+ readonly __TAG__: "CheckedAddress";
44
+ }) | undefined;
45
+ readonly arbitrum?: (`0x${string}` & {
46
+ readonly __TAG__: "CheckedAddress";
47
+ }) | undefined;
48
+ readonly optimism?: (`0x${string}` & {
49
+ readonly __TAG__: "CheckedAddress";
50
+ }) | undefined;
51
+ readonly worldchain?: (`0x${string}` & {
52
+ readonly __TAG__: "CheckedAddress";
53
+ }) | undefined;
54
+ readonly unichain?: (`0x${string}` & {
55
+ readonly __TAG__: "CheckedAddress";
56
+ }) | undefined;
57
+ };
58
+ }, never, "?:", {
59
+ readonly [x: string]: {
60
+ readonly [x: string]: string | undefined;
61
+ };
62
+ }, false, never>;
63
+ contracts: Schema.PropertySignature<":", {
64
+ readonly [x: string]: {
65
+ readonly mainnet?: (`0x${string}` & {
66
+ readonly __TAG__: "CheckedAddress";
67
+ }) | undefined;
68
+ readonly base?: (`0x${string}` & {
69
+ readonly __TAG__: "CheckedAddress";
70
+ }) | undefined;
71
+ readonly gnosis?: (`0x${string}` & {
72
+ readonly __TAG__: "CheckedAddress";
73
+ }) | undefined;
74
+ readonly arbitrum?: (`0x${string}` & {
75
+ readonly __TAG__: "CheckedAddress";
76
+ }) | undefined;
77
+ readonly optimism?: (`0x${string}` & {
78
+ readonly __TAG__: "CheckedAddress";
79
+ }) | undefined;
80
+ readonly worldchain?: (`0x${string}` & {
81
+ readonly __TAG__: "CheckedAddress";
82
+ }) | undefined;
83
+ readonly unichain?: (`0x${string}` & {
84
+ readonly __TAG__: "CheckedAddress";
85
+ }) | undefined;
86
+ };
87
+ }, never, "?:", {
88
+ readonly [x: string]: {
89
+ readonly [x: string]: string | undefined;
90
+ };
91
+ }, false, never>;
92
+ interfaces: Schema.PropertySignature<":", {
93
+ readonly [x: string]: {
94
+ readonly methods?: readonly string[] | undefined;
95
+ readonly events?: readonly string[] | undefined;
96
+ readonly addresses: {
97
+ readonly mainnet?: (`0x${string}` & {
98
+ readonly __TAG__: "CheckedAddress";
99
+ }) | undefined;
100
+ readonly base?: (`0x${string}` & {
101
+ readonly __TAG__: "CheckedAddress";
102
+ }) | undefined;
103
+ readonly gnosis?: (`0x${string}` & {
104
+ readonly __TAG__: "CheckedAddress";
105
+ }) | undefined;
106
+ readonly arbitrum?: (`0x${string}` & {
107
+ readonly __TAG__: "CheckedAddress";
108
+ }) | undefined;
109
+ readonly optimism?: (`0x${string}` & {
110
+ readonly __TAG__: "CheckedAddress";
111
+ }) | undefined;
112
+ readonly worldchain?: (`0x${string}` & {
113
+ readonly __TAG__: "CheckedAddress";
114
+ }) | undefined;
115
+ readonly unichain?: (`0x${string}` & {
116
+ readonly __TAG__: "CheckedAddress";
117
+ }) | undefined;
118
+ };
119
+ };
120
+ }, never, "?:", {
121
+ readonly [x: string]: {
122
+ readonly addresses: {
123
+ readonly [x: string]: string | undefined;
124
+ };
125
+ readonly methods?: readonly string[] | undefined;
126
+ readonly events?: readonly string[] | undefined;
127
+ };
128
+ }, false, never>;
129
+ }>;
130
+ export type AbiCliConfig = Schema.Schema.Encoded<typeof configSchema>;
131
+ export type GlobalConfig = Schema.Schema.Type<typeof configSchema>;
132
+ export type AddressesConfig = Schema.Schema.Type<typeof addressesSchema>;
133
+ export type InterfaceConfig = NonNullable<Schema.Schema.Type<typeof configSchema>['interfaces']>[number];
134
+ export {};
135
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAA;AAWvC,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;SAOpB,CAAA;AAgBD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBvB,CAAA;AAGF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,YAAY,CAAC,CAAA;AACrE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,YAAY,CAAC,CAAA;AAElE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC,CAAA;AAExE,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { CheckedAddress, allSparkDomains } from '@sparkdotfi/common-universal';
2
+ import { Option, Schema } from 'effect';
3
+ const CheckedAddressSchema = Schema.declare((input) => input === CheckedAddress(input));
4
+ const StringToCheckedAddress = Schema.transform(Schema.String, CheckedAddressSchema, {
5
+ decode: (fromA) => CheckedAddress(fromA),
6
+ encode: (address) => address.toString(),
7
+ });
8
+ const addressesSchema = Schema.partial(Schema.Record({
9
+ key: Schema.String.pipe(Schema.filter(isSparkDomain)).annotations({ message: () => 'Must be valid spark domain' }),
10
+ value: StringToCheckedAddress.annotations({
11
+ message: () => 'Must be valid address',
12
+ }),
13
+ }));
14
+ const interfaceSchema = Schema.Record({
15
+ key: Schema.String,
16
+ value: Schema.Struct({
17
+ methods: Schema.optional(Schema.Array(Schema.String)),
18
+ events: Schema.optional(Schema.Array(Schema.String)),
19
+ addresses: addressesSchema,
20
+ }),
21
+ });
22
+ const contractsMapSchema = Schema.Record({
23
+ key: Schema.String,
24
+ value: addressesSchema,
25
+ });
26
+ export const configSchema = Schema.Struct({
27
+ out: Schema.String,
28
+ secrets: Schema.Struct({
29
+ alchemyApiKey: Schema.String,
30
+ drpcApiKey: Schema.String,
31
+ etherscanApiKey: Schema.String,
32
+ }),
33
+ eoa: Schema.optionalToRequired(contractsMapSchema, contractsMapSchema, {
34
+ decode: (o) => (Option.isNone(o) ? {} : o.value),
35
+ encode: (ti) => Option.some(ti),
36
+ }),
37
+ contracts: Schema.optionalToRequired(contractsMapSchema, contractsMapSchema, {
38
+ decode: (o) => (Option.isNone(o) ? {} : o.value),
39
+ encode: (ti) => Option.some(ti),
40
+ }),
41
+ interfaces: Schema.optionalToRequired(interfaceSchema, interfaceSchema, {
42
+ decode: (o) => (Option.isNone(o) ? {} : o.value),
43
+ encode: (ti) => Option.some(ti),
44
+ }),
45
+ });
46
+ function isSparkDomain(data) {
47
+ return allSparkDomains.includes(data);
48
+ }
@@ -0,0 +1,29 @@
1
+ import { CheckedAddress } from '@sparkdotfi/common-universal';
2
+ import { Context, Effect, Layer } from 'effect';
3
+ import { TimeoutException } from 'effect/Cause';
4
+ import { DefaultError } from '../common';
5
+ import { CliConfig } from '../config/Config';
6
+ import { Abi, ContractIndex } from '../types';
7
+ import { BlockchainClientNotFoundError, BlockchainClientRepository } from './BlockchainClientRepository';
8
+ declare const AbiFetcher_base: Context.TagClass<AbiFetcher, "AbiFetcher", AbiFetcherService>;
9
+ export declare class AbiFetcher extends AbiFetcher_base {
10
+ }
11
+ export interface AbiFetcherService {
12
+ readonly fetchAbi: (contract: ContractIndex) => Effect.Effect<Abi, AbiFetcherError | AbiNotFoundError | TimeoutException | BlockchainClientNotFoundError>;
13
+ }
14
+ declare const AbiNotFoundError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
15
+ readonly _tag: "AbiNotFoundError";
16
+ } & Readonly<A>;
17
+ export declare class AbiNotFoundError extends AbiNotFoundError_base<{
18
+ address: CheckedAddress;
19
+ chainId: number;
20
+ }> {
21
+ }
22
+ declare const AbiFetcherError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
23
+ readonly _tag: "AbiFetcherError";
24
+ } & Readonly<A>;
25
+ export declare class AbiFetcherError extends AbiFetcherError_base<DefaultError> {
26
+ }
27
+ export declare const abiFetcherLayer: Layer.Layer<AbiFetcher, never, CliConfig | BlockchainClientRepository>;
28
+ export {};
29
+ //# sourceMappingURL=AbiFetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbiFetcher.d.ts","sourceRoot":"","sources":["../../src/fetch/AbiFetcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAmB,MAAM,8BAA8B,CAAA;AAC9E,OAAO,EAAE,OAAO,EAAkB,MAAM,EAAE,KAAK,EAAY,MAAM,QAAQ,CAAA;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,EAAE,6BAA6B,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;;AAExG,qBAAa,UAAW,SAAQ,eAA0D;CAAG;AAE7F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,CACjB,QAAQ,EAAE,aAAa,KACpB,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,eAAe,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,6BAA6B,CAAC,CAAA;CAC/G;;;;AAED,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAC;CAAG;;;;AACL,qBAAa,eAAgB,SAAQ,qBAAoC,YAAY,CAAC;CAAG;AAyBzF,eAAO,MAAM,eAAe,wEAAiC,CAAA"}
@@ -0,0 +1,63 @@
1
+ import { whatsabi } from '@shazow/whatsabi';
2
+ import { domainToChainId } from '@sparkdotfi/common-universal';
3
+ import { Context, Data, Duration, Effect, Layer, Schedule } from 'effect';
4
+ import { CliConfig } from '../config/Config';
5
+ import { BlockchainClientRepository } from './BlockchainClientRepository';
6
+ export class AbiFetcher extends Context.Tag('AbiFetcher')() {
7
+ }
8
+ export class AbiNotFoundError extends Data.TaggedError('AbiNotFoundError') {
9
+ }
10
+ export class AbiFetcherError extends Data.TaggedError('AbiFetcherError') {
11
+ }
12
+ const make = Effect.gen(function* () {
13
+ const blockchainClientRepository = yield* BlockchainClientRepository;
14
+ const config = yield* (yield* CliConfig).getConfig();
15
+ const fetchAbi = (contract) => Effect.gen(function* () {
16
+ const { address, domain } = contract;
17
+ const client = yield* blockchainClientRepository.getClientForDomain(domain);
18
+ return yield* whatsAbiFetchEffect({
19
+ address,
20
+ chainId: domainToChainId[domain],
21
+ etherscanApiKey: config.secrets.etherscanApiKey,
22
+ client,
23
+ });
24
+ });
25
+ return {
26
+ fetchAbi,
27
+ };
28
+ });
29
+ export const abiFetcherLayer = Layer.effect(AbiFetcher, make);
30
+ const whatsAbiFetchEffect = (args) => {
31
+ const { chainId, etherscanApiKey, client, address } = args;
32
+ return Effect.tryPromise({
33
+ try: async () => {
34
+ const loader = new whatsabi.loaders.EtherscanV2ABILoader({
35
+ apiKey: etherscanApiKey,
36
+ chainId,
37
+ });
38
+ return await whatsabi.autoload(address, {
39
+ provider: client,
40
+ followProxies: true,
41
+ abiLoader: loader,
42
+ onError: (_name, err) => {
43
+ // @todo this api is odd: will the promise reject on such error?
44
+ throw err;
45
+ },
46
+ });
47
+ },
48
+ catch: (error) => {
49
+ return new AbiFetcherError({
50
+ cause: error,
51
+ message: 'WhatsABI failure',
52
+ });
53
+ },
54
+ }).pipe(Effect.timeout('10 seconds'), Effect.retry(Schedule.intersect(Schedule.exponential(Duration.millis(250), 1.5), Schedule.recurs(3))), Effect.andThen((data) => {
55
+ if (data.abi.length === 0) {
56
+ return Effect.fail(new AbiNotFoundError({ address, chainId }));
57
+ }
58
+ if (!data.abiLoadedFrom || !data.abiLoadedFrom.name.toLowerCase().includes('etherscan')) {
59
+ return Effect.fail(new AbiNotFoundError({ address, chainId }));
60
+ }
61
+ return Effect.succeed(data.abi);
62
+ }));
63
+ };
@@ -0,0 +1,29 @@
1
+ import { SparkDomain } from '@sparkdotfi/common-universal';
2
+ import { Context, Effect, Layer } from 'effect';
3
+ import { PublicClient } from 'viem';
4
+ import { DefaultError } from '../common';
5
+ import { CliConfig } from '../config/Config';
6
+ declare const BlockchainClientNotFoundError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
7
+ readonly _tag: "BlockchainClientNotFoundError";
8
+ } & Readonly<A>;
9
+ export declare class BlockchainClientNotFoundError extends BlockchainClientNotFoundError_base<DefaultError> {
10
+ }
11
+ declare const BuildBlockchainRepositoryError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
12
+ readonly _tag: "BuildBlockchainRepositoryError";
13
+ } & Readonly<A>;
14
+ export declare class BuildBlockchainRepositoryError extends BuildBlockchainRepositoryError_base<DefaultError> {
15
+ }
16
+ export interface BlockchainClientRepositoryService {
17
+ readonly getClientForDomain: (domain: SparkDomain) => Effect.Effect<PublicClient, BlockchainClientNotFoundError, never>;
18
+ }
19
+ declare const BlockchainClientRepository_base: Context.TagClass<BlockchainClientRepository, "BlockchainClientRepository", BlockchainClientRepositoryService>;
20
+ export declare class BlockchainClientRepository extends BlockchainClientRepository_base {
21
+ }
22
+ export declare const blockchainClientRepositoryLayer: Layer.Layer<BlockchainClientRepository, BuildBlockchainRepositoryError, CliConfig>;
23
+ export declare function getRpcUrls({ domain, alchemyApiKey, drpcApiKey, }: {
24
+ domain: SparkDomain;
25
+ alchemyApiKey: string;
26
+ drpcApiKey: string;
27
+ }): string[];
28
+ export {};
29
+ //# sourceMappingURL=BlockchainClientRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockchainClientRepository.d.ts","sourceRoot":"","sources":["../../src/fetch/BlockchainClientRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAKZ,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,OAAO,EAAQ,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACrD,OAAO,EAAe,YAAY,EAAgC,MAAM,MAAM,CAAA;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;;;;AAE5C,qBAAa,6BAA8B,SAAQ,mCAAkD,YAAY,CAAC;CAAG;;;;AACrH,qBAAa,8BAA+B,SAAQ,oCAAmD,YAAY,CAAC;CAAG;AAEvH,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,kBAAkB,EAAE,CAC3B,MAAM,EAAE,WAAW,KAChB,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,6BAA6B,EAAE,KAAK,CAAC,CAAA;CACvE;;AAmDD,qBAAa,0BAA2B,SAAQ,+BAG7C;CAAG;AAEN,eAAO,MAAM,+BAA+B,oFAAmD,CAAA;AAE/F,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,aAAa,EACb,UAAU,GACX,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,EAAE,CAE/E"}
@@ -0,0 +1,51 @@
1
+ import { allSparkDomains, domainToChain, getAlchemyRpcUrl, getDrpcRpcUrl, } from '@sparkdotfi/common-universal';
2
+ import { Context, Data, Effect, Layer } from 'effect';
3
+ import { http, createPublicClient, fallback } from 'viem';
4
+ import { CliConfig } from '../config/Config';
5
+ export class BlockchainClientNotFoundError extends Data.TaggedError('BlockchainClientNotFoundError') {
6
+ }
7
+ export class BuildBlockchainRepositoryError extends Data.TaggedError('BuildBlockchainRepositoryError') {
8
+ }
9
+ const effect = Effect.gen(function* () {
10
+ const config = yield* (yield* CliConfig).getConfig();
11
+ const clients = yield* Effect.try({
12
+ try: () => {
13
+ return allSparkDomains.map((domain) => {
14
+ const chain = domainToChain[domain];
15
+ const rpcUrls = getRpcUrls({
16
+ domain,
17
+ alchemyApiKey: config.secrets.alchemyApiKey,
18
+ drpcApiKey: config.secrets.drpcApiKey,
19
+ });
20
+ const transport = fallback(rpcUrls.map((rpcUrl) => http(rpcUrl)));
21
+ return createPublicClient({
22
+ chain,
23
+ transport,
24
+ });
25
+ });
26
+ },
27
+ catch: (error) => new BuildBlockchainRepositoryError({
28
+ cause: error,
29
+ message: 'Failed to build blockchain client repository',
30
+ }),
31
+ }).pipe(Effect.withSpan('buildBlockchainClientRepository'));
32
+ const getClientForDomain = (domain) => Effect.gen(function* () {
33
+ const client = clients.find((client) => client.chain?.id === domainToChain[domain].id);
34
+ if (!client) {
35
+ return yield* Effect.fail(new BlockchainClientNotFoundError({
36
+ cause: clients,
37
+ message: `Blockchain client not found for domain ${domain}`,
38
+ }));
39
+ }
40
+ return yield* Effect.succeed(client);
41
+ }).pipe(Effect.withSpan('getClientForDomain'));
42
+ return {
43
+ getClientForDomain,
44
+ };
45
+ });
46
+ export class BlockchainClientRepository extends Context.Tag('BlockchainClientRepository')() {
47
+ }
48
+ export const blockchainClientRepositoryLayer = Layer.effect(BlockchainClientRepository, effect);
49
+ export function getRpcUrls({ domain, alchemyApiKey, drpcApiKey, }) {
50
+ return [getAlchemyRpcUrl({ domain, alchemyApiKey }), getDrpcRpcUrl({ domain, drpcApiKey })];
51
+ }
@@ -0,0 +1,8 @@
1
+ import { Effect } from 'effect';
2
+ import { CliConfig } from '../config/Config';
3
+ import { ProgressBar } from '../services/ProgressBar';
4
+ import { AbiFetcher } from './AbiFetcher';
5
+ export declare const fetchAbis: Effect.Effect<Partial<Record<"mainnet" | "base" | "gnosis" | "arbitrum" | "optimism" | "worldchain" | "unichain", Record<`0x${string}` & {
6
+ readonly __TAG__: "CheckedAddress";
7
+ }, import("../types").ContractMetadata>>>, import("./BlockchainClientRepository").BlockchainClientNotFoundError | import("./AbiFetcher").AbiFetcherError | import("./AbiFetcher").AbiNotFoundError | import("effect/Cause").TimeoutException | import("../services/ProgressBar").ProgressBarError, CliConfig | AbiFetcher | ProgressBar>;
8
+ //# sourceMappingURL=fetchAbis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchAbis.d.ts","sourceRoot":"","sources":["../../src/fetch/fetchAbis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,eAAO,MAAM,SAAS;;wUAoBwE,CAAA"}
@@ -0,0 +1,38 @@
1
+ import { Effect } from 'effect';
2
+ import { entries, uniqueBy } from 'remeda';
3
+ import { CliConfig } from '../config/Config';
4
+ import { ProgressBar } from '../services/ProgressBar';
5
+ import { AbiFetcher } from './AbiFetcher';
6
+ export const fetchAbis = Effect.gen(function* () {
7
+ const config = yield* (yield* CliConfig).getConfig();
8
+ const abiFetcher = yield* AbiFetcher;
9
+ const progressBar = yield* ProgressBar;
10
+ const contracts = getContractsToFetch(config);
11
+ yield* Effect.logInfo(`Fetching ${contracts.length} ABIs`);
12
+ yield* progressBar.create(contracts.length);
13
+ const effects = contracts.map((contract) => abiFetcher.fetchAbi(contract).pipe(Effect.map((abi) => ({ contract, abi })), Effect.tap(() => progressBar.increment)));
14
+ const downloadedAbis = yield* Effect.all(effects, { concurrency: 5 });
15
+ yield* progressBar.finish;
16
+ return getMetadataStore(downloadedAbis);
17
+ }).pipe(Effect.withSpan('fetchAbis'));
18
+ function getContractsToFetch(config) {
19
+ const contractsPart = entries(config.contracts).flatMap(([name, chainAddresses]) => {
20
+ return entries(chainAddresses).map(([domain, address]) => ({ domain, address, name }));
21
+ });
22
+ const interfacesPart = entries(config.interfaces || {}).flatMap(([name, interfaceConfig]) => {
23
+ return entries(interfaceConfig.addresses).map(([domain, address]) => ({ domain, address, name }));
24
+ });
25
+ return uniqueBy(contractsPart.concat(interfacesPart), (c) => `${c.domain}-${c.address}`);
26
+ }
27
+ // @todo this should be possible to replace with nested usage of remeda's groupBy
28
+ function getMetadataStore(downloadedAbis) {
29
+ return downloadedAbis.reduce((acc, data) => {
30
+ const domain = data.contract.domain;
31
+ const address = data.contract.address;
32
+ if (!acc[domain]) {
33
+ acc[domain] = {};
34
+ }
35
+ acc[domain][address] = { abi: data.abi };
36
+ return acc;
37
+ }, {});
38
+ }
@@ -0,0 +1,8 @@
1
+ import { GlobalConfig } from '../config/schema';
2
+ import { ContractMetadataStore, EoaEntity, PreprocessedEntity } from '../types';
3
+ export declare function preprocess(store: ContractMetadataStore, config: GlobalConfig): {
4
+ contracts: PreprocessedEntity[];
5
+ eoas: EoaEntity[];
6
+ interfaces: never[];
7
+ };
8
+ //# sourceMappingURL=dataProcessing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataProcessing.d.ts","sourceRoot":"","sources":["../../src/generate/dataProcessing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAoB,qBAAqB,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAEjG,wBAAgB,UAAU,CAAC,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,YAAY;;;;EAkB5E"}
@@ -0,0 +1,25 @@
1
+ import { assert } from '@sparkdotfi/common-universal';
2
+ import { entries, keys } from 'remeda';
3
+ export function preprocess(store, config) {
4
+ const contractsData = entries(config.contracts).map(([name, addresses]) => {
5
+ const metadata = getMetadataFromStore(store, addresses);
6
+ return {
7
+ addresses,
8
+ name,
9
+ abi: metadata.abi,
10
+ };
11
+ });
12
+ const eoaData = entries(config.eoa || {}).map(([name, addresses]) => ({ name, addresses }));
13
+ return {
14
+ contracts: contractsData,
15
+ eoas: eoaData,
16
+ interfaces: [],
17
+ };
18
+ }
19
+ function getMetadataFromStore(store, addresses) {
20
+ const firstDomain = keys(addresses)[0];
21
+ assert(firstDomain !== undefined, 'Addresses are empty');
22
+ const firstAddress = addresses[firstDomain];
23
+ assert(firstAddress !== undefined);
24
+ return store[firstDomain][firstAddress];
25
+ }
@@ -0,0 +1,2 @@
1
+ export declare function format(content: string): Promise<string>;
2
+ //# sourceMappingURL=formatOutput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatOutput.d.ts","sourceRoot":"","sources":["../../src/generate/formatOutput.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAe7D"}
@@ -0,0 +1,19 @@
1
+ import prettier from 'prettier';
2
+ // This code is basically copied from wagmi-cli. Any idea how to improve it?
3
+ // Theoretically, we could not do it at all and rely on repo configured linting.
4
+ // In practice e.g. when I hit ctrl+s in not formatted file - I had a huge lag.
5
+ export async function format(content) {
6
+ const config = await prettier.resolveConfig(process.cwd());
7
+ return prettier.format(content, {
8
+ arrowParens: 'always',
9
+ endOfLine: 'lf',
10
+ parser: 'typescript',
11
+ printWidth: 120,
12
+ semi: false,
13
+ singleQuote: true,
14
+ tabWidth: 2,
15
+ trailingComma: 'all',
16
+ ...config,
17
+ plugins: [],
18
+ });
19
+ }
@@ -0,0 +1,11 @@
1
+ import { FileSystem } from '@effect/platform';
2
+ import { Effect } from 'effect';
3
+ import { AddressesConfig, GlobalConfig } from '../config/schema';
4
+ import { ContractMetadataStore, PreprocessedContractsData } from '../types';
5
+ export declare const generate: (metadataStore: ContractMetadataStore, config: GlobalConfig) => Effect.Effect<void, import("effect/Cause").UnknownException | import("@effect/platform/Error").PlatformError, FileSystem.FileSystem>;
6
+ /**
7
+ * @internal
8
+ */
9
+ export declare function generateModule(data: PreprocessedContractsData): Promise<string>;
10
+ export declare function getOrderedAddresses(data: AddressesConfig): AddressesConfig;
11
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/generate/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAO,qBAAqB,EAAa,yBAAyB,EAAsB,MAAM,UAAU,CAAA;AAI/G,eAAO,MAAM,QAAQ,kBAAmB,qBAAqB,UAAU,YAAY,yIAWL,CAAA;AAE9E;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/E;AAiBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe,CAE1E"}
@@ -0,0 +1,56 @@
1
+ import { FileSystem } from '@effect/platform';
2
+ import { Effect } from 'effect';
3
+ import { entries, fromEntries } from 'remeda';
4
+ import { preprocess } from './dataProcessing';
5
+ import { format } from './formatOutput';
6
+ export const generate = (metadataStore, config) => Effect.gen(function* () {
7
+ yield* Effect.logInfo('Validating ...');
8
+ const fs = yield* FileSystem.FileSystem;
9
+ const data = preprocess(metadataStore, config);
10
+ yield* Effect.logInfo('Writing ...');
11
+ const module = yield* Effect.tryPromise(() => generateModule(data));
12
+ yield* fs.writeFileString(config.out, module);
13
+ }).pipe(Effect.withSpan('generate'));
14
+ /**
15
+ * @internal
16
+ */
17
+ export function generateModule(data) {
18
+ const sections = data.eoas.map(eoaSection).concat(data.contracts.map(contractSection));
19
+ const source = sections.join('\n');
20
+ return format(source);
21
+ }
22
+ function eoaSection(data) {
23
+ return `
24
+ ${header(data.name, 'EOA')}
25
+ ${addressesSection(data.name, data.addresses, 'EOA')}
26
+ `;
27
+ }
28
+ function contractSection(data) {
29
+ return `
30
+ ${header(data.name, 'Contract')}
31
+ ${abiSection(data.name, data.abi)}
32
+ ${addressesSection(data.name, data.addresses, 'Contract')}
33
+ `;
34
+ }
35
+ export function getOrderedAddresses(data) {
36
+ return fromEntries(entries(data).sort(([a], [b]) => a.localeCompare(b)));
37
+ }
38
+ function header(name, type) {
39
+ return `
40
+ //////////////////////////////////////////////////////////////////
41
+ // ${name} ${type}
42
+ //////////////////////////////////////////////////////////////////
43
+ `;
44
+ }
45
+ function addressesSection(name, data, type) {
46
+ const sortedData = getOrderedAddresses(data);
47
+ const stringified = JSON.stringify(sortedData, null, 2);
48
+ return wrapWithConstDeclaration(`${name}${type}Address`, stringified);
49
+ }
50
+ function abiSection(name, data) {
51
+ const stringified = JSON.stringify(data);
52
+ return wrapWithConstDeclaration(`${name}Abi`, stringified);
53
+ }
54
+ function wrapWithConstDeclaration(name, content) {
55
+ return `export const ${name} = ${content} as const\n`;
56
+ }
@@ -0,0 +1,5 @@
1
+ import { AbiCliConfig } from './config/schema';
2
+ declare function defineConfig(config: AbiCliConfig): AbiCliConfig;
3
+ export type { AbiCliConfig };
4
+ export { defineConfig };
5
+ //# 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,iBAAiB,CAAA;AAE9C,iBAAS,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAExD;AAED,YAAY,EAAE,YAAY,EAAE,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ function defineConfig(config) {
2
+ return config;
3
+ }
4
+ export { defineConfig };
package/dist/main.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { Effect } from 'effect';
2
+ import { CliConfig } from './config/Config';
3
+ export declare const main: Effect.Effect<void, import("./fetch/BlockchainClientRepository").BlockchainClientNotFoundError | import("./fetch/AbiFetcher").AbiFetcherError | import("./fetch/AbiFetcher").AbiNotFoundError | import("effect/Cause").TimeoutException | import("effect/Cause").UnknownException | import("./services/ProgressBar").ProgressBarError | import("@effect/platform/Error").PlatformError | import("./verify/verify").ValidationError, CliConfig | import("./fetch/AbiFetcher").AbiFetcher | import("./services/ProgressBar").ProgressBar | import("@effect/platform/FileSystem").FileSystem>;
4
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAK3C,eAAO,MAAM,IAAI,4jBAOe,CAAA"}
package/dist/main.js ADDED
@@ -0,0 +1,11 @@
1
+ import { Effect } from 'effect';
2
+ import { CliConfig } from './config/Config';
3
+ import { fetchAbis } from './fetch/fetchAbis';
4
+ import { generate } from './generate/generate';
5
+ import { verify } from './verify/verify';
6
+ export const main = Effect.gen(function* () {
7
+ const config = yield* (yield* CliConfig).getConfig();
8
+ const metadataStore = yield* fetchAbis;
9
+ yield* verify(metadataStore, config);
10
+ yield* generate(metadataStore, config);
11
+ }).pipe(Effect.withSpan('main'));
@@ -0,0 +1,19 @@
1
+ import { Context, Effect, Layer } from 'effect';
2
+ import { DefaultError } from '../common';
3
+ declare const ProgressBarError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
4
+ readonly _tag: "ProgressBarError";
5
+ } & Readonly<A>;
6
+ export declare class ProgressBarError extends ProgressBarError_base<DefaultError> {
7
+ }
8
+ export interface ProgressBarService {
9
+ readonly create: (total: number) => Effect.Effect<void, ProgressBarError, never>;
10
+ readonly increment: Effect.Effect<void, ProgressBarError, never>;
11
+ readonly finish: Effect.Effect<void, ProgressBarError, never>;
12
+ }
13
+ declare const ProgressBar_base: Context.TagClass<ProgressBar, "ProgressBar", ProgressBarService>;
14
+ export declare class ProgressBar extends ProgressBar_base {
15
+ }
16
+ export declare const progressBarLayer: Layer.Layer<ProgressBar, never, never>;
17
+ export declare const progressBarMockLayer: Layer.Layer<ProgressBar, never, never>;
18
+ export {};
19
+ //# sourceMappingURL=ProgressBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgressBar.d.ts","sourceRoot":"","sources":["../../src/services/ProgressBar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAQ,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;;;;AAExC,qBAAa,gBAAiB,SAAQ,sBAAqC,YAAY,CAAC;CAAG;AAE3F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAA;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAA;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAA;CAC9D;;AA4BD,qBAAa,WAAY,SAAQ,gBAA6D;CAAG;AAEjG,eAAO,MAAM,gBAAgB,wCAAoC,CAAA;AASjE,eAAO,MAAM,oBAAoB,wCAAwC,CAAA"}
@@ -0,0 +1,35 @@
1
+ import cliProgress from 'cli-progress';
2
+ import { Context, Data, Effect, Layer } from 'effect';
3
+ export class ProgressBarError extends Data.TaggedError('ProgressBarError') {
4
+ }
5
+ const effect = Effect.gen(function* () {
6
+ const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
7
+ const create = (total) => Effect.try({
8
+ try: () => bar.start(total, 0),
9
+ catch: (error) => new ProgressBarError({ message: 'Failed to create progress bar', cause: error }),
10
+ }).pipe(Effect.withSpan('create'));
11
+ const increment = Effect.try({
12
+ try: () => bar.increment(),
13
+ catch: (error) => new ProgressBarError({ message: 'Failed to update progress bar', cause: error }),
14
+ }).pipe(Effect.withSpan('increment'));
15
+ const finish = Effect.try({
16
+ try: () => bar.stop(),
17
+ catch: (error) => new ProgressBarError({ message: 'Failed to stop progress bar', cause: error }),
18
+ }).pipe(Effect.withSpan('finish'));
19
+ return {
20
+ create,
21
+ increment,
22
+ finish,
23
+ };
24
+ });
25
+ export class ProgressBar extends Context.Tag('ProgressBar')() {
26
+ }
27
+ export const progressBarLayer = Layer.effect(ProgressBar, effect);
28
+ const mockEffect = Effect.gen(function* () {
29
+ return {
30
+ create: () => Effect.succeed(undefined),
31
+ finish: Effect.succeed(undefined),
32
+ increment: Effect.succeed(undefined),
33
+ };
34
+ });
35
+ export const progressBarMockLayer = Layer.effect(ProgressBar, mockEffect);
@@ -0,0 +1,2 @@
1
+ export declare function loadModuleFromString(source: string): Promise<unknown>;
2
+ //# sourceMappingURL=loadModuleFromString.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadModuleFromString.d.ts","sourceRoot":"","sources":["../../src/test/loadModuleFromString.ts"],"names":[],"mappings":"AAEA,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAe3E"}
@@ -0,0 +1,14 @@
1
+ import * as ts from 'typescript';
2
+ export async function loadModuleFromString(source) {
3
+ const jsSource = ts.transpileModule(source, {
4
+ compilerOptions: {
5
+ module: ts.ModuleKind.ESNext,
6
+ target: ts.ScriptTarget.ES2020,
7
+ },
8
+ }).outputText;
9
+ const base64 = Buffer.from(jsSource).toString('base64');
10
+ const url = `data:text/javascript;base64,${base64}`;
11
+ const module = await import(url);
12
+ // removes custom prototype from the module object
13
+ return { ...module };
14
+ }
@@ -0,0 +1,5 @@
1
+ import { GlobalConfig } from '../config/schema';
2
+ export declare function getMockConfig<T extends Partial<GlobalConfig>>(config: T): typeof basicMockConfig & T;
3
+ declare const basicMockConfig: GlobalConfig;
4
+ export {};
5
+ //# sourceMappingURL=mockConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockConfig.d.ts","sourceRoot":"","sources":["../../src/test/mockConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,eAAe,GAAG,CAAC,CAEpG;AAED,QAAA,MAAM,eAAe,EAAE,YAUb,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { mergeDeep } from 'remeda';
2
+ export function getMockConfig(config) {
3
+ return mergeDeep(basicMockConfig, config);
4
+ }
5
+ const basicMockConfig = {
6
+ out: '/out',
7
+ secrets: {
8
+ alchemyApiKey: 'alchemy-api-key',
9
+ drpcApiKey: 'drpc-api-key',
10
+ etherscanApiKey: 'etherscan-api-key',
11
+ },
12
+ contracts: {},
13
+ eoa: {},
14
+ interfaces: {},
15
+ };
@@ -0,0 +1,27 @@
1
+ import { whatsabi } from '@shazow/whatsabi';
2
+ import { CheckedAddress, SparkDomain } from '@sparkdotfi/common-universal';
3
+ import { AddressesConfig } from './config/schema';
4
+ export type Abi = whatsabi.abi.ABI;
5
+ export interface ContractIndex {
6
+ address: CheckedAddress;
7
+ domain: SparkDomain;
8
+ }
9
+ export interface ContractMetadata {
10
+ abi: Abi;
11
+ }
12
+ export type ContractMetadataStore = Partial<Record<SparkDomain, Record<CheckedAddress, ContractMetadata>>>;
13
+ export interface PreprocessedEntity {
14
+ name: string;
15
+ abi: Abi;
16
+ addresses: AddressesConfig;
17
+ }
18
+ export interface EoaEntity {
19
+ name: string;
20
+ addresses: AddressesConfig;
21
+ }
22
+ export interface PreprocessedContractsData {
23
+ contracts: PreprocessedEntity[];
24
+ interfaces: PreprocessedEntity[];
25
+ eoas: EoaEntity[];
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAA;AAElC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,CAAA;IACvB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,GAAG,CAAA;CACT;AAED,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;AAE1G,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,GAAG,CAAA;IACR,SAAS,EAAE,eAAe,CAAA;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,eAAe,CAAA;CAC3B;AACD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,UAAU,EAAE,kBAAkB,EAAE,CAAA;IAChC,IAAI,EAAE,SAAS,EAAE,CAAA;CAClB"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { Effect } from 'effect';
2
+ import { GlobalConfig } from '../config/schema';
3
+ import { ContractMetadataStore } from '../types';
4
+ export declare const verify: (store: ContractMetadataStore, config: GlobalConfig) => Effect.Effect<void, ValidationError, never>;
5
+ declare const ValidationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
6
+ readonly _tag: "ValidationError";
7
+ } & Readonly<A>;
8
+ export declare class ValidationError extends ValidationError_base<{
9
+ message: string;
10
+ }> {
11
+ }
12
+ export {};
13
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/verify/verify.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,EAAmB,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAEhD,eAAO,MAAM,MAAM,UAAW,qBAAqB,UAAU,YAAY,gDAIrE,CAAA;;;;AAwCJ,qBAAa,eAAgB,SAAQ,qBAAoC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;CAAG"}
@@ -0,0 +1,35 @@
1
+ import { assert } from '@sparkdotfi/common-universal';
2
+ import { Data, Effect } from 'effect';
3
+ import { entries } from 'remeda';
4
+ export const verify = (store, config) => Effect.gen(function* () {
5
+ yield* hasAddresses(config);
6
+ yield* hasMatchingAbis(config, store);
7
+ });
8
+ const hasAddresses = (config) => Effect.gen(function* () {
9
+ const contracts = Object.entries(config.contracts);
10
+ const emptyAddresses = contracts.filter(([_name, addresses]) => {
11
+ return Object.keys(addresses).length === 0;
12
+ });
13
+ if (emptyAddresses.length > 0) {
14
+ return yield* Effect.fail(new ValidationError({ message: `Found empty addresses definitions: ${emptyAddresses.map((e) => e[0])}` }));
15
+ }
16
+ }).pipe(Effect.withSpan('hasAddresses'));
17
+ const hasMatchingAbis = (config, store) => Effect.gen(function* () {
18
+ const contracts = Object.entries(config.contracts);
19
+ const result = contracts.filter(([_name, addresses]) => {
20
+ return !validateAbisAreTheSame(addresses, store);
21
+ });
22
+ if (result.length > 0) {
23
+ return yield* Effect.fail(new ValidationError({ message: `Found not matching abis: ${result.map((e) => e[0])}` }));
24
+ }
25
+ }).pipe(Effect.withSpan('hasMatchingAbis'));
26
+ const validateAbisAreTheSame = (addresses, store) => {
27
+ const abis = entries(addresses).map(([domain, address]) => {
28
+ return store[domain][address];
29
+ });
30
+ assert(abis.length > 0, 'abis cant be empty');
31
+ const masterAbi = JSON.stringify(abis[0]);
32
+ return abis.every((a) => JSON.stringify(a) === masterAbi);
33
+ };
34
+ export class ValidationError extends Data.TaggedError('ValidationError') {
35
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@sparkdotfi/abi-cli",
3
+ "version": "0.0.1",
4
+ "engines": {
5
+ "node": ">=22.0.0"
6
+ },
7
+ "type": "module",
8
+ "bin": {
9
+ "abi-cli": "./dist/cli.js"
10
+ },
11
+ "module": "./src/index.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./src/index.ts"
15
+ }
16
+ },
17
+ "files": ["dist"],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/sparkdotfi/spark-app.git",
21
+ "directory": "packages/abi-cli"
22
+ },
23
+ "scripts": {
24
+ "lint": "eslint src",
25
+ "verify": "concurrently --names \"LINT,TYPECHECK\" -c \"bgMagenta.bold,bgBlue.bold\" \"pnpm run lint\" \"pnpm run test --silent\" \"pnpm run typecheck\"",
26
+ "fix": "cd ../../ && pnpm run check:fix && cd - && pnpm run verify",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "mocha \"src/**/*.test.ts\"",
29
+ "cli": "tsx ./src/cli.ts",
30
+ "clean": "rm -rf dist",
31
+ "prepublishOnly": "pnpm run clean && pnpm run build",
32
+ "build": "tsc -p tsconfig.build.json"
33
+ },
34
+ "devDependencies": {
35
+ "@sparkdotfi/common-nodejs": "workspace:^",
36
+ "@sparkdotfi/common-universal": "workspace:^",
37
+ "@types/node": "^22.0.0",
38
+ "tsx": "^4.15.6",
39
+ "earl": "^1.3.0",
40
+ "mocha": "^10.8.2",
41
+ "@types/mocha": "^10.0.10",
42
+ "typescript": "*"
43
+ },
44
+ "dependencies": {
45
+ "@effect/platform": "^0.90.0",
46
+ "@effect/platform-node": "^0.94.0",
47
+ "@shazow/whatsabi": "^0.22.2",
48
+ "@types/cli-progress": "^3.11.6",
49
+ "cli-progress": "^3.12.0",
50
+ "effect": "^3.17.4",
51
+ "prettier": "^3.6.2",
52
+ "remeda": "^2.20.2",
53
+ "viem": "^2.33.2"
54
+ }
55
+ }