From 2c5bde8213ca7d77fc8be9ec55cb7ad3ed0ad2cf Mon Sep 17 00:00:00 2001 From: stanlou Date: Tue, 20 Jan 2026 03:25:43 +0100 Subject: [PATCH 01/14] feat: add default modules and configurations for sequencer presets --- package-lock.json | 4 + packages/stack/package.json | 6 +- packages/stack/src/index.ts | 4 + packages/stack/src/presets/app-chain/index.ts | 122 ++++ packages/stack/src/presets/config.ts | 193 ++++++ packages/stack/src/presets/modules/index.ts | 619 ++++++++++++++++++ packages/stack/src/presets/modules/types.ts | 86 +++ packages/stack/src/presets/modules/utils.ts | 381 +++++++++++ packages/stack/src/presets/sequencer/index.ts | 136 ++++ 9 files changed, 1550 insertions(+), 1 deletion(-) create mode 100644 packages/stack/src/presets/app-chain/index.ts create mode 100644 packages/stack/src/presets/config.ts create mode 100644 packages/stack/src/presets/modules/index.ts create mode 100644 packages/stack/src/presets/modules/types.ts create mode 100644 packages/stack/src/presets/modules/utils.ts create mode 100644 packages/stack/src/presets/sequencer/index.ts diff --git a/package-lock.json b/package-lock.json index 10d879a4e..f73837ac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27932,6 +27932,8 @@ "version": "0.1.1-develop.833+397881ed", "license": "MIT", "dependencies": { + "@prisma/client": "^5.19.1", + "mina-fungible-token": "^1.1.0", "reflect-metadata": "^0.1.13" }, "devDependencies": { @@ -27941,9 +27943,11 @@ "@proto-kit/api": "*", "@proto-kit/common": "*", "@proto-kit/deployment": "*", + "@proto-kit/indexer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/persistance": "*", + "@proto-kit/processor": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", diff --git a/packages/stack/package.json b/packages/stack/package.json index 2851234e1..e983b9fe9 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -28,6 +28,8 @@ "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", + "@proto-kit/indexer": "*", + "@proto-kit/processor": "*", "o1js": "^2.10.0", "tsyringe": "^4.10.0" }, @@ -35,7 +37,9 @@ "@jest/globals": "^29.5.0" }, "dependencies": { - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "@prisma/client": "^5.19.1", + "mina-fungible-token": "^1.1.0" }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } diff --git a/packages/stack/src/index.ts b/packages/stack/src/index.ts index 35d49ee83..3c5da028c 100644 --- a/packages/stack/src/index.ts +++ b/packages/stack/src/index.ts @@ -1 +1,5 @@ export * from "./scripts/graphql/server"; +export * from "./presets/app-chain"; +export * from "./presets/sequencer"; +export * from "./presets/config"; +export * from "./presets/modules"; \ No newline at end of file diff --git a/packages/stack/src/presets/app-chain/index.ts b/packages/stack/src/presets/app-chain/index.ts new file mode 100644 index 000000000..48db076cc --- /dev/null +++ b/packages/stack/src/presets/app-chain/index.ts @@ -0,0 +1,122 @@ +import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; +import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; +import { + MandatoryProtocolModulesRecord, + Protocol, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; +import { + AppChain, + AppChainModulesRecord, + SequencerModulesRecord, +} from "@proto-kit/sequencer"; +import { DefaultModules, DefaultConfigs } from "../modules"; +import { DefaultSequencer, DefaultSequencerConfig } from "../sequencer"; + +export class DefaultAppChain { + static inmemory( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.inmemory({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } + static development( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.development({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } + static sovereign( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.sovereign({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } +} + +export class DefaultAppChainConfig { + static inmemory(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.inmemory({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } + static development(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.development({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } + static sovereign(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.sovereign({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } +} diff --git a/packages/stack/src/presets/config.ts b/packages/stack/src/presets/config.ts new file mode 100644 index 000000000..835402150 --- /dev/null +++ b/packages/stack/src/presets/config.ts @@ -0,0 +1,193 @@ +export const inmemoryConfig = { + PROTOKIT_BLOCK_INTERVAL: 5000, + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHQL_HOST: "localhost", + PROTOKIT_GRAPHIQL_ENABLED: true, +}; + +export const developmentConfig = { + PROTOKIT_PROOFS_ENABLED: false, + PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + PROTOKIT_PRUNE_ON_STARTUP: false, + PROTOKIT_LOG_LEVEL: "INFO", + + PROTOKIT_BLOCK_INTERVAL: 30000, + PROTOKIT_SETTLEMENT_INTERVAL: 60000, + PROTOKIT_SETTLEMENT_ENABLED: true, + + REDIS_HOST: "localhost", + REDIS_PORT: 6379, + REDIS_PASSWORD: "password", + + DATABASE_URL: + "postgresql://admin:password@localhost:5432/protokit?schema=public", + + INDEXER_DATABASE_URL: + "postgresql://admin:password@localhost:5433/protokit-indexer?schema=public", + + PROCESSOR_DATABASE_URL: + "postgresql://admin:password@localhost:5434/protokit-processor?schema=public", + + PROTOKIT_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHIQL_ENABLED: true, + + PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + + PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "0.0.0.0", + + MINA_NETWORK: "lightnet", + MINA_NODE_GRAPHQL_HOST: "http://localhost", + MINA_NODE_GRAPHQL_PORT: 8083, + + MINA_ARCHIVE_GRAPHQL_HOST: "http://localhost", + MINA_ARCHIVE_GRAPHQL_PORT: 8085, + + MINA_ACCOUNT_MANAGER_HOST: "http://localhost", + MINA_ACCOUNT_MANAGER_PORT: 8084, + MINA_EXPLORER_PORT: 3001, + + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", + + PROTOKIT_SEQUENCER_PRIVATE_KEY: + "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + PROTOKIT_SEQUENCER_PUBLIC_KEY: + "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", + PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", + + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", + PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", + + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", + PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", + + PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY: + "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", + + PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY: + "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", + + PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY: + "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", + + TEST_ACCOUNT_1_PRIVATE_KEY: + "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", + TEST_ACCOUNT_1_PUBLIC_KEY: + "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", + + OPEN_TELEMETRY_TRACING_ENABLED: true, + OPEN_TELEMETRY_TRACING_URL: "http://localhost:4318", + + OPEN_TELEMETRY_METRICS_ENABLED: true, + OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", + OPEN_TELEMETRY_METRICS_PORT: 4320, + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + +}; + +export const sovereignConfig = { + PROTOKIT_BLOCK_INTERVAL: 10000, + PROTOKIT_SETTLEMENT_INTERVAL: 30000, + PROTOKIT_SETTLEMENT_ENABLED: true, + + PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + + PROTOKIT_PRUNE_ON_STARTUP: false, + PROTOKIT_LOG_LEVEL: "INFO", + + REDIS_HOST: "redis", + REDIS_PORT: 6379, + REDIS_PASSWORD: "password", + + DATABASE_URL: + "postgresql://admin:password@postgres:5432/protokit?schema=public", + + INDEXER_DATABASE_URL: + "postgresql://admin:password@indexer-postgres:5432/protokit-indexer?schema=public", + + PROCESSOR_DATABASE_URL: + "postgresql://admin:password@processor-postgres:5432/protokit-processor?schema=public", + + PROTOKIT_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHIQL_ENABLED: true, + + PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + + PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "indexer", + + MINA_NETWORK: "lightnet", + MINA_NODE_GRAPHQL_HOST: "http://lightnet", + MINA_NODE_GRAPHQL_PORT: 8080, + + MINA_ARCHIVE_GRAPHQL_HOST: "http://lightnet", + MINA_ARCHIVE_GRAPHQL_PORT: 8282, + + MINA_ACCOUNT_MANAGER_HOST: "http://lightnet", + MINA_ACCOUNT_MANAGER_PORT: 8084, + MINA_EXPLORER_PORT: 3001, + + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", + + PROTOKIT_SEQUENCER_PRIVATE_KEY: + "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + PROTOKIT_SEQUENCER_PUBLIC_KEY: + "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", + PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", + + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", + PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", + + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", + PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", + + TEST_ACCOUNT_1_PRIVATE_KEY: + "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", + TEST_ACCOUNT_1_PUBLIC_KEY: + "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", + + OPEN_TELEMETRY_TRACING_ENABLED: true, + OPEN_TELEMETRY_TRACING_URL: "http://otel-collector:4317", + + OPEN_TELEMETRY_METRICS_ENABLED: true, + OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", + OPEN_TELEMETRY_METRICS_PORT: 4320, + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + +}; \ No newline at end of file diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts new file mode 100644 index 000000000..4561e14e1 --- /dev/null +++ b/packages/stack/src/presets/modules/index.ts @@ -0,0 +1,619 @@ +import { + VanillaGraphqlModules, + GraphqlSequencerModule, + GraphqlServer, + OpenTelemetryServer, +} from "@proto-kit/api"; +import { + PrivateMempool, + SequencerModulesRecord, + TimedBlockTrigger, + BlockProducerModule, + SequencerStartupModule, + LocalTaskWorkerModule, + VanillaTaskWorkerModules, + MinaBaseLayer, + ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + DatabasePruneModule, + InMemoryDatabase, + LocalTaskQueue, +} from "@proto-kit/sequencer"; +import { + IndexerNotifier, + GeneratedResolverFactoryGraphqlModule, + IndexBlockTask, +} from "@proto-kit/indexer"; +import { PrivateKey } from "o1js"; +import { PrismaRedisDatabase } from "@proto-kit/persistance"; +import { BullQueue } from "@proto-kit/deployment"; +import { + TimedProcessorTrigger, + BlockFetching, + HandlersExecutor, + ResolverFactoryGraphqlModule, + HandlersRecord, + BasePrismaClient, +} from "@proto-kit/processor"; +import { + BlockStorageNetworkStateModule, + InMemoryTransactionSender, + StateServiceQueryModule, +} from "@proto-kit/sdk"; +import { AppChainModulesRecord } from "@proto-kit/sequencer"; +import { + buildCustomTokenConfig, + buildSettlementTokenConfig, + definePreset, + orderModulesByDependencies, + parseApiEnv, + parseCoreEnv, + parseMetricsEnv, + parseSettlementEnv, + parseIndexerEnv, + parseProcessorEnv, + parseDatabaseEnv, + parseDatabasePruneEnv, + parseGraphqlServerEnv, + parseRedisEnv, + resolveEnv, +} from "./utils"; +import { NonEmptyArray } from "type-graphql"; +import { + Environment, + ModuleOverrides, + ApiEnv, + ConfigOverrides, + CoreEnv, + MetricsEnv, + IndexerEnv, + ProcessorEnv, + SettlementEnv, + DatabaseEnv, + TaskQueueEnv, + DatabasePruneEnv, + GraphqlServerEnv, + RedisEnv, +} from "./types"; + +export class DefaultModules { + static api(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + GraphqlServer, + Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), + }, + options?.overrides + ); + } + static core(options?: { + overrides?: ModuleOverrides; + settlementEnabled?: boolean; + }): SequencerModulesRecord { + return definePreset( + { + ...DefaultModules.api(), + Mempool: PrivateMempool, + BlockProducerModule, + BlockTrigger: TimedBlockTrigger, + SequencerStartupModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.withoutSettlement() + ), + ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), + }, + options?.overrides + ); + } + static metrics(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + OpenTelemetryServer, + }, + options?.overrides + ); + } + static settlement(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + BaseLayer: MinaBaseLayer, + FeeStrategy: ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + }, + options?.overrides + ); + } + static sequencerIndexer(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + IndexerNotifier, + }, + options?.overrides + ); + } + static indexer(options?: { overrides?: ModuleOverrides }) { + return definePreset( + { + Database: PrismaRedisDatabase, + TaskQueue: BullQueue, + TaskWorker: LocalTaskWorkerModule.from({ + IndexBlockTask, + }), + GraphqlServer, + Graphql: GraphqlSequencerModule.from({ + GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, + }), + }, + options?.overrides + ); + } + static processor( + resolvers: NonEmptyArray, + handlers: HandlersRecord, + options?: { overrides?: ModuleOverrides } + ) { + return definePreset( + { + GraphqlServer, + GraphqlSequencerModule: GraphqlSequencerModule.from({ + ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), + }), + HandlersExecutor: HandlersExecutor.from(handlers), + BlockFetching, + Trigger: TimedProcessorTrigger, + }, + options?.overrides + ); + } + static database(options?: { + overrides?: ModuleOverrides; + preset?: Environment; + }): SequencerModulesRecord { + const preset = options?.preset ?? "inmemory"; + + return definePreset( + { + Database: + preset === "inmemory" ? InMemoryDatabase : PrismaRedisDatabase, + }, + options?.overrides + ); + } + static taskQueue(options?: { + overrides?: ModuleOverrides; + preset?: Environment; + }): SequencerModulesRecord { + const preset = options?.preset ?? "inmemory"; + return definePreset( + { + TaskQueue: preset === "inmemory" ? LocalTaskQueue : BullQueue, + }, + options?.overrides + ); + } + static databasePrune(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + DatabasePruneModule, + }, + options?.overrides + ); + } + static worker(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + TaskQueue: BullQueue, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + }, + options?.overrides + ); + } + static appChainBase(options?: { + overrides?: Partial; + }): AppChainModulesRecord { + return definePreset( + { + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + }, + options?.overrides + ) as AppChainModulesRecord; + } + static settlementScript(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + ...DefaultModules.settlement(), + Mempool: PrivateMempool, + TaskQueue: LocalTaskQueue, + SequencerStartupModule, + }, + options?.overrides + ); + } + static ordered(modules: any) { + return orderModulesByDependencies(modules); + } +} +export class DefaultConfigs { + static api(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset( + { + Graphql: VanillaGraphqlModules.defaultConfig(), + GraphqlServer: DefaultConfigs.graphqlServer({ + type: "protokit", + preset: options?.preset, + envs: options?.envs, + }), + }, + options?.overrides + ); + } + static core(options?: { + preset?: Environment; + envs?: Partial & Partial & Partial; + overrides?: ConfigOverrides; + settlementEnabled?: boolean; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseCoreEnv( + { ...config, ...options?.envs }, + options?.settlementEnabled + ); + const apiConfig = DefaultConfigs.api({ + preset: options?.preset, + envs: options?.envs, + }); + const settlementConfig = options?.settlementEnabled + ? DefaultConfigs.settlement({ + preset: options?.preset, + envs: options?.envs, + }) + : {}; + const blockTriggerConfig = { + blockInterval: parsed.blockInterval, + produceEmptyBlocks: true, + ...(options?.settlementEnabled + ? { + settlementInterval: parsed.settlementInterval, + settlementTokenConfig: buildSettlementTokenConfig( + parsed.minaBridgeKey!, + buildCustomTokenConfig( + parsed.customTokenKey, + parsed.customTokenBridgeKey + ) + ), + } + : { settlementTokenConfig: {} }), + }; + + return definePreset( + { + ...apiConfig, + Mempool: {}, + BlockProducerModule: {}, + BlockTrigger: blockTriggerConfig, + SequencerStartupModule: {}, + LocalTaskWorkerModule: VanillaGraphqlModules.defaultConfig(), + ...settlementConfig, + }, + options?.overrides + ); + } + static metrics(options?: { + preset?: Environment; + envs?: MetricsEnv; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const configs = resolveEnv(options?.preset, options?.envs); + const parsed = parseMetricsEnv(configs); + return definePreset( + { + OpenTelemetryServer: { + metrics: { + enabled: parsed.metricsEnabled, + prometheus: { + host: parsed.metricsHost, + port: parsed.metricsPort, + appendTimestamp: true, + }, + nodeScrapeInterval: parsed.metricsScrapingFrequency, + }, + tracing: { + enabled: parsed.tracingEnabled, + otlp: { + url: parsed.tracingUrl, + }, + }, + }, + }, + options?.overrides + ); + } + static sequencerIndexer(options?: { + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset({ IndexerNotifier: {} }, options?.overrides); + } + static indexer(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseIndexerEnv(config); + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + const databaseConfig = DefaultConfigs.database({ + preset: options?.preset, + envs: options?.envs, + }); + const graphqlServerConfig = DefaultConfigs.graphqlServer({ + type: "indexer", + preset: options?.preset, + envs: options?.envs, + }); + + return definePreset( + { + ...databaseConfig, + TaskQueue: redisConfig.TaskQueue, + TaskWorker: { + IndexBlockTask: {}, + }, + ...graphqlServerConfig, + Graphql: { + GeneratedResolverFactory: {}, + }, + }, + options?.overrides + ); + } + static processor(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseProcessorEnv(config); + const graphqlServerConfig = DefaultConfigs.graphqlServer({ + type: "processor", + preset: options?.preset, + envs: options?.envs, + }); + return definePreset( + { + HandlersExecutor: {}, + BlockFetching: { + url: `http://${parsed.processorIndexerGraphqlHost}:${parsed.indexerGraphqlPort}`, + }, + Trigger: { + interval: (parsed.blockInterval ?? 5000) / 5, + }, + ...graphqlServerConfig, + GraphqlSequencerModule: { + ResolverFactory: {}, + }, + }, + options?.overrides + ); + } + static settlement(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseSettlementEnv(config); + + return definePreset( + { + BaseLayer: { + network: { + type: parsed.network, + graphql: parsed.graphql, + archive: parsed.archive, + accountManager: parsed.accountManager, + }, + }, + SettlementModule: { + feepayer: PrivateKey.fromBase58(parsed.sequencerPrivateKey), + keys: { + settlement: PrivateKey.fromBase58( + parsed.settlementContractPrivateKey + ), + dispatch: PrivateKey.fromBase58( + parsed.dispatcherContractPrivateKey + ), + minaBridge: PrivateKey.fromBase58( + parsed.minaBridgeContractPrivateKey + ), + }, + }, + FeeStrategy: {}, + BatchProducerModule: {}, + }, + options?.overrides + ); + } + static database(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const preset = options?.preset ?? "inmemory"; + if (preset === "inmemory") { + return { Database: definePreset({}, options?.overrides) }; + } + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseDatabaseEnv(config); + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + return { + Database: definePreset( + { + ...redisConfig, + prisma: { + connection: parsed.databaseUrl, + }, + }, + options?.overrides + ), + }; + } + static taskQueue(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const preset = options?.preset ?? "inmemory"; + if (preset === "inmemory") { + return { + TaskQueue: definePreset({}, options?.overrides), + }; + } + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + + return { TaskQueue: definePreset(redisConfig, options?.overrides) }; + } + static databasePrune(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseDatabasePruneEnv(config); + + return { + DatabasePruneModule: definePreset( + { + pruneOnStartup: parsed.pruneOnStartup, + }, + options?.overrides + ), + }; + } + static graphqlServer(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + type?: "indexer" | "processor" | "protokit"; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseGraphqlServerEnv(config, options?.type); + + return definePreset( + { + port: parsed.graphqlPort, + host: parsed.graphqlHost, + graphiql: parsed.graphiqlEnabled, + }, + options?.overrides + ); + } + static redis(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseRedisEnv(config); + + return { + redis: definePreset( + { + host: parsed.redisHost, + port: parsed.redisPort, + password: parsed.redisPassword, + }, + options?.overrides + ), + }; + } + static appChainBase(options?: { + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset( + { + QueryTransportModule: {}, + NetworkStateTransportModule: {}, + TransactionSender: {}, + }, + options?.overrides + ); + } + static worker(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + overrides: { + db: 1, + }, + }); + + return definePreset( + { + TaskQueue: redisConfig, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }, + options?.overrides + ); + } + static settlementScript(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const settlementConfig = DefaultConfigs.settlement({ + preset: options?.preset, + envs: options?.envs, + }); + return definePreset( + { + ...settlementConfig, + SequencerStartupModule: {}, + TaskQueue: { + simulatedDuration: 0, + }, + Mempool: {}, + }, + options?.overrides + ); + } +} diff --git a/packages/stack/src/presets/modules/types.ts b/packages/stack/src/presets/modules/types.ts new file mode 100644 index 000000000..712511168 --- /dev/null +++ b/packages/stack/src/presets/modules/types.ts @@ -0,0 +1,86 @@ +import { RecursivePartial, ModulesConfig } from "@proto-kit/common"; +import { SequencerModulesRecord } from "@proto-kit/sequencer"; + +export type ModuleOverrides = Partial; +export type ConfigOverrides = RecursivePartial>; +export type Environment = "inmemory" | "development" | "sovereign"; +export type ApiEnv = { + PROTOKIT_GRAPHQL_PORT: number | string; + PROTOKIT_GRAPHQL_HOST: string; + PROTOKIT_GRAPHIQL_ENABLED: boolean | string; +}; +export type CoreEnv = { + PROTOKIT_BLOCK_INTERVAL: number | string; + PROTOKIT_SETTLEMENT_INTERVAL?: number | string; + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY?: string; + PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY?: string; + PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY?: string; +}; +export type MetricsEnv = { + OPEN_TELEMETRY_METRICS_ENABLED: boolean | string; + OPEN_TELEMETRY_METRICS_HOST: string; + OPEN_TELEMETRY_METRICS_PORT: number | string; + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: number | string; + OPEN_TELEMETRY_TRACING_ENABLED: boolean | string; + OPEN_TELEMETRY_TRACING_URL: string; +}; +export type SettlementEnv = { + MINA_NETWORK: string; + MINA_NODE_GRAPHQL_HOST: string; + MINA_NODE_GRAPHQL_PORT: number | string; + MINA_ARCHIVE_GRAPHQL_HOST: string; + MINA_ARCHIVE_GRAPHQL_PORT: number | string; + MINA_ACCOUNT_MANAGER_HOST: string; + MINA_ACCOUNT_MANAGER_PORT: number | string; + PROTOKIT_SEQUENCER_PRIVATE_KEY: string; + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: string; + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: string; + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: string; +}; +export type IndexerEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; + INDEXER_DATABASE_URL: string; + PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; + PROTOKIT_INDEXER_GRAPHQL_HOST: string; + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: boolean | string; +}; +export type ProcessorEnv = { + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: string; + PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; + PROTOKIT_BLOCK_INTERVAL: number | string; + PROTOKIT_PROCESSOR_GRAPHQL_HOST: string; + PROTOKIT_PROCESSOR_GRAPHQL_PORT: number | string; + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: boolean | string; +}; +export type DatabaseEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; + DATABASE_URL: string; +}; +export type TaskQueueEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; +}; +export type DatabasePruneEnv = { + PRUNE_ON_STARTUP?: boolean | string; +}; +export type GraphqlServerEnv = { + PROTOKIT_GRAPHQL_PORT?: number | string; + PROTOKIT_GRAPHQL_HOST?: string; + PROTOKIT_GRAPHIQL_ENABLED?: boolean | string; + PROTOKIT_INDEXER_GRAPHQL_HOST?: string; + PROTOKIT_INDEXER_GRAPHQL_PORT?: number | string; + PROTOKIT_INDEXER_GRAPHIQL_ENABLED?: boolean | string; + PROTOKIT_PROCESSOR_GRAPHQL_HOST?: string; + PROTOKIT_PROCESSOR_GRAPHQL_PORT?: number | string; + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED?: boolean | string; +}; +export type RedisEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; +}; diff --git a/packages/stack/src/presets/modules/utils.ts b/packages/stack/src/presets/modules/utils.ts new file mode 100644 index 000000000..679b35165 --- /dev/null +++ b/packages/stack/src/presets/modules/utils.ts @@ -0,0 +1,381 @@ +import { PrivateKey, TokenId } from "o1js"; +import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; +import { FungibleToken } from "mina-fungible-token"; +import { assertDefined } from "@proto-kit/common"; +import { + ApiEnv, + CoreEnv, + DatabaseEnv, + DatabasePruneEnv, + Environment, + GraphqlServerEnv, + IndexerEnv, + MetricsEnv, + ProcessorEnv, + RedisEnv, + SettlementEnv, + TaskQueueEnv, +} from "./types"; + +function ensureDefined(obj: Record, keys: string[]) { + keys.forEach((k) => assertDefined(obj[k], `${k} is required`)); +} +export function definePreset( + base: T, + overrides?: Partial +): T { + return { + ...base, + ...overrides, + }; +} +const MODULE_DEPENDENCIES: Record = { + Database: [], + TaskQueue: [], + OpenTelemetryServer: [], + BaseLayer: [], + FeeStrategy: [], + + Protocol: [], + Mempool: ["Database"], + BlockProducerModule: ["Database"], + + LocalTaskWorkerModule: ["TaskQueue"], + TaskWorker: ["TaskQueue"], + SequencerStartupModule: ["Database"], + BatchProducerModule: ["Database"], + BlockTrigger: ["BlockProducerModule", "BatchProducerModule"], + + SettlementModule: ["Database", "BaseLayer", "FeeStrategy"], + + DatabasePruneModule: ["Database"], + + Graphql: [], + GraphqlServer: [], + + IndexBlockTask: ["Database", "TaskQueue"], + IndexerNotifier: ["Database", "TaskQueue"], + GeneratedResolverFactory: [], + + BlockFetching: [], + HandlersExecutor: [], + Trigger: ["BlockFetching", "HandlersExecutor"], + ResolverFactory: [], +}; +export function orderModulesByDependencies( + modules: Record +): Record { + const moduleSet = new Set(Object.keys(modules)); + const ordered: Record = {}; + const visited = new Set(); + + function visit(name: string) { + if (!moduleSet.has(name) || visited.has(name)) return; + + const deps = MODULE_DEPENDENCIES[name] ?? []; + for (const dep of deps) { + visit(dep); + } + + visited.add(name); + ordered[name] = modules[name]; + } + + for (const name of moduleSet) { + visit(name); + } + + return ordered; +} +export function resolveEnv( + preset: Environment = "inmemory", + envs?: Partial +): T { + return { + ...getConfigs(preset), + ...envs, + } as T; +} +export function buildCustomTokenConfig( + customTokenPrivateKey?: string, + customTokenBridgePrivateKey?: string +): Record { + if (!customTokenPrivateKey || !customTokenBridgePrivateKey) { + return {}; + } + const pk = PrivateKey.fromBase58(customTokenPrivateKey); + const tokenId = TokenId.derive(pk.toPublicKey()).toString(); + return { + [tokenId]: { + bridgingContractPrivateKey: PrivateKey.fromBase58( + customTokenBridgePrivateKey + ), + tokenOwner: FungibleToken, + tokenOwnerPrivateKey: customTokenPrivateKey, + }, + }; +} +export function buildSettlementTokenConfig( + bridgePrivateKey: string, + customTokens: Record = {} +): Record { + return { + "1": { + bridgingContractPrivateKey: PrivateKey.fromBase58(bridgePrivateKey), + }, + ...customTokens, + }; +} +export function getConfigs(preset: Environment) { + switch (preset) { + case "development": + return developmentConfig; + case "sovereign": + return sovereignConfig; + case "inmemory": + default: + return inmemoryConfig; + } +} + +export function parseApiEnv(envs: ApiEnv): { + graphqlPort: number; + graphqlHost: string; + graphiqlEnabled: boolean; +} { + ensureDefined(envs, [ + "PROTOKIT_GRAPHIQL_ENABLED", + "PROTOKIT_GRAPHQL_HOST", + "PROTOKIT_GRAPHQL_PORT", + ]); + return { + graphqlPort: Number(envs.PROTOKIT_GRAPHQL_PORT), + graphqlHost: envs.PROTOKIT_GRAPHQL_HOST, + graphiqlEnabled: Boolean(envs.PROTOKIT_GRAPHIQL_ENABLED), + }; +} +export function parseCoreEnv( + envs: CoreEnv, + settlementEnabled?: boolean +): { + blockInterval: number; + settlementInterval?: number; + minaBridgeKey?: string; + customTokenKey?: string; + customTokenBridgeKey?: string; +} { + ensureDefined(envs, ["PROTOKIT_BLOCK_INTERVAL"]); + if (settlementEnabled) { + ensureDefined(envs, [ + "PROTOKIT_SETTLEMENT_INTERVAL", + "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + ]); + if (envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY) { + ensureDefined(envs, ["PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY"]); + } + } + return { + blockInterval: Number(envs.PROTOKIT_BLOCK_INTERVAL), + settlementInterval: Number(envs.PROTOKIT_SETTLEMENT_INTERVAL), + minaBridgeKey: envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY, + customTokenKey: envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, + customTokenBridgeKey: envs.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, + }; +} +export function parseMetricsEnv(envs: MetricsEnv): { + metricsEnabled: boolean; + metricsHost?: string; + metricsPort?: number; + metricsScrapingFrequency?: number; + tracingEnabled: boolean; + tracingUrl?: string; +} { + ensureDefined(envs as Record, [ + "OPEN_TELEMETRY_METRICS_ENABLED", + "OPEN_TELEMETRY_TRACING_ENABLED", + "OPEN_TELEMETRY_TRACING_URL", + "OPEN_TELEMETRY_METRICS_HOST", + "OPEN_TELEMETRY_METRICS_PORT", + "OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY", + ]); + return { + metricsEnabled: Boolean(envs.OPEN_TELEMETRY_METRICS_ENABLED), + metricsHost: envs.OPEN_TELEMETRY_METRICS_HOST, + metricsPort: Number(envs.OPEN_TELEMETRY_METRICS_PORT), + metricsScrapingFrequency: Number( + envs.OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY + ), + tracingEnabled: Boolean(envs.OPEN_TELEMETRY_TRACING_ENABLED), + tracingUrl: envs.OPEN_TELEMETRY_TRACING_URL, + }; +} +export function parseSettlementEnv(envs: SettlementEnv): { + network: string; + graphql: string; + archive: string; + accountManager: string; + sequencerPrivateKey: string; + settlementContractPrivateKey: string; + dispatcherContractPrivateKey: string; + minaBridgeContractPrivateKey: string; +} { + ensureDefined(envs as Record, [ + "MINA_ACCOUNT_MANAGER_HOST", + "MINA_ACCOUNT_MANAGER_PORT", + "MINA_ARCHIVE_GRAPHQL_HOST", + "MINA_ARCHIVE_GRAPHQL_PORT", + "MINA_NODE_GRAPHQL_HOST", + "MINA_NODE_GRAPHQL_PORT", + "MINA_NETWORK", + "PROTOKIT_SEQUENCER_PRIVATE_KEY", + "PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY", + "PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY", + "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + ]); + + return { + network: envs.MINA_NETWORK!, + graphql: `${envs.MINA_NODE_GRAPHQL_HOST}:${envs.MINA_NODE_GRAPHQL_PORT}/graphql`, + archive: `${envs.MINA_ARCHIVE_GRAPHQL_HOST}:${envs.MINA_ARCHIVE_GRAPHQL_PORT}`, + accountManager: `${envs.MINA_ACCOUNT_MANAGER_HOST}:${envs.MINA_ACCOUNT_MANAGER_PORT}`, + sequencerPrivateKey: envs.PROTOKIT_SEQUENCER_PRIVATE_KEY!, + settlementContractPrivateKey: + envs.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY!, + dispatcherContractPrivateKey: + envs.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY!, + minaBridgeContractPrivateKey: + envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY!, + }; +} +export function parseIndexerEnv(envs: IndexerEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; + indexerDatabaseUrl?: string; + graphqlPort?: number; + graphqlHost?: string; + graphiqlEnabled?: boolean; +} { + ensureDefined(envs as Record, [ + "REDIS_HOST", + "REDIS_PORT", + "REDIS_PASSWORD", + "INDEXER_DATABASE_URL", + "PROTOKIT_INDEXER_GRAPHQL_PORT", + "PROTOKIT_INDEXER_GRAPHQL_HOST", + "PROTOKIT_INDEXER_GRAPHIQL_ENABLED", + ]); + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + indexerDatabaseUrl: envs.INDEXER_DATABASE_URL, + graphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT + ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) + : undefined, + graphqlHost: envs.PROTOKIT_INDEXER_GRAPHQL_HOST, + graphiqlEnabled: envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED + ? Boolean(envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED) + : undefined, + }; +} +export function parseProcessorEnv(envs: ProcessorEnv): { + processorIndexerGraphqlHost?: string; + indexerGraphqlPort?: number; + blockInterval?: number; + processorGraphqlHost?: string; + processorGraphqlPort?: number; + processorGraphiqlEnabled?: boolean; +} { + ensureDefined(envs as Record, [ + "PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST", + "PROTOKIT_INDEXER_GRAPHQL_PORT", + "PROTOKIT_BLOCK_INTERVAL", + "PROTOKIT_PROCESSOR_GRAPHQL_HOST", + "PROTOKIT_PROCESSOR_GRAPHQL_PORT", + "PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED", + ]); + return { + processorIndexerGraphqlHost: envs.PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST, + indexerGraphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT + ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) + : undefined, + blockInterval: envs.PROTOKIT_BLOCK_INTERVAL + ? Number(envs.PROTOKIT_BLOCK_INTERVAL) + : undefined, + processorGraphqlHost: envs.PROTOKIT_PROCESSOR_GRAPHQL_HOST, + processorGraphqlPort: envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT + ? Number(envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT) + : undefined, + processorGraphiqlEnabled: envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED + ? Boolean(envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED) + : undefined, + }; +} +export function parseDatabaseEnv(envs: DatabaseEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; + databaseUrl?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + databaseUrl: envs.DATABASE_URL, + }; +} +export function parseTaskQueueEnv(envs: TaskQueueEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + }; +} +export function parseDatabasePruneEnv(envs: DatabasePruneEnv): { + pruneOnStartup: boolean; +} { + return { + pruneOnStartup: + envs.PRUNE_ON_STARTUP === "true" || envs.PRUNE_ON_STARTUP === true, + }; +} +export function parseGraphqlServerEnv( + envs: GraphqlServerEnv, + type: "protokit" | "indexer" | "processor" = "protokit" +): { + graphqlPort?: number; + graphqlHost?: string; + graphiqlEnabled?: boolean; +} { + const prefix = + type === "indexer" + ? "PROTOKIT_INDEXER" + : type === "processor" + ? "PROTOKIT_PROCESSOR" + : "PROTOKIT"; + return { + graphqlPort: envs[`${prefix}_GRAPHQL_PORT`] + ? Number(envs[`${prefix}_GRAPHQL_PORT`]) + : undefined, + graphqlHost: envs[`${prefix}_GRAPHQL_HOST`], + graphiqlEnabled: envs[`${prefix}_GRAPHIQL_ENABLED`] + ? Boolean(envs[`${prefix}_GRAPHIQL_ENABLED`]) + : undefined, + }; +} +export function parseRedisEnv(envs: RedisEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + }; +} diff --git a/packages/stack/src/presets/sequencer/index.ts b/packages/stack/src/presets/sequencer/index.ts new file mode 100644 index 000000000..1fd6919fb --- /dev/null +++ b/packages/stack/src/presets/sequencer/index.ts @@ -0,0 +1,136 @@ +import { SequencerModulesRecord, Sequencer } from "@proto-kit/sequencer"; +import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; +import { DefaultConfigs, DefaultModules } from "../modules"; +import { definePreset } from "../modules/utils"; + +export class DefaultSequencer { + static inmemory(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + ...DefaultModules.database(), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.taskQueue(), + ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } + static development(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + // ordering of the modules matters due to dependency resolution + ...DefaultModules.database({ preset: "development" }), + ...DefaultModules.databasePrune(), + ...DefaultModules.metrics(), + ...DefaultModules.taskQueue({ preset: "development" }), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.sequencerIndexer(), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } + static sovereign(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + // ordering of the modules matters due to dependency resolution + ...DefaultModules.database(), + ...DefaultModules.taskQueue(), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.sequencerIndexer(), + ...DefaultModules.metrics(), + ...DefaultModules.databasePrune(), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } +} +export class DefaultSequencerConfig { + static inmemory(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ settlementEnabled: options?.settlementEnabled }), + ...DefaultConfigs.database({ preset: "inmemory" }), + ...DefaultConfigs.taskQueue({ preset: "inmemory" }), + ...options?.overrideConfig, + }; + } + static development(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ + settlementEnabled: options?.settlementEnabled, + preset: "development", + }), + ...DefaultConfigs.sequencerIndexer(), + ...DefaultConfigs.metrics({ preset: "development" }), + ...DefaultConfigs.databasePrune({ preset: "development" }), + ...DefaultConfigs.taskQueue({ + preset: "development", + overrides: { + ...DefaultConfigs.redis({ + preset: "development", + overrides: { db: 1 }, + }), + }, + }), + ...DefaultConfigs.database({ preset: "development" }), + ...options?.overrideConfig, + }; + } + static sovereign(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ + settlementEnabled: options?.settlementEnabled, + preset: "sovereign", + }), + ...DefaultConfigs.sequencerIndexer(), + ...DefaultConfigs.metrics({ preset: "sovereign" }), + ...DefaultConfigs.databasePrune({ preset: "sovereign" }), + ...DefaultConfigs.taskQueue({ + preset: "sovereign", + overrides: { + ...DefaultConfigs.redis({ + preset: "sovereign", + overrides: { db: 1 }, + }), + }, + }), + ...DefaultConfigs.database({ preset: "sovereign" }), + ...options?.overrideConfig, + }; + } +} From 61c9abb73a8290e49d0a3005543dd1a4cea366ad Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 05:44:12 +0100 Subject: [PATCH 02/14] use default modules --- packages/processor/src/index.ts | 1 + packages/stack/package.json | 4 +- packages/stack/src/index.ts | 6 +- packages/stack/src/presets/app-chain/index.ts | 122 --- packages/stack/src/presets/config.ts | 235 +++--- packages/stack/src/presets/modules/index.ts | 779 ++++++++---------- packages/stack/src/presets/modules/types.ts | 123 ++- packages/stack/src/presets/modules/utils.ts | 369 +-------- packages/stack/src/presets/sequencer/index.ts | 136 --- 9 files changed, 533 insertions(+), 1242 deletions(-) delete mode 100644 packages/stack/src/presets/app-chain/index.ts delete mode 100644 packages/stack/src/presets/sequencer/index.ts diff --git a/packages/processor/src/index.ts b/packages/processor/src/index.ts index b6bc05ff7..918e46e03 100644 --- a/packages/processor/src/index.ts +++ b/packages/processor/src/index.ts @@ -1,6 +1,7 @@ export * from "./Processor"; export * from "./ProcessorModule"; export * from "./handlers/HandlersExecutor"; +export * from "./handlers/BasePrismaClient"; export * from "./storage/Database"; export * from "./triggers/TimedProcessorTrigger"; export * from "./indexer/BlockFetching"; diff --git a/packages/stack/package.json b/packages/stack/package.json index e983b9fe9..dc3c50ad3 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -39,7 +39,9 @@ "dependencies": { "reflect-metadata": "^0.1.13", "@prisma/client": "^5.19.1", - "mina-fungible-token": "^1.1.0" + "mina-fungible-token": "^1.1.0", + "type-graphql": "2.0.0-rc.2" + }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } diff --git a/packages/stack/src/index.ts b/packages/stack/src/index.ts index 3c5da028c..fb31453cc 100644 --- a/packages/stack/src/index.ts +++ b/packages/stack/src/index.ts @@ -1,5 +1,5 @@ export * from "./scripts/graphql/server"; -export * from "./presets/app-chain"; -export * from "./presets/sequencer"; export * from "./presets/config"; -export * from "./presets/modules"; \ No newline at end of file +export * from "./presets/modules/types"; +export * from "./presets/modules/utils"; +export * from "./presets/modules"; diff --git a/packages/stack/src/presets/app-chain/index.ts b/packages/stack/src/presets/app-chain/index.ts deleted file mode 100644 index 48db076cc..000000000 --- a/packages/stack/src/presets/app-chain/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; -import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; -import { - MandatoryProtocolModulesRecord, - Protocol, - ProtocolModulesRecord, -} from "@proto-kit/protocol"; -import { - AppChain, - AppChainModulesRecord, - SequencerModulesRecord, -} from "@proto-kit/sequencer"; -import { DefaultModules, DefaultConfigs } from "../modules"; -import { DefaultSequencer, DefaultSequencerConfig } from "../sequencer"; - -export class DefaultAppChain { - static inmemory( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.inmemory({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } - static development( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.development({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } - static sovereign( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.sovereign({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } -} - -export class DefaultAppChainConfig { - static inmemory(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.inmemory({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } - static development(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.development({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } - static sovereign(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.sovereign({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } -} diff --git a/packages/stack/src/presets/config.ts b/packages/stack/src/presets/config.ts index 835402150..040b14f42 100644 --- a/packages/stack/src/presets/config.ts +++ b/packages/stack/src/presets/config.ts @@ -1,193 +1,184 @@ export const inmemoryConfig = { - PROTOKIT_BLOCK_INTERVAL: 5000, - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHQL_HOST: "localhost", - PROTOKIT_GRAPHIQL_ENABLED: true, + blockInterval: 5000, + graphqlHost: "localhost", + graphqlPort: 8080, + graphiqlEnabled: true, }; - export const developmentConfig = { - PROTOKIT_PROOFS_ENABLED: false, - PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, - PROTOKIT_PRUNE_ON_STARTUP: false, - PROTOKIT_LOG_LEVEL: "INFO", - - PROTOKIT_BLOCK_INTERVAL: 30000, - PROTOKIT_SETTLEMENT_INTERVAL: 60000, - PROTOKIT_SETTLEMENT_ENABLED: true, - - REDIS_HOST: "localhost", - REDIS_PORT: 6379, - REDIS_PASSWORD: "password", - - DATABASE_URL: + proofsEnabled: false, + + shouldAttemptDbMigration: true, + shouldAttemptIndexerDbMigration: true, + shouldAttemptProcessorDbMigration: true, + + pruneOnStartup: false, + + blockInterval: 30000, + settlementInterval: 60000, + settlementEnabled: true, + + redisHost: "localhost", + redisPort: 6379, + redisPassword: "password", + + databaseUrl: "postgresql://admin:password@localhost:5432/protokit?schema=public", - INDEXER_DATABASE_URL: + indexerDatabaseUrl: "postgresql://admin:password@localhost:5433/protokit-indexer?schema=public", - PROCESSOR_DATABASE_URL: + processorDatabaseUrl: "postgresql://admin:password@localhost:5434/protokit-processor?schema=public", - PROTOKIT_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHIQL_ENABLED: true, + graphqlHost: "0.0.0.0", + graphqlPort: 8080, + graphiqlEnabled: true, - PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + indexerGraphqlHost: "0.0.0.0", + indexerGraphqlPort: 8081, + indexerGraphqlEnabled: true, - PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "0.0.0.0", + processorGraphqlHost: "0.0.0.0", + processorGraphqlPort: 8082, + processorGraphqlEnabled: true, - MINA_NETWORK: "lightnet", - MINA_NODE_GRAPHQL_HOST: "http://localhost", - MINA_NODE_GRAPHQL_PORT: 8083, + processorIndexerGraphqlHost: "0.0.0.0", - MINA_ARCHIVE_GRAPHQL_HOST: "http://localhost", - MINA_ARCHIVE_GRAPHQL_PORT: 8085, + minaNetwork: "lightnet", + minaNodeGraphqlHost: "http://localhost", + minaNodeGraphqlPort: 8083, - MINA_ACCOUNT_MANAGER_HOST: "http://localhost", - MINA_ACCOUNT_MANAGER_PORT: 8084, - MINA_EXPLORER_PORT: 3001, + minaArchiveGraphqlHost: "http://localhost", + minaArchiveGraphqlPort: 8085, - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + minaAccountManagerHost: "http://localhost", + minaAccountManagerPort: 8084, + minaExplorerPort: 3001, + + transactionFeeRecipientPrivateKey: "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + transactionFeeRecipientPublicKey: "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", - PROTOKIT_SEQUENCER_PRIVATE_KEY: - "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", - PROTOKIT_SEQUENCER_PUBLIC_KEY: - "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + sequencerPrivateKey: "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + sequencerPublicKey: "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + settlementContractPrivateKey: "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", - PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + settlementContractPublicKey: "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + dispatcherContractPrivateKey: "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", - PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + dispatcherContractPublicKey: "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + minaBridgeContractPrivateKey: "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", - PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + minaBridgeContractPublicKey: "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", - PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY: - "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", + customTokenPrivateKey: "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", - PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY: + customTokenAdminPrivateKey: "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", - PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY: + customTokenBridgePrivateKey: "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", - TEST_ACCOUNT_1_PRIVATE_KEY: + testAccount1PrivateKey: "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", - TEST_ACCOUNT_1_PUBLIC_KEY: + testAccount1PublicKey: "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", - OPEN_TELEMETRY_TRACING_ENABLED: true, - OPEN_TELEMETRY_TRACING_URL: "http://localhost:4318", - - OPEN_TELEMETRY_METRICS_ENABLED: true, - OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", - OPEN_TELEMETRY_METRICS_PORT: 4320, - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + openTelemetryTracingEnabled: true, + openTelemetryTracingUrl: "http://localhost:4318", + openTelemetryMetricsEnabled: true, + openTelemetryMetricsHost: "0.0.0.0", + openTelemetryMetricsPort: 4320, + openTelemetryMetricsScrapingFrequency: 10, }; - export const sovereignConfig = { - PROTOKIT_BLOCK_INTERVAL: 10000, - PROTOKIT_SETTLEMENT_INTERVAL: 30000, - PROTOKIT_SETTLEMENT_ENABLED: true, + blockInterval: 10000, + settlementInterval: 30000, + settlementEnabled: true, - PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + shouldAttemptDbMigration: true, + shouldAttemptIndexerDbMigration: true, + shouldAttemptProcessorDbMigration: true, - PROTOKIT_PRUNE_ON_STARTUP: false, - PROTOKIT_LOG_LEVEL: "INFO", + pruneOnStartup: false, - REDIS_HOST: "redis", - REDIS_PORT: 6379, - REDIS_PASSWORD: "password", + redisHost: "redis", + redisPort: 6379, + redisPassword: "password", - DATABASE_URL: + databaseUrl: "postgresql://admin:password@postgres:5432/protokit?schema=public", - INDEXER_DATABASE_URL: + indexerDatabaseUrl: "postgresql://admin:password@indexer-postgres:5432/protokit-indexer?schema=public", - PROCESSOR_DATABASE_URL: + processorDatabaseUrl: "postgresql://admin:password@processor-postgres:5432/protokit-processor?schema=public", - PROTOKIT_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHIQL_ENABLED: true, + graphqlHost: "0.0.0.0", + graphqlPort: 8080, + graphiqlEnabled: true, - PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + indexerGraphqlHost: "0.0.0.0", + indexerGraphqlPort: 8081, + indexerGraphqlEnabled: true, - PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "indexer", + processorGraphqlHost: "0.0.0.0", + processorGraphqlPort: 8082, + processorGraphqlEnabled: true, + processorIndexerGraphqlHost: "indexer", - MINA_NETWORK: "lightnet", - MINA_NODE_GRAPHQL_HOST: "http://lightnet", - MINA_NODE_GRAPHQL_PORT: 8080, + minaNetwork: "lightnet", + minaNodeGraphqlHost: "http://lightnet", + minaNodeGraphqlPort: 8080, - MINA_ARCHIVE_GRAPHQL_HOST: "http://lightnet", - MINA_ARCHIVE_GRAPHQL_PORT: 8282, + minaArchiveGraphqlHost: "http://lightnet", + minaArchiveGraphqlPort: 8282, - MINA_ACCOUNT_MANAGER_HOST: "http://lightnet", - MINA_ACCOUNT_MANAGER_PORT: 8084, - MINA_EXPLORER_PORT: 3001, - - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + minaAccountManagerHost: "http://lightnet", + minaAccountManagerPort: 8084, + minaExplorerPort: 3001, + transactionFeeRecipientPrivateKey: "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + transactionFeeRecipientPublicKey: "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", - PROTOKIT_SEQUENCER_PRIVATE_KEY: - "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", - PROTOKIT_SEQUENCER_PUBLIC_KEY: - "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + sequencerPrivateKey: "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + sequencerPublicKey: "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + settlementContractPrivateKey: "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", - PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + settlementContractPublicKey: "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + dispatcherContractPrivateKey: "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", - PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + dispatcherContractPublicKey: "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + minaBridgeContractPrivateKey: "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", - PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + minaBridgeContractPublicKey: "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", - TEST_ACCOUNT_1_PRIVATE_KEY: + testAccount1PrivateKey: "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", - TEST_ACCOUNT_1_PUBLIC_KEY: + testAccount1PublicKey: "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", - - OPEN_TELEMETRY_TRACING_ENABLED: true, - OPEN_TELEMETRY_TRACING_URL: "http://otel-collector:4317", - OPEN_TELEMETRY_METRICS_ENABLED: true, - OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", - OPEN_TELEMETRY_METRICS_PORT: 4320, - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + openTelemetryTracingEnabled: true, + openTelemetryTracingUrl: "http://otel-collector:4317", -}; \ No newline at end of file + openTelemetryMetricsEnabled: true, + openTelemetryMetricsHost: "0.0.0.0", + openTelemetryMetricsPort: 4320, + openTelemetryMetricsScrapingFrequency: 10, +}; diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index 4561e14e1..d1067aadd 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -19,13 +19,13 @@ import { DatabasePruneModule, InMemoryDatabase, LocalTaskQueue, + AppChainModulesRecord, } from "@proto-kit/sequencer"; import { IndexerNotifier, GeneratedResolverFactoryGraphqlModule, IndexBlockTask, } from "@proto-kit/indexer"; -import { PrivateKey } from "o1js"; import { PrismaRedisDatabase } from "@proto-kit/persistance"; import { BullQueue } from "@proto-kit/deployment"; import { @@ -41,579 +41,476 @@ import { InMemoryTransactionSender, StateServiceQueryModule, } from "@proto-kit/sdk"; -import { AppChainModulesRecord } from "@proto-kit/sequencer"; +import { PrivateKey } from "o1js"; +import { NonEmptyArray } from "type-graphql"; + import { buildCustomTokenConfig, buildSettlementTokenConfig, - definePreset, - orderModulesByDependencies, - parseApiEnv, - parseCoreEnv, - parseMetricsEnv, - parseSettlementEnv, - parseIndexerEnv, - parseProcessorEnv, - parseDatabaseEnv, - parseDatabasePruneEnv, - parseGraphqlServerEnv, - parseRedisEnv, resolveEnv, } from "./utils"; -import { NonEmptyArray } from "type-graphql"; import { Environment, - ModuleOverrides, - ApiEnv, - ConfigOverrides, CoreEnv, MetricsEnv, IndexerEnv, ProcessorEnv, SettlementEnv, + RedisEnv, DatabaseEnv, - TaskQueueEnv, - DatabasePruneEnv, + RedisTaskQueueEnv, GraphqlServerEnv, - RedisEnv, } from "./types"; export class DefaultModules { - static api(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - GraphqlServer, - Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), - }, - options?.overrides - ); + static api() { + return { + GraphqlServer, + Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), + } satisfies SequencerModulesRecord; } - static core(options?: { - overrides?: ModuleOverrides; - settlementEnabled?: boolean; - }): SequencerModulesRecord { - return definePreset( - { - ...DefaultModules.api(), - Mempool: PrivateMempool, - BlockProducerModule, - BlockTrigger: TimedBlockTrigger, - SequencerStartupModule, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.withoutSettlement() - ), - ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), - }, - options?.overrides - ); + + static core(options?: { settlementEnabled?: boolean }) { + const settlementEnabled = options?.settlementEnabled ?? false; + return { + ...(settlementEnabled ? DefaultModules.settlement() : {}), + ...DefaultModules.api(), + Mempool: PrivateMempool, + BlockProducerModule, + BlockTrigger: TimedBlockTrigger, + SequencerStartupModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.withoutSettlement() + ), + } satisfies SequencerModulesRecord; } - static metrics(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - OpenTelemetryServer, - }, - options?.overrides - ); + + static metrics() { + return { + OpenTelemetryServer, + } satisfies SequencerModulesRecord; } - static settlement(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - BaseLayer: MinaBaseLayer, - FeeStrategy: ConstantFeeStrategy, - BatchProducerModule, - SettlementModule, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.allTasks() - ), - }, - options?.overrides - ); + + static settlement() { + return { + BaseLayer: MinaBaseLayer, + FeeStrategy: ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + } satisfies SequencerModulesRecord; } - static sequencerIndexer(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - IndexerNotifier, - }, - options?.overrides - ); + + static sequencerIndexer() { + return { + IndexerNotifier, + } satisfies SequencerModulesRecord; } - static indexer(options?: { overrides?: ModuleOverrides }) { - return definePreset( - { - Database: PrismaRedisDatabase, - TaskQueue: BullQueue, - TaskWorker: LocalTaskWorkerModule.from({ - IndexBlockTask, - }), - GraphqlServer, - Graphql: GraphqlSequencerModule.from({ - GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, - }), - }, - options?.overrides - ); + + static indexer() { + return { + Database: PrismaRedisDatabase, + TaskQueue: BullQueue, + TaskWorker: LocalTaskWorkerModule.from({ + IndexBlockTask, + }), + GraphqlServer, + Graphql: GraphqlSequencerModule.from({ + GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, + }), + } satisfies SequencerModulesRecord; } + static processor( resolvers: NonEmptyArray, - handlers: HandlersRecord, - options?: { overrides?: ModuleOverrides } + handlers: HandlersRecord ) { - return definePreset( - { - GraphqlServer, - GraphqlSequencerModule: GraphqlSequencerModule.from({ - ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), - }), - HandlersExecutor: HandlersExecutor.from(handlers), - BlockFetching, - Trigger: TimedProcessorTrigger, - }, - options?.overrides - ); + return { + GraphqlServer, + GraphqlSequencerModule: GraphqlSequencerModule.from({ + ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), + }), + HandlersExecutor: HandlersExecutor.from(handlers), + BlockFetching, + Trigger: TimedProcessorTrigger, + } satisfies SequencerModulesRecord; } - static database(options?: { - overrides?: ModuleOverrides; - preset?: Environment; - }): SequencerModulesRecord { - const preset = options?.preset ?? "inmemory"; - return definePreset( - { - Database: - preset === "inmemory" ? InMemoryDatabase : PrismaRedisDatabase, - }, - options?.overrides - ); + static inMemoryDatabase() { + return { + Database: InMemoryDatabase, + } satisfies SequencerModulesRecord; } - static taskQueue(options?: { - overrides?: ModuleOverrides; - preset?: Environment; - }): SequencerModulesRecord { - const preset = options?.preset ?? "inmemory"; - return definePreset( - { - TaskQueue: preset === "inmemory" ? LocalTaskQueue : BullQueue, - }, - options?.overrides - ); + + static PrismaRedisDatabase() { + return { + Database: PrismaRedisDatabase, + DatabasePruneModule, + } satisfies SequencerModulesRecord; } - static databasePrune(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - DatabasePruneModule, - }, - options?.overrides - ); + + static localTaskQueue() { + return { + TaskQueue: LocalTaskQueue, + } satisfies SequencerModulesRecord; } - static worker(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - TaskQueue: BullQueue, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.allTasks() - ), - }, - options?.overrides - ); + + static RedisTaskQueue() { + return { + TaskQueue: BullQueue, + } satisfies SequencerModulesRecord; } - static appChainBase(options?: { - overrides?: Partial; - }): AppChainModulesRecord { - return definePreset( - { - TransactionSender: InMemoryTransactionSender, - QueryTransportModule: StateServiceQueryModule, - NetworkStateTransportModule: BlockStorageNetworkStateModule, - }, - options?.overrides - ) as AppChainModulesRecord; + + static worker() { + return { + TaskQueue: BullQueue, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + } satisfies SequencerModulesRecord; } - static settlementScript(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - ...DefaultModules.settlement(), - Mempool: PrivateMempool, - TaskQueue: LocalTaskQueue, - SequencerStartupModule, - }, - options?.overrides - ); + + static appChainBase() { + return { + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + } satisfies AppChainModulesRecord; } - static ordered(modules: any) { - return orderModulesByDependencies(modules); + + static settlementScript() { + return { + ...DefaultModules.settlement(), + Mempool: PrivateMempool, + TaskQueue: LocalTaskQueue, + SequencerStartupModule, + } satisfies SequencerModulesRecord; } } export class DefaultConfigs { static api(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset( - { - Graphql: VanillaGraphqlModules.defaultConfig(), - GraphqlServer: DefaultConfigs.graphqlServer({ - type: "protokit", - preset: options?.preset, - envs: options?.envs, - }), - }, - options?.overrides - ); + overrides?: Partial; + }) { + return { + Graphql: VanillaGraphqlModules.defaultConfig(), + GraphqlServer: DefaultConfigs.graphqlServer({ + preset: options?.preset, + overrides: options?.overrides, + }), + }; } + static core(options?: { preset?: Environment; - envs?: Partial & Partial & Partial; - overrides?: ConfigOverrides; + overrides?: Partial & + Partial & + Partial; settlementEnabled?: boolean; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseCoreEnv( - { ...config, ...options?.envs }, - options?.settlementEnabled - ); + }) { + const settlementEnabled = options?.settlementEnabled ?? false; + const config = resolveEnv(options?.preset, options?.overrides); const apiConfig = DefaultConfigs.api({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - const settlementConfig = options?.settlementEnabled + const settlementConfig = settlementEnabled ? DefaultConfigs.settlement({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }) : {}; const blockTriggerConfig = { - blockInterval: parsed.blockInterval, + blockInterval: config.blockInterval, produceEmptyBlocks: true, - ...(options?.settlementEnabled + ...(settlementEnabled ? { - settlementInterval: parsed.settlementInterval, + settlementInterval: config.settlementInterval, settlementTokenConfig: buildSettlementTokenConfig( - parsed.minaBridgeKey!, + config.minaBridgeContractPrivateKey!, buildCustomTokenConfig( - parsed.customTokenKey, - parsed.customTokenBridgeKey + config.customTokenPrivateKey, + config.customTokenBridgePrivateKey ) ), } : { settlementTokenConfig: {} }), }; - return definePreset( - { - ...apiConfig, - Mempool: {}, - BlockProducerModule: {}, - BlockTrigger: blockTriggerConfig, - SequencerStartupModule: {}, - LocalTaskWorkerModule: VanillaGraphqlModules.defaultConfig(), - ...settlementConfig, - }, - options?.overrides - ); + return { + ...apiConfig, + Mempool: {}, + BlockProducerModule: {}, + BlockTrigger: blockTriggerConfig, + SequencerStartupModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + ...settlementConfig, + }; } + static metrics(options?: { preset?: Environment; - envs?: MetricsEnv; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const configs = resolveEnv(options?.preset, options?.envs); - const parsed = parseMetricsEnv(configs); - return definePreset( - { - OpenTelemetryServer: { - metrics: { - enabled: parsed.metricsEnabled, - prometheus: { - host: parsed.metricsHost, - port: parsed.metricsPort, - appendTimestamp: true, - }, - nodeScrapeInterval: parsed.metricsScrapingFrequency, + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); + return { + OpenTelemetryServer: { + metrics: { + enabled: config.metricsEnabled, + prometheus: { + host: config.metricsHost, + port: config.metricsPort, + appendTimestamp: true, }, - tracing: { - enabled: parsed.tracingEnabled, - otlp: { - url: parsed.tracingUrl, - }, + nodeScrapeInterval: config.metricsScrapingFrequency, + }, + tracing: { + enabled: config.tracingEnabled, + otlp: { + url: config.tracingUrl, }, }, }, - options?.overrides - ); + }; } - static sequencerIndexer(options?: { - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset({ IndexerNotifier: {} }, options?.overrides); + + static sequencerIndexer() { + return { IndexerNotifier: {} }; } + static indexer(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseIndexerEnv(config); - const redisConfig = DefaultConfigs.redis({ + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); + const taskQueueConfig = DefaultConfigs.redisTaskQueue({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - const databaseConfig = DefaultConfigs.database({ + const databaseConfig = DefaultConfigs.prismaRedisDatabase({ preset: options?.preset, - envs: options?.envs, + overrides: { + databaseUrl: config.indexerDatabaseUrl, + ...options?.overrides, + }, }); const graphqlServerConfig = DefaultConfigs.graphqlServer({ - type: "indexer", preset: options?.preset, - envs: options?.envs, + overrides: { + graphqlHost: config.indexerGraphqlHost, + graphqlPort: config.indexerGraphqlPort, + graphiqlEnabled: config.indexerGraphqlEnabled, + ...options?.overrides, + }, }); - return definePreset( - { - ...databaseConfig, - TaskQueue: redisConfig.TaskQueue, - TaskWorker: { - IndexBlockTask: {}, - }, - ...graphqlServerConfig, - Graphql: { - GeneratedResolverFactory: {}, - }, + return { + ...databaseConfig, + ...taskQueueConfig, + TaskWorker: { + IndexBlockTask: {}, }, - options?.overrides - ); + ...graphqlServerConfig, + Graphql: { + GeneratedResolverFactory: {}, + }, + }; } + static processor(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseProcessorEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); const graphqlServerConfig = DefaultConfigs.graphqlServer({ - type: "processor", preset: options?.preset, - envs: options?.envs, + overrides: { + graphqlHost: config.processorGraphqlHost, + graphqlPort: config.processorGraphqlPort, + graphiqlEnabled: config.processorGraphqlEnabled, + ...options?.overrides, + }, }); - return definePreset( - { - HandlersExecutor: {}, - BlockFetching: { - url: `http://${parsed.processorIndexerGraphqlHost}:${parsed.indexerGraphqlPort}`, - }, - Trigger: { - interval: (parsed.blockInterval ?? 5000) / 5, - }, - ...graphqlServerConfig, - GraphqlSequencerModule: { - ResolverFactory: {}, - }, + return { + HandlersExecutor: {}, + BlockFetching: { + url: `http://${config.processorIndexerGraphqlHost}:${config.indexerGraphqlPort}`, }, - options?.overrides - ); + Trigger: { + interval: Number(config.blockInterval) / 5, + }, + ...graphqlServerConfig, + GraphqlSequencerModule: { + ResolverFactory: {}, + }, + }; } + static settlement(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseSettlementEnv(config); - - return definePreset( - { - BaseLayer: { - network: { - type: parsed.network, - graphql: parsed.graphql, - archive: parsed.archive, - accountManager: parsed.accountManager, - }, + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); + + return { + BaseLayer: { + network: { + type: "lightnet" as const, + graphql: config.minaNodeGraphqlHost, + archive: config.minaArchiveGraphqlHost, + accountManager: config.minaAccountManagerHost, }, - SettlementModule: { - feepayer: PrivateKey.fromBase58(parsed.sequencerPrivateKey), - keys: { - settlement: PrivateKey.fromBase58( - parsed.settlementContractPrivateKey - ), - dispatch: PrivateKey.fromBase58( - parsed.dispatcherContractPrivateKey - ), - minaBridge: PrivateKey.fromBase58( - parsed.minaBridgeContractPrivateKey - ), - }, + }, + SettlementModule: { + feepayer: PrivateKey.fromBase58(config.sequencerPrivateKey), + keys: { + settlement: PrivateKey.fromBase58( + config.settlementContractPrivateKey + ), + dispatch: PrivateKey.fromBase58(config.dispatcherContractPrivateKey), + minaBridge: PrivateKey.fromBase58( + config.minaBridgeContractPrivateKey + ), }, - FeeStrategy: {}, - BatchProducerModule: {}, }, - options?.overrides - ); + FeeStrategy: {}, + BatchProducerModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }; + } + + static inMemoryDatabase() { + return { Database: {} }; } - static database(options?: { + + static prismaRedisDatabase(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const preset = options?.preset ?? "inmemory"; - if (preset === "inmemory") { - return { Database: definePreset({}, options?.overrides) }; - } - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseDatabaseEnv(config); + overrides?: Partial; + }) { + const preset = options?.preset ?? "development"; + const config = resolveEnv(preset, options?.overrides); const redisConfig = DefaultConfigs.redis({ - preset: options?.preset, - envs: options?.envs, + preset, + overrides: options?.overrides, }); return { - Database: definePreset( - { - ...redisConfig, - prisma: { - connection: parsed.databaseUrl, - }, + Database: { + ...redisConfig, + prisma: { + connection: config.databaseUrl, }, - options?.overrides - ), + }, + DatabasePruneModule: { + pruneOnStartup: config.pruneOnStartup, + }, }; } - static taskQueue(options?: { - preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const preset = options?.preset ?? "inmemory"; - if (preset === "inmemory") { - return { - TaskQueue: definePreset({}, options?.overrides), - }; - } - const redisConfig = DefaultConfigs.redis({ - preset: options?.preset, - envs: options?.envs, - }); - return { TaskQueue: definePreset(redisConfig, options?.overrides) }; + static localTaskQueue() { + return { + TaskQueue: {}, + }; } - static databasePrune(options?: { + + static redisTaskQueue(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseDatabasePruneEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); return { - DatabasePruneModule: definePreset( - { - pruneOnStartup: parsed.pruneOnStartup, + TaskQueue: { + redis: { + host: config.redisHost, + port: config.redisPort, + password: config.redisPassword, + db: config.redisDb, }, - options?.overrides - ), + retryAttempts: config.retryAttempts, + }, }; } + static graphqlServer(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - type?: "indexer" | "processor" | "protokit"; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseGraphqlServerEnv(config, options?.type); - - return definePreset( - { - port: parsed.graphqlPort, - host: parsed.graphqlHost, - graphiql: parsed.graphiqlEnabled, - }, + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, options?.overrides ); + + return { + port: config.graphqlPort, + host: config.graphqlHost, + graphiql: config.graphiqlEnabled, + }; } + static redis(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseRedisEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); return { - redis: definePreset( - { - host: parsed.redisHost, - port: parsed.redisPort, - password: parsed.redisPassword, - }, - options?.overrides - ), + redis: { + host: config.redisHost, + port: config.redisPort, + password: config.redisPassword, + }, }; } - static appChainBase(options?: { - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset( - { - QueryTransportModule: {}, - NetworkStateTransportModule: {}, - TransactionSender: {}, - }, - options?.overrides - ); + + static appChainBase() { + return { + QueryTransportModule: {}, + NetworkStateTransportModule: {}, + TransactionSender: {}, + }; } + static worker(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const redisConfig = DefaultConfigs.redis({ + overrides?: Partial; + }) { + const taskQueueConfig = DefaultConfigs.redisTaskQueue({ preset: options?.preset, - envs: options?.envs, - overrides: { - db: 1, - }, + overrides: options?.overrides, }); - return definePreset( - { - TaskQueue: redisConfig, - LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), - }, - options?.overrides - ); + return { + ...taskQueueConfig, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }; } + static settlementScript(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { + overrides?: Partial; + }) { const settlementConfig = DefaultConfigs.settlement({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - return definePreset( - { - ...settlementConfig, - SequencerStartupModule: {}, - TaskQueue: { - simulatedDuration: 0, - }, - Mempool: {}, + return { + ...settlementConfig, + SequencerStartupModule: {}, + TaskQueue: { + simulatedDuration: 0, }, - options?.overrides - ); + Mempool: {}, + }; } } diff --git a/packages/stack/src/presets/modules/types.ts b/packages/stack/src/presets/modules/types.ts index 712511168..65efe1603 100644 --- a/packages/stack/src/presets/modules/types.ts +++ b/packages/stack/src/presets/modules/types.ts @@ -1,86 +1,63 @@ -import { RecursivePartial, ModulesConfig } from "@proto-kit/common"; -import { SequencerModulesRecord } from "@proto-kit/sequencer"; - -export type ModuleOverrides = Partial; -export type ConfigOverrides = RecursivePartial>; export type Environment = "inmemory" | "development" | "sovereign"; -export type ApiEnv = { - PROTOKIT_GRAPHQL_PORT: number | string; - PROTOKIT_GRAPHQL_HOST: string; - PROTOKIT_GRAPHIQL_ENABLED: boolean | string; + +export type GraphqlServerEnv = { + graphqlPort: number; + graphqlHost: string; + graphiqlEnabled: boolean; }; export type CoreEnv = { - PROTOKIT_BLOCK_INTERVAL: number | string; - PROTOKIT_SETTLEMENT_INTERVAL?: number | string; - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY?: string; - PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY?: string; - PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY?: string; + blockInterval: number; + settlementInterval?: number; + minaBridgeContractPrivateKey?: string; + customTokenPrivateKey?: string; + customTokenBridgePrivateKey?: string; }; export type MetricsEnv = { - OPEN_TELEMETRY_METRICS_ENABLED: boolean | string; - OPEN_TELEMETRY_METRICS_HOST: string; - OPEN_TELEMETRY_METRICS_PORT: number | string; - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: number | string; - OPEN_TELEMETRY_TRACING_ENABLED: boolean | string; - OPEN_TELEMETRY_TRACING_URL: string; + metricsEnabled: boolean; + metricsHost: string; + metricsPort: number; + metricsScrapingFrequency: number; + tracingEnabled: boolean; + tracingUrl: string; }; export type SettlementEnv = { - MINA_NETWORK: string; - MINA_NODE_GRAPHQL_HOST: string; - MINA_NODE_GRAPHQL_PORT: number | string; - MINA_ARCHIVE_GRAPHQL_HOST: string; - MINA_ARCHIVE_GRAPHQL_PORT: number | string; - MINA_ACCOUNT_MANAGER_HOST: string; - MINA_ACCOUNT_MANAGER_PORT: number | string; - PROTOKIT_SEQUENCER_PRIVATE_KEY: string; - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: string; - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: string; - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: string; -}; -export type IndexerEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; - INDEXER_DATABASE_URL: string; - PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; - PROTOKIT_INDEXER_GRAPHQL_HOST: string; - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: boolean | string; + minaNetwork: string; + minaNodeGraphqlHost: string; + minaNodeGraphqlPort: number; + minaArchiveGraphqlHost: string; + minaArchiveGraphqlPort: number; + minaAccountManagerHost: string; + minaAccountManagerPort: number; + sequencerPrivateKey: string; + settlementContractPrivateKey: string; + dispatcherContractPrivateKey: string; + minaBridgeContractPrivateKey: string; +}; +export type IndexerEnv = RedisTaskQueueEnv & { + indexerDatabaseUrl: string; + indexerGraphqlHost: string; + indexerGraphqlPort: number; + indexerGraphqlEnabled: boolean; + pruneOnStartup?: boolean; }; export type ProcessorEnv = { - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: string; - PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; - PROTOKIT_BLOCK_INTERVAL: number | string; - PROTOKIT_PROCESSOR_GRAPHQL_HOST: string; - PROTOKIT_PROCESSOR_GRAPHQL_PORT: number | string; - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: boolean | string; -}; -export type DatabaseEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; - DATABASE_URL: string; + processorIndexerGraphqlHost: string; + indexerGraphqlPort: number; + blockInterval: number; + processorGraphqlHost: string; + processorGraphqlPort: number; + processorGraphqlEnabled: boolean; }; -export type TaskQueueEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; -}; -export type DatabasePruneEnv = { - PRUNE_ON_STARTUP?: boolean | string; -}; -export type GraphqlServerEnv = { - PROTOKIT_GRAPHQL_PORT?: number | string; - PROTOKIT_GRAPHQL_HOST?: string; - PROTOKIT_GRAPHIQL_ENABLED?: boolean | string; - PROTOKIT_INDEXER_GRAPHQL_HOST?: string; - PROTOKIT_INDEXER_GRAPHQL_PORT?: number | string; - PROTOKIT_INDEXER_GRAPHIQL_ENABLED?: boolean | string; - PROTOKIT_PROCESSOR_GRAPHQL_HOST?: string; - PROTOKIT_PROCESSOR_GRAPHQL_PORT?: number | string; - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED?: boolean | string; +export type DatabaseEnv = RedisEnv & { + databaseUrl: string; + pruneOnStartup?: boolean; }; export type RedisEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; + redisHost: string; + redisPort: number; + redisPassword: string; +}; +export type RedisTaskQueueEnv = RedisEnv & { + redisDb?: number; + retryAttempts?: number; }; diff --git a/packages/stack/src/presets/modules/utils.ts b/packages/stack/src/presets/modules/utils.ts index 679b35165..ebe16616f 100644 --- a/packages/stack/src/presets/modules/utils.ts +++ b/packages/stack/src/presets/modules/utils.ts @@ -1,106 +1,40 @@ import { PrivateKey, TokenId } from "o1js"; -import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; import { FungibleToken } from "mina-fungible-token"; -import { assertDefined } from "@proto-kit/common"; -import { - ApiEnv, - CoreEnv, - DatabaseEnv, - DatabasePruneEnv, - Environment, - GraphqlServerEnv, - IndexerEnv, - MetricsEnv, - ProcessorEnv, - RedisEnv, - SettlementEnv, - TaskQueueEnv, -} from "./types"; - -function ensureDefined(obj: Record, keys: string[]) { - keys.forEach((k) => assertDefined(obj[k], `${k} is required`)); -} -export function definePreset( - base: T, - overrides?: Partial -): T { - return { - ...base, - ...overrides, - }; -} -const MODULE_DEPENDENCIES: Record = { - Database: [], - TaskQueue: [], - OpenTelemetryServer: [], - BaseLayer: [], - FeeStrategy: [], - - Protocol: [], - Mempool: ["Database"], - BlockProducerModule: ["Database"], - - LocalTaskWorkerModule: ["TaskQueue"], - TaskWorker: ["TaskQueue"], - SequencerStartupModule: ["Database"], - BatchProducerModule: ["Database"], - BlockTrigger: ["BlockProducerModule", "BatchProducerModule"], - - SettlementModule: ["Database", "BaseLayer", "FeeStrategy"], - DatabasePruneModule: ["Database"], - - Graphql: [], - GraphqlServer: [], - - IndexBlockTask: ["Database", "TaskQueue"], - IndexerNotifier: ["Database", "TaskQueue"], - GeneratedResolverFactory: [], - - BlockFetching: [], - HandlersExecutor: [], - Trigger: ["BlockFetching", "HandlersExecutor"], - ResolverFactory: [], -}; -export function orderModulesByDependencies( - modules: Record -): Record { - const moduleSet = new Set(Object.keys(modules)); - const ordered: Record = {}; - const visited = new Set(); - - function visit(name: string) { - if (!moduleSet.has(name) || visited.has(name)) return; - - const deps = MODULE_DEPENDENCIES[name] ?? []; - for (const dep of deps) { - visit(dep); - } +import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; - visited.add(name); - ordered[name] = modules[name]; - } +import { Environment } from "./types"; - for (const name of moduleSet) { - visit(name); +export function getConfigs(preset: Environment) { + switch (preset) { + case "development": + return developmentConfig; + case "sovereign": + return sovereignConfig; + case "inmemory": + default: + return inmemoryConfig; } - - return ordered; } export function resolveEnv( preset: Environment = "inmemory", - envs?: Partial + envs?: Partial | undefined ): T { - return { - ...getConfigs(preset), - ...envs, - } as T; + const config = getConfigs(preset); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + if (!envs) return config as T; + const resolved = { ...config, ...envs } satisfies Partial; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return resolved as T; } export function buildCustomTokenConfig( customTokenPrivateKey?: string, customTokenBridgePrivateKey?: string -): Record { - if (!customTokenPrivateKey || !customTokenBridgePrivateKey) { +) { + if ( + customTokenPrivateKey === undefined || + customTokenBridgePrivateKey === undefined + ) { return {}; } const pk = PrivateKey.fromBase58(customTokenPrivateKey); @@ -117,8 +51,8 @@ export function buildCustomTokenConfig( } export function buildSettlementTokenConfig( bridgePrivateKey: string, - customTokens: Record = {} -): Record { + customTokens: Record = {} +) { return { "1": { bridgingContractPrivateKey: PrivateKey.fromBase58(bridgePrivateKey), @@ -126,256 +60,3 @@ export function buildSettlementTokenConfig( ...customTokens, }; } -export function getConfigs(preset: Environment) { - switch (preset) { - case "development": - return developmentConfig; - case "sovereign": - return sovereignConfig; - case "inmemory": - default: - return inmemoryConfig; - } -} - -export function parseApiEnv(envs: ApiEnv): { - graphqlPort: number; - graphqlHost: string; - graphiqlEnabled: boolean; -} { - ensureDefined(envs, [ - "PROTOKIT_GRAPHIQL_ENABLED", - "PROTOKIT_GRAPHQL_HOST", - "PROTOKIT_GRAPHQL_PORT", - ]); - return { - graphqlPort: Number(envs.PROTOKIT_GRAPHQL_PORT), - graphqlHost: envs.PROTOKIT_GRAPHQL_HOST, - graphiqlEnabled: Boolean(envs.PROTOKIT_GRAPHIQL_ENABLED), - }; -} -export function parseCoreEnv( - envs: CoreEnv, - settlementEnabled?: boolean -): { - blockInterval: number; - settlementInterval?: number; - minaBridgeKey?: string; - customTokenKey?: string; - customTokenBridgeKey?: string; -} { - ensureDefined(envs, ["PROTOKIT_BLOCK_INTERVAL"]); - if (settlementEnabled) { - ensureDefined(envs, [ - "PROTOKIT_SETTLEMENT_INTERVAL", - "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", - ]); - if (envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY) { - ensureDefined(envs, ["PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY"]); - } - } - return { - blockInterval: Number(envs.PROTOKIT_BLOCK_INTERVAL), - settlementInterval: Number(envs.PROTOKIT_SETTLEMENT_INTERVAL), - minaBridgeKey: envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY, - customTokenKey: envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, - customTokenBridgeKey: envs.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, - }; -} -export function parseMetricsEnv(envs: MetricsEnv): { - metricsEnabled: boolean; - metricsHost?: string; - metricsPort?: number; - metricsScrapingFrequency?: number; - tracingEnabled: boolean; - tracingUrl?: string; -} { - ensureDefined(envs as Record, [ - "OPEN_TELEMETRY_METRICS_ENABLED", - "OPEN_TELEMETRY_TRACING_ENABLED", - "OPEN_TELEMETRY_TRACING_URL", - "OPEN_TELEMETRY_METRICS_HOST", - "OPEN_TELEMETRY_METRICS_PORT", - "OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY", - ]); - return { - metricsEnabled: Boolean(envs.OPEN_TELEMETRY_METRICS_ENABLED), - metricsHost: envs.OPEN_TELEMETRY_METRICS_HOST, - metricsPort: Number(envs.OPEN_TELEMETRY_METRICS_PORT), - metricsScrapingFrequency: Number( - envs.OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY - ), - tracingEnabled: Boolean(envs.OPEN_TELEMETRY_TRACING_ENABLED), - tracingUrl: envs.OPEN_TELEMETRY_TRACING_URL, - }; -} -export function parseSettlementEnv(envs: SettlementEnv): { - network: string; - graphql: string; - archive: string; - accountManager: string; - sequencerPrivateKey: string; - settlementContractPrivateKey: string; - dispatcherContractPrivateKey: string; - minaBridgeContractPrivateKey: string; -} { - ensureDefined(envs as Record, [ - "MINA_ACCOUNT_MANAGER_HOST", - "MINA_ACCOUNT_MANAGER_PORT", - "MINA_ARCHIVE_GRAPHQL_HOST", - "MINA_ARCHIVE_GRAPHQL_PORT", - "MINA_NODE_GRAPHQL_HOST", - "MINA_NODE_GRAPHQL_PORT", - "MINA_NETWORK", - "PROTOKIT_SEQUENCER_PRIVATE_KEY", - "PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY", - "PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY", - "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", - ]); - - return { - network: envs.MINA_NETWORK!, - graphql: `${envs.MINA_NODE_GRAPHQL_HOST}:${envs.MINA_NODE_GRAPHQL_PORT}/graphql`, - archive: `${envs.MINA_ARCHIVE_GRAPHQL_HOST}:${envs.MINA_ARCHIVE_GRAPHQL_PORT}`, - accountManager: `${envs.MINA_ACCOUNT_MANAGER_HOST}:${envs.MINA_ACCOUNT_MANAGER_PORT}`, - sequencerPrivateKey: envs.PROTOKIT_SEQUENCER_PRIVATE_KEY!, - settlementContractPrivateKey: - envs.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY!, - dispatcherContractPrivateKey: - envs.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY!, - minaBridgeContractPrivateKey: - envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY!, - }; -} -export function parseIndexerEnv(envs: IndexerEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; - indexerDatabaseUrl?: string; - graphqlPort?: number; - graphqlHost?: string; - graphiqlEnabled?: boolean; -} { - ensureDefined(envs as Record, [ - "REDIS_HOST", - "REDIS_PORT", - "REDIS_PASSWORD", - "INDEXER_DATABASE_URL", - "PROTOKIT_INDEXER_GRAPHQL_PORT", - "PROTOKIT_INDEXER_GRAPHQL_HOST", - "PROTOKIT_INDEXER_GRAPHIQL_ENABLED", - ]); - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - indexerDatabaseUrl: envs.INDEXER_DATABASE_URL, - graphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT - ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) - : undefined, - graphqlHost: envs.PROTOKIT_INDEXER_GRAPHQL_HOST, - graphiqlEnabled: envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED - ? Boolean(envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED) - : undefined, - }; -} -export function parseProcessorEnv(envs: ProcessorEnv): { - processorIndexerGraphqlHost?: string; - indexerGraphqlPort?: number; - blockInterval?: number; - processorGraphqlHost?: string; - processorGraphqlPort?: number; - processorGraphiqlEnabled?: boolean; -} { - ensureDefined(envs as Record, [ - "PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST", - "PROTOKIT_INDEXER_GRAPHQL_PORT", - "PROTOKIT_BLOCK_INTERVAL", - "PROTOKIT_PROCESSOR_GRAPHQL_HOST", - "PROTOKIT_PROCESSOR_GRAPHQL_PORT", - "PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED", - ]); - return { - processorIndexerGraphqlHost: envs.PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST, - indexerGraphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT - ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) - : undefined, - blockInterval: envs.PROTOKIT_BLOCK_INTERVAL - ? Number(envs.PROTOKIT_BLOCK_INTERVAL) - : undefined, - processorGraphqlHost: envs.PROTOKIT_PROCESSOR_GRAPHQL_HOST, - processorGraphqlPort: envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT - ? Number(envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT) - : undefined, - processorGraphiqlEnabled: envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED - ? Boolean(envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED) - : undefined, - }; -} -export function parseDatabaseEnv(envs: DatabaseEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; - databaseUrl?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - databaseUrl: envs.DATABASE_URL, - }; -} -export function parseTaskQueueEnv(envs: TaskQueueEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - }; -} -export function parseDatabasePruneEnv(envs: DatabasePruneEnv): { - pruneOnStartup: boolean; -} { - return { - pruneOnStartup: - envs.PRUNE_ON_STARTUP === "true" || envs.PRUNE_ON_STARTUP === true, - }; -} -export function parseGraphqlServerEnv( - envs: GraphqlServerEnv, - type: "protokit" | "indexer" | "processor" = "protokit" -): { - graphqlPort?: number; - graphqlHost?: string; - graphiqlEnabled?: boolean; -} { - const prefix = - type === "indexer" - ? "PROTOKIT_INDEXER" - : type === "processor" - ? "PROTOKIT_PROCESSOR" - : "PROTOKIT"; - return { - graphqlPort: envs[`${prefix}_GRAPHQL_PORT`] - ? Number(envs[`${prefix}_GRAPHQL_PORT`]) - : undefined, - graphqlHost: envs[`${prefix}_GRAPHQL_HOST`], - graphiqlEnabled: envs[`${prefix}_GRAPHIQL_ENABLED`] - ? Boolean(envs[`${prefix}_GRAPHIQL_ENABLED`]) - : undefined, - }; -} -export function parseRedisEnv(envs: RedisEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - }; -} diff --git a/packages/stack/src/presets/sequencer/index.ts b/packages/stack/src/presets/sequencer/index.ts deleted file mode 100644 index 1fd6919fb..000000000 --- a/packages/stack/src/presets/sequencer/index.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { SequencerModulesRecord, Sequencer } from "@proto-kit/sequencer"; -import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; -import { DefaultConfigs, DefaultModules } from "../modules"; -import { definePreset } from "../modules/utils"; - -export class DefaultSequencer { - static inmemory(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - ...DefaultModules.database(), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.taskQueue(), - ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } - static development(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - // ordering of the modules matters due to dependency resolution - ...DefaultModules.database({ preset: "development" }), - ...DefaultModules.databasePrune(), - ...DefaultModules.metrics(), - ...DefaultModules.taskQueue({ preset: "development" }), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.sequencerIndexer(), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } - static sovereign(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - // ordering of the modules matters due to dependency resolution - ...DefaultModules.database(), - ...DefaultModules.taskQueue(), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.sequencerIndexer(), - ...DefaultModules.metrics(), - ...DefaultModules.databasePrune(), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } -} -export class DefaultSequencerConfig { - static inmemory(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ settlementEnabled: options?.settlementEnabled }), - ...DefaultConfigs.database({ preset: "inmemory" }), - ...DefaultConfigs.taskQueue({ preset: "inmemory" }), - ...options?.overrideConfig, - }; - } - static development(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ - settlementEnabled: options?.settlementEnabled, - preset: "development", - }), - ...DefaultConfigs.sequencerIndexer(), - ...DefaultConfigs.metrics({ preset: "development" }), - ...DefaultConfigs.databasePrune({ preset: "development" }), - ...DefaultConfigs.taskQueue({ - preset: "development", - overrides: { - ...DefaultConfigs.redis({ - preset: "development", - overrides: { db: 1 }, - }), - }, - }), - ...DefaultConfigs.database({ preset: "development" }), - ...options?.overrideConfig, - }; - } - static sovereign(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ - settlementEnabled: options?.settlementEnabled, - preset: "sovereign", - }), - ...DefaultConfigs.sequencerIndexer(), - ...DefaultConfigs.metrics({ preset: "sovereign" }), - ...DefaultConfigs.databasePrune({ preset: "sovereign" }), - ...DefaultConfigs.taskQueue({ - preset: "sovereign", - overrides: { - ...DefaultConfigs.redis({ - preset: "sovereign", - overrides: { db: 1 }, - }), - }, - }), - ...DefaultConfigs.database({ preset: "sovereign" }), - ...options?.overrideConfig, - }; - } -} From 3a05782bc6fc4b16160d48e9f567305ce16ae385 Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 11:25:45 +0100 Subject: [PATCH 03/14] fix code format --- packages/stack/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stack/package.json b/packages/stack/package.json index dc3c50ad3..9258a54cc 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -41,7 +41,6 @@ "@prisma/client": "^5.19.1", "mina-fungible-token": "^1.1.0", "type-graphql": "2.0.0-rc.2" - }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } From ea5e64a1db874da16685645c86ca79746266e93e Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 12:49:56 +0100 Subject: [PATCH 04/14] fix graphql server config --- packages/stack/src/presets/modules/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index d1067aadd..9c61fbdaf 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -196,7 +196,7 @@ export class DefaultConfigs { }) { return { Graphql: VanillaGraphqlModules.defaultConfig(), - GraphqlServer: DefaultConfigs.graphqlServer({ + ...DefaultConfigs.graphqlServer({ preset: options?.preset, overrides: options?.overrides, }), @@ -452,9 +452,11 @@ export class DefaultConfigs { ); return { - port: config.graphqlPort, - host: config.graphqlHost, - graphiql: config.graphiqlEnabled, + GraphqlServer: { + port: config.graphqlPort, + host: config.graphqlHost, + graphiql: config.graphiqlEnabled, + }, }; } From 6438771a30a878cc99db7165e2c83dc2ede1d379 Mon Sep 17 00:00:00 2001 From: stanlou Date: Wed, 28 Jan 2026 16:55:36 +0300 Subject: [PATCH 05/14] extend CLI with interactive setup and operational commands --- package-lock.json | 195 +++++++-- packages/cli/package.json | 16 +- packages/cli/src/index.ts | 358 ++++++++++++++++- packages/cli/src/scripts/bridge/deposit.ts | 183 +++++++++ packages/cli/src/scripts/bridge/redeem.ts | 151 +++++++ packages/cli/src/scripts/bridge/withdraw.ts | 78 ++++ .../cli/src/scripts/env/create-environment.ts | 102 +++++ packages/cli/src/scripts/explorer/start.ts | 57 +++ packages/cli/src/scripts/generateKeys.ts | 21 + .../graphqlDocs}/generateGqlDocs.ts | 4 +- packages/cli/src/scripts/lightnet/faucet.ts | 79 ++++ .../src/scripts/lightnet/wait-for-network.ts | 41 ++ .../cli/src/scripts/lightnetInitialize.ts | 32 ++ .../src/scripts/settlement/deploy-token.ts | 248 ++++++++++++ packages/cli/src/scripts/settlement/deploy.ts | 86 ++++ packages/cli/src/utils/create-environment.ts | 372 ++++++++++++++++++ .../src/{utils.ts => utils/graphqlDocs.ts} | 3 + packages/cli/src/utils/loadEnv.ts | 65 +++ packages/cli/src/utils/loadUserModules.ts | 60 +++ packages/explorer/src/config.ts | 3 +- packages/stack/src/presets/modules/index.ts | 9 + 21 files changed, 2122 insertions(+), 41 deletions(-) create mode 100644 packages/cli/src/scripts/bridge/deposit.ts create mode 100644 packages/cli/src/scripts/bridge/redeem.ts create mode 100644 packages/cli/src/scripts/bridge/withdraw.ts create mode 100644 packages/cli/src/scripts/env/create-environment.ts create mode 100644 packages/cli/src/scripts/explorer/start.ts create mode 100644 packages/cli/src/scripts/generateKeys.ts rename packages/cli/src/{commands => scripts/graphqlDocs}/generateGqlDocs.ts (94%) create mode 100644 packages/cli/src/scripts/lightnet/faucet.ts create mode 100644 packages/cli/src/scripts/lightnet/wait-for-network.ts create mode 100644 packages/cli/src/scripts/lightnetInitialize.ts create mode 100644 packages/cli/src/scripts/settlement/deploy-token.ts create mode 100644 packages/cli/src/scripts/settlement/deploy.ts create mode 100644 packages/cli/src/utils/create-environment.ts rename packages/cli/src/{utils.ts => utils/graphqlDocs.ts} (98%) create mode 100644 packages/cli/src/utils/loadEnv.ts create mode 100644 packages/cli/src/utils/loadUserModules.ts diff --git a/package-lock.json b/package-lock.json index f73837ac0..62c5fb92a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6714,6 +6714,16 @@ "ink": "*" } }, + "node_modules/@types/inquirer": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz", + "integrity": "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "license": "MIT" @@ -6920,6 +6930,15 @@ "@types/node": "*" } }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "dev": true, @@ -8343,7 +8362,6 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "dev": true, "funding": [ { "type": "github", @@ -8463,7 +8481,6 @@ }, "node_modules/bl": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -8635,7 +8652,6 @@ }, "node_modules/buffer": { "version": "5.7.1", - "dev": true, "funding": [ { "type": "github", @@ -9031,7 +9047,6 @@ }, "node_modules/chardet": { "version": "0.7.0", - "dev": true, "license": "MIT" }, "node_modules/cheerio": { @@ -9188,7 +9203,6 @@ }, "node_modules/cli-spinners": { "version": "2.6.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9278,7 +9292,6 @@ }, "node_modules/clone": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -10250,7 +10263,6 @@ }, "node_modules/defaults": { "version": "1.0.4", - "dev": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -11940,7 +11952,6 @@ }, "node_modules/external-editor": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "chardet": "^0.7.0", @@ -14381,7 +14392,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "dev": true, "funding": [ { "type": "github", @@ -15570,7 +15580,6 @@ }, "node_modules/is-interactive": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15833,7 +15842,6 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -18966,6 +18974,11 @@ "license": "MIT", "optional": true }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "license": "MIT" @@ -18983,7 +18996,6 @@ }, "node_modules/log-symbols": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -18998,7 +19010,6 @@ }, "node_modules/log-symbols/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -19012,7 +19023,6 @@ }, "node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -19027,7 +19037,6 @@ }, "node_modules/log-symbols/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -19038,12 +19047,10 @@ }, "node_modules/log-symbols/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/log-symbols/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -19051,7 +19058,6 @@ }, "node_modules/log-symbols/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -21836,7 +21842,6 @@ }, "node_modules/ora": { "version": "5.4.1", - "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -21858,7 +21863,6 @@ }, "node_modules/ora/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -21872,7 +21876,6 @@ }, "node_modules/ora/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -21887,7 +21890,6 @@ }, "node_modules/ora/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -21898,12 +21900,10 @@ }, "node_modules/ora/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/ora/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -21911,7 +21911,6 @@ }, "node_modules/ora/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -23664,7 +23663,6 @@ }, "node_modules/readable-stream": { "version": "3.6.2", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -24009,7 +24007,6 @@ }, "node_modules/rxjs": { "version": "7.8.1", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -25005,7 +25002,6 @@ }, "node_modules/string_decoder": { "version": "1.3.0", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -25830,7 +25826,6 @@ }, "node_modules/tmp": { "version": "0.0.33", - "dev": true, "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" @@ -26967,7 +26962,6 @@ }, "node_modules/wcwidth": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" @@ -27568,6 +27562,12 @@ "version": "0.1.0", "license": "ISC", "dependencies": { + "@inquirer/figures": "^2.0.3", + "dotenv": "^17.2.3", + "inquirer": "^9.3.0", + "kleur": "^4.1.5", + "mina-fungible-token": "^1.1.0", + "reflect-metadata": "^0.1.13", "spectaql": "3.0.5", "ts-node": "^10.9.1", "yargs": "17.7.2" @@ -27576,18 +27576,148 @@ "proto-kit": "bin/protokit-cli.js" }, "devDependencies": { + "@types/inquirer": "^9.0.9", "@types/node": "^20.19.24", "@types/yargs": "17.0.32" }, "peerDependencies": { "@proto-kit/api": "*", "@proto-kit/common": "*", + "@proto-kit/explorer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", - "o1js": "^2.10.0" + "@proto-kit/stack": "*", + "o1js": "^2.10.0", + "tsyringe": "^4.10.0" + } + }, + "packages/cli/node_modules/@inquirer/figures": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.3.tgz", + "integrity": "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "packages/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/cli/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "packages/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "packages/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "packages/cli/node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "packages/cli/node_modules/inquirer": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.0.tgz", + "integrity": "sha512-zdopqPUKWmnOcaBJYMMtjqWCB2HHXrteAou9tCYgkTJu01QheLfYOrkzigDfidPBtCizmkdpSU0fp2DKaMdFPA==", + "dependencies": { + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "picocolors": "^1.0.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/cli/node_modules/inquirer/node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "engines": { + "node": ">=18" + } + }, + "packages/cli/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "packages/cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "packages/cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" + } + }, + "packages/cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, "packages/common": { @@ -27934,7 +28064,8 @@ "dependencies": { "@prisma/client": "^5.19.1", "mina-fungible-token": "^1.1.0", - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "type-graphql": "2.0.0-rc.2" }, "devDependencies": { "@jest/globals": "^29.5.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 7fbd14143..a37868ff3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -21,21 +21,31 @@ "author": "", "license": "ISC", "dependencies": { - "yargs": "17.7.2", + "@inquirer/figures": "^2.0.3", + "dotenv": "^17.2.3", + "inquirer": "^9.3.0", + "kleur": "^4.1.5", + "mina-fungible-token": "^1.1.0", + "reflect-metadata": "^0.1.13", + "spectaql": "3.0.5", "ts-node": "^10.9.1", - "spectaql": "3.0.5" + "yargs": "17.7.2" }, "peerDependencies": { "@proto-kit/api": "*", "@proto-kit/common": "*", + "@proto-kit/explorer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", - "o1js": "^2.10.0" + "@proto-kit/stack": "*", + "o1js": "^2.10.0", + "tsyringe": "^4.10.0" }, "devDependencies": { + "@types/inquirer": "^9.0.9", "@types/node": "^20.19.24", "@types/yargs": "17.0.32" } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index fbd97cd2c..d8f53fad7 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node + +/* eslint-disable no-console */ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; -import { generateGqlDocsCommand } from "./commands/generateGqlDocs"; +import { parseEnvArgs } from "./utils/loadEnv"; process.removeAllListeners("warning"); process.env.NODE_NO_WARNINGS = "1"; await yargs(hideBin(process.argv)) + .scriptName("proto-kit") + .usage("$0 [options]") + .strict() .command( "generate-gql-docs", - "generate GraphQL docs", + "Generate GraphQL docs", (yarg) => yarg .option("port", { @@ -33,6 +38,9 @@ await yargs(hideBin(process.argv)) }), async (args) => { try { + const { generateGqlDocsCommand } = await import( + "./scripts/graphqlDocs/generateGqlDocs" + ); await generateGqlDocsCommand(args); process.exit(0); } catch (error) { @@ -41,7 +49,349 @@ await yargs(hideBin(process.argv)) } } ) - .demandCommand() - .help() + .command( + "generate-keys [count]", + "Generate private/public key pairs for development", + (yarg) => + yarg.positional("count", { + type: "number", + default: 1, + describe: "number of keys to generate", + }), + async (args) => { + try { + const { generateKeysCommand } = await import("./scripts/generateKeys"); + await generateKeysCommand({ count: args.count }); + process.exit(0); + } catch (error) { + console.error("Failed to generate keys:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:wait-for-network", + "Wait for lightnet network to be ready\n\nRequires: MINA_NODE_GRAPHQL_HOST, MINA_NODE_GRAPHQL_PORT", + (yarg) => + yarg + .option("env-path", { + type: "string", + describe: "path to .env file", + }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: lightnetWaitForNetworkScript } = await import( + "./scripts/lightnet/wait-for-network" + ); + await lightnetWaitForNetworkScript({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to wait for network:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:faucet ", + "Send MINA to an account from the lightnet faucet", + (yarg) => + yarg + .positional("publicKey", { + type: "string", + describe: "public key to send MINA to", + demandOption: true, + }) + .option("env-path", { + type: "string", + describe: "path to .env file", + }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: lightnetFaucetScript } = await import( + "./scripts/lightnet/faucet" + ); + await lightnetFaucetScript(args.publicKey); + process.exit(0); + } catch (error) { + console.error("Failed to send funds from faucet:", error); + process.exit(1); + } + } + ) + .command( + "settlement:deploy", + "Deploy settlement contracts\n\nRequires: PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: settlementDeployScript } = await import( + "./scripts/settlement/deploy" + ); + await settlementDeployScript({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to deploy settlement:", error); + process.exit(1); + } + } + ) + .command( + "settlement:token:deploy [mintAmount]", + "Deploy custom fungible token for settlement\n\nRequires: PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenSymbol", { type: "string", demandOption: true }) + .positional("feepayerKey", { type: "string", demandOption: true }) + .positional("receiverPublicKey", { + type: "string", + demandOption: true, + }) + .positional("mintAmount", { type: "number", default: 0 }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: settlementTokenDeployScript } = await import( + "./scripts/settlement/deploy-token" + ); + await settlementTokenDeployScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenSymbol: args.tokenSymbol, + feepayerKey: args.feepayerKey, + receiverPublicKey: args.receiverPublicKey, + mintAmount: args.mintAmount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to deploy settlement token:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:initialize", + "Initialize lightnet: wait for network, fund accounts, and deploy settlement\n\nRequires: MINA_NODE_GRAPHQL_HOST, MINA_NODE_GRAPHQL_PORT, MINA_ARCHIVE_GRAPHQL_HOST, MINA_ARCHIVE_GRAPHQL_PORT, MINA_ACCOUNT_MANAGER_HOST, MINA_ACCOUNT_MANAGER_PORT, PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { lightnetInitializeCommand } = await import( + "./scripts/lightnetInitialize" + ); + await lightnetInitializeCommand({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to initialize lightnet:", error); + process.exit(1); + } + } + ) + .command( + "bridge:deposit ", + "Deposit tokens to the bridge\n\nRequires: PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY (for custom tokens), PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("fromKey", { type: "string", demandOption: true }) + .positional("toKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeDepositScript } = await import( + "./scripts/bridge/deposit" + ); + await bridgeDepositScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + fromKey: args.fromKey, + toKey: args.toKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to deposit to bridge:", error); + process.exit(1); + } + } + ) + .command( + "bridge:redeem ", + "Redeem tokens from the bridge\n\nRequires: PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("toKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeRedeemScript } = await import( + "./scripts/bridge/redeem" + ); + await bridgeRedeemScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + toKey: args.toKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to redeem from bridge:", error); + process.exit(1); + } + } + ) + .command( + "bridge:withdraw ", + "Withdraw tokens\n\nRequires: NEXT_PUBLIC_PROTOKIT_GRAPHQL_URL", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("senderKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeWithdrawScript } = await import( + "./scripts/bridge/withdraw" + ); + await bridgeWithdrawScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + senderKey: args.senderKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to withdraw from bridge:", error); + process.exit(1); + } + } + ) + .command( + "env:create", + "Create a new environment configuration with guided wizard", + () => ({}), + async () => { + try { + const { default: createEnvironmentScript } = await import( + "./scripts/env/create-environment" + ); + await createEnvironmentScript(); + process.exit(0); + } catch (error) { + console.error("Failed to create environment:", error); + process.exit(1); + } + } + ) + .command( + "explorer:start", + "Start the explorer UI", + (yarg) => + yarg + .option("port", { + alias: "p", + type: "number", + default: 5003, + describe: "port to run the explorer on", + }) + .option("indexer-url", { + type: "string", + describe: "GraphQL endpoint URL for the indexer", + }), + async (args) => { + try { + const { default: explorerStartScript } = await import( + "./scripts/explorer/start" + ); + await explorerStartScript(args.port, args["indexer-url"]); + process.exit(0); + } catch (error) { + console.error("Failed to start explorer:", error); + process.exit(1); + } + } + ) + .demandCommand( + 1, + "You must specify a command. Use --help to see available commands." + ) + .help("help") + .alias("help", "h") + .option("help", { describe: "Show help" }) .strict() .parse(); +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/bridge/deposit.ts b/packages/cli/src/scripts/bridge/deposit.ts new file mode 100644 index 000000000..0f222b80a --- /dev/null +++ b/packages/cli/src/scripts/bridge/deposit.ts @@ -0,0 +1,183 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ + +import { + BridgingModule, + MinaTransactionSender, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; +import { + AccountUpdate, + fetchAccount, + Field, + Mina, + PrivateKey, + Provable, + PublicKey, + UInt64, +} from "o1js"; +import { FungibleToken } from "mina-fungible-token"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeDepositArgs { + tokenId: string; + fromKey: string; + toKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeDepositArgs +) { + if (!bridgeArgs) { + throw new Error( + "Bridge deposit arguments required: tokenId, fromKey, toKey, amount" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const fromPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.fromKey] ?? bridgeArgs.fromKey + ); + const toPublicKey = PublicKey.fromBase58( + process.env[bridgeArgs.toKey] ?? bridgeArgs.toKey + ); + const amount = bridgeArgs.amount * 1e9; + const fee = 0.1 * 1e9; + + const isCustomToken = tokenId.toBigInt() !== 1n; + const tokenOwnerPrivateKey = isCustomToken + ? PrivateKey.fromBase58(getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY")) + : PrivateKey.random(); + const bridgeContractKey = isCustomToken + ? PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY") + ) + : PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") + ); + + Provable.log("Preparing to deposit", { + tokenId, + fromPrivateKey, + toPublicKey, + amount, + fee, + }); + + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.inMemoryDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + + const { settlement, dispatch } = settlementModule.getContracts(); + + await fetchAccount({ publicKey: fromPrivateKey.toPublicKey() }); + await fetchAccount({ publicKey: settlement.address }); + await fetchAccount({ publicKey: dispatch.address }); + const bridgeAddress = await bridgingModule.getBridgeAddress(tokenId); + await fetchAccount({ publicKey: bridgeAddress!, tokenId: tokenId }); + await fetchAccount({ publicKey: bridgeAddress!, tokenId: tokenId }); + + const attestation = + await bridgingModule.getDepositContractAttestation(tokenId); + + console.log("Forging transaction..."); + const tx = await Mina.transaction( + { + memo: "User deposit", + sender: fromPrivateKey.toPublicKey(), + fee, + }, + async () => { + const au = AccountUpdate.createSigned( + fromPrivateKey.toPublicKey(), + tokenId + ); + au.balance.subInPlace(UInt64.from(amount)); + + await dispatch.deposit( + UInt64.from(amount), + tokenId, + bridgeContractKey.toPublicKey(), + attestation, + toPublicKey + ); + + if (isCustomToken) { + await new FungibleToken( + tokenOwnerPrivateKey.toPublicKey() + )!.approveAccountUpdates([au, dispatch.self]); + } + } + ); + console.log(tx.toPretty()); + + settlementModule.signTransaction( + tx, + [fromPrivateKey], + [tokenOwnerPrivateKey], + [dispatch.address] + ); + + console.log("Sending..."); + console.log(tx.toPretty()); + + const { hash } = await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log(`Deposit transaction included in a block: ${hash}`); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/bridge/redeem.ts b/packages/cli/src/scripts/bridge/redeem.ts new file mode 100644 index 000000000..35bc126d5 --- /dev/null +++ b/packages/cli/src/scripts/bridge/redeem.ts @@ -0,0 +1,151 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { + BridgingModule, + MinaTransactionSender, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + AccountUpdate, + fetchAccount, + Field, + Mina, + PrivateKey, + Provable, + UInt64, +} from "o1js"; +import { FungibleToken } from "mina-fungible-token"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeRedeemArgs { + tokenId: string; + toKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeRedeemArgs +) { + if (!bridgeArgs) { + throw new Error("Bridge redeem arguments required: tokenId, toKey, amount"); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const toPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.toKey] ?? bridgeArgs.toKey + ); + const amount = bridgeArgs.amount * 1e9; + const fee = 0.1 * 1e9; + + const isCustomToken = tokenId.toBigInt() !== 1n; + const tokenOwnerPrivateKey = isCustomToken + ? PrivateKey.fromBase58(getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY")) + : PrivateKey.random(); + + Provable.log("Preparing to redeem", { + tokenId, + to: toPrivateKey.toPublicKey(), + amount, + fee, + }); + + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.inMemoryDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled); + + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + + const bridgeContract = await bridgingModule.getBridgeContract(tokenId); + + const customAcc = await fetchAccount({ + publicKey: toPrivateKey.toPublicKey(), + tokenId: bridgeContract.deriveTokenId(), + }); + + Provable.log("Custom account", customAcc.account?.balance); + + console.log("Forging transaction..."); + const tx = await Mina.transaction( + { + sender: toPrivateKey.toPublicKey(), + fee, + }, + async () => { + const au = AccountUpdate.createSigned( + toPrivateKey.toPublicKey(), + tokenId + ); + au.balance.addInPlace(UInt64.from(amount)); + + await bridgeContract.redeem(au); + + if (isCustomToken) { + await new FungibleToken( + tokenOwnerPrivateKey.toPublicKey() + )!.approveAccountUpdate(bridgeContract.self); + } + } + ); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + settlementModule.signTransaction(tx, [toPrivateKey], [tokenOwnerPrivateKey]); + + console.log("Sending..."); + + const { hash } = await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log(`Redeem transaction included in a block: ${hash}`); + console.log(tx.toPretty()); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/bridge/withdraw.ts b/packages/cli/src/scripts/bridge/withdraw.ts new file mode 100644 index 000000000..346576db2 --- /dev/null +++ b/packages/cli/src/scripts/bridge/withdraw.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { ClientAppChain, InMemorySigner } from "@proto-kit/sdk"; +import { Field, PrivateKey, Provable } from "o1js"; +import { UInt64 } from "@proto-kit/library"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; + +import { loadEnvironmentVariables, LoadEnvOptions } from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeWithdrawArgs { + tokenId: string; + senderKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeWithdrawArgs +) { + if (!bridgeArgs) { + throw new Error( + "Bridge withdraw arguments required: tokenId, senderKey, amount" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const amount = UInt64.from(bridgeArgs.amount * 1e9); + const appChain = ClientAppChain.fromRemoteEndpoint( + Runtime.from(runtime.modules), + Protocol.from({ ...protocol.modules, ...protocol.settlementModules }), + InMemorySigner + ); + + appChain.configurePartial({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + GraphqlClient: { + url: process.env.NEXT_PUBLIC_PROTOKIT_GRAPHQL_URL, + }, + }); + + await appChain.start(); + + const senderPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.senderKey] ?? bridgeArgs.senderKey + ); + const senderPublicKey = senderPrivateKey.toPublicKey(); + const signer = appChain.resolve("Signer"); + signer.config.signer = senderPrivateKey; + + Provable.log("debug", { + senderPrivateKey, + senderPublicKey, + amount, + tokenId, + }); + + const withdrawals = appChain.runtime.resolve("Withdrawals"); + const tx = await appChain.transaction(senderPublicKey, async () => { + await withdrawals.withdraw(senderPublicKey, amount, tokenId); + }); + + await tx.sign(); + await tx.send(); + + console.log("withdrawal tx sent"); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/env/create-environment.ts b/packages/cli/src/scripts/env/create-environment.ts new file mode 100644 index 000000000..ea3c3d576 --- /dev/null +++ b/packages/cli/src/scripts/env/create-environment.ts @@ -0,0 +1,102 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import * as fs from "fs"; +import * as path from "path"; + +import { cyan, green, red, bold } from "kleur/colors"; + +import { + copyAndUpdateEnvFile, + generateChainConfig, + generateIndexerConfig, + generateProcessorConfig, + icons, + promptUser, +} from "../../utils/create-environment"; + +export default async function () { + try { + const answers = await promptUser(); + + const cwd = process.cwd(); + const envDir = path.join( + cwd, + "src/core/environments", + answers.environmentName + ); + + if (!fs.existsSync(envDir)) { + fs.mkdirSync(envDir, { recursive: true }); + } + + const chainConfigPath = path.join(envDir, "chain.config.ts"); + const indexerConfigPath = path.join(envDir, "indexer.config.ts"); + const processorConfigPath = path.join(envDir, "processor.config.ts"); + + if (fs.existsSync(chainConfigPath)) { + console.log(`\nEnvironment already exists at ${envDir}`); + return; + } + + copyAndUpdateEnvFile(answers, cwd, envDir); + const chainConfig = generateChainConfig(answers); + fs.writeFileSync(chainConfigPath, chainConfig); + + if (answers.includeIndexer) { + const indexerConfig = generateIndexerConfig(answers); + if (indexerConfig) { + fs.writeFileSync(indexerConfigPath, indexerConfig); + } + } + + if (answers.includeProcessor && answers.includeIndexer) { + const processorConfig = generateProcessorConfig(answers); + if (processorConfig) { + fs.writeFileSync(processorConfigPath, processorConfig); + } + } + + console.log( + `\n${bold(green(" ╔════════════════════════════════════════╗"))}` + ); + console.log( + `${bold(green(" ║ ✓ Environment Created Successfully ║"))}` + ); + console.log( + `${bold(green(" ╚════════════════════════════════════════╝"))}` + ); + console.log(""); + + console.log(`${bold("Location:")}`); + console.log(` ${cyan(envDir)}\n`); + + console.log(`${bold("Generated Files:")}`); + console.log(` ${green(icons.checkmark)} .env`); + console.log(` ${green(icons.checkmark)} chain.config.ts`); + if (answers.includeIndexer) { + console.log(` ${green(icons.checkmark)} indexer.config.ts`); + } + if (answers.includeProcessor && answers.includeIndexer) { + console.log(` ${green(icons.checkmark)} processor.config.ts`); + } + + console.log(`\n${bold("Next Steps:")}`); + const cdCommand = `cd ${path.relative(process.cwd(), envDir)}`; + console.log(` 1. ${cyan(cdCommand)}`); + console.log(` 2. Update environment variables in ${cyan(".env")}`); + console.log( + ` 3. Add the following script to your root ${cyan("package.json")}:` + ); + const scriptCommand = `"env:${answers.environmentName}": "dotenv -e ./packages/chain/src/core/environments/${answers.environmentName}/.env -- pnpm"`; + console.log(`${cyan(scriptCommand)}`); + console.log(" 4. Start your application\n"); + } catch (error) { + console.log(`\n${bold(red("✗ Error"))}`); + console.log(`${red("-".repeat(50))}`); + console.error(` ${error}`); + console.log(`${red("-".repeat(50))}\n`); + process.exit(1); + } +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/explorer/start.ts b/packages/cli/src/scripts/explorer/start.ts new file mode 100644 index 000000000..4ce12db6f --- /dev/null +++ b/packages/cli/src/scripts/explorer/start.ts @@ -0,0 +1,57 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { spawn } from "child_process"; +import path from "path"; +import { fileURLToPath } from "url"; + +export default async function (port?: number, indexerUrl?: string) { + let explorerDir: string; + + try { + const pkgUrl = await import.meta.resolve( + "@proto-kit/explorer/package.json" + ); + const pkgPath = fileURLToPath(pkgUrl); + explorerDir = path.dirname(pkgPath); + } catch (error) { + console.error("Failed to find @proto-kit/explorer package."); + throw error; + } + + return await new Promise((resolve, reject) => { + const child = spawn("npm", ["run", "dev", "--", "-p", String(port)], { + cwd: explorerDir, + stdio: "inherit", + env: { + ...process.env, + NODE_OPTIONS: "", + NEXT_PUBLIC_INDEXER_URL: indexerUrl, + }, + }); + + child.on("error", (error) => { + console.error("Failed to start explorer:", error); + reject(error); + }); + + child.on("exit", (code) => { + if (code !== null && code !== 0 && code !== 143) { + reject(new Error(`Explorer process exited with code ${code}`)); + } else { + resolve(); + } + }); + + process.on("SIGINT", () => { + child.kill(); + resolve(); + }); + + process.on("SIGTERM", () => { + child.kill(); + resolve(); + }); + }); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/generateKeys.ts b/packages/cli/src/scripts/generateKeys.ts new file mode 100644 index 000000000..d4d5fd663 --- /dev/null +++ b/packages/cli/src/scripts/generateKeys.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-console */ + +import { PrivateKey } from "o1js"; + +type GenerateKeysArgs = { + count?: number; +}; + +export async function generateKeysCommand(args: GenerateKeysArgs) { + const count = args.count ?? 1; + console.log(`Generated ${count} keys for development purposes:`); + console.log("-".repeat(70)); + for (let i = 0; i < count; i++) { + const privateKey = PrivateKey.random(); + const publicKey = privateKey.toPublicKey(); + console.log("Private key:", privateKey.toBase58()); + console.log("Public key:", publicKey.toBase58()); + console.log("-".repeat(70)); + } +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/commands/generateGqlDocs.ts b/packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts similarity index 94% rename from packages/cli/src/commands/generateGqlDocs.ts rename to packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts index f9fcedc17..2ab3c680d 100644 --- a/packages/cli/src/commands/generateGqlDocs.ts +++ b/packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { BlockStorageNetworkStateModule, InMemoryTransactionSender, @@ -21,7 +22,7 @@ import { } from "@proto-kit/api"; import { Runtime } from "@proto-kit/module"; -import { generateGqlDocs } from "../utils"; +import { generateGqlDocs } from "../../utils/graphqlDocs"; export async function generateGqlDocsCommand(args: { empty: boolean; @@ -76,3 +77,4 @@ export async function generateGqlDocsCommand(args: { await generateGqlDocs(args.url); } } +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/lightnet/faucet.ts b/packages/cli/src/scripts/lightnet/faucet.ts new file mode 100644 index 000000000..b3af98c63 --- /dev/null +++ b/packages/cli/src/scripts/lightnet/faucet.ts @@ -0,0 +1,79 @@ +/* eslint-disable func-names */ +import { + AccountUpdate, + fetchAccount, + Lightnet, + Mina, + Provable, + PublicKey, +} from "o1js"; + +import "reflect-metadata"; +import { getRequiredEnv } from "../../utils/loadEnv"; + +export default async function (publicKey: string) { + // configuration + const fee = 0.1 * 1e9; + const fundingAmount = 1000 * 1e9; + + const net = Mina.Network({ + mina: `${getRequiredEnv("MINA_NODE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_NODE_GRAPHQL_PORT")}/graphql`, + archive: `${getRequiredEnv("MINA_ARCHIVE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_ARCHIVE_GRAPHQL_PORT")}/graphql`, + lightnetAccountManager: `${getRequiredEnv("MINA_ACCOUNT_MANAGER_HOST")}:${getRequiredEnv("MINA_ACCOUNT_MANAGER_PORT")}`, + }); + + Mina.setActiveInstance(net); + + // get the source account from the account manager + const pair = await Lightnet.acquireKeyPair({ + isRegularAccount: true, + }); + + // which account to drip to + const keyArg = process.env[publicKey] ?? publicKey; + + if (keyArg?.length === 0) { + throw new Error("No key provided"); + } + + const key = PublicKey.fromBase58(keyArg); + + await fetchAccount({ publicKey: pair.publicKey }); + + Provable.log( + `Dripping ${fundingAmount / 1e9} MINA from ${pair.publicKey.toBase58()} to ${key.toBase58()}` + ); + + const tx = await Mina.transaction( + { + sender: pair.publicKey, + fee, + }, + async () => { + const account = await fetchAccount({ publicKey: key }); + // if the destination account does not exist yet, pay the creation fee for it + if (account.error) { + AccountUpdate.fundNewAccount(pair.publicKey); + } + + AccountUpdate.createSigned(pair.publicKey).balance.subInPlace( + fundingAmount + ); + AccountUpdate.create(key).balance.addInPlace(fundingAmount); + } + ); + + tx.sign([pair.privateKey]); + + const sentTx = await tx.send(); + await sentTx.wait(); + + Provable.log( + `Funded account ${key.toBase58()} with ${fundingAmount / 1e9} MINA` + ); + + await Lightnet.releaseKeyPair({ + publicKey: pair.publicKey.toBase58(), + }); +} +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/lightnet/wait-for-network.ts b/packages/cli/src/scripts/lightnet/wait-for-network.ts new file mode 100644 index 000000000..9f7ccbae6 --- /dev/null +++ b/packages/cli/src/scripts/lightnet/wait-for-network.ts @@ -0,0 +1,41 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { sleep } from "@proto-kit/common"; +import { fetchLastBlock, Provable } from "o1js"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; + +const maxAttempts = 24; +const delay = 5000; + +export default async function (options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + const graphqlEndpoint = `${getRequiredEnv("MINA_NODE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_NODE_GRAPHQL_PORT")}/graphql`; + let lastBlock; + let attempt = 0; + console.log("Waiting for network to be ready..."); + while (!lastBlock) { + attempt += 1; + if (attempt > maxAttempts) { + throw new Error( + `Network was still not ready after ${(delay / 1000) * (attempt - 1)}s` + ); + } + try { + // eslint-disable-next-line no-await-in-loop + lastBlock = await fetchLastBlock(graphqlEndpoint); + } catch (e) { + // continue + } + // eslint-disable-next-line no-await-in-loop + await sleep(delay); + } + + Provable.log("Network is ready", lastBlock); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/lightnetInitialize.ts b/packages/cli/src/scripts/lightnetInitialize.ts new file mode 100644 index 000000000..ab1f97d2f --- /dev/null +++ b/packages/cli/src/scripts/lightnetInitialize.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-console */ + +import { + LoadEnvOptions, + getRequiredEnv, + loadEnvironmentVariables, +} from "../utils/loadEnv"; + +import lightnetWaitForNetworkScript from "./lightnet/wait-for-network"; +import lightnetFaucetScript from "./lightnet/faucet"; +import settlementDeployScript from "./settlement/deploy"; + +export async function lightnetInitializeCommand(options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + + console.log("Step 1: Waiting for network to be ready..."); + await lightnetWaitForNetworkScript(options); + + console.log("Step 2: Funding PROTOKIT_SEQUENCER_PUBLIC_KEY from faucet..."); + await lightnetFaucetScript(getRequiredEnv("PROTOKIT_SEQUENCER_PUBLIC_KEY")); + + console.log("Step 3: Funding TEST_ACCOUNT_1_PUBLIC_KEY from faucet..."); + await lightnetFaucetScript(getRequiredEnv("TEST_ACCOUNT_1_PUBLIC_KEY")); + + console.log("Step 4: Deploying settlement contracts..."); + await settlementDeployScript(options); + + console.log( + "Lightnet initialization complete! Settlement contracts are deployed." + ); +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/settlement/deploy-token.ts b/packages/cli/src/scripts/settlement/deploy-token.ts new file mode 100644 index 000000000..28c707158 --- /dev/null +++ b/packages/cli/src/scripts/settlement/deploy-token.ts @@ -0,0 +1,248 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + ArchiveNode, + MinaTransactionSender, + ProvenSettlementPermissions, + Sequencer, + SettlementModule, + SignedSettlementPermissions, + AppChain, +} from "@proto-kit/sequencer"; +import { + AccountUpdate, + Bool, + fetchAccount, + Mina, + PrivateKey, + Provable, + PublicKey, + UInt64, + UInt8, +} from "o1js"; +import "reflect-metadata"; +import { container } from "tsyringe"; +import { FungibleToken, FungibleTokenAdmin } from "mina-fungible-token"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { loadEnvironmentVariables, LoadEnvOptions } from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface TokenDeployArgs { + tokenSymbol: string; + feepayerKey: string; + receiverPublicKey: string; + mintAmount: number; +} + +export default async function ( + options?: LoadEnvOptions, + tokenArgs?: TokenDeployArgs +) { + if (!tokenArgs) { + throw new Error( + "Token deployment arguments required: tokenSymbol, feepayerKey, receiverPublicKey, [mintAmount]" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.PrismaRedisDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.prismaRedisDatabase({ + preset: "development", + overrides: { + pruneOnStartup: false, + }, + }), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const chainContainer = container.createChildContainer(); + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled, chainContainer); + const { tokenSymbol } = tokenArgs; + const feepayerPrivateKey = PrivateKey.fromBase58( + process.env[tokenArgs.feepayerKey] ?? tokenArgs.feepayerKey + ); + const receiverPublicKey = PublicKey.fromBase58( + process.env[tokenArgs.receiverPublicKey] ?? tokenArgs.receiverPublicKey + ); + const mintAmount = tokenArgs.mintAmount * 1e9; + const fee = 0.1 * 1e9; + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + const isSignedSettlement = settlementModule.utils.isSignedSettlement(); + + const tokenOwnerKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + const tokenAdminKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + const tokenBridgeKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + + await ArchiveNode.waitOnSync(appChain.sequencer.resolve("BaseLayer").config); + + async function deployTokenContracts() { + const permissions = isSignedSettlement + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions(); + + const tx = await Mina.transaction( + { + sender: feepayerPrivateKey.toPublicKey(), + memo: "Deploy custom token", + fee, + }, + async () => { + AccountUpdate.fundNewAccount(feepayerPrivateKey.toPublicKey(), 3); + + const admin = new FungibleTokenAdmin(tokenAdminKey.toPublicKey()); + await admin.deploy({ + adminPublicKey: feepayerPrivateKey.toPublicKey(), + }); + admin.self.account.permissions.set(permissions.bridgeContractToken()); + + const fungibleToken = new FungibleToken(tokenOwnerKey.toPublicKey()); + await fungibleToken.deploy({ + src: "", + symbol: tokenSymbol, + allowUpdates: false, + }); + fungibleToken!.self.account.permissions.set( + permissions.bridgeContractToken() + ); + + await fungibleToken.initialize( + tokenAdminKey.toPublicKey(), + UInt8.from(9), + Bool(false) + ); + } + ); + console.log("Sending deploy transaction..."); + console.log(tx.toPretty()); + + settlementModule.signTransaction( + tx, + [feepayerPrivateKey, tokenOwnerKey, tokenAdminKey], + [tokenOwnerKey, tokenAdminKey] + ); + + await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log("Deploy transaction included"); + } + + async function mint() { + const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); + await settlementModule.utils.fetchContractAccounts( + { + address: tokenOwner!.address, + tokenId: tokenOwner!.tokenId, + }, + { + address: tokenOwner!.address, + tokenId: tokenOwner!.deriveTokenId(), + } + ); + + const tx = await Mina.transaction( + { + sender: feepayerPrivateKey.toPublicKey(), + memo: "Mint custom token", + fee, + }, + async () => { + AccountUpdate.fundNewAccount(feepayerPrivateKey.toPublicKey(), 1); + + await tokenOwner!.mint(receiverPublicKey, UInt64.from(mintAmount)); + } + ); + settlementModule.utils.signTransaction( + tx, + [feepayerPrivateKey], + [tokenOwnerKey, tokenAdminKey] + ); + + await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + } + + async function deployBridge() { + const { settlement, dispatch } = settlementModule.getAddresses(); + await fetchAccount({ + publicKey: settlementModule.config.feepayer.toPublicKey(), + }); + await fetchAccount({ publicKey: settlement }); + await fetchAccount({ publicKey: dispatch }); + + const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); + // SetAdminEvent. + await settlementModule.deployTokenBridge( + tokenOwner, + tokenOwnerKey, + tokenBridgeKey, + {} + ); + console.log( + `Token bridge address: ${tokenBridgeKey.toPublicKey().toBase58()} @ ${tokenOwner.deriveTokenId().toString()}` + ); + } + + await deployTokenContracts(); + await mint(); + await deployBridge(); + + console.log( + `Deployed custom token with id ${new FungibleToken(tokenOwnerKey.toPublicKey())!.deriveTokenId()}` + ); + + Provable.log("Deployed and initialized settlement contracts", { + settlement: PrivateKey.fromBase58( + process.env.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY! + ).toPublicKey(), + dispatcher: PrivateKey.fromBase58( + process.env.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY! + ).toPublicKey(), + }); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/settlement/deploy.ts b/packages/cli/src/scripts/settlement/deploy.ts new file mode 100644 index 000000000..9e0530fb0 --- /dev/null +++ b/packages/cli/src/scripts/settlement/deploy.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ + +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + InMemoryDatabase, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { PrivateKey, Provable } from "o1js"; +import "reflect-metadata"; +import { container } from "tsyringe"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export default async function (options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + Database: InMemoryDatabase, + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ preset: "development" }), + }, + }); + + const chainContainer = container.createChildContainer(); + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled, chainContainer); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + console.log("Deploying settlement contracts..."); + + await settlementModule.deploy( + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + ), + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + ), + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") + ) + ); + + Provable.log("Deployed and initialized settlement contracts", { + settlement: PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + ).toPublicKey(), + dispatcher: PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + ).toPublicKey(), + }); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/utils/create-environment.ts b/packages/cli/src/utils/create-environment.ts new file mode 100644 index 000000000..6f4b5baad --- /dev/null +++ b/packages/cli/src/utils/create-environment.ts @@ -0,0 +1,372 @@ +import * as fs from "fs"; +import * as path from "path"; + +import inquirer from "inquirer"; +import figuresLib from "@inquirer/figures"; +import { cyan, green, blue, gray, bold } from "kleur/colors"; + +/* eslint-disable no-console */ + +export const icons = { + checkmark: figuresLib.tick, + cross: figuresLib.cross, + arrow: figuresLib.pointerSmall, + circle: figuresLib.bullet, + square: figuresLib.square, +}; + +export type PresetType = "inmemory" | "development" | "sovereign"; + +export interface WizardAnswers { + environmentName: string; + preset: PresetType; + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; +} + +export const PRESET_ENV_NAMES: Record = { + inmemory: "inmemory", + development: "development", + sovereign: "sovereign", +}; + +export const PRESET_DESCRIPTIONS: Record = { + inmemory: "Fast testing and development environment", + development: "Local development environment", + sovereign: "Production-ready environment", +}; + +export const PRESET_LABELS: Record = { + inmemory: "In-Memory", + development: "Development", + sovereign: "Sovereign", +}; + +export function printHeader(): void { + console.log(bold(cyan(" ╔════════════════════════════════════════╗"))); + console.log(bold(cyan(" ║ 🚀 Proto-Kit Environment Wizard ║"))); + console.log(bold(cyan(" ╚════════════════════════════════════════╝"))); + console.log(""); +} + +export function printSection(title: string): void { + const section = `${icons.square} ${title}`; + console.log(`\n${bold(blue(section))}`); + console.log(gray("-".repeat(50))); + console.log(""); +} + +export async function selectPreset(): Promise { + const presetTypes: PresetType[] = ["inmemory", "development", "sovereign"]; + const answer = await inquirer.prompt<{ preset: PresetType }>([ + { + type: "list", + name: "preset", + message: "Select Environment Preset", + choices: presetTypes.map((type) => { + const label = PRESET_LABELS[type]; + const description = PRESET_DESCRIPTIONS[type]; + return { + name: `${label} - ${description}`, + value: type, + }; + }), + }, + ]); + + return answer.preset; +} + +export async function selectModules(preset: PresetType): Promise<{ + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; +}> { + const isInMemory = preset === "inmemory"; + + const answers = await inquirer.prompt<{ + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; + }>([ + { + type: "confirm", + name: "includeIndexer", + message: "Include Indexer Module?", + default: false, + when: !isInMemory, + }, + { + type: "confirm", + name: "includeProcessor", + message: "Include Processor Module? (requires Indexer)", + default: false, + when: (ans: WizardAnswers) => ans.includeIndexer === true, + }, + { + type: "confirm", + name: "includeMetrics", + message: "Include OpenTelemetry Metrics?", + default: false, + }, + { + type: "confirm", + name: "settlementEnabled", + message: "Enable Settlement Module?", + default: false, + }, + ]); + if (isInMemory && answers.includeIndexer === false) { + answers.includeIndexer = false; + } + if (answers.includeProcessor === false) { + answers.includeProcessor = false; + } + + return answers; +} + +export async function promptUser(): Promise { + printHeader(); + + printSection("Environment Configuration"); + + const answers = await inquirer.prompt<{ environmentName: string }>([ + { + type: "input", + name: "environmentName", + message: "Environment name (e.g 'production')", + validate: (input: string) => { + if (!input.trim()) { + return "Environment name is required"; + } + return true; + }, + }, + ]); + + const environmentName = (answers.environmentName ?? "").trim(); + const confirmationMessage = `${icons.checkmark} Environment: ${environmentName}`; + console.log(`${green(confirmationMessage)}\n`); + + const preset = await selectPreset(); + + printSection("Configure Modules"); + const modules = await selectModules(preset); + + return { + environmentName, + preset, + ...modules, + }; +} + +export function generateChainConfig(answers: WizardAnswers): string { + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + const isInMemory = answers.preset === "inmemory"; + + const moduleParts: string[] = []; + + if (answers.includeMetrics) { + moduleParts.push(" ...DefaultModules.metrics(),"); + } + if (isInMemory) { + moduleParts.push(" ...DefaultModules.inMemoryDatabase(),"); + } else { + moduleParts.push(" ...DefaultModules.PrismaRedisDatabase(),"); + } + moduleParts.push( + ` ...DefaultModules.core({ settlementEnabled: ${answers.settlementEnabled} }),` + ); + if (isInMemory) { + moduleParts.push(" ...DefaultModules.localTaskQueue(),"); + } else { + moduleParts.push(" ...DefaultModules.RedisTaskQueue(),"); + } + if (answers.includeIndexer) { + moduleParts.push(" ...DefaultModules.sequencerIndexer(),"); + } + if (answers.settlementEnabled) { + moduleParts.push(" ...DefaultModules.settlement(),"); + } + const modulesString = moduleParts.join("\n"); + const configParts: string[] = []; + const coreConfig = ` ...DefaultConfigs.core({ settlementEnabled: ${answers.settlementEnabled}, preset: "${presetEnv}" }),`; + configParts.push(coreConfig); + if (answers.includeIndexer) { + configParts.push(" ...DefaultConfigs.sequencerIndexer(),"); + } + if (answers.includeMetrics) { + configParts.push( + ` ...DefaultConfigs.metrics({ preset: "${presetEnv}" }),` + ); + } + if (isInMemory) { + configParts.push(" ...DefaultConfigs.localTaskQueue(),"); + configParts.push(" ...DefaultConfigs.inMemoryDatabase(),"); + } else { + configParts.push( + ` ...DefaultConfigs.redisTaskQueue({ + preset: "${presetEnv}", + }),` + ); + configParts.push( + ` ...DefaultConfigs.prismaRedisDatabase({ + preset: "${presetEnv}", + }),` + ); + } + if (answers.settlementEnabled) { + configParts.push( + ` ...DefaultConfigs.settlement({ preset: "${presetEnv}" }),` + ); + } + const configString = configParts.join("\n"); + return `import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { AppChain, Sequencer } from "@proto-kit/sequencer"; +import runtime from "../../../runtime"; +import * as protocol from "../../../protocol"; + +import { Arguments } from "../../../start"; +import { Startable } from "@proto-kit/common"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +const settlementEnabled = process.env.PROTOKIT_SETTLEMENT_ENABLED! === "true"; + +const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...(settlementEnabled ? protocol.settlementModules : {}), + }), + Sequencer: Sequencer.from({ + // ordering of the modules matters due to dependency resolution +${modulesString} + }), + ...DefaultModules.appChainBase(), +}); + +export default async (args: Arguments): Promise => { + appChain.configurePartial({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...(settlementEnabled ? protocol.settlementModulesConfig : {}), + }, + Sequencer: { +${configString} + }, + ...DefaultConfigs.appChainBase(), + }); + + return appChain; +};`; +} + +export function generateIndexerConfig(answers: WizardAnswers): string { + if (!answers.includeIndexer) { + return ""; + } + + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + + return `import { Indexer } from "@proto-kit/indexer"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +const indexer = Indexer.from({ + ...DefaultModules.indexer(), +}); + +export default async (): Promise => { + indexer.configurePartial({ + ...DefaultConfigs.indexer({ + preset: "${presetEnv}", + }), + }); + + return indexer; +};`; +} + +export function generateProcessorConfig(answers: WizardAnswers): string { + if (!answers.includeProcessor || !answers.includeIndexer) { + return ""; + } + + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + + return `import { Processor } from "@proto-kit/processor"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { handlers } from "../../processor/handlers"; +import { resolvers } from "../../processor/api/resolvers"; + +const processor = Processor.from({ + ...DefaultModules.processor(resolvers, handlers), +}); + +export default async (): Promise => { + processor.configurePartial({ + ...DefaultConfigs.processor({ + preset: "${presetEnv}", + }), + }); + + return processor; +};`; +} + +export function copyAndUpdateEnvFile( + answers: WizardAnswers, + cwd: string, + envDir: string +): boolean { + const presetEnvPath = path.join( + cwd, + "src/core/environments", + answers.preset, + ".env" + ); + + if (!fs.existsSync(presetEnvPath)) { + console.warn(`Could not find .env file at ${presetEnvPath}`); + return false; + } + + try { + let envContent = fs.readFileSync(presetEnvPath, "utf-8"); + + if (envContent.includes("PROTOKIT_ENV_FOLDER=")) { + envContent = envContent.replace( + /PROTOKIT_ENV_FOLDER=.*/g, + `PROTOKIT_ENV_FOLDER=${answers.environmentName}` + ); + } else { + const envFolder = `PROTOKIT_ENV_FOLDER=${answers.environmentName}`; + envContent = `${envFolder}\n${envContent}`; + } + + if (envContent.includes("PROTOKIT_SETTLEMENT_ENABLED=")) { + envContent = envContent.replace( + /PROTOKIT_SETTLEMENT_ENABLED=.*/g, + `PROTOKIT_SETTLEMENT_ENABLED=${answers.settlementEnabled}` + ); + } else { + envContent += `\nPROTOKIT_SETTLEMENT_ENABLED=${answers.settlementEnabled}\n`; + } + + const envFilePath = path.join(envDir, ".env"); + fs.writeFileSync(envFilePath, envContent); + + return true; + } catch (error) { + console.error(`Error copying .env file: ${error}}`); + return false; + } +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils/graphqlDocs.ts similarity index 98% rename from packages/cli/src/utils.ts rename to packages/cli/src/utils/graphqlDocs.ts index fca7a9956..cd896cd13 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils/graphqlDocs.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { spawn } from "child_process"; import fs from "fs"; import path from "path"; @@ -110,3 +112,4 @@ export async function generateGqlDocs(gqlUrl: string) { console.log("Docs generated successfully!"); cleanUp(generatedPath); } +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils/loadEnv.ts b/packages/cli/src/utils/loadEnv.ts new file mode 100644 index 000000000..3601c6cf7 --- /dev/null +++ b/packages/cli/src/utils/loadEnv.ts @@ -0,0 +1,65 @@ +/* eslint-disable no-console */ + +import path from "path"; +import fs from "fs"; + +import dotenv from "dotenv"; + +export type LoadEnvOptions = { + envPath?: string; + envVars?: Record; +}; + +export function loadEnvironmentVariables(options?: LoadEnvOptions) { + const cwd = process.cwd(); + + if (options?.envPath !== undefined) { + if (fs.existsSync(options.envPath)) { + dotenv.config({ path: options.envPath }); + console.log(`Loaded environment from ${options.envPath}`); + } else { + throw new Error(`Environment file not found at ${options.envPath}`); + } + } else { + const envPath = path.join(cwd, "./src/core/environments/development/.env"); + + if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); + console.log(`Loaded environment from ${envPath}`); + } else { + console.warn(`.env file not found at ${envPath}`); + } + } + if (options?.envVars !== undefined) { + Object.entries(options.envVars).forEach(([key, value]) => { + process.env[key] = value; + }); + console.log( + `Loaded ${Object.keys(options.envVars).length} environment variables from arguments` + ); + } +} + +export function getRequiredEnv(key: string): string { + const value = process.env[key]; + if (value === undefined) { + throw new Error( + `Required environment variable "${key}" is not defined. Please check your .env file or pass it as an argument.` + ); + } + return value; +} + +export function parseEnvArgs(args: string[]): Record { + const envVars: Record = {}; + + for (const arg of args) { + if (arg.includes("=")) { + const [key, value] = arg.split("=", 2); + envVars[key.trim()] = value.trim(); + } + } + + return envVars; +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils/loadUserModules.ts b/packages/cli/src/utils/loadUserModules.ts new file mode 100644 index 000000000..3ac32d5f1 --- /dev/null +++ b/packages/cli/src/utils/loadUserModules.ts @@ -0,0 +1,60 @@ +import path from "path"; + +import { + MandatoryProtocolModulesRecord, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; +import { RuntimeModulesRecord } from "@proto-kit/module"; +import { ModulesConfig } from "@proto-kit/common"; +import { Withdrawals } from "@proto-kit/library"; + +/* eslint-disable no-console */ + +type AppRuntimeModules = RuntimeModulesRecord & { + Withdrawals: typeof Withdrawals; +}; + +interface RuntimeModule { + modules: AppRuntimeModules; + config: ModulesConfig; +} + +interface ProtocolModule { + modules: ProtocolModulesRecord & MandatoryProtocolModulesRecord; + + config: ModulesConfig; + + settlementModules?: ProtocolModulesRecord; + + settlementModulesConfig?: ModulesConfig; +} + +interface LoadedModules { + runtime: RuntimeModule; + protocol: ProtocolModule; +} + +export async function loadUserModules(): Promise { + const cwd = process.cwd(); + + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const runtimeImport: { default: RuntimeModule } = await import( + path.join(cwd, "src/runtime") + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const protocolImport: { default: ProtocolModule } = await import( + path.join(cwd, "src/protocol") + ); + + return { + runtime: runtimeImport.default, + protocol: protocolImport.default, + }; + } catch (error) { + console.error("Failed to load runtime or protocol modules."); + throw error; + } +} + +/* eslint-enable no-console */ diff --git a/packages/explorer/src/config.ts b/packages/explorer/src/config.ts index 97529e1ab..47c6a2c6f 100644 --- a/packages/explorer/src/config.ts +++ b/packages/explorer/src/config.ts @@ -1,5 +1,6 @@ const config = { - INDEXER_URL: "http://localhost:8081/graphql", + INDEXER_URL: + process.env.NEXT_PUBLIC_INDEXER_URL ?? "http://localhost:8081/graphql", }; export default config; diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index 9c61fbdaf..bdb606004 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -25,6 +25,9 @@ import { IndexerNotifier, GeneratedResolverFactoryGraphqlModule, IndexBlockTask, + IndexBatchTask, + IndexPendingTxTask, + IndexSettlementTask, } from "@proto-kit/indexer"; import { PrismaRedisDatabase } from "@proto-kit/persistance"; import { BullQueue } from "@proto-kit/deployment"; @@ -115,6 +118,9 @@ export class DefaultModules { TaskQueue: BullQueue, TaskWorker: LocalTaskWorkerModule.from({ IndexBlockTask, + IndexPendingTxTask, + IndexBatchTask, + IndexSettlementTask, }), GraphqlServer, Graphql: GraphqlSequencerModule.from({ @@ -311,6 +317,9 @@ export class DefaultConfigs { ...taskQueueConfig, TaskWorker: { IndexBlockTask: {}, + IndexBatchTask: {}, + IndexPendingTxTask: {}, + IndexSettlementTask: {}, }, ...graphqlServerConfig, Graphql: { From f8b8fbfebf13392a403e27e06d23944aab63ab78 Mon Sep 17 00:00:00 2001 From: stanlou Date: Tue, 20 Jan 2026 03:25:43 +0100 Subject: [PATCH 06/14] feat: add default modules and configurations for sequencer presets --- package-lock.json | 4 + packages/stack/package.json | 6 +- packages/stack/src/index.ts | 4 + packages/stack/src/presets/app-chain/index.ts | 122 ++++ packages/stack/src/presets/config.ts | 193 ++++++ packages/stack/src/presets/modules/index.ts | 619 ++++++++++++++++++ packages/stack/src/presets/modules/types.ts | 86 +++ packages/stack/src/presets/modules/utils.ts | 381 +++++++++++ packages/stack/src/presets/sequencer/index.ts | 136 ++++ 9 files changed, 1550 insertions(+), 1 deletion(-) create mode 100644 packages/stack/src/presets/app-chain/index.ts create mode 100644 packages/stack/src/presets/config.ts create mode 100644 packages/stack/src/presets/modules/index.ts create mode 100644 packages/stack/src/presets/modules/types.ts create mode 100644 packages/stack/src/presets/modules/utils.ts create mode 100644 packages/stack/src/presets/sequencer/index.ts diff --git a/package-lock.json b/package-lock.json index f5626d39d..5c83cdcdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28777,6 +28777,8 @@ "version": "0.1.1-develop.833+397881ed", "license": "MIT", "dependencies": { + "@prisma/client": "^5.19.1", + "mina-fungible-token": "^1.1.0", "reflect-metadata": "^0.1.13" }, "devDependencies": { @@ -28786,9 +28788,11 @@ "@proto-kit/api": "*", "@proto-kit/common": "*", "@proto-kit/deployment": "*", + "@proto-kit/indexer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/persistance": "*", + "@proto-kit/processor": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", diff --git a/packages/stack/package.json b/packages/stack/package.json index 2851234e1..e983b9fe9 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -28,6 +28,8 @@ "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", + "@proto-kit/indexer": "*", + "@proto-kit/processor": "*", "o1js": "^2.10.0", "tsyringe": "^4.10.0" }, @@ -35,7 +37,9 @@ "@jest/globals": "^29.5.0" }, "dependencies": { - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "@prisma/client": "^5.19.1", + "mina-fungible-token": "^1.1.0" }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } diff --git a/packages/stack/src/index.ts b/packages/stack/src/index.ts index 35d49ee83..3c5da028c 100644 --- a/packages/stack/src/index.ts +++ b/packages/stack/src/index.ts @@ -1 +1,5 @@ export * from "./scripts/graphql/server"; +export * from "./presets/app-chain"; +export * from "./presets/sequencer"; +export * from "./presets/config"; +export * from "./presets/modules"; \ No newline at end of file diff --git a/packages/stack/src/presets/app-chain/index.ts b/packages/stack/src/presets/app-chain/index.ts new file mode 100644 index 000000000..48db076cc --- /dev/null +++ b/packages/stack/src/presets/app-chain/index.ts @@ -0,0 +1,122 @@ +import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; +import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; +import { + MandatoryProtocolModulesRecord, + Protocol, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; +import { + AppChain, + AppChainModulesRecord, + SequencerModulesRecord, +} from "@proto-kit/sequencer"; +import { DefaultModules, DefaultConfigs } from "../modules"; +import { DefaultSequencer, DefaultSequencerConfig } from "../sequencer"; + +export class DefaultAppChain { + static inmemory( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.inmemory({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } + static development( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.development({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } + static sovereign( + runtimeModules: RuntimeModulesRecord, + protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, + options?: { + overrideAppChainModules?: Partial; + settlementEnabled?: boolean; + overrideSequencerModules?: Partial; + } + ) { + return AppChain.from({ + Runtime: Runtime.from(runtimeModules), + Protocol: Protocol.from(protocolModules), + Sequencer: DefaultSequencer.sovereign({ + settlementEnabled: options?.settlementEnabled, + overrideModules: options?.overrideSequencerModules, + }), + ...DefaultModules.appChainBase(), + ...options?.overrideAppChainModules, + }); + } +} + +export class DefaultAppChainConfig { + static inmemory(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.inmemory({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } + static development(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.development({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } + static sovereign(options?: { + overrideAppChainConfig?: RecursivePartial>; + settlementEnabled?: boolean; + overrideSequencerConfig?: RecursivePartial>; + }) { + return { + Sequencer: DefaultSequencerConfig.sovereign({ + settlementEnabled: options?.settlementEnabled, + overrideConfig: options?.overrideSequencerConfig, + }), + ...DefaultConfigs.appChainBase(), + ...options?.overrideAppChainConfig, + }; + } +} diff --git a/packages/stack/src/presets/config.ts b/packages/stack/src/presets/config.ts new file mode 100644 index 000000000..835402150 --- /dev/null +++ b/packages/stack/src/presets/config.ts @@ -0,0 +1,193 @@ +export const inmemoryConfig = { + PROTOKIT_BLOCK_INTERVAL: 5000, + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHQL_HOST: "localhost", + PROTOKIT_GRAPHIQL_ENABLED: true, +}; + +export const developmentConfig = { + PROTOKIT_PROOFS_ENABLED: false, + PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + PROTOKIT_PRUNE_ON_STARTUP: false, + PROTOKIT_LOG_LEVEL: "INFO", + + PROTOKIT_BLOCK_INTERVAL: 30000, + PROTOKIT_SETTLEMENT_INTERVAL: 60000, + PROTOKIT_SETTLEMENT_ENABLED: true, + + REDIS_HOST: "localhost", + REDIS_PORT: 6379, + REDIS_PASSWORD: "password", + + DATABASE_URL: + "postgresql://admin:password@localhost:5432/protokit?schema=public", + + INDEXER_DATABASE_URL: + "postgresql://admin:password@localhost:5433/protokit-indexer?schema=public", + + PROCESSOR_DATABASE_URL: + "postgresql://admin:password@localhost:5434/protokit-processor?schema=public", + + PROTOKIT_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHIQL_ENABLED: true, + + PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + + PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "0.0.0.0", + + MINA_NETWORK: "lightnet", + MINA_NODE_GRAPHQL_HOST: "http://localhost", + MINA_NODE_GRAPHQL_PORT: 8083, + + MINA_ARCHIVE_GRAPHQL_HOST: "http://localhost", + MINA_ARCHIVE_GRAPHQL_PORT: 8085, + + MINA_ACCOUNT_MANAGER_HOST: "http://localhost", + MINA_ACCOUNT_MANAGER_PORT: 8084, + MINA_EXPLORER_PORT: 3001, + + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", + + PROTOKIT_SEQUENCER_PRIVATE_KEY: + "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + PROTOKIT_SEQUENCER_PUBLIC_KEY: + "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", + PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", + + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", + PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", + + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", + PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", + + PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY: + "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", + + PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY: + "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", + + PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY: + "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", + + TEST_ACCOUNT_1_PRIVATE_KEY: + "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", + TEST_ACCOUNT_1_PUBLIC_KEY: + "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", + + OPEN_TELEMETRY_TRACING_ENABLED: true, + OPEN_TELEMETRY_TRACING_URL: "http://localhost:4318", + + OPEN_TELEMETRY_METRICS_ENABLED: true, + OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", + OPEN_TELEMETRY_METRICS_PORT: 4320, + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + +}; + +export const sovereignConfig = { + PROTOKIT_BLOCK_INTERVAL: 10000, + PROTOKIT_SETTLEMENT_INTERVAL: 30000, + PROTOKIT_SETTLEMENT_ENABLED: true, + + PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, + PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + + PROTOKIT_PRUNE_ON_STARTUP: false, + PROTOKIT_LOG_LEVEL: "INFO", + + REDIS_HOST: "redis", + REDIS_PORT: 6379, + REDIS_PASSWORD: "password", + + DATABASE_URL: + "postgresql://admin:password@postgres:5432/protokit?schema=public", + + INDEXER_DATABASE_URL: + "postgresql://admin:password@indexer-postgres:5432/protokit-indexer?schema=public", + + PROCESSOR_DATABASE_URL: + "postgresql://admin:password@processor-postgres:5432/protokit-processor?schema=public", + + PROTOKIT_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_GRAPHQL_PORT: 8080, + PROTOKIT_GRAPHIQL_ENABLED: true, + + PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + + PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", + PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "indexer", + + MINA_NETWORK: "lightnet", + MINA_NODE_GRAPHQL_HOST: "http://lightnet", + MINA_NODE_GRAPHQL_PORT: 8080, + + MINA_ARCHIVE_GRAPHQL_HOST: "http://lightnet", + MINA_ARCHIVE_GRAPHQL_PORT: 8282, + + MINA_ACCOUNT_MANAGER_HOST: "http://lightnet", + MINA_ACCOUNT_MANAGER_PORT: 8084, + MINA_EXPLORER_PORT: 3001, + + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", + PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", + + PROTOKIT_SEQUENCER_PRIVATE_KEY: + "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + PROTOKIT_SEQUENCER_PUBLIC_KEY: + "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", + PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", + + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", + PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", + + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", + PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", + + TEST_ACCOUNT_1_PRIVATE_KEY: + "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", + TEST_ACCOUNT_1_PUBLIC_KEY: + "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", + + OPEN_TELEMETRY_TRACING_ENABLED: true, + OPEN_TELEMETRY_TRACING_URL: "http://otel-collector:4317", + + OPEN_TELEMETRY_METRICS_ENABLED: true, + OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", + OPEN_TELEMETRY_METRICS_PORT: 4320, + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + +}; \ No newline at end of file diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts new file mode 100644 index 000000000..4561e14e1 --- /dev/null +++ b/packages/stack/src/presets/modules/index.ts @@ -0,0 +1,619 @@ +import { + VanillaGraphqlModules, + GraphqlSequencerModule, + GraphqlServer, + OpenTelemetryServer, +} from "@proto-kit/api"; +import { + PrivateMempool, + SequencerModulesRecord, + TimedBlockTrigger, + BlockProducerModule, + SequencerStartupModule, + LocalTaskWorkerModule, + VanillaTaskWorkerModules, + MinaBaseLayer, + ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + DatabasePruneModule, + InMemoryDatabase, + LocalTaskQueue, +} from "@proto-kit/sequencer"; +import { + IndexerNotifier, + GeneratedResolverFactoryGraphqlModule, + IndexBlockTask, +} from "@proto-kit/indexer"; +import { PrivateKey } from "o1js"; +import { PrismaRedisDatabase } from "@proto-kit/persistance"; +import { BullQueue } from "@proto-kit/deployment"; +import { + TimedProcessorTrigger, + BlockFetching, + HandlersExecutor, + ResolverFactoryGraphqlModule, + HandlersRecord, + BasePrismaClient, +} from "@proto-kit/processor"; +import { + BlockStorageNetworkStateModule, + InMemoryTransactionSender, + StateServiceQueryModule, +} from "@proto-kit/sdk"; +import { AppChainModulesRecord } from "@proto-kit/sequencer"; +import { + buildCustomTokenConfig, + buildSettlementTokenConfig, + definePreset, + orderModulesByDependencies, + parseApiEnv, + parseCoreEnv, + parseMetricsEnv, + parseSettlementEnv, + parseIndexerEnv, + parseProcessorEnv, + parseDatabaseEnv, + parseDatabasePruneEnv, + parseGraphqlServerEnv, + parseRedisEnv, + resolveEnv, +} from "./utils"; +import { NonEmptyArray } from "type-graphql"; +import { + Environment, + ModuleOverrides, + ApiEnv, + ConfigOverrides, + CoreEnv, + MetricsEnv, + IndexerEnv, + ProcessorEnv, + SettlementEnv, + DatabaseEnv, + TaskQueueEnv, + DatabasePruneEnv, + GraphqlServerEnv, + RedisEnv, +} from "./types"; + +export class DefaultModules { + static api(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + GraphqlServer, + Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), + }, + options?.overrides + ); + } + static core(options?: { + overrides?: ModuleOverrides; + settlementEnabled?: boolean; + }): SequencerModulesRecord { + return definePreset( + { + ...DefaultModules.api(), + Mempool: PrivateMempool, + BlockProducerModule, + BlockTrigger: TimedBlockTrigger, + SequencerStartupModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.withoutSettlement() + ), + ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), + }, + options?.overrides + ); + } + static metrics(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + OpenTelemetryServer, + }, + options?.overrides + ); + } + static settlement(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + BaseLayer: MinaBaseLayer, + FeeStrategy: ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + }, + options?.overrides + ); + } + static sequencerIndexer(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + IndexerNotifier, + }, + options?.overrides + ); + } + static indexer(options?: { overrides?: ModuleOverrides }) { + return definePreset( + { + Database: PrismaRedisDatabase, + TaskQueue: BullQueue, + TaskWorker: LocalTaskWorkerModule.from({ + IndexBlockTask, + }), + GraphqlServer, + Graphql: GraphqlSequencerModule.from({ + GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, + }), + }, + options?.overrides + ); + } + static processor( + resolvers: NonEmptyArray, + handlers: HandlersRecord, + options?: { overrides?: ModuleOverrides } + ) { + return definePreset( + { + GraphqlServer, + GraphqlSequencerModule: GraphqlSequencerModule.from({ + ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), + }), + HandlersExecutor: HandlersExecutor.from(handlers), + BlockFetching, + Trigger: TimedProcessorTrigger, + }, + options?.overrides + ); + } + static database(options?: { + overrides?: ModuleOverrides; + preset?: Environment; + }): SequencerModulesRecord { + const preset = options?.preset ?? "inmemory"; + + return definePreset( + { + Database: + preset === "inmemory" ? InMemoryDatabase : PrismaRedisDatabase, + }, + options?.overrides + ); + } + static taskQueue(options?: { + overrides?: ModuleOverrides; + preset?: Environment; + }): SequencerModulesRecord { + const preset = options?.preset ?? "inmemory"; + return definePreset( + { + TaskQueue: preset === "inmemory" ? LocalTaskQueue : BullQueue, + }, + options?.overrides + ); + } + static databasePrune(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + DatabasePruneModule, + }, + options?.overrides + ); + } + static worker(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + TaskQueue: BullQueue, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + }, + options?.overrides + ); + } + static appChainBase(options?: { + overrides?: Partial; + }): AppChainModulesRecord { + return definePreset( + { + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + }, + options?.overrides + ) as AppChainModulesRecord; + } + static settlementScript(options?: { + overrides?: ModuleOverrides; + }): SequencerModulesRecord { + return definePreset( + { + ...DefaultModules.settlement(), + Mempool: PrivateMempool, + TaskQueue: LocalTaskQueue, + SequencerStartupModule, + }, + options?.overrides + ); + } + static ordered(modules: any) { + return orderModulesByDependencies(modules); + } +} +export class DefaultConfigs { + static api(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset( + { + Graphql: VanillaGraphqlModules.defaultConfig(), + GraphqlServer: DefaultConfigs.graphqlServer({ + type: "protokit", + preset: options?.preset, + envs: options?.envs, + }), + }, + options?.overrides + ); + } + static core(options?: { + preset?: Environment; + envs?: Partial & Partial & Partial; + overrides?: ConfigOverrides; + settlementEnabled?: boolean; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseCoreEnv( + { ...config, ...options?.envs }, + options?.settlementEnabled + ); + const apiConfig = DefaultConfigs.api({ + preset: options?.preset, + envs: options?.envs, + }); + const settlementConfig = options?.settlementEnabled + ? DefaultConfigs.settlement({ + preset: options?.preset, + envs: options?.envs, + }) + : {}; + const blockTriggerConfig = { + blockInterval: parsed.blockInterval, + produceEmptyBlocks: true, + ...(options?.settlementEnabled + ? { + settlementInterval: parsed.settlementInterval, + settlementTokenConfig: buildSettlementTokenConfig( + parsed.minaBridgeKey!, + buildCustomTokenConfig( + parsed.customTokenKey, + parsed.customTokenBridgeKey + ) + ), + } + : { settlementTokenConfig: {} }), + }; + + return definePreset( + { + ...apiConfig, + Mempool: {}, + BlockProducerModule: {}, + BlockTrigger: blockTriggerConfig, + SequencerStartupModule: {}, + LocalTaskWorkerModule: VanillaGraphqlModules.defaultConfig(), + ...settlementConfig, + }, + options?.overrides + ); + } + static metrics(options?: { + preset?: Environment; + envs?: MetricsEnv; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const configs = resolveEnv(options?.preset, options?.envs); + const parsed = parseMetricsEnv(configs); + return definePreset( + { + OpenTelemetryServer: { + metrics: { + enabled: parsed.metricsEnabled, + prometheus: { + host: parsed.metricsHost, + port: parsed.metricsPort, + appendTimestamp: true, + }, + nodeScrapeInterval: parsed.metricsScrapingFrequency, + }, + tracing: { + enabled: parsed.tracingEnabled, + otlp: { + url: parsed.tracingUrl, + }, + }, + }, + }, + options?.overrides + ); + } + static sequencerIndexer(options?: { + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset({ IndexerNotifier: {} }, options?.overrides); + } + static indexer(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseIndexerEnv(config); + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + const databaseConfig = DefaultConfigs.database({ + preset: options?.preset, + envs: options?.envs, + }); + const graphqlServerConfig = DefaultConfigs.graphqlServer({ + type: "indexer", + preset: options?.preset, + envs: options?.envs, + }); + + return definePreset( + { + ...databaseConfig, + TaskQueue: redisConfig.TaskQueue, + TaskWorker: { + IndexBlockTask: {}, + }, + ...graphqlServerConfig, + Graphql: { + GeneratedResolverFactory: {}, + }, + }, + options?.overrides + ); + } + static processor(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseProcessorEnv(config); + const graphqlServerConfig = DefaultConfigs.graphqlServer({ + type: "processor", + preset: options?.preset, + envs: options?.envs, + }); + return definePreset( + { + HandlersExecutor: {}, + BlockFetching: { + url: `http://${parsed.processorIndexerGraphqlHost}:${parsed.indexerGraphqlPort}`, + }, + Trigger: { + interval: (parsed.blockInterval ?? 5000) / 5, + }, + ...graphqlServerConfig, + GraphqlSequencerModule: { + ResolverFactory: {}, + }, + }, + options?.overrides + ); + } + static settlement(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseSettlementEnv(config); + + return definePreset( + { + BaseLayer: { + network: { + type: parsed.network, + graphql: parsed.graphql, + archive: parsed.archive, + accountManager: parsed.accountManager, + }, + }, + SettlementModule: { + feepayer: PrivateKey.fromBase58(parsed.sequencerPrivateKey), + keys: { + settlement: PrivateKey.fromBase58( + parsed.settlementContractPrivateKey + ), + dispatch: PrivateKey.fromBase58( + parsed.dispatcherContractPrivateKey + ), + minaBridge: PrivateKey.fromBase58( + parsed.minaBridgeContractPrivateKey + ), + }, + }, + FeeStrategy: {}, + BatchProducerModule: {}, + }, + options?.overrides + ); + } + static database(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const preset = options?.preset ?? "inmemory"; + if (preset === "inmemory") { + return { Database: definePreset({}, options?.overrides) }; + } + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseDatabaseEnv(config); + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + return { + Database: definePreset( + { + ...redisConfig, + prisma: { + connection: parsed.databaseUrl, + }, + }, + options?.overrides + ), + }; + } + static taskQueue(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const preset = options?.preset ?? "inmemory"; + if (preset === "inmemory") { + return { + TaskQueue: definePreset({}, options?.overrides), + }; + } + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + }); + + return { TaskQueue: definePreset(redisConfig, options?.overrides) }; + } + static databasePrune(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseDatabasePruneEnv(config); + + return { + DatabasePruneModule: definePreset( + { + pruneOnStartup: parsed.pruneOnStartup, + }, + options?.overrides + ), + }; + } + static graphqlServer(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + type?: "indexer" | "processor" | "protokit"; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseGraphqlServerEnv(config, options?.type); + + return definePreset( + { + port: parsed.graphqlPort, + host: parsed.graphqlHost, + graphiql: parsed.graphiqlEnabled, + }, + options?.overrides + ); + } + static redis(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const config = resolveEnv(options?.preset, options?.envs); + const parsed = parseRedisEnv(config); + + return { + redis: definePreset( + { + host: parsed.redisHost, + port: parsed.redisPort, + password: parsed.redisPassword, + }, + options?.overrides + ), + }; + } + static appChainBase(options?: { + overrides?: ConfigOverrides; + }): ConfigOverrides { + return definePreset( + { + QueryTransportModule: {}, + NetworkStateTransportModule: {}, + TransactionSender: {}, + }, + options?.overrides + ); + } + static worker(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const redisConfig = DefaultConfigs.redis({ + preset: options?.preset, + envs: options?.envs, + overrides: { + db: 1, + }, + }); + + return definePreset( + { + TaskQueue: redisConfig, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }, + options?.overrides + ); + } + static settlementScript(options?: { + preset?: Environment; + envs?: Partial; + overrides?: ConfigOverrides; + }): ConfigOverrides { + const settlementConfig = DefaultConfigs.settlement({ + preset: options?.preset, + envs: options?.envs, + }); + return definePreset( + { + ...settlementConfig, + SequencerStartupModule: {}, + TaskQueue: { + simulatedDuration: 0, + }, + Mempool: {}, + }, + options?.overrides + ); + } +} diff --git a/packages/stack/src/presets/modules/types.ts b/packages/stack/src/presets/modules/types.ts new file mode 100644 index 000000000..712511168 --- /dev/null +++ b/packages/stack/src/presets/modules/types.ts @@ -0,0 +1,86 @@ +import { RecursivePartial, ModulesConfig } from "@proto-kit/common"; +import { SequencerModulesRecord } from "@proto-kit/sequencer"; + +export type ModuleOverrides = Partial; +export type ConfigOverrides = RecursivePartial>; +export type Environment = "inmemory" | "development" | "sovereign"; +export type ApiEnv = { + PROTOKIT_GRAPHQL_PORT: number | string; + PROTOKIT_GRAPHQL_HOST: string; + PROTOKIT_GRAPHIQL_ENABLED: boolean | string; +}; +export type CoreEnv = { + PROTOKIT_BLOCK_INTERVAL: number | string; + PROTOKIT_SETTLEMENT_INTERVAL?: number | string; + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY?: string; + PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY?: string; + PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY?: string; +}; +export type MetricsEnv = { + OPEN_TELEMETRY_METRICS_ENABLED: boolean | string; + OPEN_TELEMETRY_METRICS_HOST: string; + OPEN_TELEMETRY_METRICS_PORT: number | string; + OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: number | string; + OPEN_TELEMETRY_TRACING_ENABLED: boolean | string; + OPEN_TELEMETRY_TRACING_URL: string; +}; +export type SettlementEnv = { + MINA_NETWORK: string; + MINA_NODE_GRAPHQL_HOST: string; + MINA_NODE_GRAPHQL_PORT: number | string; + MINA_ARCHIVE_GRAPHQL_HOST: string; + MINA_ARCHIVE_GRAPHQL_PORT: number | string; + MINA_ACCOUNT_MANAGER_HOST: string; + MINA_ACCOUNT_MANAGER_PORT: number | string; + PROTOKIT_SEQUENCER_PRIVATE_KEY: string; + PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: string; + PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: string; + PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: string; +}; +export type IndexerEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; + INDEXER_DATABASE_URL: string; + PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; + PROTOKIT_INDEXER_GRAPHQL_HOST: string; + PROTOKIT_INDEXER_GRAPHIQL_ENABLED: boolean | string; +}; +export type ProcessorEnv = { + PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: string; + PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; + PROTOKIT_BLOCK_INTERVAL: number | string; + PROTOKIT_PROCESSOR_GRAPHQL_HOST: string; + PROTOKIT_PROCESSOR_GRAPHQL_PORT: number | string; + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: boolean | string; +}; +export type DatabaseEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; + DATABASE_URL: string; +}; +export type TaskQueueEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; +}; +export type DatabasePruneEnv = { + PRUNE_ON_STARTUP?: boolean | string; +}; +export type GraphqlServerEnv = { + PROTOKIT_GRAPHQL_PORT?: number | string; + PROTOKIT_GRAPHQL_HOST?: string; + PROTOKIT_GRAPHIQL_ENABLED?: boolean | string; + PROTOKIT_INDEXER_GRAPHQL_HOST?: string; + PROTOKIT_INDEXER_GRAPHQL_PORT?: number | string; + PROTOKIT_INDEXER_GRAPHIQL_ENABLED?: boolean | string; + PROTOKIT_PROCESSOR_GRAPHQL_HOST?: string; + PROTOKIT_PROCESSOR_GRAPHQL_PORT?: number | string; + PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED?: boolean | string; +}; +export type RedisEnv = { + REDIS_HOST: string; + REDIS_PORT: number | string; + REDIS_PASSWORD: string; +}; diff --git a/packages/stack/src/presets/modules/utils.ts b/packages/stack/src/presets/modules/utils.ts new file mode 100644 index 000000000..679b35165 --- /dev/null +++ b/packages/stack/src/presets/modules/utils.ts @@ -0,0 +1,381 @@ +import { PrivateKey, TokenId } from "o1js"; +import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; +import { FungibleToken } from "mina-fungible-token"; +import { assertDefined } from "@proto-kit/common"; +import { + ApiEnv, + CoreEnv, + DatabaseEnv, + DatabasePruneEnv, + Environment, + GraphqlServerEnv, + IndexerEnv, + MetricsEnv, + ProcessorEnv, + RedisEnv, + SettlementEnv, + TaskQueueEnv, +} from "./types"; + +function ensureDefined(obj: Record, keys: string[]) { + keys.forEach((k) => assertDefined(obj[k], `${k} is required`)); +} +export function definePreset( + base: T, + overrides?: Partial +): T { + return { + ...base, + ...overrides, + }; +} +const MODULE_DEPENDENCIES: Record = { + Database: [], + TaskQueue: [], + OpenTelemetryServer: [], + BaseLayer: [], + FeeStrategy: [], + + Protocol: [], + Mempool: ["Database"], + BlockProducerModule: ["Database"], + + LocalTaskWorkerModule: ["TaskQueue"], + TaskWorker: ["TaskQueue"], + SequencerStartupModule: ["Database"], + BatchProducerModule: ["Database"], + BlockTrigger: ["BlockProducerModule", "BatchProducerModule"], + + SettlementModule: ["Database", "BaseLayer", "FeeStrategy"], + + DatabasePruneModule: ["Database"], + + Graphql: [], + GraphqlServer: [], + + IndexBlockTask: ["Database", "TaskQueue"], + IndexerNotifier: ["Database", "TaskQueue"], + GeneratedResolverFactory: [], + + BlockFetching: [], + HandlersExecutor: [], + Trigger: ["BlockFetching", "HandlersExecutor"], + ResolverFactory: [], +}; +export function orderModulesByDependencies( + modules: Record +): Record { + const moduleSet = new Set(Object.keys(modules)); + const ordered: Record = {}; + const visited = new Set(); + + function visit(name: string) { + if (!moduleSet.has(name) || visited.has(name)) return; + + const deps = MODULE_DEPENDENCIES[name] ?? []; + for (const dep of deps) { + visit(dep); + } + + visited.add(name); + ordered[name] = modules[name]; + } + + for (const name of moduleSet) { + visit(name); + } + + return ordered; +} +export function resolveEnv( + preset: Environment = "inmemory", + envs?: Partial +): T { + return { + ...getConfigs(preset), + ...envs, + } as T; +} +export function buildCustomTokenConfig( + customTokenPrivateKey?: string, + customTokenBridgePrivateKey?: string +): Record { + if (!customTokenPrivateKey || !customTokenBridgePrivateKey) { + return {}; + } + const pk = PrivateKey.fromBase58(customTokenPrivateKey); + const tokenId = TokenId.derive(pk.toPublicKey()).toString(); + return { + [tokenId]: { + bridgingContractPrivateKey: PrivateKey.fromBase58( + customTokenBridgePrivateKey + ), + tokenOwner: FungibleToken, + tokenOwnerPrivateKey: customTokenPrivateKey, + }, + }; +} +export function buildSettlementTokenConfig( + bridgePrivateKey: string, + customTokens: Record = {} +): Record { + return { + "1": { + bridgingContractPrivateKey: PrivateKey.fromBase58(bridgePrivateKey), + }, + ...customTokens, + }; +} +export function getConfigs(preset: Environment) { + switch (preset) { + case "development": + return developmentConfig; + case "sovereign": + return sovereignConfig; + case "inmemory": + default: + return inmemoryConfig; + } +} + +export function parseApiEnv(envs: ApiEnv): { + graphqlPort: number; + graphqlHost: string; + graphiqlEnabled: boolean; +} { + ensureDefined(envs, [ + "PROTOKIT_GRAPHIQL_ENABLED", + "PROTOKIT_GRAPHQL_HOST", + "PROTOKIT_GRAPHQL_PORT", + ]); + return { + graphqlPort: Number(envs.PROTOKIT_GRAPHQL_PORT), + graphqlHost: envs.PROTOKIT_GRAPHQL_HOST, + graphiqlEnabled: Boolean(envs.PROTOKIT_GRAPHIQL_ENABLED), + }; +} +export function parseCoreEnv( + envs: CoreEnv, + settlementEnabled?: boolean +): { + blockInterval: number; + settlementInterval?: number; + minaBridgeKey?: string; + customTokenKey?: string; + customTokenBridgeKey?: string; +} { + ensureDefined(envs, ["PROTOKIT_BLOCK_INTERVAL"]); + if (settlementEnabled) { + ensureDefined(envs, [ + "PROTOKIT_SETTLEMENT_INTERVAL", + "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + ]); + if (envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY) { + ensureDefined(envs, ["PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY"]); + } + } + return { + blockInterval: Number(envs.PROTOKIT_BLOCK_INTERVAL), + settlementInterval: Number(envs.PROTOKIT_SETTLEMENT_INTERVAL), + minaBridgeKey: envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY, + customTokenKey: envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, + customTokenBridgeKey: envs.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, + }; +} +export function parseMetricsEnv(envs: MetricsEnv): { + metricsEnabled: boolean; + metricsHost?: string; + metricsPort?: number; + metricsScrapingFrequency?: number; + tracingEnabled: boolean; + tracingUrl?: string; +} { + ensureDefined(envs as Record, [ + "OPEN_TELEMETRY_METRICS_ENABLED", + "OPEN_TELEMETRY_TRACING_ENABLED", + "OPEN_TELEMETRY_TRACING_URL", + "OPEN_TELEMETRY_METRICS_HOST", + "OPEN_TELEMETRY_METRICS_PORT", + "OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY", + ]); + return { + metricsEnabled: Boolean(envs.OPEN_TELEMETRY_METRICS_ENABLED), + metricsHost: envs.OPEN_TELEMETRY_METRICS_HOST, + metricsPort: Number(envs.OPEN_TELEMETRY_METRICS_PORT), + metricsScrapingFrequency: Number( + envs.OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY + ), + tracingEnabled: Boolean(envs.OPEN_TELEMETRY_TRACING_ENABLED), + tracingUrl: envs.OPEN_TELEMETRY_TRACING_URL, + }; +} +export function parseSettlementEnv(envs: SettlementEnv): { + network: string; + graphql: string; + archive: string; + accountManager: string; + sequencerPrivateKey: string; + settlementContractPrivateKey: string; + dispatcherContractPrivateKey: string; + minaBridgeContractPrivateKey: string; +} { + ensureDefined(envs as Record, [ + "MINA_ACCOUNT_MANAGER_HOST", + "MINA_ACCOUNT_MANAGER_PORT", + "MINA_ARCHIVE_GRAPHQL_HOST", + "MINA_ARCHIVE_GRAPHQL_PORT", + "MINA_NODE_GRAPHQL_HOST", + "MINA_NODE_GRAPHQL_PORT", + "MINA_NETWORK", + "PROTOKIT_SEQUENCER_PRIVATE_KEY", + "PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY", + "PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY", + "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + ]); + + return { + network: envs.MINA_NETWORK!, + graphql: `${envs.MINA_NODE_GRAPHQL_HOST}:${envs.MINA_NODE_GRAPHQL_PORT}/graphql`, + archive: `${envs.MINA_ARCHIVE_GRAPHQL_HOST}:${envs.MINA_ARCHIVE_GRAPHQL_PORT}`, + accountManager: `${envs.MINA_ACCOUNT_MANAGER_HOST}:${envs.MINA_ACCOUNT_MANAGER_PORT}`, + sequencerPrivateKey: envs.PROTOKIT_SEQUENCER_PRIVATE_KEY!, + settlementContractPrivateKey: + envs.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY!, + dispatcherContractPrivateKey: + envs.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY!, + minaBridgeContractPrivateKey: + envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY!, + }; +} +export function parseIndexerEnv(envs: IndexerEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; + indexerDatabaseUrl?: string; + graphqlPort?: number; + graphqlHost?: string; + graphiqlEnabled?: boolean; +} { + ensureDefined(envs as Record, [ + "REDIS_HOST", + "REDIS_PORT", + "REDIS_PASSWORD", + "INDEXER_DATABASE_URL", + "PROTOKIT_INDEXER_GRAPHQL_PORT", + "PROTOKIT_INDEXER_GRAPHQL_HOST", + "PROTOKIT_INDEXER_GRAPHIQL_ENABLED", + ]); + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + indexerDatabaseUrl: envs.INDEXER_DATABASE_URL, + graphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT + ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) + : undefined, + graphqlHost: envs.PROTOKIT_INDEXER_GRAPHQL_HOST, + graphiqlEnabled: envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED + ? Boolean(envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED) + : undefined, + }; +} +export function parseProcessorEnv(envs: ProcessorEnv): { + processorIndexerGraphqlHost?: string; + indexerGraphqlPort?: number; + blockInterval?: number; + processorGraphqlHost?: string; + processorGraphqlPort?: number; + processorGraphiqlEnabled?: boolean; +} { + ensureDefined(envs as Record, [ + "PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST", + "PROTOKIT_INDEXER_GRAPHQL_PORT", + "PROTOKIT_BLOCK_INTERVAL", + "PROTOKIT_PROCESSOR_GRAPHQL_HOST", + "PROTOKIT_PROCESSOR_GRAPHQL_PORT", + "PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED", + ]); + return { + processorIndexerGraphqlHost: envs.PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST, + indexerGraphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT + ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) + : undefined, + blockInterval: envs.PROTOKIT_BLOCK_INTERVAL + ? Number(envs.PROTOKIT_BLOCK_INTERVAL) + : undefined, + processorGraphqlHost: envs.PROTOKIT_PROCESSOR_GRAPHQL_HOST, + processorGraphqlPort: envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT + ? Number(envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT) + : undefined, + processorGraphiqlEnabled: envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED + ? Boolean(envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED) + : undefined, + }; +} +export function parseDatabaseEnv(envs: DatabaseEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; + databaseUrl?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + databaseUrl: envs.DATABASE_URL, + }; +} +export function parseTaskQueueEnv(envs: TaskQueueEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + }; +} +export function parseDatabasePruneEnv(envs: DatabasePruneEnv): { + pruneOnStartup: boolean; +} { + return { + pruneOnStartup: + envs.PRUNE_ON_STARTUP === "true" || envs.PRUNE_ON_STARTUP === true, + }; +} +export function parseGraphqlServerEnv( + envs: GraphqlServerEnv, + type: "protokit" | "indexer" | "processor" = "protokit" +): { + graphqlPort?: number; + graphqlHost?: string; + graphiqlEnabled?: boolean; +} { + const prefix = + type === "indexer" + ? "PROTOKIT_INDEXER" + : type === "processor" + ? "PROTOKIT_PROCESSOR" + : "PROTOKIT"; + return { + graphqlPort: envs[`${prefix}_GRAPHQL_PORT`] + ? Number(envs[`${prefix}_GRAPHQL_PORT`]) + : undefined, + graphqlHost: envs[`${prefix}_GRAPHQL_HOST`], + graphiqlEnabled: envs[`${prefix}_GRAPHIQL_ENABLED`] + ? Boolean(envs[`${prefix}_GRAPHIQL_ENABLED`]) + : undefined, + }; +} +export function parseRedisEnv(envs: RedisEnv): { + redisHost?: string; + redisPort?: number; + redisPassword?: string; +} { + return { + redisHost: envs.REDIS_HOST, + redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, + redisPassword: envs.REDIS_PASSWORD, + }; +} diff --git a/packages/stack/src/presets/sequencer/index.ts b/packages/stack/src/presets/sequencer/index.ts new file mode 100644 index 000000000..1fd6919fb --- /dev/null +++ b/packages/stack/src/presets/sequencer/index.ts @@ -0,0 +1,136 @@ +import { SequencerModulesRecord, Sequencer } from "@proto-kit/sequencer"; +import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; +import { DefaultConfigs, DefaultModules } from "../modules"; +import { definePreset } from "../modules/utils"; + +export class DefaultSequencer { + static inmemory(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + ...DefaultModules.database(), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.taskQueue(), + ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } + static development(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + // ordering of the modules matters due to dependency resolution + ...DefaultModules.database({ preset: "development" }), + ...DefaultModules.databasePrune(), + ...DefaultModules.metrics(), + ...DefaultModules.taskQueue({ preset: "development" }), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.sequencerIndexer(), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } + static sovereign(options?: { + overrideModules?: Partial; + settlementEnabled?: boolean; + }) { + const modules = DefaultModules.ordered( + definePreset( + { + // ordering of the modules matters due to dependency resolution + ...DefaultModules.database(), + ...DefaultModules.taskQueue(), + ...DefaultModules.core({ + settlementEnabled: options?.settlementEnabled, + }), + ...DefaultModules.sequencerIndexer(), + ...DefaultModules.metrics(), + ...DefaultModules.databasePrune(), + }, + options?.overrideModules + ) + ); + + return Sequencer.from(modules); + } +} +export class DefaultSequencerConfig { + static inmemory(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ settlementEnabled: options?.settlementEnabled }), + ...DefaultConfigs.database({ preset: "inmemory" }), + ...DefaultConfigs.taskQueue({ preset: "inmemory" }), + ...options?.overrideConfig, + }; + } + static development(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ + settlementEnabled: options?.settlementEnabled, + preset: "development", + }), + ...DefaultConfigs.sequencerIndexer(), + ...DefaultConfigs.metrics({ preset: "development" }), + ...DefaultConfigs.databasePrune({ preset: "development" }), + ...DefaultConfigs.taskQueue({ + preset: "development", + overrides: { + ...DefaultConfigs.redis({ + preset: "development", + overrides: { db: 1 }, + }), + }, + }), + ...DefaultConfigs.database({ preset: "development" }), + ...options?.overrideConfig, + }; + } + static sovereign(options?: { + overrideConfig?: RecursivePartial>; + settlementEnabled?: boolean; + }) { + return { + ...DefaultConfigs.core({ + settlementEnabled: options?.settlementEnabled, + preset: "sovereign", + }), + ...DefaultConfigs.sequencerIndexer(), + ...DefaultConfigs.metrics({ preset: "sovereign" }), + ...DefaultConfigs.databasePrune({ preset: "sovereign" }), + ...DefaultConfigs.taskQueue({ + preset: "sovereign", + overrides: { + ...DefaultConfigs.redis({ + preset: "sovereign", + overrides: { db: 1 }, + }), + }, + }), + ...DefaultConfigs.database({ preset: "sovereign" }), + ...options?.overrideConfig, + }; + } +} From bd8862197f4b5760db90b8865835c0b3514a3edc Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 05:44:12 +0100 Subject: [PATCH 07/14] use default modules --- packages/processor/src/index.ts | 1 + packages/stack/package.json | 4 +- packages/stack/src/index.ts | 6 +- packages/stack/src/presets/app-chain/index.ts | 122 --- packages/stack/src/presets/config.ts | 235 +++--- packages/stack/src/presets/modules/index.ts | 779 ++++++++---------- packages/stack/src/presets/modules/types.ts | 123 ++- packages/stack/src/presets/modules/utils.ts | 369 +-------- packages/stack/src/presets/sequencer/index.ts | 136 --- 9 files changed, 533 insertions(+), 1242 deletions(-) delete mode 100644 packages/stack/src/presets/app-chain/index.ts delete mode 100644 packages/stack/src/presets/sequencer/index.ts diff --git a/packages/processor/src/index.ts b/packages/processor/src/index.ts index b6bc05ff7..918e46e03 100644 --- a/packages/processor/src/index.ts +++ b/packages/processor/src/index.ts @@ -1,6 +1,7 @@ export * from "./Processor"; export * from "./ProcessorModule"; export * from "./handlers/HandlersExecutor"; +export * from "./handlers/BasePrismaClient"; export * from "./storage/Database"; export * from "./triggers/TimedProcessorTrigger"; export * from "./indexer/BlockFetching"; diff --git a/packages/stack/package.json b/packages/stack/package.json index e983b9fe9..dc3c50ad3 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -39,7 +39,9 @@ "dependencies": { "reflect-metadata": "^0.1.13", "@prisma/client": "^5.19.1", - "mina-fungible-token": "^1.1.0" + "mina-fungible-token": "^1.1.0", + "type-graphql": "2.0.0-rc.2" + }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } diff --git a/packages/stack/src/index.ts b/packages/stack/src/index.ts index 3c5da028c..fb31453cc 100644 --- a/packages/stack/src/index.ts +++ b/packages/stack/src/index.ts @@ -1,5 +1,5 @@ export * from "./scripts/graphql/server"; -export * from "./presets/app-chain"; -export * from "./presets/sequencer"; export * from "./presets/config"; -export * from "./presets/modules"; \ No newline at end of file +export * from "./presets/modules/types"; +export * from "./presets/modules/utils"; +export * from "./presets/modules"; diff --git a/packages/stack/src/presets/app-chain/index.ts b/packages/stack/src/presets/app-chain/index.ts deleted file mode 100644 index 48db076cc..000000000 --- a/packages/stack/src/presets/app-chain/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; -import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; -import { - MandatoryProtocolModulesRecord, - Protocol, - ProtocolModulesRecord, -} from "@proto-kit/protocol"; -import { - AppChain, - AppChainModulesRecord, - SequencerModulesRecord, -} from "@proto-kit/sequencer"; -import { DefaultModules, DefaultConfigs } from "../modules"; -import { DefaultSequencer, DefaultSequencerConfig } from "../sequencer"; - -export class DefaultAppChain { - static inmemory( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.inmemory({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } - static development( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.development({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } - static sovereign( - runtimeModules: RuntimeModulesRecord, - protocolModules: ProtocolModulesRecord & MandatoryProtocolModulesRecord, - options?: { - overrideAppChainModules?: Partial; - settlementEnabled?: boolean; - overrideSequencerModules?: Partial; - } - ) { - return AppChain.from({ - Runtime: Runtime.from(runtimeModules), - Protocol: Protocol.from(protocolModules), - Sequencer: DefaultSequencer.sovereign({ - settlementEnabled: options?.settlementEnabled, - overrideModules: options?.overrideSequencerModules, - }), - ...DefaultModules.appChainBase(), - ...options?.overrideAppChainModules, - }); - } -} - -export class DefaultAppChainConfig { - static inmemory(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.inmemory({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } - static development(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.development({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } - static sovereign(options?: { - overrideAppChainConfig?: RecursivePartial>; - settlementEnabled?: boolean; - overrideSequencerConfig?: RecursivePartial>; - }) { - return { - Sequencer: DefaultSequencerConfig.sovereign({ - settlementEnabled: options?.settlementEnabled, - overrideConfig: options?.overrideSequencerConfig, - }), - ...DefaultConfigs.appChainBase(), - ...options?.overrideAppChainConfig, - }; - } -} diff --git a/packages/stack/src/presets/config.ts b/packages/stack/src/presets/config.ts index 835402150..040b14f42 100644 --- a/packages/stack/src/presets/config.ts +++ b/packages/stack/src/presets/config.ts @@ -1,193 +1,184 @@ export const inmemoryConfig = { - PROTOKIT_BLOCK_INTERVAL: 5000, - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHQL_HOST: "localhost", - PROTOKIT_GRAPHIQL_ENABLED: true, + blockInterval: 5000, + graphqlHost: "localhost", + graphqlPort: 8080, + graphiqlEnabled: true, }; - export const developmentConfig = { - PROTOKIT_PROOFS_ENABLED: false, - PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, - PROTOKIT_PRUNE_ON_STARTUP: false, - PROTOKIT_LOG_LEVEL: "INFO", - - PROTOKIT_BLOCK_INTERVAL: 30000, - PROTOKIT_SETTLEMENT_INTERVAL: 60000, - PROTOKIT_SETTLEMENT_ENABLED: true, - - REDIS_HOST: "localhost", - REDIS_PORT: 6379, - REDIS_PASSWORD: "password", - - DATABASE_URL: + proofsEnabled: false, + + shouldAttemptDbMigration: true, + shouldAttemptIndexerDbMigration: true, + shouldAttemptProcessorDbMigration: true, + + pruneOnStartup: false, + + blockInterval: 30000, + settlementInterval: 60000, + settlementEnabled: true, + + redisHost: "localhost", + redisPort: 6379, + redisPassword: "password", + + databaseUrl: "postgresql://admin:password@localhost:5432/protokit?schema=public", - INDEXER_DATABASE_URL: + indexerDatabaseUrl: "postgresql://admin:password@localhost:5433/protokit-indexer?schema=public", - PROCESSOR_DATABASE_URL: + processorDatabaseUrl: "postgresql://admin:password@localhost:5434/protokit-processor?schema=public", - PROTOKIT_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHIQL_ENABLED: true, + graphqlHost: "0.0.0.0", + graphqlPort: 8080, + graphiqlEnabled: true, - PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + indexerGraphqlHost: "0.0.0.0", + indexerGraphqlPort: 8081, + indexerGraphqlEnabled: true, - PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "0.0.0.0", + processorGraphqlHost: "0.0.0.0", + processorGraphqlPort: 8082, + processorGraphqlEnabled: true, - MINA_NETWORK: "lightnet", - MINA_NODE_GRAPHQL_HOST: "http://localhost", - MINA_NODE_GRAPHQL_PORT: 8083, + processorIndexerGraphqlHost: "0.0.0.0", - MINA_ARCHIVE_GRAPHQL_HOST: "http://localhost", - MINA_ARCHIVE_GRAPHQL_PORT: 8085, + minaNetwork: "lightnet", + minaNodeGraphqlHost: "http://localhost", + minaNodeGraphqlPort: 8083, - MINA_ACCOUNT_MANAGER_HOST: "http://localhost", - MINA_ACCOUNT_MANAGER_PORT: 8084, - MINA_EXPLORER_PORT: 3001, + minaArchiveGraphqlHost: "http://localhost", + minaArchiveGraphqlPort: 8085, - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + minaAccountManagerHost: "http://localhost", + minaAccountManagerPort: 8084, + minaExplorerPort: 3001, + + transactionFeeRecipientPrivateKey: "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + transactionFeeRecipientPublicKey: "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", - PROTOKIT_SEQUENCER_PRIVATE_KEY: - "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", - PROTOKIT_SEQUENCER_PUBLIC_KEY: - "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + sequencerPrivateKey: "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + sequencerPublicKey: "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + settlementContractPrivateKey: "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", - PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + settlementContractPublicKey: "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + dispatcherContractPrivateKey: "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", - PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + dispatcherContractPublicKey: "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + minaBridgeContractPrivateKey: "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", - PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + minaBridgeContractPublicKey: "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", - PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY: - "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", + customTokenPrivateKey: "EKFZHQSo5YdrcU7neDaNZruYHvCiNncvdZyKXuS6MDCW1fyCFKDP", - PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY: + customTokenAdminPrivateKey: "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", - PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY: + customTokenBridgePrivateKey: "EKENQ2QRc4gAJkZjQXU86ZS9MDm1e7HFiNN6LgRJnniHJt1WXDn1", - TEST_ACCOUNT_1_PRIVATE_KEY: + testAccount1PrivateKey: "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", - TEST_ACCOUNT_1_PUBLIC_KEY: + testAccount1PublicKey: "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", - OPEN_TELEMETRY_TRACING_ENABLED: true, - OPEN_TELEMETRY_TRACING_URL: "http://localhost:4318", - - OPEN_TELEMETRY_METRICS_ENABLED: true, - OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", - OPEN_TELEMETRY_METRICS_PORT: 4320, - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + openTelemetryTracingEnabled: true, + openTelemetryTracingUrl: "http://localhost:4318", + openTelemetryMetricsEnabled: true, + openTelemetryMetricsHost: "0.0.0.0", + openTelemetryMetricsPort: 4320, + openTelemetryMetricsScrapingFrequency: 10, }; - export const sovereignConfig = { - PROTOKIT_BLOCK_INTERVAL: 10000, - PROTOKIT_SETTLEMENT_INTERVAL: 30000, - PROTOKIT_SETTLEMENT_ENABLED: true, + blockInterval: 10000, + settlementInterval: 30000, + settlementEnabled: true, - PROTOKIT_SHOULD_ATTEMPT_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_INDEXER_DB_MIGRATION: true, - PROTOKIT_SHOULD_ATTEMPT_PROCESSOR_DB_MIGRATION: true, + shouldAttemptDbMigration: true, + shouldAttemptIndexerDbMigration: true, + shouldAttemptProcessorDbMigration: true, - PROTOKIT_PRUNE_ON_STARTUP: false, - PROTOKIT_LOG_LEVEL: "INFO", + pruneOnStartup: false, - REDIS_HOST: "redis", - REDIS_PORT: 6379, - REDIS_PASSWORD: "password", + redisHost: "redis", + redisPort: 6379, + redisPassword: "password", - DATABASE_URL: + databaseUrl: "postgresql://admin:password@postgres:5432/protokit?schema=public", - INDEXER_DATABASE_URL: + indexerDatabaseUrl: "postgresql://admin:password@indexer-postgres:5432/protokit-indexer?schema=public", - PROCESSOR_DATABASE_URL: + processorDatabaseUrl: "postgresql://admin:password@processor-postgres:5432/protokit-processor?schema=public", - PROTOKIT_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_GRAPHQL_PORT: 8080, - PROTOKIT_GRAPHIQL_ENABLED: true, + graphqlHost: "0.0.0.0", + graphqlPort: 8080, + graphiqlEnabled: true, - PROTOKIT_INDEXER_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_INDEXER_GRAPHQL_PORT: 8081, - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: true, + indexerGraphqlHost: "0.0.0.0", + indexerGraphqlPort: 8081, + indexerGraphqlEnabled: true, - PROTOKIT_PROCESSOR_GRAPHQL_HOST: "0.0.0.0", - PROTOKIT_PROCESSOR_GRAPHQL_PORT: 8082, - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: true, - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: "indexer", + processorGraphqlHost: "0.0.0.0", + processorGraphqlPort: 8082, + processorGraphqlEnabled: true, + processorIndexerGraphqlHost: "indexer", - MINA_NETWORK: "lightnet", - MINA_NODE_GRAPHQL_HOST: "http://lightnet", - MINA_NODE_GRAPHQL_PORT: 8080, + minaNetwork: "lightnet", + minaNodeGraphqlHost: "http://lightnet", + minaNodeGraphqlPort: 8080, - MINA_ARCHIVE_GRAPHQL_HOST: "http://lightnet", - MINA_ARCHIVE_GRAPHQL_PORT: 8282, + minaArchiveGraphqlHost: "http://lightnet", + minaArchiveGraphqlPort: 8282, - MINA_ACCOUNT_MANAGER_HOST: "http://lightnet", - MINA_ACCOUNT_MANAGER_PORT: 8084, - MINA_EXPLORER_PORT: 3001, - - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PRIVATE_KEY: + minaAccountManagerHost: "http://lightnet", + minaAccountManagerPort: 8084, + minaExplorerPort: 3001, + transactionFeeRecipientPrivateKey: "EKEssvj33MMBCg2tcybTzL32nTKbbwFHm6yUxd3JassdhL3J5aT8", - PROTOKIT_TRANSACTION_FEE_RECIPIENT_PUBLIC_KEY: + transactionFeeRecipientPublicKey: "B62qk4sNnzZqqjHp8YQXZUV3dBpnjiNieJVnsuh7mD2bMJ9PdbskH5H", - PROTOKIT_SEQUENCER_PRIVATE_KEY: - "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", - PROTOKIT_SEQUENCER_PUBLIC_KEY: - "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", + sequencerPrivateKey: "EKEdKhgUHMuDvwWJEg2TdCMCeiTSd9hh2HrEr6uYJfPVuwur1s43", + sequencerPublicKey: "B62qizW6aroTxQorJz4ywVNZom4jA6W4QPPCK3wLeyhnJHtVStUNniL", - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: + settlementContractPrivateKey: "EKErS9gYHZNawqKuwfMiwYYJtNptCrvca491QEvB3tz8sFsS5w66", - PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY: + settlementContractPublicKey: "B62qjKhzrvDgTPXCp34ozmpFSx4sC9owZe6eDzhdGPdoiUbGPmBkHTt", - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: + dispatcherContractPrivateKey: "EKF9Ei5G9PeB5ULMh9R6P5LfWX2gs15XxPNsect1pbcbMY9vs6v7", - PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY: + dispatcherContractPublicKey: "B62qmAzUJ1jqcsEf2V3K1k2Ec4MLsEKnodEvvJ5uweTFSLYEUALe1zs", - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: + minaBridgeContractPrivateKey: "EKFKTGqWU2egLKhMgoxX8mQ21zXSE1RZYkY82mmK9F3BxdSA7E5M", - PROTOKIT_MINA_BRIDGE_CONTRACT_PUBLIC_KEY: + minaBridgeContractPublicKey: "B62qn8XRkWcaBvv6F7kvarKs4cViaKRMbTUHT8FrDXLnvxuV6n7CHsN", - TEST_ACCOUNT_1_PRIVATE_KEY: + testAccount1PrivateKey: "EKF5p3wQTFd4tRBiGicRf93yXK82bcRryokC1qoazRM6wq6gMzWJ", - TEST_ACCOUNT_1_PUBLIC_KEY: + testAccount1PublicKey: "B62qkVfEwyfkm5yucHEqrRjxbyx98pgdWz82pHv7LYq9Qigs812iWZ8", - - OPEN_TELEMETRY_TRACING_ENABLED: true, - OPEN_TELEMETRY_TRACING_URL: "http://otel-collector:4317", - OPEN_TELEMETRY_METRICS_ENABLED: true, - OPEN_TELEMETRY_METRICS_HOST: "0.0.0.0", - OPEN_TELEMETRY_METRICS_PORT: 4320, - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: 10, + openTelemetryTracingEnabled: true, + openTelemetryTracingUrl: "http://otel-collector:4317", -}; \ No newline at end of file + openTelemetryMetricsEnabled: true, + openTelemetryMetricsHost: "0.0.0.0", + openTelemetryMetricsPort: 4320, + openTelemetryMetricsScrapingFrequency: 10, +}; diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index 4561e14e1..d1067aadd 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -19,13 +19,13 @@ import { DatabasePruneModule, InMemoryDatabase, LocalTaskQueue, + AppChainModulesRecord, } from "@proto-kit/sequencer"; import { IndexerNotifier, GeneratedResolverFactoryGraphqlModule, IndexBlockTask, } from "@proto-kit/indexer"; -import { PrivateKey } from "o1js"; import { PrismaRedisDatabase } from "@proto-kit/persistance"; import { BullQueue } from "@proto-kit/deployment"; import { @@ -41,579 +41,476 @@ import { InMemoryTransactionSender, StateServiceQueryModule, } from "@proto-kit/sdk"; -import { AppChainModulesRecord } from "@proto-kit/sequencer"; +import { PrivateKey } from "o1js"; +import { NonEmptyArray } from "type-graphql"; + import { buildCustomTokenConfig, buildSettlementTokenConfig, - definePreset, - orderModulesByDependencies, - parseApiEnv, - parseCoreEnv, - parseMetricsEnv, - parseSettlementEnv, - parseIndexerEnv, - parseProcessorEnv, - parseDatabaseEnv, - parseDatabasePruneEnv, - parseGraphqlServerEnv, - parseRedisEnv, resolveEnv, } from "./utils"; -import { NonEmptyArray } from "type-graphql"; import { Environment, - ModuleOverrides, - ApiEnv, - ConfigOverrides, CoreEnv, MetricsEnv, IndexerEnv, ProcessorEnv, SettlementEnv, + RedisEnv, DatabaseEnv, - TaskQueueEnv, - DatabasePruneEnv, + RedisTaskQueueEnv, GraphqlServerEnv, - RedisEnv, } from "./types"; export class DefaultModules { - static api(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - GraphqlServer, - Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), - }, - options?.overrides - ); + static api() { + return { + GraphqlServer, + Graphql: GraphqlSequencerModule.from(VanillaGraphqlModules.with({})), + } satisfies SequencerModulesRecord; } - static core(options?: { - overrides?: ModuleOverrides; - settlementEnabled?: boolean; - }): SequencerModulesRecord { - return definePreset( - { - ...DefaultModules.api(), - Mempool: PrivateMempool, - BlockProducerModule, - BlockTrigger: TimedBlockTrigger, - SequencerStartupModule, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.withoutSettlement() - ), - ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), - }, - options?.overrides - ); + + static core(options?: { settlementEnabled?: boolean }) { + const settlementEnabled = options?.settlementEnabled ?? false; + return { + ...(settlementEnabled ? DefaultModules.settlement() : {}), + ...DefaultModules.api(), + Mempool: PrivateMempool, + BlockProducerModule, + BlockTrigger: TimedBlockTrigger, + SequencerStartupModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.withoutSettlement() + ), + } satisfies SequencerModulesRecord; } - static metrics(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - OpenTelemetryServer, - }, - options?.overrides - ); + + static metrics() { + return { + OpenTelemetryServer, + } satisfies SequencerModulesRecord; } - static settlement(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - BaseLayer: MinaBaseLayer, - FeeStrategy: ConstantFeeStrategy, - BatchProducerModule, - SettlementModule, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.allTasks() - ), - }, - options?.overrides - ); + + static settlement() { + return { + BaseLayer: MinaBaseLayer, + FeeStrategy: ConstantFeeStrategy, + BatchProducerModule, + SettlementModule, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + } satisfies SequencerModulesRecord; } - static sequencerIndexer(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - IndexerNotifier, - }, - options?.overrides - ); + + static sequencerIndexer() { + return { + IndexerNotifier, + } satisfies SequencerModulesRecord; } - static indexer(options?: { overrides?: ModuleOverrides }) { - return definePreset( - { - Database: PrismaRedisDatabase, - TaskQueue: BullQueue, - TaskWorker: LocalTaskWorkerModule.from({ - IndexBlockTask, - }), - GraphqlServer, - Graphql: GraphqlSequencerModule.from({ - GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, - }), - }, - options?.overrides - ); + + static indexer() { + return { + Database: PrismaRedisDatabase, + TaskQueue: BullQueue, + TaskWorker: LocalTaskWorkerModule.from({ + IndexBlockTask, + }), + GraphqlServer, + Graphql: GraphqlSequencerModule.from({ + GeneratedResolverFactory: GeneratedResolverFactoryGraphqlModule, + }), + } satisfies SequencerModulesRecord; } + static processor( resolvers: NonEmptyArray, - handlers: HandlersRecord, - options?: { overrides?: ModuleOverrides } + handlers: HandlersRecord ) { - return definePreset( - { - GraphqlServer, - GraphqlSequencerModule: GraphqlSequencerModule.from({ - ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), - }), - HandlersExecutor: HandlersExecutor.from(handlers), - BlockFetching, - Trigger: TimedProcessorTrigger, - }, - options?.overrides - ); + return { + GraphqlServer, + GraphqlSequencerModule: GraphqlSequencerModule.from({ + ResolverFactory: ResolverFactoryGraphqlModule.from(resolvers), + }), + HandlersExecutor: HandlersExecutor.from(handlers), + BlockFetching, + Trigger: TimedProcessorTrigger, + } satisfies SequencerModulesRecord; } - static database(options?: { - overrides?: ModuleOverrides; - preset?: Environment; - }): SequencerModulesRecord { - const preset = options?.preset ?? "inmemory"; - return definePreset( - { - Database: - preset === "inmemory" ? InMemoryDatabase : PrismaRedisDatabase, - }, - options?.overrides - ); + static inMemoryDatabase() { + return { + Database: InMemoryDatabase, + } satisfies SequencerModulesRecord; } - static taskQueue(options?: { - overrides?: ModuleOverrides; - preset?: Environment; - }): SequencerModulesRecord { - const preset = options?.preset ?? "inmemory"; - return definePreset( - { - TaskQueue: preset === "inmemory" ? LocalTaskQueue : BullQueue, - }, - options?.overrides - ); + + static PrismaRedisDatabase() { + return { + Database: PrismaRedisDatabase, + DatabasePruneModule, + } satisfies SequencerModulesRecord; } - static databasePrune(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - DatabasePruneModule, - }, - options?.overrides - ); + + static localTaskQueue() { + return { + TaskQueue: LocalTaskQueue, + } satisfies SequencerModulesRecord; } - static worker(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - TaskQueue: BullQueue, - LocalTaskWorkerModule: LocalTaskWorkerModule.from( - VanillaTaskWorkerModules.allTasks() - ), - }, - options?.overrides - ); + + static RedisTaskQueue() { + return { + TaskQueue: BullQueue, + } satisfies SequencerModulesRecord; } - static appChainBase(options?: { - overrides?: Partial; - }): AppChainModulesRecord { - return definePreset( - { - TransactionSender: InMemoryTransactionSender, - QueryTransportModule: StateServiceQueryModule, - NetworkStateTransportModule: BlockStorageNetworkStateModule, - }, - options?.overrides - ) as AppChainModulesRecord; + + static worker() { + return { + TaskQueue: BullQueue, + LocalTaskWorkerModule: LocalTaskWorkerModule.from( + VanillaTaskWorkerModules.allTasks() + ), + } satisfies SequencerModulesRecord; } - static settlementScript(options?: { - overrides?: ModuleOverrides; - }): SequencerModulesRecord { - return definePreset( - { - ...DefaultModules.settlement(), - Mempool: PrivateMempool, - TaskQueue: LocalTaskQueue, - SequencerStartupModule, - }, - options?.overrides - ); + + static appChainBase() { + return { + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + } satisfies AppChainModulesRecord; } - static ordered(modules: any) { - return orderModulesByDependencies(modules); + + static settlementScript() { + return { + ...DefaultModules.settlement(), + Mempool: PrivateMempool, + TaskQueue: LocalTaskQueue, + SequencerStartupModule, + } satisfies SequencerModulesRecord; } } export class DefaultConfigs { static api(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset( - { - Graphql: VanillaGraphqlModules.defaultConfig(), - GraphqlServer: DefaultConfigs.graphqlServer({ - type: "protokit", - preset: options?.preset, - envs: options?.envs, - }), - }, - options?.overrides - ); + overrides?: Partial; + }) { + return { + Graphql: VanillaGraphqlModules.defaultConfig(), + GraphqlServer: DefaultConfigs.graphqlServer({ + preset: options?.preset, + overrides: options?.overrides, + }), + }; } + static core(options?: { preset?: Environment; - envs?: Partial & Partial & Partial; - overrides?: ConfigOverrides; + overrides?: Partial & + Partial & + Partial; settlementEnabled?: boolean; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseCoreEnv( - { ...config, ...options?.envs }, - options?.settlementEnabled - ); + }) { + const settlementEnabled = options?.settlementEnabled ?? false; + const config = resolveEnv(options?.preset, options?.overrides); const apiConfig = DefaultConfigs.api({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - const settlementConfig = options?.settlementEnabled + const settlementConfig = settlementEnabled ? DefaultConfigs.settlement({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }) : {}; const blockTriggerConfig = { - blockInterval: parsed.blockInterval, + blockInterval: config.blockInterval, produceEmptyBlocks: true, - ...(options?.settlementEnabled + ...(settlementEnabled ? { - settlementInterval: parsed.settlementInterval, + settlementInterval: config.settlementInterval, settlementTokenConfig: buildSettlementTokenConfig( - parsed.minaBridgeKey!, + config.minaBridgeContractPrivateKey!, buildCustomTokenConfig( - parsed.customTokenKey, - parsed.customTokenBridgeKey + config.customTokenPrivateKey, + config.customTokenBridgePrivateKey ) ), } : { settlementTokenConfig: {} }), }; - return definePreset( - { - ...apiConfig, - Mempool: {}, - BlockProducerModule: {}, - BlockTrigger: blockTriggerConfig, - SequencerStartupModule: {}, - LocalTaskWorkerModule: VanillaGraphqlModules.defaultConfig(), - ...settlementConfig, - }, - options?.overrides - ); + return { + ...apiConfig, + Mempool: {}, + BlockProducerModule: {}, + BlockTrigger: blockTriggerConfig, + SequencerStartupModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + ...settlementConfig, + }; } + static metrics(options?: { preset?: Environment; - envs?: MetricsEnv; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const configs = resolveEnv(options?.preset, options?.envs); - const parsed = parseMetricsEnv(configs); - return definePreset( - { - OpenTelemetryServer: { - metrics: { - enabled: parsed.metricsEnabled, - prometheus: { - host: parsed.metricsHost, - port: parsed.metricsPort, - appendTimestamp: true, - }, - nodeScrapeInterval: parsed.metricsScrapingFrequency, + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); + return { + OpenTelemetryServer: { + metrics: { + enabled: config.metricsEnabled, + prometheus: { + host: config.metricsHost, + port: config.metricsPort, + appendTimestamp: true, }, - tracing: { - enabled: parsed.tracingEnabled, - otlp: { - url: parsed.tracingUrl, - }, + nodeScrapeInterval: config.metricsScrapingFrequency, + }, + tracing: { + enabled: config.tracingEnabled, + otlp: { + url: config.tracingUrl, }, }, }, - options?.overrides - ); + }; } - static sequencerIndexer(options?: { - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset({ IndexerNotifier: {} }, options?.overrides); + + static sequencerIndexer() { + return { IndexerNotifier: {} }; } + static indexer(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseIndexerEnv(config); - const redisConfig = DefaultConfigs.redis({ + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); + const taskQueueConfig = DefaultConfigs.redisTaskQueue({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - const databaseConfig = DefaultConfigs.database({ + const databaseConfig = DefaultConfigs.prismaRedisDatabase({ preset: options?.preset, - envs: options?.envs, + overrides: { + databaseUrl: config.indexerDatabaseUrl, + ...options?.overrides, + }, }); const graphqlServerConfig = DefaultConfigs.graphqlServer({ - type: "indexer", preset: options?.preset, - envs: options?.envs, + overrides: { + graphqlHost: config.indexerGraphqlHost, + graphqlPort: config.indexerGraphqlPort, + graphiqlEnabled: config.indexerGraphqlEnabled, + ...options?.overrides, + }, }); - return definePreset( - { - ...databaseConfig, - TaskQueue: redisConfig.TaskQueue, - TaskWorker: { - IndexBlockTask: {}, - }, - ...graphqlServerConfig, - Graphql: { - GeneratedResolverFactory: {}, - }, + return { + ...databaseConfig, + ...taskQueueConfig, + TaskWorker: { + IndexBlockTask: {}, }, - options?.overrides - ); + ...graphqlServerConfig, + Graphql: { + GeneratedResolverFactory: {}, + }, + }; } + static processor(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseProcessorEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); const graphqlServerConfig = DefaultConfigs.graphqlServer({ - type: "processor", preset: options?.preset, - envs: options?.envs, + overrides: { + graphqlHost: config.processorGraphqlHost, + graphqlPort: config.processorGraphqlPort, + graphiqlEnabled: config.processorGraphqlEnabled, + ...options?.overrides, + }, }); - return definePreset( - { - HandlersExecutor: {}, - BlockFetching: { - url: `http://${parsed.processorIndexerGraphqlHost}:${parsed.indexerGraphqlPort}`, - }, - Trigger: { - interval: (parsed.blockInterval ?? 5000) / 5, - }, - ...graphqlServerConfig, - GraphqlSequencerModule: { - ResolverFactory: {}, - }, + return { + HandlersExecutor: {}, + BlockFetching: { + url: `http://${config.processorIndexerGraphqlHost}:${config.indexerGraphqlPort}`, }, - options?.overrides - ); + Trigger: { + interval: Number(config.blockInterval) / 5, + }, + ...graphqlServerConfig, + GraphqlSequencerModule: { + ResolverFactory: {}, + }, + }; } + static settlement(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseSettlementEnv(config); - - return definePreset( - { - BaseLayer: { - network: { - type: parsed.network, - graphql: parsed.graphql, - archive: parsed.archive, - accountManager: parsed.accountManager, - }, + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); + + return { + BaseLayer: { + network: { + type: "lightnet" as const, + graphql: config.minaNodeGraphqlHost, + archive: config.minaArchiveGraphqlHost, + accountManager: config.minaAccountManagerHost, }, - SettlementModule: { - feepayer: PrivateKey.fromBase58(parsed.sequencerPrivateKey), - keys: { - settlement: PrivateKey.fromBase58( - parsed.settlementContractPrivateKey - ), - dispatch: PrivateKey.fromBase58( - parsed.dispatcherContractPrivateKey - ), - minaBridge: PrivateKey.fromBase58( - parsed.minaBridgeContractPrivateKey - ), - }, + }, + SettlementModule: { + feepayer: PrivateKey.fromBase58(config.sequencerPrivateKey), + keys: { + settlement: PrivateKey.fromBase58( + config.settlementContractPrivateKey + ), + dispatch: PrivateKey.fromBase58(config.dispatcherContractPrivateKey), + minaBridge: PrivateKey.fromBase58( + config.minaBridgeContractPrivateKey + ), }, - FeeStrategy: {}, - BatchProducerModule: {}, }, - options?.overrides - ); + FeeStrategy: {}, + BatchProducerModule: {}, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }; + } + + static inMemoryDatabase() { + return { Database: {} }; } - static database(options?: { + + static prismaRedisDatabase(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const preset = options?.preset ?? "inmemory"; - if (preset === "inmemory") { - return { Database: definePreset({}, options?.overrides) }; - } - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseDatabaseEnv(config); + overrides?: Partial; + }) { + const preset = options?.preset ?? "development"; + const config = resolveEnv(preset, options?.overrides); const redisConfig = DefaultConfigs.redis({ - preset: options?.preset, - envs: options?.envs, + preset, + overrides: options?.overrides, }); return { - Database: definePreset( - { - ...redisConfig, - prisma: { - connection: parsed.databaseUrl, - }, + Database: { + ...redisConfig, + prisma: { + connection: config.databaseUrl, }, - options?.overrides - ), + }, + DatabasePruneModule: { + pruneOnStartup: config.pruneOnStartup, + }, }; } - static taskQueue(options?: { - preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const preset = options?.preset ?? "inmemory"; - if (preset === "inmemory") { - return { - TaskQueue: definePreset({}, options?.overrides), - }; - } - const redisConfig = DefaultConfigs.redis({ - preset: options?.preset, - envs: options?.envs, - }); - return { TaskQueue: definePreset(redisConfig, options?.overrides) }; + static localTaskQueue() { + return { + TaskQueue: {}, + }; } - static databasePrune(options?: { + + static redisTaskQueue(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseDatabasePruneEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, + options?.overrides + ); return { - DatabasePruneModule: definePreset( - { - pruneOnStartup: parsed.pruneOnStartup, + TaskQueue: { + redis: { + host: config.redisHost, + port: config.redisPort, + password: config.redisPassword, + db: config.redisDb, }, - options?.overrides - ), + retryAttempts: config.retryAttempts, + }, }; } + static graphqlServer(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - type?: "indexer" | "processor" | "protokit"; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseGraphqlServerEnv(config, options?.type); - - return definePreset( - { - port: parsed.graphqlPort, - host: parsed.graphqlHost, - graphiql: parsed.graphiqlEnabled, - }, + overrides?: Partial; + }) { + const config = resolveEnv( + options?.preset, options?.overrides ); + + return { + port: config.graphqlPort, + host: config.graphqlHost, + graphiql: config.graphiqlEnabled, + }; } + static redis(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const config = resolveEnv(options?.preset, options?.envs); - const parsed = parseRedisEnv(config); + overrides?: Partial; + }) { + const config = resolveEnv(options?.preset, options?.overrides); return { - redis: definePreset( - { - host: parsed.redisHost, - port: parsed.redisPort, - password: parsed.redisPassword, - }, - options?.overrides - ), + redis: { + host: config.redisHost, + port: config.redisPort, + password: config.redisPassword, + }, }; } - static appChainBase(options?: { - overrides?: ConfigOverrides; - }): ConfigOverrides { - return definePreset( - { - QueryTransportModule: {}, - NetworkStateTransportModule: {}, - TransactionSender: {}, - }, - options?.overrides - ); + + static appChainBase() { + return { + QueryTransportModule: {}, + NetworkStateTransportModule: {}, + TransactionSender: {}, + }; } + static worker(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { - const redisConfig = DefaultConfigs.redis({ + overrides?: Partial; + }) { + const taskQueueConfig = DefaultConfigs.redisTaskQueue({ preset: options?.preset, - envs: options?.envs, - overrides: { - db: 1, - }, + overrides: options?.overrides, }); - return definePreset( - { - TaskQueue: redisConfig, - LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), - }, - options?.overrides - ); + return { + ...taskQueueConfig, + LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), + }; } + static settlementScript(options?: { preset?: Environment; - envs?: Partial; - overrides?: ConfigOverrides; - }): ConfigOverrides { + overrides?: Partial; + }) { const settlementConfig = DefaultConfigs.settlement({ preset: options?.preset, - envs: options?.envs, + overrides: options?.overrides, }); - return definePreset( - { - ...settlementConfig, - SequencerStartupModule: {}, - TaskQueue: { - simulatedDuration: 0, - }, - Mempool: {}, + return { + ...settlementConfig, + SequencerStartupModule: {}, + TaskQueue: { + simulatedDuration: 0, }, - options?.overrides - ); + Mempool: {}, + }; } } diff --git a/packages/stack/src/presets/modules/types.ts b/packages/stack/src/presets/modules/types.ts index 712511168..65efe1603 100644 --- a/packages/stack/src/presets/modules/types.ts +++ b/packages/stack/src/presets/modules/types.ts @@ -1,86 +1,63 @@ -import { RecursivePartial, ModulesConfig } from "@proto-kit/common"; -import { SequencerModulesRecord } from "@proto-kit/sequencer"; - -export type ModuleOverrides = Partial; -export type ConfigOverrides = RecursivePartial>; export type Environment = "inmemory" | "development" | "sovereign"; -export type ApiEnv = { - PROTOKIT_GRAPHQL_PORT: number | string; - PROTOKIT_GRAPHQL_HOST: string; - PROTOKIT_GRAPHIQL_ENABLED: boolean | string; + +export type GraphqlServerEnv = { + graphqlPort: number; + graphqlHost: string; + graphiqlEnabled: boolean; }; export type CoreEnv = { - PROTOKIT_BLOCK_INTERVAL: number | string; - PROTOKIT_SETTLEMENT_INTERVAL?: number | string; - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY?: string; - PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY?: string; - PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY?: string; + blockInterval: number; + settlementInterval?: number; + minaBridgeContractPrivateKey?: string; + customTokenPrivateKey?: string; + customTokenBridgePrivateKey?: string; }; export type MetricsEnv = { - OPEN_TELEMETRY_METRICS_ENABLED: boolean | string; - OPEN_TELEMETRY_METRICS_HOST: string; - OPEN_TELEMETRY_METRICS_PORT: number | string; - OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY: number | string; - OPEN_TELEMETRY_TRACING_ENABLED: boolean | string; - OPEN_TELEMETRY_TRACING_URL: string; + metricsEnabled: boolean; + metricsHost: string; + metricsPort: number; + metricsScrapingFrequency: number; + tracingEnabled: boolean; + tracingUrl: string; }; export type SettlementEnv = { - MINA_NETWORK: string; - MINA_NODE_GRAPHQL_HOST: string; - MINA_NODE_GRAPHQL_PORT: number | string; - MINA_ARCHIVE_GRAPHQL_HOST: string; - MINA_ARCHIVE_GRAPHQL_PORT: number | string; - MINA_ACCOUNT_MANAGER_HOST: string; - MINA_ACCOUNT_MANAGER_PORT: number | string; - PROTOKIT_SEQUENCER_PRIVATE_KEY: string; - PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY: string; - PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY: string; - PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY: string; -}; -export type IndexerEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; - INDEXER_DATABASE_URL: string; - PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; - PROTOKIT_INDEXER_GRAPHQL_HOST: string; - PROTOKIT_INDEXER_GRAPHIQL_ENABLED: boolean | string; + minaNetwork: string; + minaNodeGraphqlHost: string; + minaNodeGraphqlPort: number; + minaArchiveGraphqlHost: string; + minaArchiveGraphqlPort: number; + minaAccountManagerHost: string; + minaAccountManagerPort: number; + sequencerPrivateKey: string; + settlementContractPrivateKey: string; + dispatcherContractPrivateKey: string; + minaBridgeContractPrivateKey: string; +}; +export type IndexerEnv = RedisTaskQueueEnv & { + indexerDatabaseUrl: string; + indexerGraphqlHost: string; + indexerGraphqlPort: number; + indexerGraphqlEnabled: boolean; + pruneOnStartup?: boolean; }; export type ProcessorEnv = { - PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST: string; - PROTOKIT_INDEXER_GRAPHQL_PORT: number | string; - PROTOKIT_BLOCK_INTERVAL: number | string; - PROTOKIT_PROCESSOR_GRAPHQL_HOST: string; - PROTOKIT_PROCESSOR_GRAPHQL_PORT: number | string; - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED: boolean | string; -}; -export type DatabaseEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; - DATABASE_URL: string; + processorIndexerGraphqlHost: string; + indexerGraphqlPort: number; + blockInterval: number; + processorGraphqlHost: string; + processorGraphqlPort: number; + processorGraphqlEnabled: boolean; }; -export type TaskQueueEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; -}; -export type DatabasePruneEnv = { - PRUNE_ON_STARTUP?: boolean | string; -}; -export type GraphqlServerEnv = { - PROTOKIT_GRAPHQL_PORT?: number | string; - PROTOKIT_GRAPHQL_HOST?: string; - PROTOKIT_GRAPHIQL_ENABLED?: boolean | string; - PROTOKIT_INDEXER_GRAPHQL_HOST?: string; - PROTOKIT_INDEXER_GRAPHQL_PORT?: number | string; - PROTOKIT_INDEXER_GRAPHIQL_ENABLED?: boolean | string; - PROTOKIT_PROCESSOR_GRAPHQL_HOST?: string; - PROTOKIT_PROCESSOR_GRAPHQL_PORT?: number | string; - PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED?: boolean | string; +export type DatabaseEnv = RedisEnv & { + databaseUrl: string; + pruneOnStartup?: boolean; }; export type RedisEnv = { - REDIS_HOST: string; - REDIS_PORT: number | string; - REDIS_PASSWORD: string; + redisHost: string; + redisPort: number; + redisPassword: string; +}; +export type RedisTaskQueueEnv = RedisEnv & { + redisDb?: number; + retryAttempts?: number; }; diff --git a/packages/stack/src/presets/modules/utils.ts b/packages/stack/src/presets/modules/utils.ts index 679b35165..ebe16616f 100644 --- a/packages/stack/src/presets/modules/utils.ts +++ b/packages/stack/src/presets/modules/utils.ts @@ -1,106 +1,40 @@ import { PrivateKey, TokenId } from "o1js"; -import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; import { FungibleToken } from "mina-fungible-token"; -import { assertDefined } from "@proto-kit/common"; -import { - ApiEnv, - CoreEnv, - DatabaseEnv, - DatabasePruneEnv, - Environment, - GraphqlServerEnv, - IndexerEnv, - MetricsEnv, - ProcessorEnv, - RedisEnv, - SettlementEnv, - TaskQueueEnv, -} from "./types"; - -function ensureDefined(obj: Record, keys: string[]) { - keys.forEach((k) => assertDefined(obj[k], `${k} is required`)); -} -export function definePreset( - base: T, - overrides?: Partial -): T { - return { - ...base, - ...overrides, - }; -} -const MODULE_DEPENDENCIES: Record = { - Database: [], - TaskQueue: [], - OpenTelemetryServer: [], - BaseLayer: [], - FeeStrategy: [], - - Protocol: [], - Mempool: ["Database"], - BlockProducerModule: ["Database"], - - LocalTaskWorkerModule: ["TaskQueue"], - TaskWorker: ["TaskQueue"], - SequencerStartupModule: ["Database"], - BatchProducerModule: ["Database"], - BlockTrigger: ["BlockProducerModule", "BatchProducerModule"], - - SettlementModule: ["Database", "BaseLayer", "FeeStrategy"], - DatabasePruneModule: ["Database"], - - Graphql: [], - GraphqlServer: [], - - IndexBlockTask: ["Database", "TaskQueue"], - IndexerNotifier: ["Database", "TaskQueue"], - GeneratedResolverFactory: [], - - BlockFetching: [], - HandlersExecutor: [], - Trigger: ["BlockFetching", "HandlersExecutor"], - ResolverFactory: [], -}; -export function orderModulesByDependencies( - modules: Record -): Record { - const moduleSet = new Set(Object.keys(modules)); - const ordered: Record = {}; - const visited = new Set(); - - function visit(name: string) { - if (!moduleSet.has(name) || visited.has(name)) return; - - const deps = MODULE_DEPENDENCIES[name] ?? []; - for (const dep of deps) { - visit(dep); - } +import { developmentConfig, inmemoryConfig, sovereignConfig } from "../config"; - visited.add(name); - ordered[name] = modules[name]; - } +import { Environment } from "./types"; - for (const name of moduleSet) { - visit(name); +export function getConfigs(preset: Environment) { + switch (preset) { + case "development": + return developmentConfig; + case "sovereign": + return sovereignConfig; + case "inmemory": + default: + return inmemoryConfig; } - - return ordered; } export function resolveEnv( preset: Environment = "inmemory", - envs?: Partial + envs?: Partial | undefined ): T { - return { - ...getConfigs(preset), - ...envs, - } as T; + const config = getConfigs(preset); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + if (!envs) return config as T; + const resolved = { ...config, ...envs } satisfies Partial; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return resolved as T; } export function buildCustomTokenConfig( customTokenPrivateKey?: string, customTokenBridgePrivateKey?: string -): Record { - if (!customTokenPrivateKey || !customTokenBridgePrivateKey) { +) { + if ( + customTokenPrivateKey === undefined || + customTokenBridgePrivateKey === undefined + ) { return {}; } const pk = PrivateKey.fromBase58(customTokenPrivateKey); @@ -117,8 +51,8 @@ export function buildCustomTokenConfig( } export function buildSettlementTokenConfig( bridgePrivateKey: string, - customTokens: Record = {} -): Record { + customTokens: Record = {} +) { return { "1": { bridgingContractPrivateKey: PrivateKey.fromBase58(bridgePrivateKey), @@ -126,256 +60,3 @@ export function buildSettlementTokenConfig( ...customTokens, }; } -export function getConfigs(preset: Environment) { - switch (preset) { - case "development": - return developmentConfig; - case "sovereign": - return sovereignConfig; - case "inmemory": - default: - return inmemoryConfig; - } -} - -export function parseApiEnv(envs: ApiEnv): { - graphqlPort: number; - graphqlHost: string; - graphiqlEnabled: boolean; -} { - ensureDefined(envs, [ - "PROTOKIT_GRAPHIQL_ENABLED", - "PROTOKIT_GRAPHQL_HOST", - "PROTOKIT_GRAPHQL_PORT", - ]); - return { - graphqlPort: Number(envs.PROTOKIT_GRAPHQL_PORT), - graphqlHost: envs.PROTOKIT_GRAPHQL_HOST, - graphiqlEnabled: Boolean(envs.PROTOKIT_GRAPHIQL_ENABLED), - }; -} -export function parseCoreEnv( - envs: CoreEnv, - settlementEnabled?: boolean -): { - blockInterval: number; - settlementInterval?: number; - minaBridgeKey?: string; - customTokenKey?: string; - customTokenBridgeKey?: string; -} { - ensureDefined(envs, ["PROTOKIT_BLOCK_INTERVAL"]); - if (settlementEnabled) { - ensureDefined(envs, [ - "PROTOKIT_SETTLEMENT_INTERVAL", - "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", - ]); - if (envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY) { - ensureDefined(envs, ["PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY"]); - } - } - return { - blockInterval: Number(envs.PROTOKIT_BLOCK_INTERVAL), - settlementInterval: Number(envs.PROTOKIT_SETTLEMENT_INTERVAL), - minaBridgeKey: envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY, - customTokenKey: envs.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, - customTokenBridgeKey: envs.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, - }; -} -export function parseMetricsEnv(envs: MetricsEnv): { - metricsEnabled: boolean; - metricsHost?: string; - metricsPort?: number; - metricsScrapingFrequency?: number; - tracingEnabled: boolean; - tracingUrl?: string; -} { - ensureDefined(envs as Record, [ - "OPEN_TELEMETRY_METRICS_ENABLED", - "OPEN_TELEMETRY_TRACING_ENABLED", - "OPEN_TELEMETRY_TRACING_URL", - "OPEN_TELEMETRY_METRICS_HOST", - "OPEN_TELEMETRY_METRICS_PORT", - "OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY", - ]); - return { - metricsEnabled: Boolean(envs.OPEN_TELEMETRY_METRICS_ENABLED), - metricsHost: envs.OPEN_TELEMETRY_METRICS_HOST, - metricsPort: Number(envs.OPEN_TELEMETRY_METRICS_PORT), - metricsScrapingFrequency: Number( - envs.OPEN_TELEMETRY_METRICS_SCRAPING_FREQUENCY - ), - tracingEnabled: Boolean(envs.OPEN_TELEMETRY_TRACING_ENABLED), - tracingUrl: envs.OPEN_TELEMETRY_TRACING_URL, - }; -} -export function parseSettlementEnv(envs: SettlementEnv): { - network: string; - graphql: string; - archive: string; - accountManager: string; - sequencerPrivateKey: string; - settlementContractPrivateKey: string; - dispatcherContractPrivateKey: string; - minaBridgeContractPrivateKey: string; -} { - ensureDefined(envs as Record, [ - "MINA_ACCOUNT_MANAGER_HOST", - "MINA_ACCOUNT_MANAGER_PORT", - "MINA_ARCHIVE_GRAPHQL_HOST", - "MINA_ARCHIVE_GRAPHQL_PORT", - "MINA_NODE_GRAPHQL_HOST", - "MINA_NODE_GRAPHQL_PORT", - "MINA_NETWORK", - "PROTOKIT_SEQUENCER_PRIVATE_KEY", - "PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY", - "PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY", - "PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", - ]); - - return { - network: envs.MINA_NETWORK!, - graphql: `${envs.MINA_NODE_GRAPHQL_HOST}:${envs.MINA_NODE_GRAPHQL_PORT}/graphql`, - archive: `${envs.MINA_ARCHIVE_GRAPHQL_HOST}:${envs.MINA_ARCHIVE_GRAPHQL_PORT}`, - accountManager: `${envs.MINA_ACCOUNT_MANAGER_HOST}:${envs.MINA_ACCOUNT_MANAGER_PORT}`, - sequencerPrivateKey: envs.PROTOKIT_SEQUENCER_PRIVATE_KEY!, - settlementContractPrivateKey: - envs.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY!, - dispatcherContractPrivateKey: - envs.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY!, - minaBridgeContractPrivateKey: - envs.PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY!, - }; -} -export function parseIndexerEnv(envs: IndexerEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; - indexerDatabaseUrl?: string; - graphqlPort?: number; - graphqlHost?: string; - graphiqlEnabled?: boolean; -} { - ensureDefined(envs as Record, [ - "REDIS_HOST", - "REDIS_PORT", - "REDIS_PASSWORD", - "INDEXER_DATABASE_URL", - "PROTOKIT_INDEXER_GRAPHQL_PORT", - "PROTOKIT_INDEXER_GRAPHQL_HOST", - "PROTOKIT_INDEXER_GRAPHIQL_ENABLED", - ]); - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - indexerDatabaseUrl: envs.INDEXER_DATABASE_URL, - graphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT - ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) - : undefined, - graphqlHost: envs.PROTOKIT_INDEXER_GRAPHQL_HOST, - graphiqlEnabled: envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED - ? Boolean(envs.PROTOKIT_INDEXER_GRAPHIQL_ENABLED) - : undefined, - }; -} -export function parseProcessorEnv(envs: ProcessorEnv): { - processorIndexerGraphqlHost?: string; - indexerGraphqlPort?: number; - blockInterval?: number; - processorGraphqlHost?: string; - processorGraphqlPort?: number; - processorGraphiqlEnabled?: boolean; -} { - ensureDefined(envs as Record, [ - "PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST", - "PROTOKIT_INDEXER_GRAPHQL_PORT", - "PROTOKIT_BLOCK_INTERVAL", - "PROTOKIT_PROCESSOR_GRAPHQL_HOST", - "PROTOKIT_PROCESSOR_GRAPHQL_PORT", - "PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED", - ]); - return { - processorIndexerGraphqlHost: envs.PROTOKIT_PROCESSOR_INDEXER_GRAPHQL_HOST, - indexerGraphqlPort: envs.PROTOKIT_INDEXER_GRAPHQL_PORT - ? Number(envs.PROTOKIT_INDEXER_GRAPHQL_PORT) - : undefined, - blockInterval: envs.PROTOKIT_BLOCK_INTERVAL - ? Number(envs.PROTOKIT_BLOCK_INTERVAL) - : undefined, - processorGraphqlHost: envs.PROTOKIT_PROCESSOR_GRAPHQL_HOST, - processorGraphqlPort: envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT - ? Number(envs.PROTOKIT_PROCESSOR_GRAPHQL_PORT) - : undefined, - processorGraphiqlEnabled: envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED - ? Boolean(envs.PROTOKIT_PROCESSOR_GRAPHIQL_ENABLED) - : undefined, - }; -} -export function parseDatabaseEnv(envs: DatabaseEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; - databaseUrl?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - databaseUrl: envs.DATABASE_URL, - }; -} -export function parseTaskQueueEnv(envs: TaskQueueEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - }; -} -export function parseDatabasePruneEnv(envs: DatabasePruneEnv): { - pruneOnStartup: boolean; -} { - return { - pruneOnStartup: - envs.PRUNE_ON_STARTUP === "true" || envs.PRUNE_ON_STARTUP === true, - }; -} -export function parseGraphqlServerEnv( - envs: GraphqlServerEnv, - type: "protokit" | "indexer" | "processor" = "protokit" -): { - graphqlPort?: number; - graphqlHost?: string; - graphiqlEnabled?: boolean; -} { - const prefix = - type === "indexer" - ? "PROTOKIT_INDEXER" - : type === "processor" - ? "PROTOKIT_PROCESSOR" - : "PROTOKIT"; - return { - graphqlPort: envs[`${prefix}_GRAPHQL_PORT`] - ? Number(envs[`${prefix}_GRAPHQL_PORT`]) - : undefined, - graphqlHost: envs[`${prefix}_GRAPHQL_HOST`], - graphiqlEnabled: envs[`${prefix}_GRAPHIQL_ENABLED`] - ? Boolean(envs[`${prefix}_GRAPHIQL_ENABLED`]) - : undefined, - }; -} -export function parseRedisEnv(envs: RedisEnv): { - redisHost?: string; - redisPort?: number; - redisPassword?: string; -} { - return { - redisHost: envs.REDIS_HOST, - redisPort: envs.REDIS_PORT ? Number(envs.REDIS_PORT) : undefined, - redisPassword: envs.REDIS_PASSWORD, - }; -} diff --git a/packages/stack/src/presets/sequencer/index.ts b/packages/stack/src/presets/sequencer/index.ts deleted file mode 100644 index 1fd6919fb..000000000 --- a/packages/stack/src/presets/sequencer/index.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { SequencerModulesRecord, Sequencer } from "@proto-kit/sequencer"; -import { ModulesConfig, RecursivePartial } from "@proto-kit/common"; -import { DefaultConfigs, DefaultModules } from "../modules"; -import { definePreset } from "../modules/utils"; - -export class DefaultSequencer { - static inmemory(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - ...DefaultModules.database(), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.taskQueue(), - ...(options?.settlementEnabled ? DefaultModules.settlement() : {}), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } - static development(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - // ordering of the modules matters due to dependency resolution - ...DefaultModules.database({ preset: "development" }), - ...DefaultModules.databasePrune(), - ...DefaultModules.metrics(), - ...DefaultModules.taskQueue({ preset: "development" }), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.sequencerIndexer(), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } - static sovereign(options?: { - overrideModules?: Partial; - settlementEnabled?: boolean; - }) { - const modules = DefaultModules.ordered( - definePreset( - { - // ordering of the modules matters due to dependency resolution - ...DefaultModules.database(), - ...DefaultModules.taskQueue(), - ...DefaultModules.core({ - settlementEnabled: options?.settlementEnabled, - }), - ...DefaultModules.sequencerIndexer(), - ...DefaultModules.metrics(), - ...DefaultModules.databasePrune(), - }, - options?.overrideModules - ) - ); - - return Sequencer.from(modules); - } -} -export class DefaultSequencerConfig { - static inmemory(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ settlementEnabled: options?.settlementEnabled }), - ...DefaultConfigs.database({ preset: "inmemory" }), - ...DefaultConfigs.taskQueue({ preset: "inmemory" }), - ...options?.overrideConfig, - }; - } - static development(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ - settlementEnabled: options?.settlementEnabled, - preset: "development", - }), - ...DefaultConfigs.sequencerIndexer(), - ...DefaultConfigs.metrics({ preset: "development" }), - ...DefaultConfigs.databasePrune({ preset: "development" }), - ...DefaultConfigs.taskQueue({ - preset: "development", - overrides: { - ...DefaultConfigs.redis({ - preset: "development", - overrides: { db: 1 }, - }), - }, - }), - ...DefaultConfigs.database({ preset: "development" }), - ...options?.overrideConfig, - }; - } - static sovereign(options?: { - overrideConfig?: RecursivePartial>; - settlementEnabled?: boolean; - }) { - return { - ...DefaultConfigs.core({ - settlementEnabled: options?.settlementEnabled, - preset: "sovereign", - }), - ...DefaultConfigs.sequencerIndexer(), - ...DefaultConfigs.metrics({ preset: "sovereign" }), - ...DefaultConfigs.databasePrune({ preset: "sovereign" }), - ...DefaultConfigs.taskQueue({ - preset: "sovereign", - overrides: { - ...DefaultConfigs.redis({ - preset: "sovereign", - overrides: { db: 1 }, - }), - }, - }), - ...DefaultConfigs.database({ preset: "sovereign" }), - ...options?.overrideConfig, - }; - } -} From 4f0a0b8c221dd5e7be17d460fe8d230b551d8cce Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 11:25:45 +0100 Subject: [PATCH 08/14] fix code format --- packages/stack/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stack/package.json b/packages/stack/package.json index dc3c50ad3..9258a54cc 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -41,7 +41,6 @@ "@prisma/client": "^5.19.1", "mina-fungible-token": "^1.1.0", "type-graphql": "2.0.0-rc.2" - }, "gitHead": "397881ed5d8f98f5005bcd7be7f5a12b3bc6f956" } From 19ad5b9f9929cb9113d2fc7045fcf6ce2bd373f1 Mon Sep 17 00:00:00 2001 From: stanlou Date: Sat, 24 Jan 2026 12:49:56 +0100 Subject: [PATCH 09/14] fix graphql server config --- packages/stack/src/presets/modules/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index d1067aadd..9c61fbdaf 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -196,7 +196,7 @@ export class DefaultConfigs { }) { return { Graphql: VanillaGraphqlModules.defaultConfig(), - GraphqlServer: DefaultConfigs.graphqlServer({ + ...DefaultConfigs.graphqlServer({ preset: options?.preset, overrides: options?.overrides, }), @@ -452,9 +452,11 @@ export class DefaultConfigs { ); return { - port: config.graphqlPort, - host: config.graphqlHost, - graphiql: config.graphiqlEnabled, + GraphqlServer: { + port: config.graphqlPort, + host: config.graphqlHost, + graphiql: config.graphiqlEnabled, + }, }; } From cfafd175c308e3112249b375d42f64ce029d7296 Mon Sep 17 00:00:00 2001 From: stanlou Date: Wed, 28 Jan 2026 16:55:36 +0300 Subject: [PATCH 10/14] extend CLI with interactive setup and operational commands --- package-lock.json | 195 +++++++-- packages/cli/package.json | 16 +- packages/cli/src/index.ts | 358 ++++++++++++++++- packages/cli/src/scripts/bridge/deposit.ts | 183 +++++++++ packages/cli/src/scripts/bridge/redeem.ts | 151 +++++++ packages/cli/src/scripts/bridge/withdraw.ts | 78 ++++ .../cli/src/scripts/env/create-environment.ts | 102 +++++ packages/cli/src/scripts/explorer/start.ts | 57 +++ packages/cli/src/scripts/generateKeys.ts | 21 + .../graphqlDocs}/generateGqlDocs.ts | 4 +- packages/cli/src/scripts/lightnet/faucet.ts | 79 ++++ .../src/scripts/lightnet/wait-for-network.ts | 41 ++ .../cli/src/scripts/lightnetInitialize.ts | 32 ++ .../src/scripts/settlement/deploy-token.ts | 248 ++++++++++++ packages/cli/src/scripts/settlement/deploy.ts | 86 ++++ packages/cli/src/utils/create-environment.ts | 372 ++++++++++++++++++ .../src/{utils.ts => utils/graphqlDocs.ts} | 3 + packages/cli/src/utils/loadEnv.ts | 65 +++ packages/cli/src/utils/loadUserModules.ts | 60 +++ packages/explorer/src/config.ts | 3 +- packages/stack/src/presets/modules/index.ts | 9 + 21 files changed, 2122 insertions(+), 41 deletions(-) create mode 100644 packages/cli/src/scripts/bridge/deposit.ts create mode 100644 packages/cli/src/scripts/bridge/redeem.ts create mode 100644 packages/cli/src/scripts/bridge/withdraw.ts create mode 100644 packages/cli/src/scripts/env/create-environment.ts create mode 100644 packages/cli/src/scripts/explorer/start.ts create mode 100644 packages/cli/src/scripts/generateKeys.ts rename packages/cli/src/{commands => scripts/graphqlDocs}/generateGqlDocs.ts (94%) create mode 100644 packages/cli/src/scripts/lightnet/faucet.ts create mode 100644 packages/cli/src/scripts/lightnet/wait-for-network.ts create mode 100644 packages/cli/src/scripts/lightnetInitialize.ts create mode 100644 packages/cli/src/scripts/settlement/deploy-token.ts create mode 100644 packages/cli/src/scripts/settlement/deploy.ts create mode 100644 packages/cli/src/utils/create-environment.ts rename packages/cli/src/{utils.ts => utils/graphqlDocs.ts} (98%) create mode 100644 packages/cli/src/utils/loadEnv.ts create mode 100644 packages/cli/src/utils/loadUserModules.ts diff --git a/package-lock.json b/package-lock.json index 5c83cdcdb..61f19dbb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7144,6 +7144,16 @@ "ink": "*" } }, + "node_modules/@types/inquirer": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz", + "integrity": "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "license": "MIT" @@ -7351,6 +7361,15 @@ "@types/node": "*" } }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "dev": true, @@ -8808,7 +8827,6 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "dev": true, "funding": [ { "type": "github", @@ -8929,7 +8947,6 @@ }, "node_modules/bl": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -9101,7 +9118,6 @@ }, "node_modules/buffer": { "version": "5.7.1", - "dev": true, "funding": [ { "type": "github", @@ -9507,7 +9523,6 @@ }, "node_modules/chardet": { "version": "0.7.0", - "dev": true, "license": "MIT" }, "node_modules/cheerio": { @@ -9665,7 +9680,6 @@ }, "node_modules/cli-spinners": { "version": "2.6.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9756,7 +9770,6 @@ }, "node_modules/clone": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -10752,7 +10765,6 @@ }, "node_modules/defaults": { "version": "1.0.4", - "dev": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -12467,7 +12479,6 @@ }, "node_modules/external-editor": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "chardet": "^0.7.0", @@ -14930,7 +14941,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "dev": true, "funding": [ { "type": "github", @@ -16129,7 +16139,6 @@ }, "node_modules/is-interactive": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16400,7 +16409,6 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -19555,6 +19563,11 @@ "license": "MIT", "optional": true }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "license": "MIT" @@ -19572,7 +19585,6 @@ }, "node_modules/log-symbols": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -19587,7 +19599,6 @@ }, "node_modules/log-symbols/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -19601,7 +19612,6 @@ }, "node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -19616,7 +19626,6 @@ }, "node_modules/log-symbols/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -19627,12 +19636,10 @@ }, "node_modules/log-symbols/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/log-symbols/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -19640,7 +19647,6 @@ }, "node_modules/log-symbols/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -22451,7 +22457,6 @@ }, "node_modules/ora": { "version": "5.4.1", - "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -22473,7 +22478,6 @@ }, "node_modules/ora/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -22487,7 +22491,6 @@ }, "node_modules/ora/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -22502,7 +22505,6 @@ }, "node_modules/ora/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -22513,12 +22515,10 @@ }, "node_modules/ora/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/ora/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -22526,7 +22526,6 @@ }, "node_modules/ora/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -24394,7 +24393,6 @@ }, "node_modules/readable-stream": { "version": "3.6.2", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -24742,7 +24740,6 @@ }, "node_modules/rxjs": { "version": "7.8.1", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -25762,7 +25759,6 @@ }, "node_modules/string_decoder": { "version": "1.3.0", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -26608,7 +26604,6 @@ }, "node_modules/tmp": { "version": "0.0.33", - "dev": true, "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" @@ -27794,7 +27789,6 @@ }, "node_modules/wcwidth": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" @@ -28413,6 +28407,12 @@ "version": "0.1.0", "license": "ISC", "dependencies": { + "@inquirer/figures": "^2.0.3", + "dotenv": "^17.2.3", + "inquirer": "^9.3.0", + "kleur": "^4.1.5", + "mina-fungible-token": "^1.1.0", + "reflect-metadata": "^0.1.13", "spectaql": "3.0.5", "ts-node": "^10.9.1", "yargs": "17.7.2" @@ -28421,18 +28421,148 @@ "proto-kit": "bin/protokit-cli.js" }, "devDependencies": { + "@types/inquirer": "^9.0.9", "@types/node": "^20.19.24", "@types/yargs": "17.0.32" }, "peerDependencies": { "@proto-kit/api": "*", "@proto-kit/common": "*", + "@proto-kit/explorer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", - "o1js": "^2.10.0" + "@proto-kit/stack": "*", + "o1js": "^2.10.0", + "tsyringe": "^4.10.0" + } + }, + "packages/cli/node_modules/@inquirer/figures": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.3.tgz", + "integrity": "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "packages/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/cli/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "packages/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "packages/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "packages/cli/node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "packages/cli/node_modules/inquirer": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.0.tgz", + "integrity": "sha512-zdopqPUKWmnOcaBJYMMtjqWCB2HHXrteAou9tCYgkTJu01QheLfYOrkzigDfidPBtCizmkdpSU0fp2DKaMdFPA==", + "dependencies": { + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "picocolors": "^1.0.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/cli/node_modules/inquirer/node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "engines": { + "node": ">=18" + } + }, + "packages/cli/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "packages/cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "packages/cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" + } + }, + "packages/cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, "packages/common": { @@ -28779,7 +28909,8 @@ "dependencies": { "@prisma/client": "^5.19.1", "mina-fungible-token": "^1.1.0", - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "type-graphql": "2.0.0-rc.2" }, "devDependencies": { "@jest/globals": "^29.5.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 7fbd14143..a37868ff3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -21,21 +21,31 @@ "author": "", "license": "ISC", "dependencies": { - "yargs": "17.7.2", + "@inquirer/figures": "^2.0.3", + "dotenv": "^17.2.3", + "inquirer": "^9.3.0", + "kleur": "^4.1.5", + "mina-fungible-token": "^1.1.0", + "reflect-metadata": "^0.1.13", + "spectaql": "3.0.5", "ts-node": "^10.9.1", - "spectaql": "3.0.5" + "yargs": "17.7.2" }, "peerDependencies": { "@proto-kit/api": "*", "@proto-kit/common": "*", + "@proto-kit/explorer": "*", "@proto-kit/library": "*", "@proto-kit/module": "*", "@proto-kit/protocol": "*", "@proto-kit/sdk": "*", "@proto-kit/sequencer": "*", - "o1js": "^2.10.0" + "@proto-kit/stack": "*", + "o1js": "^2.10.0", + "tsyringe": "^4.10.0" }, "devDependencies": { + "@types/inquirer": "^9.0.9", "@types/node": "^20.19.24", "@types/yargs": "17.0.32" } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index fbd97cd2c..d8f53fad7 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node + +/* eslint-disable no-console */ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; -import { generateGqlDocsCommand } from "./commands/generateGqlDocs"; +import { parseEnvArgs } from "./utils/loadEnv"; process.removeAllListeners("warning"); process.env.NODE_NO_WARNINGS = "1"; await yargs(hideBin(process.argv)) + .scriptName("proto-kit") + .usage("$0 [options]") + .strict() .command( "generate-gql-docs", - "generate GraphQL docs", + "Generate GraphQL docs", (yarg) => yarg .option("port", { @@ -33,6 +38,9 @@ await yargs(hideBin(process.argv)) }), async (args) => { try { + const { generateGqlDocsCommand } = await import( + "./scripts/graphqlDocs/generateGqlDocs" + ); await generateGqlDocsCommand(args); process.exit(0); } catch (error) { @@ -41,7 +49,349 @@ await yargs(hideBin(process.argv)) } } ) - .demandCommand() - .help() + .command( + "generate-keys [count]", + "Generate private/public key pairs for development", + (yarg) => + yarg.positional("count", { + type: "number", + default: 1, + describe: "number of keys to generate", + }), + async (args) => { + try { + const { generateKeysCommand } = await import("./scripts/generateKeys"); + await generateKeysCommand({ count: args.count }); + process.exit(0); + } catch (error) { + console.error("Failed to generate keys:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:wait-for-network", + "Wait for lightnet network to be ready\n\nRequires: MINA_NODE_GRAPHQL_HOST, MINA_NODE_GRAPHQL_PORT", + (yarg) => + yarg + .option("env-path", { + type: "string", + describe: "path to .env file", + }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: lightnetWaitForNetworkScript } = await import( + "./scripts/lightnet/wait-for-network" + ); + await lightnetWaitForNetworkScript({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to wait for network:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:faucet ", + "Send MINA to an account from the lightnet faucet", + (yarg) => + yarg + .positional("publicKey", { + type: "string", + describe: "public key to send MINA to", + demandOption: true, + }) + .option("env-path", { + type: "string", + describe: "path to .env file", + }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: lightnetFaucetScript } = await import( + "./scripts/lightnet/faucet" + ); + await lightnetFaucetScript(args.publicKey); + process.exit(0); + } catch (error) { + console.error("Failed to send funds from faucet:", error); + process.exit(1); + } + } + ) + .command( + "settlement:deploy", + "Deploy settlement contracts\n\nRequires: PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: settlementDeployScript } = await import( + "./scripts/settlement/deploy" + ); + await settlementDeployScript({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to deploy settlement:", error); + process.exit(1); + } + } + ) + .command( + "settlement:token:deploy [mintAmount]", + "Deploy custom fungible token for settlement\n\nRequires: PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY, PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenSymbol", { type: "string", demandOption: true }) + .positional("feepayerKey", { type: "string", demandOption: true }) + .positional("receiverPublicKey", { + type: "string", + demandOption: true, + }) + .positional("mintAmount", { type: "number", default: 0 }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: settlementTokenDeployScript } = await import( + "./scripts/settlement/deploy-token" + ); + await settlementTokenDeployScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenSymbol: args.tokenSymbol, + feepayerKey: args.feepayerKey, + receiverPublicKey: args.receiverPublicKey, + mintAmount: args.mintAmount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to deploy settlement token:", error); + process.exit(1); + } + } + ) + .command( + "lightnet:initialize", + "Initialize lightnet: wait for network, fund accounts, and deploy settlement\n\nRequires: MINA_NODE_GRAPHQL_HOST, MINA_NODE_GRAPHQL_PORT, MINA_ARCHIVE_GRAPHQL_HOST, MINA_ARCHIVE_GRAPHQL_PORT, MINA_ACCOUNT_MANAGER_HOST, MINA_ACCOUNT_MANAGER_PORT, PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY, PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { lightnetInitializeCommand } = await import( + "./scripts/lightnetInitialize" + ); + await lightnetInitializeCommand({ + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }); + process.exit(0); + } catch (error) { + console.error("Failed to initialize lightnet:", error); + process.exit(1); + } + } + ) + .command( + "bridge:deposit ", + "Deposit tokens to the bridge\n\nRequires: PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY (for custom tokens), PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY, PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("fromKey", { type: "string", demandOption: true }) + .positional("toKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeDepositScript } = await import( + "./scripts/bridge/deposit" + ); + await bridgeDepositScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + fromKey: args.fromKey, + toKey: args.toKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to deposit to bridge:", error); + process.exit(1); + } + } + ) + .command( + "bridge:redeem ", + "Redeem tokens from the bridge\n\nRequires: PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("toKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeRedeemScript } = await import( + "./scripts/bridge/redeem" + ); + await bridgeRedeemScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + toKey: args.toKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to redeem from bridge:", error); + process.exit(1); + } + } + ) + .command( + "bridge:withdraw ", + "Withdraw tokens\n\nRequires: NEXT_PUBLIC_PROTOKIT_GRAPHQL_URL", + (yarg) => + yarg + .positional("tokenId", { type: "string", demandOption: true }) + .positional("senderKey", { type: "string", demandOption: true }) + .positional("amount", { type: "number", demandOption: true }) + .option("env-path", { type: "string", describe: "path to .env file" }) + .option("env", { + type: "string", + array: true, + describe: "environment variables as KEY=value", + }), + async (args) => { + try { + const { default: bridgeWithdrawScript } = await import( + "./scripts/bridge/withdraw" + ); + await bridgeWithdrawScript( + { + envPath: args["env-path"], + envVars: parseEnvArgs(args.env ?? []), + }, + { + tokenId: args.tokenId, + senderKey: args.senderKey, + amount: args.amount, + } + ); + process.exit(0); + } catch (error) { + console.error("Failed to withdraw from bridge:", error); + process.exit(1); + } + } + ) + .command( + "env:create", + "Create a new environment configuration with guided wizard", + () => ({}), + async () => { + try { + const { default: createEnvironmentScript } = await import( + "./scripts/env/create-environment" + ); + await createEnvironmentScript(); + process.exit(0); + } catch (error) { + console.error("Failed to create environment:", error); + process.exit(1); + } + } + ) + .command( + "explorer:start", + "Start the explorer UI", + (yarg) => + yarg + .option("port", { + alias: "p", + type: "number", + default: 5003, + describe: "port to run the explorer on", + }) + .option("indexer-url", { + type: "string", + describe: "GraphQL endpoint URL for the indexer", + }), + async (args) => { + try { + const { default: explorerStartScript } = await import( + "./scripts/explorer/start" + ); + await explorerStartScript(args.port, args["indexer-url"]); + process.exit(0); + } catch (error) { + console.error("Failed to start explorer:", error); + process.exit(1); + } + } + ) + .demandCommand( + 1, + "You must specify a command. Use --help to see available commands." + ) + .help("help") + .alias("help", "h") + .option("help", { describe: "Show help" }) .strict() .parse(); +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/bridge/deposit.ts b/packages/cli/src/scripts/bridge/deposit.ts new file mode 100644 index 000000000..0f222b80a --- /dev/null +++ b/packages/cli/src/scripts/bridge/deposit.ts @@ -0,0 +1,183 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ + +import { + BridgingModule, + MinaTransactionSender, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; +import { + AccountUpdate, + fetchAccount, + Field, + Mina, + PrivateKey, + Provable, + PublicKey, + UInt64, +} from "o1js"; +import { FungibleToken } from "mina-fungible-token"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeDepositArgs { + tokenId: string; + fromKey: string; + toKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeDepositArgs +) { + if (!bridgeArgs) { + throw new Error( + "Bridge deposit arguments required: tokenId, fromKey, toKey, amount" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const fromPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.fromKey] ?? bridgeArgs.fromKey + ); + const toPublicKey = PublicKey.fromBase58( + process.env[bridgeArgs.toKey] ?? bridgeArgs.toKey + ); + const amount = bridgeArgs.amount * 1e9; + const fee = 0.1 * 1e9; + + const isCustomToken = tokenId.toBigInt() !== 1n; + const tokenOwnerPrivateKey = isCustomToken + ? PrivateKey.fromBase58(getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY")) + : PrivateKey.random(); + const bridgeContractKey = isCustomToken + ? PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY") + ) + : PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") + ); + + Provable.log("Preparing to deposit", { + tokenId, + fromPrivateKey, + toPublicKey, + amount, + fee, + }); + + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.inMemoryDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + + const { settlement, dispatch } = settlementModule.getContracts(); + + await fetchAccount({ publicKey: fromPrivateKey.toPublicKey() }); + await fetchAccount({ publicKey: settlement.address }); + await fetchAccount({ publicKey: dispatch.address }); + const bridgeAddress = await bridgingModule.getBridgeAddress(tokenId); + await fetchAccount({ publicKey: bridgeAddress!, tokenId: tokenId }); + await fetchAccount({ publicKey: bridgeAddress!, tokenId: tokenId }); + + const attestation = + await bridgingModule.getDepositContractAttestation(tokenId); + + console.log("Forging transaction..."); + const tx = await Mina.transaction( + { + memo: "User deposit", + sender: fromPrivateKey.toPublicKey(), + fee, + }, + async () => { + const au = AccountUpdate.createSigned( + fromPrivateKey.toPublicKey(), + tokenId + ); + au.balance.subInPlace(UInt64.from(amount)); + + await dispatch.deposit( + UInt64.from(amount), + tokenId, + bridgeContractKey.toPublicKey(), + attestation, + toPublicKey + ); + + if (isCustomToken) { + await new FungibleToken( + tokenOwnerPrivateKey.toPublicKey() + )!.approveAccountUpdates([au, dispatch.self]); + } + } + ); + console.log(tx.toPretty()); + + settlementModule.signTransaction( + tx, + [fromPrivateKey], + [tokenOwnerPrivateKey], + [dispatch.address] + ); + + console.log("Sending..."); + console.log(tx.toPretty()); + + const { hash } = await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log(`Deposit transaction included in a block: ${hash}`); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/bridge/redeem.ts b/packages/cli/src/scripts/bridge/redeem.ts new file mode 100644 index 000000000..35bc126d5 --- /dev/null +++ b/packages/cli/src/scripts/bridge/redeem.ts @@ -0,0 +1,151 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { + BridgingModule, + MinaTransactionSender, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + AccountUpdate, + fetchAccount, + Field, + Mina, + PrivateKey, + Provable, + UInt64, +} from "o1js"; +import { FungibleToken } from "mina-fungible-token"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeRedeemArgs { + tokenId: string; + toKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeRedeemArgs +) { + if (!bridgeArgs) { + throw new Error("Bridge redeem arguments required: tokenId, toKey, amount"); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const toPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.toKey] ?? bridgeArgs.toKey + ); + const amount = bridgeArgs.amount * 1e9; + const fee = 0.1 * 1e9; + + const isCustomToken = tokenId.toBigInt() !== 1n; + const tokenOwnerPrivateKey = isCustomToken + ? PrivateKey.fromBase58(getRequiredEnv("PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY")) + : PrivateKey.random(); + + Provable.log("Preparing to redeem", { + tokenId, + to: toPrivateKey.toPublicKey(), + amount, + fee, + }); + + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.inMemoryDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled); + + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + + const bridgeContract = await bridgingModule.getBridgeContract(tokenId); + + const customAcc = await fetchAccount({ + publicKey: toPrivateKey.toPublicKey(), + tokenId: bridgeContract.deriveTokenId(), + }); + + Provable.log("Custom account", customAcc.account?.balance); + + console.log("Forging transaction..."); + const tx = await Mina.transaction( + { + sender: toPrivateKey.toPublicKey(), + fee, + }, + async () => { + const au = AccountUpdate.createSigned( + toPrivateKey.toPublicKey(), + tokenId + ); + au.balance.addInPlace(UInt64.from(amount)); + + await bridgeContract.redeem(au); + + if (isCustomToken) { + await new FungibleToken( + tokenOwnerPrivateKey.toPublicKey() + )!.approveAccountUpdate(bridgeContract.self); + } + } + ); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + settlementModule.signTransaction(tx, [toPrivateKey], [tokenOwnerPrivateKey]); + + console.log("Sending..."); + + const { hash } = await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log(`Redeem transaction included in a block: ${hash}`); + console.log(tx.toPretty()); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/bridge/withdraw.ts b/packages/cli/src/scripts/bridge/withdraw.ts new file mode 100644 index 000000000..346576db2 --- /dev/null +++ b/packages/cli/src/scripts/bridge/withdraw.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { ClientAppChain, InMemorySigner } from "@proto-kit/sdk"; +import { Field, PrivateKey, Provable } from "o1js"; +import { UInt64 } from "@proto-kit/library"; +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; + +import { loadEnvironmentVariables, LoadEnvOptions } from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface BridgeWithdrawArgs { + tokenId: string; + senderKey: string; + amount: number; +} + +export default async function ( + options?: LoadEnvOptions, + bridgeArgs?: BridgeWithdrawArgs +) { + if (!bridgeArgs) { + throw new Error( + "Bridge withdraw arguments required: tokenId, senderKey, amount" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const tokenId = Field(bridgeArgs.tokenId); + const amount = UInt64.from(bridgeArgs.amount * 1e9); + const appChain = ClientAppChain.fromRemoteEndpoint( + Runtime.from(runtime.modules), + Protocol.from({ ...protocol.modules, ...protocol.settlementModules }), + InMemorySigner + ); + + appChain.configurePartial({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + GraphqlClient: { + url: process.env.NEXT_PUBLIC_PROTOKIT_GRAPHQL_URL, + }, + }); + + await appChain.start(); + + const senderPrivateKey = PrivateKey.fromBase58( + process.env[bridgeArgs.senderKey] ?? bridgeArgs.senderKey + ); + const senderPublicKey = senderPrivateKey.toPublicKey(); + const signer = appChain.resolve("Signer"); + signer.config.signer = senderPrivateKey; + + Provable.log("debug", { + senderPrivateKey, + senderPublicKey, + amount, + tokenId, + }); + + const withdrawals = appChain.runtime.resolve("Withdrawals"); + const tx = await appChain.transaction(senderPublicKey, async () => { + await withdrawals.withdraw(senderPublicKey, amount, tokenId); + }); + + await tx.sign(); + await tx.send(); + + console.log("withdrawal tx sent"); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/env/create-environment.ts b/packages/cli/src/scripts/env/create-environment.ts new file mode 100644 index 000000000..ea3c3d576 --- /dev/null +++ b/packages/cli/src/scripts/env/create-environment.ts @@ -0,0 +1,102 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import * as fs from "fs"; +import * as path from "path"; + +import { cyan, green, red, bold } from "kleur/colors"; + +import { + copyAndUpdateEnvFile, + generateChainConfig, + generateIndexerConfig, + generateProcessorConfig, + icons, + promptUser, +} from "../../utils/create-environment"; + +export default async function () { + try { + const answers = await promptUser(); + + const cwd = process.cwd(); + const envDir = path.join( + cwd, + "src/core/environments", + answers.environmentName + ); + + if (!fs.existsSync(envDir)) { + fs.mkdirSync(envDir, { recursive: true }); + } + + const chainConfigPath = path.join(envDir, "chain.config.ts"); + const indexerConfigPath = path.join(envDir, "indexer.config.ts"); + const processorConfigPath = path.join(envDir, "processor.config.ts"); + + if (fs.existsSync(chainConfigPath)) { + console.log(`\nEnvironment already exists at ${envDir}`); + return; + } + + copyAndUpdateEnvFile(answers, cwd, envDir); + const chainConfig = generateChainConfig(answers); + fs.writeFileSync(chainConfigPath, chainConfig); + + if (answers.includeIndexer) { + const indexerConfig = generateIndexerConfig(answers); + if (indexerConfig) { + fs.writeFileSync(indexerConfigPath, indexerConfig); + } + } + + if (answers.includeProcessor && answers.includeIndexer) { + const processorConfig = generateProcessorConfig(answers); + if (processorConfig) { + fs.writeFileSync(processorConfigPath, processorConfig); + } + } + + console.log( + `\n${bold(green(" ╔════════════════════════════════════════╗"))}` + ); + console.log( + `${bold(green(" ║ ✓ Environment Created Successfully ║"))}` + ); + console.log( + `${bold(green(" ╚════════════════════════════════════════╝"))}` + ); + console.log(""); + + console.log(`${bold("Location:")}`); + console.log(` ${cyan(envDir)}\n`); + + console.log(`${bold("Generated Files:")}`); + console.log(` ${green(icons.checkmark)} .env`); + console.log(` ${green(icons.checkmark)} chain.config.ts`); + if (answers.includeIndexer) { + console.log(` ${green(icons.checkmark)} indexer.config.ts`); + } + if (answers.includeProcessor && answers.includeIndexer) { + console.log(` ${green(icons.checkmark)} processor.config.ts`); + } + + console.log(`\n${bold("Next Steps:")}`); + const cdCommand = `cd ${path.relative(process.cwd(), envDir)}`; + console.log(` 1. ${cyan(cdCommand)}`); + console.log(` 2. Update environment variables in ${cyan(".env")}`); + console.log( + ` 3. Add the following script to your root ${cyan("package.json")}:` + ); + const scriptCommand = `"env:${answers.environmentName}": "dotenv -e ./packages/chain/src/core/environments/${answers.environmentName}/.env -- pnpm"`; + console.log(`${cyan(scriptCommand)}`); + console.log(" 4. Start your application\n"); + } catch (error) { + console.log(`\n${bold(red("✗ Error"))}`); + console.log(`${red("-".repeat(50))}`); + console.error(` ${error}`); + console.log(`${red("-".repeat(50))}\n`); + process.exit(1); + } +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/explorer/start.ts b/packages/cli/src/scripts/explorer/start.ts new file mode 100644 index 000000000..4ce12db6f --- /dev/null +++ b/packages/cli/src/scripts/explorer/start.ts @@ -0,0 +1,57 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { spawn } from "child_process"; +import path from "path"; +import { fileURLToPath } from "url"; + +export default async function (port?: number, indexerUrl?: string) { + let explorerDir: string; + + try { + const pkgUrl = await import.meta.resolve( + "@proto-kit/explorer/package.json" + ); + const pkgPath = fileURLToPath(pkgUrl); + explorerDir = path.dirname(pkgPath); + } catch (error) { + console.error("Failed to find @proto-kit/explorer package."); + throw error; + } + + return await new Promise((resolve, reject) => { + const child = spawn("npm", ["run", "dev", "--", "-p", String(port)], { + cwd: explorerDir, + stdio: "inherit", + env: { + ...process.env, + NODE_OPTIONS: "", + NEXT_PUBLIC_INDEXER_URL: indexerUrl, + }, + }); + + child.on("error", (error) => { + console.error("Failed to start explorer:", error); + reject(error); + }); + + child.on("exit", (code) => { + if (code !== null && code !== 0 && code !== 143) { + reject(new Error(`Explorer process exited with code ${code}`)); + } else { + resolve(); + } + }); + + process.on("SIGINT", () => { + child.kill(); + resolve(); + }); + + process.on("SIGTERM", () => { + child.kill(); + resolve(); + }); + }); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/generateKeys.ts b/packages/cli/src/scripts/generateKeys.ts new file mode 100644 index 000000000..d4d5fd663 --- /dev/null +++ b/packages/cli/src/scripts/generateKeys.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-console */ + +import { PrivateKey } from "o1js"; + +type GenerateKeysArgs = { + count?: number; +}; + +export async function generateKeysCommand(args: GenerateKeysArgs) { + const count = args.count ?? 1; + console.log(`Generated ${count} keys for development purposes:`); + console.log("-".repeat(70)); + for (let i = 0; i < count; i++) { + const privateKey = PrivateKey.random(); + const publicKey = privateKey.toPublicKey(); + console.log("Private key:", privateKey.toBase58()); + console.log("Public key:", publicKey.toBase58()); + console.log("-".repeat(70)); + } +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/commands/generateGqlDocs.ts b/packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts similarity index 94% rename from packages/cli/src/commands/generateGqlDocs.ts rename to packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts index f9fcedc17..2ab3c680d 100644 --- a/packages/cli/src/commands/generateGqlDocs.ts +++ b/packages/cli/src/scripts/graphqlDocs/generateGqlDocs.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { BlockStorageNetworkStateModule, InMemoryTransactionSender, @@ -21,7 +22,7 @@ import { } from "@proto-kit/api"; import { Runtime } from "@proto-kit/module"; -import { generateGqlDocs } from "../utils"; +import { generateGqlDocs } from "../../utils/graphqlDocs"; export async function generateGqlDocsCommand(args: { empty: boolean; @@ -76,3 +77,4 @@ export async function generateGqlDocsCommand(args: { await generateGqlDocs(args.url); } } +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/lightnet/faucet.ts b/packages/cli/src/scripts/lightnet/faucet.ts new file mode 100644 index 000000000..b3af98c63 --- /dev/null +++ b/packages/cli/src/scripts/lightnet/faucet.ts @@ -0,0 +1,79 @@ +/* eslint-disable func-names */ +import { + AccountUpdate, + fetchAccount, + Lightnet, + Mina, + Provable, + PublicKey, +} from "o1js"; + +import "reflect-metadata"; +import { getRequiredEnv } from "../../utils/loadEnv"; + +export default async function (publicKey: string) { + // configuration + const fee = 0.1 * 1e9; + const fundingAmount = 1000 * 1e9; + + const net = Mina.Network({ + mina: `${getRequiredEnv("MINA_NODE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_NODE_GRAPHQL_PORT")}/graphql`, + archive: `${getRequiredEnv("MINA_ARCHIVE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_ARCHIVE_GRAPHQL_PORT")}/graphql`, + lightnetAccountManager: `${getRequiredEnv("MINA_ACCOUNT_MANAGER_HOST")}:${getRequiredEnv("MINA_ACCOUNT_MANAGER_PORT")}`, + }); + + Mina.setActiveInstance(net); + + // get the source account from the account manager + const pair = await Lightnet.acquireKeyPair({ + isRegularAccount: true, + }); + + // which account to drip to + const keyArg = process.env[publicKey] ?? publicKey; + + if (keyArg?.length === 0) { + throw new Error("No key provided"); + } + + const key = PublicKey.fromBase58(keyArg); + + await fetchAccount({ publicKey: pair.publicKey }); + + Provable.log( + `Dripping ${fundingAmount / 1e9} MINA from ${pair.publicKey.toBase58()} to ${key.toBase58()}` + ); + + const tx = await Mina.transaction( + { + sender: pair.publicKey, + fee, + }, + async () => { + const account = await fetchAccount({ publicKey: key }); + // if the destination account does not exist yet, pay the creation fee for it + if (account.error) { + AccountUpdate.fundNewAccount(pair.publicKey); + } + + AccountUpdate.createSigned(pair.publicKey).balance.subInPlace( + fundingAmount + ); + AccountUpdate.create(key).balance.addInPlace(fundingAmount); + } + ); + + tx.sign([pair.privateKey]); + + const sentTx = await tx.send(); + await sentTx.wait(); + + Provable.log( + `Funded account ${key.toBase58()} with ${fundingAmount / 1e9} MINA` + ); + + await Lightnet.releaseKeyPair({ + publicKey: pair.publicKey.toBase58(), + }); +} +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/lightnet/wait-for-network.ts b/packages/cli/src/scripts/lightnet/wait-for-network.ts new file mode 100644 index 000000000..9f7ccbae6 --- /dev/null +++ b/packages/cli/src/scripts/lightnet/wait-for-network.ts @@ -0,0 +1,41 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { sleep } from "@proto-kit/common"; +import { fetchLastBlock, Provable } from "o1js"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; + +const maxAttempts = 24; +const delay = 5000; + +export default async function (options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + const graphqlEndpoint = `${getRequiredEnv("MINA_NODE_GRAPHQL_HOST")}:${getRequiredEnv("MINA_NODE_GRAPHQL_PORT")}/graphql`; + let lastBlock; + let attempt = 0; + console.log("Waiting for network to be ready..."); + while (!lastBlock) { + attempt += 1; + if (attempt > maxAttempts) { + throw new Error( + `Network was still not ready after ${(delay / 1000) * (attempt - 1)}s` + ); + } + try { + // eslint-disable-next-line no-await-in-loop + lastBlock = await fetchLastBlock(graphqlEndpoint); + } catch (e) { + // continue + } + // eslint-disable-next-line no-await-in-loop + await sleep(delay); + } + + Provable.log("Network is ready", lastBlock); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/lightnetInitialize.ts b/packages/cli/src/scripts/lightnetInitialize.ts new file mode 100644 index 000000000..ab1f97d2f --- /dev/null +++ b/packages/cli/src/scripts/lightnetInitialize.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-console */ + +import { + LoadEnvOptions, + getRequiredEnv, + loadEnvironmentVariables, +} from "../utils/loadEnv"; + +import lightnetWaitForNetworkScript from "./lightnet/wait-for-network"; +import lightnetFaucetScript from "./lightnet/faucet"; +import settlementDeployScript from "./settlement/deploy"; + +export async function lightnetInitializeCommand(options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + + console.log("Step 1: Waiting for network to be ready..."); + await lightnetWaitForNetworkScript(options); + + console.log("Step 2: Funding PROTOKIT_SEQUENCER_PUBLIC_KEY from faucet..."); + await lightnetFaucetScript(getRequiredEnv("PROTOKIT_SEQUENCER_PUBLIC_KEY")); + + console.log("Step 3: Funding TEST_ACCOUNT_1_PUBLIC_KEY from faucet..."); + await lightnetFaucetScript(getRequiredEnv("TEST_ACCOUNT_1_PUBLIC_KEY")); + + console.log("Step 4: Deploying settlement contracts..."); + await settlementDeployScript(options); + + console.log( + "Lightnet initialization complete! Settlement contracts are deployed." + ); +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/scripts/settlement/deploy-token.ts b/packages/cli/src/scripts/settlement/deploy-token.ts new file mode 100644 index 000000000..28c707158 --- /dev/null +++ b/packages/cli/src/scripts/settlement/deploy-token.ts @@ -0,0 +1,248 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + ArchiveNode, + MinaTransactionSender, + ProvenSettlementPermissions, + Sequencer, + SettlementModule, + SignedSettlementPermissions, + AppChain, +} from "@proto-kit/sequencer"; +import { + AccountUpdate, + Bool, + fetchAccount, + Mina, + PrivateKey, + Provable, + PublicKey, + UInt64, + UInt8, +} from "o1js"; +import "reflect-metadata"; +import { container } from "tsyringe"; +import { FungibleToken, FungibleTokenAdmin } from "mina-fungible-token"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { loadEnvironmentVariables, LoadEnvOptions } from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export interface TokenDeployArgs { + tokenSymbol: string; + feepayerKey: string; + receiverPublicKey: string; + mintAmount: number; +} + +export default async function ( + options?: LoadEnvOptions, + tokenArgs?: TokenDeployArgs +) { + if (!tokenArgs) { + throw new Error( + "Token deployment arguments required: tokenSymbol, feepayerKey, receiverPublicKey, [mintAmount]" + ); + } + + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + ...DefaultModules.PrismaRedisDatabase(), + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.prismaRedisDatabase({ + preset: "development", + overrides: { + pruneOnStartup: false, + }, + }), + ...DefaultConfigs.settlementScript({ + preset: "development", + }), + }, + }); + + const chainContainer = container.createChildContainer(); + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled, chainContainer); + const { tokenSymbol } = tokenArgs; + const feepayerPrivateKey = PrivateKey.fromBase58( + process.env[tokenArgs.feepayerKey] ?? tokenArgs.feepayerKey + ); + const receiverPublicKey = PublicKey.fromBase58( + process.env[tokenArgs.receiverPublicKey] ?? tokenArgs.receiverPublicKey + ); + const mintAmount = tokenArgs.mintAmount * 1e9; + const fee = 0.1 * 1e9; + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + const isSignedSettlement = settlementModule.utils.isSignedSettlement(); + + const tokenOwnerKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + const tokenAdminKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_ADMIN_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + const tokenBridgeKey = PrivateKey.fromBase58( + process.env.PROTOKIT_CUSTOM_TOKEN_BRIDGE_PRIVATE_KEY ?? + PrivateKey.random().toBase58() + ); + + await ArchiveNode.waitOnSync(appChain.sequencer.resolve("BaseLayer").config); + + async function deployTokenContracts() { + const permissions = isSignedSettlement + ? new SignedSettlementPermissions() + : new ProvenSettlementPermissions(); + + const tx = await Mina.transaction( + { + sender: feepayerPrivateKey.toPublicKey(), + memo: "Deploy custom token", + fee, + }, + async () => { + AccountUpdate.fundNewAccount(feepayerPrivateKey.toPublicKey(), 3); + + const admin = new FungibleTokenAdmin(tokenAdminKey.toPublicKey()); + await admin.deploy({ + adminPublicKey: feepayerPrivateKey.toPublicKey(), + }); + admin.self.account.permissions.set(permissions.bridgeContractToken()); + + const fungibleToken = new FungibleToken(tokenOwnerKey.toPublicKey()); + await fungibleToken.deploy({ + src: "", + symbol: tokenSymbol, + allowUpdates: false, + }); + fungibleToken!.self.account.permissions.set( + permissions.bridgeContractToken() + ); + + await fungibleToken.initialize( + tokenAdminKey.toPublicKey(), + UInt8.from(9), + Bool(false) + ); + } + ); + console.log("Sending deploy transaction..."); + console.log(tx.toPretty()); + + settlementModule.signTransaction( + tx, + [feepayerPrivateKey, tokenOwnerKey, tokenAdminKey], + [tokenOwnerKey, tokenAdminKey] + ); + + await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + + console.log("Deploy transaction included"); + } + + async function mint() { + const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); + await settlementModule.utils.fetchContractAccounts( + { + address: tokenOwner!.address, + tokenId: tokenOwner!.tokenId, + }, + { + address: tokenOwner!.address, + tokenId: tokenOwner!.deriveTokenId(), + } + ); + + const tx = await Mina.transaction( + { + sender: feepayerPrivateKey.toPublicKey(), + memo: "Mint custom token", + fee, + }, + async () => { + AccountUpdate.fundNewAccount(feepayerPrivateKey.toPublicKey(), 1); + + await tokenOwner!.mint(receiverPublicKey, UInt64.from(mintAmount)); + } + ); + settlementModule.utils.signTransaction( + tx, + [feepayerPrivateKey], + [tokenOwnerKey, tokenAdminKey] + ); + + await appChain.sequencer + .resolveOrFail("TransactionSender", MinaTransactionSender) + .proveAndSendTransaction(tx, "included"); + } + + async function deployBridge() { + const { settlement, dispatch } = settlementModule.getAddresses(); + await fetchAccount({ + publicKey: settlementModule.config.feepayer.toPublicKey(), + }); + await fetchAccount({ publicKey: settlement }); + await fetchAccount({ publicKey: dispatch }); + + const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); + // SetAdminEvent. + await settlementModule.deployTokenBridge( + tokenOwner, + tokenOwnerKey, + tokenBridgeKey, + {} + ); + console.log( + `Token bridge address: ${tokenBridgeKey.toPublicKey().toBase58()} @ ${tokenOwner.deriveTokenId().toString()}` + ); + } + + await deployTokenContracts(); + await mint(); + await deployBridge(); + + console.log( + `Deployed custom token with id ${new FungibleToken(tokenOwnerKey.toPublicKey())!.deriveTokenId()}` + ); + + Provable.log("Deployed and initialized settlement contracts", { + settlement: PrivateKey.fromBase58( + process.env.PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY! + ).toPublicKey(), + dispatcher: PrivateKey.fromBase58( + process.env.PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY! + ).toPublicKey(), + }); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/scripts/settlement/deploy.ts b/packages/cli/src/scripts/settlement/deploy.ts new file mode 100644 index 000000000..9e0530fb0 --- /dev/null +++ b/packages/cli/src/scripts/settlement/deploy.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ +/* eslint-disable func-names */ + +import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { + InMemoryDatabase, + Sequencer, + SettlementModule, + AppChain, +} from "@proto-kit/sequencer"; +import { PrivateKey, Provable } from "o1js"; +import "reflect-metadata"; +import { container } from "tsyringe"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { + loadEnvironmentVariables, + getRequiredEnv, + LoadEnvOptions, +} from "../../utils/loadEnv"; +import { loadUserModules } from "../../utils/loadUserModules"; + +export default async function (options?: LoadEnvOptions) { + loadEnvironmentVariables(options); + const { runtime, protocol } = await loadUserModules(); + const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...protocol.settlementModules, + }), + Sequencer: Sequencer.from({ + Database: InMemoryDatabase, + ...DefaultModules.settlementScript(), + }), + }); + + appChain.configure({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...protocol.settlementModulesConfig, + }, + Sequencer: { + ...DefaultConfigs.inMemoryDatabase(), + ...DefaultConfigs.settlementScript({ preset: "development" }), + }, + }); + + const chainContainer = container.createChildContainer(); + const proofsEnabled = process.env.PROTOKIT_PROOFS_ENABLED === "true"; + await appChain.start(proofsEnabled, chainContainer); + + const settlementModule = appChain.sequencer.resolveOrFail( + "SettlementModule", + SettlementModule + ); + + console.log("Deploying settlement contracts..."); + + await settlementModule.deploy( + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + ), + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + ), + PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") + ) + ); + + Provable.log("Deployed and initialized settlement contracts", { + settlement: PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + ).toPublicKey(), + dispatcher: PrivateKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + ).toPublicKey(), + }); + + await appChain.close(); +} +/* eslint-enable no-console */ +/* eslint-enable func-names */ diff --git a/packages/cli/src/utils/create-environment.ts b/packages/cli/src/utils/create-environment.ts new file mode 100644 index 000000000..6f4b5baad --- /dev/null +++ b/packages/cli/src/utils/create-environment.ts @@ -0,0 +1,372 @@ +import * as fs from "fs"; +import * as path from "path"; + +import inquirer from "inquirer"; +import figuresLib from "@inquirer/figures"; +import { cyan, green, blue, gray, bold } from "kleur/colors"; + +/* eslint-disable no-console */ + +export const icons = { + checkmark: figuresLib.tick, + cross: figuresLib.cross, + arrow: figuresLib.pointerSmall, + circle: figuresLib.bullet, + square: figuresLib.square, +}; + +export type PresetType = "inmemory" | "development" | "sovereign"; + +export interface WizardAnswers { + environmentName: string; + preset: PresetType; + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; +} + +export const PRESET_ENV_NAMES: Record = { + inmemory: "inmemory", + development: "development", + sovereign: "sovereign", +}; + +export const PRESET_DESCRIPTIONS: Record = { + inmemory: "Fast testing and development environment", + development: "Local development environment", + sovereign: "Production-ready environment", +}; + +export const PRESET_LABELS: Record = { + inmemory: "In-Memory", + development: "Development", + sovereign: "Sovereign", +}; + +export function printHeader(): void { + console.log(bold(cyan(" ╔════════════════════════════════════════╗"))); + console.log(bold(cyan(" ║ 🚀 Proto-Kit Environment Wizard ║"))); + console.log(bold(cyan(" ╚════════════════════════════════════════╝"))); + console.log(""); +} + +export function printSection(title: string): void { + const section = `${icons.square} ${title}`; + console.log(`\n${bold(blue(section))}`); + console.log(gray("-".repeat(50))); + console.log(""); +} + +export async function selectPreset(): Promise { + const presetTypes: PresetType[] = ["inmemory", "development", "sovereign"]; + const answer = await inquirer.prompt<{ preset: PresetType }>([ + { + type: "list", + name: "preset", + message: "Select Environment Preset", + choices: presetTypes.map((type) => { + const label = PRESET_LABELS[type]; + const description = PRESET_DESCRIPTIONS[type]; + return { + name: `${label} - ${description}`, + value: type, + }; + }), + }, + ]); + + return answer.preset; +} + +export async function selectModules(preset: PresetType): Promise<{ + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; +}> { + const isInMemory = preset === "inmemory"; + + const answers = await inquirer.prompt<{ + includeIndexer: boolean; + includeProcessor: boolean; + includeMetrics: boolean; + settlementEnabled: boolean; + }>([ + { + type: "confirm", + name: "includeIndexer", + message: "Include Indexer Module?", + default: false, + when: !isInMemory, + }, + { + type: "confirm", + name: "includeProcessor", + message: "Include Processor Module? (requires Indexer)", + default: false, + when: (ans: WizardAnswers) => ans.includeIndexer === true, + }, + { + type: "confirm", + name: "includeMetrics", + message: "Include OpenTelemetry Metrics?", + default: false, + }, + { + type: "confirm", + name: "settlementEnabled", + message: "Enable Settlement Module?", + default: false, + }, + ]); + if (isInMemory && answers.includeIndexer === false) { + answers.includeIndexer = false; + } + if (answers.includeProcessor === false) { + answers.includeProcessor = false; + } + + return answers; +} + +export async function promptUser(): Promise { + printHeader(); + + printSection("Environment Configuration"); + + const answers = await inquirer.prompt<{ environmentName: string }>([ + { + type: "input", + name: "environmentName", + message: "Environment name (e.g 'production')", + validate: (input: string) => { + if (!input.trim()) { + return "Environment name is required"; + } + return true; + }, + }, + ]); + + const environmentName = (answers.environmentName ?? "").trim(); + const confirmationMessage = `${icons.checkmark} Environment: ${environmentName}`; + console.log(`${green(confirmationMessage)}\n`); + + const preset = await selectPreset(); + + printSection("Configure Modules"); + const modules = await selectModules(preset); + + return { + environmentName, + preset, + ...modules, + }; +} + +export function generateChainConfig(answers: WizardAnswers): string { + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + const isInMemory = answers.preset === "inmemory"; + + const moduleParts: string[] = []; + + if (answers.includeMetrics) { + moduleParts.push(" ...DefaultModules.metrics(),"); + } + if (isInMemory) { + moduleParts.push(" ...DefaultModules.inMemoryDatabase(),"); + } else { + moduleParts.push(" ...DefaultModules.PrismaRedisDatabase(),"); + } + moduleParts.push( + ` ...DefaultModules.core({ settlementEnabled: ${answers.settlementEnabled} }),` + ); + if (isInMemory) { + moduleParts.push(" ...DefaultModules.localTaskQueue(),"); + } else { + moduleParts.push(" ...DefaultModules.RedisTaskQueue(),"); + } + if (answers.includeIndexer) { + moduleParts.push(" ...DefaultModules.sequencerIndexer(),"); + } + if (answers.settlementEnabled) { + moduleParts.push(" ...DefaultModules.settlement(),"); + } + const modulesString = moduleParts.join("\n"); + const configParts: string[] = []; + const coreConfig = ` ...DefaultConfigs.core({ settlementEnabled: ${answers.settlementEnabled}, preset: "${presetEnv}" }),`; + configParts.push(coreConfig); + if (answers.includeIndexer) { + configParts.push(" ...DefaultConfigs.sequencerIndexer(),"); + } + if (answers.includeMetrics) { + configParts.push( + ` ...DefaultConfigs.metrics({ preset: "${presetEnv}" }),` + ); + } + if (isInMemory) { + configParts.push(" ...DefaultConfigs.localTaskQueue(),"); + configParts.push(" ...DefaultConfigs.inMemoryDatabase(),"); + } else { + configParts.push( + ` ...DefaultConfigs.redisTaskQueue({ + preset: "${presetEnv}", + }),` + ); + configParts.push( + ` ...DefaultConfigs.prismaRedisDatabase({ + preset: "${presetEnv}", + }),` + ); + } + if (answers.settlementEnabled) { + configParts.push( + ` ...DefaultConfigs.settlement({ preset: "${presetEnv}" }),` + ); + } + const configString = configParts.join("\n"); + return `import { Runtime } from "@proto-kit/module"; +import { Protocol } from "@proto-kit/protocol"; +import { AppChain, Sequencer } from "@proto-kit/sequencer"; +import runtime from "../../../runtime"; +import * as protocol from "../../../protocol"; + +import { Arguments } from "../../../start"; +import { Startable } from "@proto-kit/common"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +const settlementEnabled = process.env.PROTOKIT_SETTLEMENT_ENABLED! === "true"; + +const appChain = AppChain.from({ + Runtime: Runtime.from(runtime.modules), + Protocol: Protocol.from({ + ...protocol.modules, + ...(settlementEnabled ? protocol.settlementModules : {}), + }), + Sequencer: Sequencer.from({ + // ordering of the modules matters due to dependency resolution +${modulesString} + }), + ...DefaultModules.appChainBase(), +}); + +export default async (args: Arguments): Promise => { + appChain.configurePartial({ + Runtime: runtime.config, + Protocol: { + ...protocol.config, + ...(settlementEnabled ? protocol.settlementModulesConfig : {}), + }, + Sequencer: { +${configString} + }, + ...DefaultConfigs.appChainBase(), + }); + + return appChain; +};`; +} + +export function generateIndexerConfig(answers: WizardAnswers): string { + if (!answers.includeIndexer) { + return ""; + } + + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + + return `import { Indexer } from "@proto-kit/indexer"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +const indexer = Indexer.from({ + ...DefaultModules.indexer(), +}); + +export default async (): Promise => { + indexer.configurePartial({ + ...DefaultConfigs.indexer({ + preset: "${presetEnv}", + }), + }); + + return indexer; +};`; +} + +export function generateProcessorConfig(answers: WizardAnswers): string { + if (!answers.includeProcessor || !answers.includeIndexer) { + return ""; + } + + const presetEnv = PRESET_ENV_NAMES[answers.preset]; + + return `import { Processor } from "@proto-kit/processor"; +import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; + +import { handlers } from "../../processor/handlers"; +import { resolvers } from "../../processor/api/resolvers"; + +const processor = Processor.from({ + ...DefaultModules.processor(resolvers, handlers), +}); + +export default async (): Promise => { + processor.configurePartial({ + ...DefaultConfigs.processor({ + preset: "${presetEnv}", + }), + }); + + return processor; +};`; +} + +export function copyAndUpdateEnvFile( + answers: WizardAnswers, + cwd: string, + envDir: string +): boolean { + const presetEnvPath = path.join( + cwd, + "src/core/environments", + answers.preset, + ".env" + ); + + if (!fs.existsSync(presetEnvPath)) { + console.warn(`Could not find .env file at ${presetEnvPath}`); + return false; + } + + try { + let envContent = fs.readFileSync(presetEnvPath, "utf-8"); + + if (envContent.includes("PROTOKIT_ENV_FOLDER=")) { + envContent = envContent.replace( + /PROTOKIT_ENV_FOLDER=.*/g, + `PROTOKIT_ENV_FOLDER=${answers.environmentName}` + ); + } else { + const envFolder = `PROTOKIT_ENV_FOLDER=${answers.environmentName}`; + envContent = `${envFolder}\n${envContent}`; + } + + if (envContent.includes("PROTOKIT_SETTLEMENT_ENABLED=")) { + envContent = envContent.replace( + /PROTOKIT_SETTLEMENT_ENABLED=.*/g, + `PROTOKIT_SETTLEMENT_ENABLED=${answers.settlementEnabled}` + ); + } else { + envContent += `\nPROTOKIT_SETTLEMENT_ENABLED=${answers.settlementEnabled}\n`; + } + + const envFilePath = path.join(envDir, ".env"); + fs.writeFileSync(envFilePath, envContent); + + return true; + } catch (error) { + console.error(`Error copying .env file: ${error}}`); + return false; + } +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils/graphqlDocs.ts similarity index 98% rename from packages/cli/src/utils.ts rename to packages/cli/src/utils/graphqlDocs.ts index fca7a9956..cd896cd13 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils/graphqlDocs.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { spawn } from "child_process"; import fs from "fs"; import path from "path"; @@ -110,3 +112,4 @@ export async function generateGqlDocs(gqlUrl: string) { console.log("Docs generated successfully!"); cleanUp(generatedPath); } +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils/loadEnv.ts b/packages/cli/src/utils/loadEnv.ts new file mode 100644 index 000000000..3601c6cf7 --- /dev/null +++ b/packages/cli/src/utils/loadEnv.ts @@ -0,0 +1,65 @@ +/* eslint-disable no-console */ + +import path from "path"; +import fs from "fs"; + +import dotenv from "dotenv"; + +export type LoadEnvOptions = { + envPath?: string; + envVars?: Record; +}; + +export function loadEnvironmentVariables(options?: LoadEnvOptions) { + const cwd = process.cwd(); + + if (options?.envPath !== undefined) { + if (fs.existsSync(options.envPath)) { + dotenv.config({ path: options.envPath }); + console.log(`Loaded environment from ${options.envPath}`); + } else { + throw new Error(`Environment file not found at ${options.envPath}`); + } + } else { + const envPath = path.join(cwd, "./src/core/environments/development/.env"); + + if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); + console.log(`Loaded environment from ${envPath}`); + } else { + console.warn(`.env file not found at ${envPath}`); + } + } + if (options?.envVars !== undefined) { + Object.entries(options.envVars).forEach(([key, value]) => { + process.env[key] = value; + }); + console.log( + `Loaded ${Object.keys(options.envVars).length} environment variables from arguments` + ); + } +} + +export function getRequiredEnv(key: string): string { + const value = process.env[key]; + if (value === undefined) { + throw new Error( + `Required environment variable "${key}" is not defined. Please check your .env file or pass it as an argument.` + ); + } + return value; +} + +export function parseEnvArgs(args: string[]): Record { + const envVars: Record = {}; + + for (const arg of args) { + if (arg.includes("=")) { + const [key, value] = arg.split("=", 2); + envVars[key.trim()] = value.trim(); + } + } + + return envVars; +} +/* eslint-enable no-console */ diff --git a/packages/cli/src/utils/loadUserModules.ts b/packages/cli/src/utils/loadUserModules.ts new file mode 100644 index 000000000..3ac32d5f1 --- /dev/null +++ b/packages/cli/src/utils/loadUserModules.ts @@ -0,0 +1,60 @@ +import path from "path"; + +import { + MandatoryProtocolModulesRecord, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; +import { RuntimeModulesRecord } from "@proto-kit/module"; +import { ModulesConfig } from "@proto-kit/common"; +import { Withdrawals } from "@proto-kit/library"; + +/* eslint-disable no-console */ + +type AppRuntimeModules = RuntimeModulesRecord & { + Withdrawals: typeof Withdrawals; +}; + +interface RuntimeModule { + modules: AppRuntimeModules; + config: ModulesConfig; +} + +interface ProtocolModule { + modules: ProtocolModulesRecord & MandatoryProtocolModulesRecord; + + config: ModulesConfig; + + settlementModules?: ProtocolModulesRecord; + + settlementModulesConfig?: ModulesConfig; +} + +interface LoadedModules { + runtime: RuntimeModule; + protocol: ProtocolModule; +} + +export async function loadUserModules(): Promise { + const cwd = process.cwd(); + + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const runtimeImport: { default: RuntimeModule } = await import( + path.join(cwd, "src/runtime") + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const protocolImport: { default: ProtocolModule } = await import( + path.join(cwd, "src/protocol") + ); + + return { + runtime: runtimeImport.default, + protocol: protocolImport.default, + }; + } catch (error) { + console.error("Failed to load runtime or protocol modules."); + throw error; + } +} + +/* eslint-enable no-console */ diff --git a/packages/explorer/src/config.ts b/packages/explorer/src/config.ts index 97529e1ab..47c6a2c6f 100644 --- a/packages/explorer/src/config.ts +++ b/packages/explorer/src/config.ts @@ -1,5 +1,6 @@ const config = { - INDEXER_URL: "http://localhost:8081/graphql", + INDEXER_URL: + process.env.NEXT_PUBLIC_INDEXER_URL ?? "http://localhost:8081/graphql", }; export default config; diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index 9c61fbdaf..bdb606004 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -25,6 +25,9 @@ import { IndexerNotifier, GeneratedResolverFactoryGraphqlModule, IndexBlockTask, + IndexBatchTask, + IndexPendingTxTask, + IndexSettlementTask, } from "@proto-kit/indexer"; import { PrismaRedisDatabase } from "@proto-kit/persistance"; import { BullQueue } from "@proto-kit/deployment"; @@ -115,6 +118,9 @@ export class DefaultModules { TaskQueue: BullQueue, TaskWorker: LocalTaskWorkerModule.from({ IndexBlockTask, + IndexPendingTxTask, + IndexBatchTask, + IndexSettlementTask, }), GraphqlServer, Graphql: GraphqlSequencerModule.from({ @@ -311,6 +317,9 @@ export class DefaultConfigs { ...taskQueueConfig, TaskWorker: { IndexBlockTask: {}, + IndexBatchTask: {}, + IndexPendingTxTask: {}, + IndexSettlementTask: {}, }, ...graphqlServerConfig, Graphql: { From c3e481baae652f01bb20633b8754a2f8f467e8a5 Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 30 Jan 2026 16:52:55 +0300 Subject: [PATCH 11/14] feat: expose isSginedSettlement to SettlemntUtils --- .../sequencer/src/settlement/utils/SettlementUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/sequencer/src/settlement/utils/SettlementUtils.ts b/packages/sequencer/src/settlement/utils/SettlementUtils.ts index 9639a2fe5..9c9f2bba8 100644 --- a/packages/sequencer/src/settlement/utils/SettlementUtils.ts +++ b/packages/sequencer/src/settlement/utils/SettlementUtils.ts @@ -109,6 +109,14 @@ export class SettlementUtils { return this.signer.registerKey(privateKey); } + public getSigner(): PublicKey { + return this.signer.getFeepayerKey(); + } + + public isSignedSettlement(): boolean { + return this.baseLayer.isSignedSettlement(); + } + /** * Fetch a set of accounts (and there update internally) with respect to what network is set */ From 709adcb8aa824991ad63d8326ff83388284af14d Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 30 Jan 2026 16:53:12 +0300 Subject: [PATCH 12/14] refactor: update scripts and configs with new SettlementModule API --- packages/cli/src/scripts/bridge/deposit.ts | 17 ++++--- packages/cli/src/scripts/bridge/redeem.ts | 5 +- .../src/scripts/settlement/deploy-token.ts | 49 ++++++++++++------- packages/cli/src/scripts/settlement/deploy.ts | 29 +++++------ packages/stack/src/presets/modules/index.ts | 21 +++++--- 5 files changed, 70 insertions(+), 51 deletions(-) diff --git a/packages/cli/src/scripts/bridge/deposit.ts b/packages/cli/src/scripts/bridge/deposit.ts index 0f222b80a..628d2c5ca 100644 --- a/packages/cli/src/scripts/bridge/deposit.ts +++ b/packages/cli/src/scripts/bridge/deposit.ts @@ -9,7 +9,7 @@ import { AppChain, } from "@proto-kit/sequencer"; import { Runtime } from "@proto-kit/module"; -import { Protocol } from "@proto-kit/protocol"; +import { DispatchSmartContract, Protocol } from "@proto-kit/protocol"; import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; import { AccountUpdate, @@ -118,7 +118,9 @@ export default async function ( BridgingModule ); - const { settlement, dispatch } = settlementModule.getContracts(); + const settlement = settlementModule.getSettlementContract(); + const dispatch = + bridgingModule.getDispatchContract() as DispatchSmartContract; await fetchAccount({ publicKey: fromPrivateKey.toPublicKey() }); await fetchAccount({ publicKey: settlement.address }); @@ -161,12 +163,11 @@ export default async function ( ); console.log(tx.toPretty()); - settlementModule.signTransaction( - tx, - [fromPrivateKey], - [tokenOwnerPrivateKey], - [dispatch.address] - ); + settlementModule.utils.signTransaction(tx, { + signingPublicKeys: [fromPrivateKey.toPublicKey()], + preventNoncePreconditionFor: [dispatch.address], + signingWithSignatureCheck: [tokenOwnerPrivateKey.toPublicKey()], + }); console.log("Sending..."); console.log(tx.toPretty()); diff --git a/packages/cli/src/scripts/bridge/redeem.ts b/packages/cli/src/scripts/bridge/redeem.ts index 35bc126d5..7a3b2c039 100644 --- a/packages/cli/src/scripts/bridge/redeem.ts +++ b/packages/cli/src/scripts/bridge/redeem.ts @@ -134,7 +134,10 @@ export default async function ( SettlementModule ); - settlementModule.signTransaction(tx, [toPrivateKey], [tokenOwnerPrivateKey]); + settlementModule.utils.signTransaction(tx, { + signingPublicKeys: [toPrivateKey.toPublicKey()], + signingWithSignatureCheck: [tokenOwnerPrivateKey.toPublicKey()], + }); console.log("Sending..."); diff --git a/packages/cli/src/scripts/settlement/deploy-token.ts b/packages/cli/src/scripts/settlement/deploy-token.ts index 28c707158..12f1828aa 100644 --- a/packages/cli/src/scripts/settlement/deploy-token.ts +++ b/packages/cli/src/scripts/settlement/deploy-token.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ /* eslint-disable func-names */ import { Runtime } from "@proto-kit/module"; -import { Protocol } from "@proto-kit/protocol"; +import { DispatchSmartContract, Protocol } from "@proto-kit/protocol"; import { ArchiveNode, MinaTransactionSender, @@ -10,6 +10,7 @@ import { SettlementModule, SignedSettlementPermissions, AppChain, + BridgingModule, } from "@proto-kit/sequencer"; import { AccountUpdate, @@ -98,6 +99,11 @@ export default async function ( SettlementModule ); + const bridgingModule = appChain.sequencer.resolveOrFail( + "BridgingModule", + BridgingModule + ); + const isSignedSettlement = settlementModule.utils.isSignedSettlement(); const tokenOwnerKey = PrivateKey.fromBase58( @@ -155,11 +161,13 @@ export default async function ( console.log("Sending deploy transaction..."); console.log(tx.toPretty()); - settlementModule.signTransaction( - tx, - [feepayerPrivateKey, tokenOwnerKey, tokenAdminKey], - [tokenOwnerKey, tokenAdminKey] - ); + settlementModule.utils.signTransaction(tx, { + signingWithSignatureCheck: [ + tokenOwnerKey.toPublicKey(), + tokenAdminKey.toPublicKey(), + ], + signingPublicKeys: [feepayerPrivateKey.toPublicKey()], + }); await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -193,11 +201,14 @@ export default async function ( await tokenOwner!.mint(receiverPublicKey, UInt64.from(mintAmount)); } ); - settlementModule.utils.signTransaction( - tx, - [feepayerPrivateKey], - [tokenOwnerKey, tokenAdminKey] - ); + + settlementModule.utils.signTransaction(tx, { + signingPublicKeys: [feepayerPrivateKey.toPublicKey()], + signingWithSignatureCheck: [ + tokenOwnerKey.toPublicKey(), + tokenAdminKey.toPublicKey(), + ], + }); await appChain.sequencer .resolveOrFail("TransactionSender", MinaTransactionSender) @@ -205,19 +216,21 @@ export default async function ( } async function deployBridge() { - const { settlement, dispatch } = settlementModule.getAddresses(); + const settlement = settlementModule.getSettlementContract(); + const dispatch = + bridgingModule.getDispatchContract() as DispatchSmartContract; + await fetchAccount({ - publicKey: settlementModule.config.feepayer.toPublicKey(), + publicKey: settlementModule.utils.getSigner(), }); - await fetchAccount({ publicKey: settlement }); - await fetchAccount({ publicKey: dispatch }); + await fetchAccount({ publicKey: settlement.address }); + await fetchAccount({ publicKey: dispatch.address }); const tokenOwner = new FungibleToken(tokenOwnerKey.toPublicKey()); // SetAdminEvent. - await settlementModule.deployTokenBridge( + await bridgingModule.deployTokenBridge( tokenOwner, - tokenOwnerKey, - tokenBridgeKey, + tokenBridgeKey.toPublicKey(), {} ); console.log( diff --git a/packages/cli/src/scripts/settlement/deploy.ts b/packages/cli/src/scripts/settlement/deploy.ts index 9e0530fb0..c36f836fe 100644 --- a/packages/cli/src/scripts/settlement/deploy.ts +++ b/packages/cli/src/scripts/settlement/deploy.ts @@ -9,7 +9,7 @@ import { SettlementModule, AppChain, } from "@proto-kit/sequencer"; -import { PrivateKey, Provable } from "o1js"; +import { Provable, PublicKey } from "o1js"; import "reflect-metadata"; import { container } from "tsyringe"; import { DefaultConfigs, DefaultModules } from "@proto-kit/stack"; @@ -59,25 +59,22 @@ export default async function (options?: LoadEnvOptions) { console.log("Deploying settlement contracts..."); - await settlementModule.deploy( - PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") + await settlementModule.deploy({ + settlementContract: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY") ), - PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") + dispatchContract: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY") ), - PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_MINA_BRIDGE_CONTRACT_PRIVATE_KEY") - ) - ); + }); Provable.log("Deployed and initialized settlement contracts", { - settlement: PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PRIVATE_KEY") - ).toPublicKey(), - dispatcher: PrivateKey.fromBase58( - getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PRIVATE_KEY") - ).toPublicKey(), + settlement: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_SETTLEMENT_CONTRACT_PUBLIC_KEY") + ), + dispatcher: PublicKey.fromBase58( + getRequiredEnv("PROTOKIT_DISPATCHER_CONTRACT_PUBLIC_KEY") + ), }); await appChain.close(); diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index bdb606004..bf1d0a4d6 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -20,6 +20,7 @@ import { InMemoryDatabase, LocalTaskQueue, AppChainModulesRecord, + InMemoryMinaSigner, } from "@proto-kit/sequencer"; import { IndexerNotifier, @@ -100,6 +101,7 @@ export class DefaultModules { FeeStrategy: ConstantFeeStrategy, BatchProducerModule, SettlementModule, + SettlementSigner: InMemoryMinaSigner, LocalTaskWorkerModule: LocalTaskWorkerModule.from( VanillaTaskWorkerModules.allTasks() ), @@ -379,17 +381,20 @@ export class DefaultConfigs { }, }, SettlementModule: { - feepayer: PrivateKey.fromBase58(config.sequencerPrivateKey), - keys: { - settlement: PrivateKey.fromBase58( + addresses: { + SettlementContract: PrivateKey.fromBase58( config.settlementContractPrivateKey - ), - dispatch: PrivateKey.fromBase58(config.dispatcherContractPrivateKey), - minaBridge: PrivateKey.fromBase58( - config.minaBridgeContractPrivateKey - ), + ).toPublicKey(), }, }, + SettlementSigner: { + feepayer: PrivateKey.fromBase58(config.sequencerPrivateKey), + contractKeys: [ + PrivateKey.fromBase58(config.settlementContractPrivateKey), + PrivateKey.fromBase58(config.dispatcherContractPrivateKey), + PrivateKey.fromBase58(config.minaBridgeContractPrivateKey), + ], + }, FeeStrategy: {}, BatchProducerModule: {}, LocalTaskWorkerModule: VanillaTaskWorkerModules.defaultConfig(), From 1c9b74547e4cd16a89a16467b81c74097f23760a Mon Sep 17 00:00:00 2001 From: saitunc Date: Fri, 30 Jan 2026 17:09:32 +0300 Subject: [PATCH 13/14] fix: run lint fix --- packages/cli/src/scripts/bridge/deposit.ts | 1 + packages/cli/src/scripts/settlement/deploy-token.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/cli/src/scripts/bridge/deposit.ts b/packages/cli/src/scripts/bridge/deposit.ts index 628d2c5ca..cfb4ad492 100644 --- a/packages/cli/src/scripts/bridge/deposit.ts +++ b/packages/cli/src/scripts/bridge/deposit.ts @@ -120,6 +120,7 @@ export default async function ( const settlement = settlementModule.getSettlementContract(); const dispatch = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions bridgingModule.getDispatchContract() as DispatchSmartContract; await fetchAccount({ publicKey: fromPrivateKey.toPublicKey() }); diff --git a/packages/cli/src/scripts/settlement/deploy-token.ts b/packages/cli/src/scripts/settlement/deploy-token.ts index 12f1828aa..96d06a92d 100644 --- a/packages/cli/src/scripts/settlement/deploy-token.ts +++ b/packages/cli/src/scripts/settlement/deploy-token.ts @@ -217,7 +217,9 @@ export default async function ( async function deployBridge() { const settlement = settlementModule.getSettlementContract(); + const dispatch = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions bridgingModule.getDispatchContract() as DispatchSmartContract; await fetchAccount({ From 1ed0f4f26020ffbb57437412a7db5491f42a5f9d Mon Sep 17 00:00:00 2001 From: stanlou Date: Fri, 30 Jan 2026 18:57:07 +0300 Subject: [PATCH 14/14] fix prismaRedisDatabase & redisTaskQueue typo --- packages/stack/src/presets/modules/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stack/src/presets/modules/index.ts b/packages/stack/src/presets/modules/index.ts index bf1d0a4d6..90347a2f1 100644 --- a/packages/stack/src/presets/modules/index.ts +++ b/packages/stack/src/presets/modules/index.ts @@ -152,7 +152,7 @@ export class DefaultModules { } satisfies SequencerModulesRecord; } - static PrismaRedisDatabase() { + static prismaRedisDatabase() { return { Database: PrismaRedisDatabase, DatabasePruneModule, @@ -165,7 +165,7 @@ export class DefaultModules { } satisfies SequencerModulesRecord; } - static RedisTaskQueue() { + static redisTaskQueue() { return { TaskQueue: BullQueue, } satisfies SequencerModulesRecord;