@manifest-network/manifest-mcp-core 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;KA+BY,mBAAA,GAAsB,OAAA,CAChC,UAAA,QAAkB,UAAA,CAAW,aAAA,CAAc,oBAAA;;AAD7C;;cA+Ca,mBAAA;EAAA,eACI,SAAA;EAAA,QAEP,MAAA;EAAA,QACA,cAAA;EAAA,QACA,WAAA;EAAA,QACA,aAAA;EAAA,QACA,WAAA;EAAA,QAGA,kBAAA;EAAA,QACA,oBAAA;EAAA,QAED,WAAA,CAAA;EA3DoC;;;AA8C7C;;;;EA9C6C,OAkFpC,WAAA,CACL,MAAA,EAAQ,iBAAA,EACR,cAAA,EAAgB,cAAA,GACf,mBAAA;EAAA;;;;EAAA,OAmDI,cAAA,CAAA;EAyKa;;;;;EA7Jd,cAAA,CAAA,GAAkB,OAAA,CAAQ,mBAAA;EAnGxB;;;;;EA8KF,gBAAA,CAAA,GAAoB,OAAA,CAAQ,qBAAA;EAtK1B;;;EAwPF,UAAA,CAAA,GAAc,OAAA;EA9NlB;;;EAqOF,SAAA,CAAA,GAAa,iBAAA;EAhLN;;;;EAwLD,gBAAA,CAAA,GAAoB,OAAA;EAjGA;;;;;EA0G1B,UAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;KA+BY,mBAAA,GAAsB,OAAA,CAChC,UAAA,QAAkB,UAAA,CAAW,aAAA,CAAc,oBAAA;;AAD7C;;cAsDa,mBAAA;EAAA,eACI,SAAA;EAAA,QAEP,MAAA;EAAA,QACA,cAAA;EAAA,QACA,WAAA;EAAA,QACA,aAAA;EAAA,QACA,WAAA;EAAA,QAGA,kBAAA;EAAA,QACA,oBAAA;EAAA,QAED,WAAA,CAAA;EAlEoC;;;AAqD7C;;;;EArD6C,OAyFpC,WAAA,CACL,MAAA,EAAQ,iBAAA,EACR,cAAA,EAAgB,cAAA,GACf,mBAAA;EAAA;;;;EAAA,OAmDI,cAAA,CAAA;EAqLa;;;;;EAzKd,cAAA,CAAA,GAAkB,OAAA,CAAQ,mBAAA;EAnGxB;;;;;EA8KF,gBAAA,CAAA,GAAoB,OAAA,CAAQ,qBAAA;EAtK1B;;;EAoQF,UAAA,CAAA,GAAc,OAAA;EA1OlB;;;EAiPF,SAAA,CAAA,GAAa,iBAAA;EA5LN;;;;EAoMD,gBAAA,CAAA,GAAoB,OAAA;EA7GA;;;;;EAsH1B,UAAA,CAAA;AAAA"}
package/dist/client.js CHANGED
@@ -12,6 +12,12 @@ const DEFAULT_BROADCAST_TIMEOUT_MS = 6e4;
12
12
  /** Default polling interval for transaction confirmation (3 seconds) */
13
13
  const DEFAULT_BROADCAST_POLL_INTERVAL_MS = 3e3;
14
14
  /**
15
+ * Gas simulation multiplier. CosmJS defaults to 1.4 but billing module
16
+ * transactions (close-lease in particular) can exceed that. 1.5 matches
17
+ * the --gas-adjustment used by the CLI.
18
+ */
19
+ const DEFAULT_GAS_MULTIPLIER = 1.5;
20
+ /**
15
21
  * Get combined signing client options with all Manifest registries
16
22
  */
17
23
  function getSigningManifestClientOptions() {
@@ -145,13 +151,18 @@ var CosmosClientManager = class CosmosClientManager {
145
151
  url: this.config.rpcUrl,
146
152
  headers: {}
147
153
  };
148
- const client = await withRetry(() => SigningStargateClient.connectWithSigner(endpoint, signer, {
149
- registry,
150
- aminoTypes,
151
- gasPrice,
152
- broadcastTimeoutMs: DEFAULT_BROADCAST_TIMEOUT_MS,
153
- broadcastPollIntervalMs: DEFAULT_BROADCAST_POLL_INTERVAL_MS
154
- }), {
154
+ const client = await withRetry(async () => {
155
+ const c = await SigningStargateClient.connectWithSigner(endpoint, signer, {
156
+ registry,
157
+ aminoTypes,
158
+ gasPrice,
159
+ broadcastTimeoutMs: DEFAULT_BROADCAST_TIMEOUT_MS,
160
+ broadcastPollIntervalMs: DEFAULT_BROADCAST_POLL_INTERVAL_MS
161
+ });
162
+ const record = c;
163
+ if (typeof record.defaultGasMultiplier === "number") record.defaultGasMultiplier = DEFAULT_GAS_MULTIPLIER;
164
+ return c;
165
+ }, {
155
166
  config: this.config.retry,
156
167
  operationName: "connect signing client"
157
168
  });
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { Registry } from '@cosmjs/proto-signing';\nimport {\n AminoTypes,\n GasPrice,\n type HttpEndpoint,\n SigningStargateClient,\n} from '@cosmjs/stargate';\nimport {\n cosmosAminoConverters,\n cosmosProtoRegistry,\n liftedinit,\n liftedinitAminoConverters,\n liftedinitProtoRegistry,\n osmosisAminoConverters,\n osmosisProtoRegistry,\n strangeloveVenturesAminoConverters,\n strangeloveVenturesProtoRegistry,\n} from '@manifest-network/manifestjs';\nimport { RateLimiter } from 'limiter';\nimport { DEFAULT_REQUESTS_PER_SECOND } from './config.js';\nimport { createLCDQueryClient } from './lcd-adapter.js';\nimport { withRetry } from './retry.js';\nimport {\n type ManifestMCPConfig,\n ManifestMCPError,\n ManifestMCPErrorCode,\n type WalletProvider,\n} from './types.js';\n\n// Type for the RPC query client from manifestjs liftedinit bundle\n// This includes cosmos modules + liftedinit-specific modules (billing, manifest, sku)\nexport type ManifestQueryClient = Awaited<\n ReturnType<typeof liftedinit.ClientFactory.createRPCQueryClient>\n>;\n\n/**\n * Extract the registry type expected by SigningStargateClient.connectWithSigner.\n *\n * The Registry type from @cosmjs/proto-signing doesn't perfectly match the registry type\n * in SigningStargateClientOptions due to telescope-generated proto types. This type alias\n * extracts the expected registry type from the function signature to enable type-safe casting.\n */\ntype SigningClientRegistry = Parameters<\n typeof SigningStargateClient.connectWithSigner\n>[2] extends { registry?: infer R }\n ? R\n : never;\n\n/** Default timeout for transaction broadcast (60 seconds) */\nconst DEFAULT_BROADCAST_TIMEOUT_MS = 60_000;\n\n/** Default polling interval for transaction confirmation (3 seconds) */\nconst DEFAULT_BROADCAST_POLL_INTERVAL_MS = 3_000;\n\n/**\n * Get combined signing client options with all Manifest registries\n */\nfunction getSigningManifestClientOptions() {\n const registry = new Registry([\n ...cosmosProtoRegistry,\n ...liftedinitProtoRegistry,\n ...strangeloveVenturesProtoRegistry,\n ...osmosisProtoRegistry,\n ]);\n\n const aminoTypes = new AminoTypes({\n ...cosmosAminoConverters,\n ...liftedinitAminoConverters,\n ...strangeloveVenturesAminoConverters,\n ...osmosisAminoConverters,\n });\n\n return { registry, aminoTypes };\n}\n\n/**\n * Manages CosmJS client instances with lazy initialization and singleton pattern\n */\nexport class CosmosClientManager {\n private static instances: Map<string, CosmosClientManager> = new Map();\n\n private config: ManifestMCPConfig;\n private walletProvider: WalletProvider;\n private queryClient: ManifestQueryClient | null = null;\n private signingClient: SigningStargateClient | null = null;\n private rateLimiter: RateLimiter;\n\n // Promises to prevent concurrent client initialization (lazy init race condition)\n private queryClientPromise: Promise<ManifestQueryClient> | null = null;\n private signingClientPromise: Promise<SigningStargateClient> | null = null;\n\n private constructor(\n config: ManifestMCPConfig,\n walletProvider: WalletProvider,\n ) {\n this.config = config;\n this.walletProvider = walletProvider;\n\n // Initialize rate limiter with configured or default requests per second\n const requestsPerSecond =\n config.rateLimit?.requestsPerSecond ?? DEFAULT_REQUESTS_PER_SECOND;\n this.rateLimiter = new RateLimiter({\n tokensPerInterval: requestsPerSecond,\n interval: 'second',\n });\n }\n\n /**\n * Get or create a singleton instance for the given config.\n * Instances are keyed by chainId:rpcUrl:restUrl. For existing instances:\n * - Config and walletProvider references are always updated\n * - Signing client is disconnected/recreated if gasPrice or walletProvider changed\n * - Rate limiter is updated if requestsPerSecond changed (without affecting signing client)\n */\n static getInstance(\n config: ManifestMCPConfig,\n walletProvider: WalletProvider,\n ): CosmosClientManager {\n const parts = [config.chainId, config.rpcUrl ?? ''];\n if (config.restUrl) parts.push(config.restUrl);\n const key = parts.join(':');\n let instance = CosmosClientManager.instances.get(key);\n\n if (!instance) {\n instance = new CosmosClientManager(config, walletProvider);\n CosmosClientManager.instances.set(key, instance);\n } else {\n // Check what changed to determine what needs updating\n const signingClientAffected =\n instance.config.gasPrice !== config.gasPrice ||\n instance.walletProvider !== walletProvider;\n\n const rateLimitChanged =\n instance.config.rateLimit?.requestsPerSecond !==\n config.rateLimit?.requestsPerSecond;\n\n // Always update config reference\n instance.config = config;\n instance.walletProvider = walletProvider;\n\n // Only invalidate signing client if fields it depends on changed\n if (signingClientAffected) {\n if (instance.signingClient) {\n instance.signingClient.disconnect();\n instance.signingClient = null;\n }\n // Also clear the promise to allow re-initialization with new config\n instance.signingClientPromise = null;\n }\n\n // Update rate limiter independently (doesn't affect signing client)\n if (rateLimitChanged) {\n const newRps =\n config.rateLimit?.requestsPerSecond ?? DEFAULT_REQUESTS_PER_SECOND;\n instance.rateLimiter = new RateLimiter({\n tokensPerInterval: newRps,\n interval: 'second',\n });\n }\n }\n\n return instance;\n }\n\n /**\n * Clear all cached instances (useful for testing or reconnection).\n * Disconnects signing clients and releases query client references before clearing.\n */\n static clearInstances(): void {\n for (const instance of CosmosClientManager.instances.values()) {\n instance.disconnect();\n }\n CosmosClientManager.instances.clear();\n }\n\n /**\n * Get the manifestjs RPC query client with all module extensions\n *\n * Automatically retries on transient connection failures with exponential backoff.\n */\n async getQueryClient(): Promise<ManifestQueryClient> {\n // Return cached client if available\n if (this.queryClient) {\n return this.queryClient;\n }\n\n // If initialization is already in progress, wait for it\n if (this.queryClientPromise) {\n return this.queryClientPromise;\n }\n\n // Start initialization and cache the promise to prevent concurrent init\n this.queryClientPromise = (async () => {\n // Capture reference to detect if superseded by disconnect/config change\n const thisInitPromise = this.queryClientPromise;\n try {\n let client: ManifestQueryClient;\n if (this.config.restUrl) {\n // Use LCD/REST for queries when restUrl is configured\n client = await withRetry(\n () => createLCDQueryClient(this.config.restUrl!),\n {\n config: this.config.retry,\n operationName: 'connect LCD query client',\n },\n );\n } else if (this.config.rpcUrl) {\n // Use RPC (existing behavior)\n client = await withRetry(\n () =>\n liftedinit.ClientFactory.createRPCQueryClient({\n rpcEndpoint: this.config.rpcUrl!,\n }),\n {\n config: this.config.retry,\n operationName: 'connect query client',\n },\n );\n } else {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Cannot create query client: neither restUrl nor rpcUrl is configured.',\n );\n }\n // Only store if this is still the active promise\n if (this.queryClientPromise === thisInitPromise) {\n this.queryClient = client;\n this.queryClientPromise = null;\n }\n return client;\n } catch (error) {\n // Clear promise on failure so retry is possible (only if still active)\n if (this.queryClientPromise === thisInitPromise) {\n this.queryClientPromise = null;\n }\n if (error instanceof ManifestMCPError) {\n throw error;\n }\n const endpoint = this.config.restUrl ?? this.config.rpcUrl;\n throw new ManifestMCPError(\n ManifestMCPErrorCode.RPC_CONNECTION_FAILED,\n `Failed to connect to ${this.config.restUrl ? 'REST' : 'RPC'} endpoint: ${error instanceof Error ? error.message : String(error)}`,\n { url: endpoint },\n );\n }\n })();\n\n return this.queryClientPromise;\n }\n\n /**\n * Get a signing client with all Manifest registries (for transactions)\n *\n * Automatically retries on transient connection failures with exponential backoff.\n */\n async getSigningClient(): Promise<SigningStargateClient> {\n if (!this.config.rpcUrl || !this.config.gasPrice) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Signing client requires rpcUrl and gasPrice configuration. Current config is query-only (REST).',\n );\n }\n\n // Return cached client if available\n if (this.signingClient) {\n return this.signingClient;\n }\n\n // If initialization is already in progress, wait for it\n if (this.signingClientPromise) {\n return this.signingClientPromise;\n }\n\n // Start initialization and cache the promise to prevent concurrent init\n this.signingClientPromise = (async () => {\n // Capture reference to detect if superseded by disconnect/config change\n const thisInitPromise = this.signingClientPromise;\n try {\n const signer = await this.walletProvider.getSigner();\n const gasPrice = GasPrice.fromString(this.config.gasPrice!);\n const { registry, aminoTypes } = getSigningManifestClientOptions();\n\n // Configure endpoint as HttpEndpoint object (required for custom options)\n const endpoint: HttpEndpoint = {\n url: this.config.rpcUrl!,\n headers: {},\n };\n\n // Note: Registry type from @cosmjs/proto-signing doesn't perfectly match\n // SigningStargateClientOptions due to telescope-generated proto types.\n // This is a known limitation with custom cosmos-sdk module registries.\n // Wrap with retry for transient connection failures\n const client = await withRetry(\n () =>\n SigningStargateClient.connectWithSigner(endpoint, signer, {\n registry: registry as SigningClientRegistry,\n aminoTypes,\n gasPrice,\n broadcastTimeoutMs: DEFAULT_BROADCAST_TIMEOUT_MS,\n broadcastPollIntervalMs: DEFAULT_BROADCAST_POLL_INTERVAL_MS,\n }),\n {\n config: this.config.retry,\n operationName: 'connect signing client',\n },\n );\n // Only store if this is still the active promise\n if (this.signingClientPromise === thisInitPromise) {\n this.signingClient = client;\n this.signingClientPromise = null;\n } else {\n // Promise was superseded, clean up the client we just created\n client.disconnect();\n }\n return client;\n } catch (error) {\n // Clear promise on failure so retry is possible (only if still active)\n if (this.signingClientPromise === thisInitPromise) {\n this.signingClientPromise = null;\n }\n if (error instanceof ManifestMCPError) {\n throw error;\n }\n throw new ManifestMCPError(\n ManifestMCPErrorCode.RPC_CONNECTION_FAILED,\n `Failed to connect signing client: ${error instanceof Error ? error.message : String(error)}`,\n { rpcUrl: this.config.rpcUrl },\n );\n }\n })();\n\n return this.signingClientPromise;\n }\n\n /**\n * Get the wallet address\n */\n async getAddress(): Promise<string> {\n return this.walletProvider.getAddress();\n }\n\n /**\n * Get the configuration\n */\n getConfig(): ManifestMCPConfig {\n return this.config;\n }\n\n /**\n * Acquire a rate limit token before making an RPC request.\n * This will wait if the rate limit has been exceeded.\n */\n async acquireRateLimit(): Promise<void> {\n await this.rateLimiter.removeTokens(1);\n }\n\n /**\n * Disconnect the signing client and release query client references.\n * The query client's underlying HTTP transport is stateless and does not\n * require an explicit disconnect.\n */\n disconnect(): void {\n if (this.signingClient) {\n this.signingClient.disconnect();\n this.signingClient = null;\n }\n this.signingClientPromise = null;\n this.queryClient = null;\n this.queryClientPromise = null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { Registry } from '@cosmjs/proto-signing';\nimport {\n AminoTypes,\n GasPrice,\n type HttpEndpoint,\n SigningStargateClient,\n} from '@cosmjs/stargate';\nimport {\n cosmosAminoConverters,\n cosmosProtoRegistry,\n liftedinit,\n liftedinitAminoConverters,\n liftedinitProtoRegistry,\n osmosisAminoConverters,\n osmosisProtoRegistry,\n strangeloveVenturesAminoConverters,\n strangeloveVenturesProtoRegistry,\n} from '@manifest-network/manifestjs';\nimport { RateLimiter } from 'limiter';\nimport { DEFAULT_REQUESTS_PER_SECOND } from './config.js';\nimport { createLCDQueryClient } from './lcd-adapter.js';\nimport { withRetry } from './retry.js';\nimport {\n type ManifestMCPConfig,\n ManifestMCPError,\n ManifestMCPErrorCode,\n type WalletProvider,\n} from './types.js';\n\n// Type for the RPC query client from manifestjs liftedinit bundle\n// This includes cosmos modules + liftedinit-specific modules (billing, manifest, sku)\nexport type ManifestQueryClient = Awaited<\n ReturnType<typeof liftedinit.ClientFactory.createRPCQueryClient>\n>;\n\n/**\n * Extract the registry type expected by SigningStargateClient.connectWithSigner.\n *\n * The Registry type from @cosmjs/proto-signing doesn't perfectly match the registry type\n * in SigningStargateClientOptions due to telescope-generated proto types. This type alias\n * extracts the expected registry type from the function signature to enable type-safe casting.\n */\ntype SigningClientRegistry = Parameters<\n typeof SigningStargateClient.connectWithSigner\n>[2] extends { registry?: infer R }\n ? R\n : never;\n\n/** Default timeout for transaction broadcast (60 seconds) */\nconst DEFAULT_BROADCAST_TIMEOUT_MS = 60_000;\n\n/** Default polling interval for transaction confirmation (3 seconds) */\nconst DEFAULT_BROADCAST_POLL_INTERVAL_MS = 3_000;\n\n/**\n * Gas simulation multiplier. CosmJS defaults to 1.4 but billing module\n * transactions (close-lease in particular) can exceed that. 1.5 matches\n * the --gas-adjustment used by the CLI.\n */\nconst DEFAULT_GAS_MULTIPLIER = 1.5;\n\n/**\n * Get combined signing client options with all Manifest registries\n */\nfunction getSigningManifestClientOptions() {\n const registry = new Registry([\n ...cosmosProtoRegistry,\n ...liftedinitProtoRegistry,\n ...strangeloveVenturesProtoRegistry,\n ...osmosisProtoRegistry,\n ]);\n\n const aminoTypes = new AminoTypes({\n ...cosmosAminoConverters,\n ...liftedinitAminoConverters,\n ...strangeloveVenturesAminoConverters,\n ...osmosisAminoConverters,\n });\n\n return { registry, aminoTypes };\n}\n\n/**\n * Manages CosmJS client instances with lazy initialization and singleton pattern\n */\nexport class CosmosClientManager {\n private static instances: Map<string, CosmosClientManager> = new Map();\n\n private config: ManifestMCPConfig;\n private walletProvider: WalletProvider;\n private queryClient: ManifestQueryClient | null = null;\n private signingClient: SigningStargateClient | null = null;\n private rateLimiter: RateLimiter;\n\n // Promises to prevent concurrent client initialization (lazy init race condition)\n private queryClientPromise: Promise<ManifestQueryClient> | null = null;\n private signingClientPromise: Promise<SigningStargateClient> | null = null;\n\n private constructor(\n config: ManifestMCPConfig,\n walletProvider: WalletProvider,\n ) {\n this.config = config;\n this.walletProvider = walletProvider;\n\n // Initialize rate limiter with configured or default requests per second\n const requestsPerSecond =\n config.rateLimit?.requestsPerSecond ?? DEFAULT_REQUESTS_PER_SECOND;\n this.rateLimiter = new RateLimiter({\n tokensPerInterval: requestsPerSecond,\n interval: 'second',\n });\n }\n\n /**\n * Get or create a singleton instance for the given config.\n * Instances are keyed by chainId:rpcUrl:restUrl. For existing instances:\n * - Config and walletProvider references are always updated\n * - Signing client is disconnected/recreated if gasPrice or walletProvider changed\n * - Rate limiter is updated if requestsPerSecond changed (without affecting signing client)\n */\n static getInstance(\n config: ManifestMCPConfig,\n walletProvider: WalletProvider,\n ): CosmosClientManager {\n const parts = [config.chainId, config.rpcUrl ?? ''];\n if (config.restUrl) parts.push(config.restUrl);\n const key = parts.join(':');\n let instance = CosmosClientManager.instances.get(key);\n\n if (!instance) {\n instance = new CosmosClientManager(config, walletProvider);\n CosmosClientManager.instances.set(key, instance);\n } else {\n // Check what changed to determine what needs updating\n const signingClientAffected =\n instance.config.gasPrice !== config.gasPrice ||\n instance.walletProvider !== walletProvider;\n\n const rateLimitChanged =\n instance.config.rateLimit?.requestsPerSecond !==\n config.rateLimit?.requestsPerSecond;\n\n // Always update config reference\n instance.config = config;\n instance.walletProvider = walletProvider;\n\n // Only invalidate signing client if fields it depends on changed\n if (signingClientAffected) {\n if (instance.signingClient) {\n instance.signingClient.disconnect();\n instance.signingClient = null;\n }\n // Also clear the promise to allow re-initialization with new config\n instance.signingClientPromise = null;\n }\n\n // Update rate limiter independently (doesn't affect signing client)\n if (rateLimitChanged) {\n const newRps =\n config.rateLimit?.requestsPerSecond ?? DEFAULT_REQUESTS_PER_SECOND;\n instance.rateLimiter = new RateLimiter({\n tokensPerInterval: newRps,\n interval: 'second',\n });\n }\n }\n\n return instance;\n }\n\n /**\n * Clear all cached instances (useful for testing or reconnection).\n * Disconnects signing clients and releases query client references before clearing.\n */\n static clearInstances(): void {\n for (const instance of CosmosClientManager.instances.values()) {\n instance.disconnect();\n }\n CosmosClientManager.instances.clear();\n }\n\n /**\n * Get the manifestjs RPC query client with all module extensions\n *\n * Automatically retries on transient connection failures with exponential backoff.\n */\n async getQueryClient(): Promise<ManifestQueryClient> {\n // Return cached client if available\n if (this.queryClient) {\n return this.queryClient;\n }\n\n // If initialization is already in progress, wait for it\n if (this.queryClientPromise) {\n return this.queryClientPromise;\n }\n\n // Start initialization and cache the promise to prevent concurrent init\n this.queryClientPromise = (async () => {\n // Capture reference to detect if superseded by disconnect/config change\n const thisInitPromise = this.queryClientPromise;\n try {\n let client: ManifestQueryClient;\n if (this.config.restUrl) {\n // Use LCD/REST for queries when restUrl is configured\n client = await withRetry(\n () => createLCDQueryClient(this.config.restUrl!),\n {\n config: this.config.retry,\n operationName: 'connect LCD query client',\n },\n );\n } else if (this.config.rpcUrl) {\n // Use RPC (existing behavior)\n client = await withRetry(\n () =>\n liftedinit.ClientFactory.createRPCQueryClient({\n rpcEndpoint: this.config.rpcUrl!,\n }),\n {\n config: this.config.retry,\n operationName: 'connect query client',\n },\n );\n } else {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Cannot create query client: neither restUrl nor rpcUrl is configured.',\n );\n }\n // Only store if this is still the active promise\n if (this.queryClientPromise === thisInitPromise) {\n this.queryClient = client;\n this.queryClientPromise = null;\n }\n return client;\n } catch (error) {\n // Clear promise on failure so retry is possible (only if still active)\n if (this.queryClientPromise === thisInitPromise) {\n this.queryClientPromise = null;\n }\n if (error instanceof ManifestMCPError) {\n throw error;\n }\n const endpoint = this.config.restUrl ?? this.config.rpcUrl;\n throw new ManifestMCPError(\n ManifestMCPErrorCode.RPC_CONNECTION_FAILED,\n `Failed to connect to ${this.config.restUrl ? 'REST' : 'RPC'} endpoint: ${error instanceof Error ? error.message : String(error)}`,\n { url: endpoint },\n );\n }\n })();\n\n return this.queryClientPromise;\n }\n\n /**\n * Get a signing client with all Manifest registries (for transactions)\n *\n * Automatically retries on transient connection failures with exponential backoff.\n */\n async getSigningClient(): Promise<SigningStargateClient> {\n if (!this.config.rpcUrl || !this.config.gasPrice) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Signing client requires rpcUrl and gasPrice configuration. Current config is query-only (REST).',\n );\n }\n\n // Return cached client if available\n if (this.signingClient) {\n return this.signingClient;\n }\n\n // If initialization is already in progress, wait for it\n if (this.signingClientPromise) {\n return this.signingClientPromise;\n }\n\n // Start initialization and cache the promise to prevent concurrent init\n this.signingClientPromise = (async () => {\n // Capture reference to detect if superseded by disconnect/config change\n const thisInitPromise = this.signingClientPromise;\n try {\n const signer = await this.walletProvider.getSigner();\n const gasPrice = GasPrice.fromString(this.config.gasPrice!);\n const { registry, aminoTypes } = getSigningManifestClientOptions();\n\n // Configure endpoint as HttpEndpoint object (required for custom options)\n const endpoint: HttpEndpoint = {\n url: this.config.rpcUrl!,\n headers: {},\n };\n\n // Note: Registry type from @cosmjs/proto-signing doesn't perfectly match\n // SigningStargateClientOptions due to telescope-generated proto types.\n // This is a known limitation with custom cosmos-sdk module registries.\n // Wrap with retry for transient connection failures\n const client = await withRetry(\n async () => {\n const c = await SigningStargateClient.connectWithSigner(\n endpoint,\n signer,\n {\n registry: registry as SigningClientRegistry,\n aminoTypes,\n gasPrice,\n broadcastTimeoutMs: DEFAULT_BROADCAST_TIMEOUT_MS,\n broadcastPollIntervalMs: DEFAULT_BROADCAST_POLL_INTERVAL_MS,\n },\n );\n // The property is private readonly with no constructor option,\n // so we must bypass TypeScript's access control to override it.\n const record = c as unknown as Record<string, unknown>;\n if (typeof record.defaultGasMultiplier === 'number') {\n record.defaultGasMultiplier = DEFAULT_GAS_MULTIPLIER;\n }\n return c;\n },\n {\n config: this.config.retry,\n operationName: 'connect signing client',\n },\n );\n // Only store if this is still the active promise\n if (this.signingClientPromise === thisInitPromise) {\n this.signingClient = client;\n this.signingClientPromise = null;\n } else {\n // Promise was superseded, clean up the client we just created\n client.disconnect();\n }\n return client;\n } catch (error) {\n // Clear promise on failure so retry is possible (only if still active)\n if (this.signingClientPromise === thisInitPromise) {\n this.signingClientPromise = null;\n }\n if (error instanceof ManifestMCPError) {\n throw error;\n }\n throw new ManifestMCPError(\n ManifestMCPErrorCode.RPC_CONNECTION_FAILED,\n `Failed to connect signing client: ${error instanceof Error ? error.message : String(error)}`,\n { rpcUrl: this.config.rpcUrl },\n );\n }\n })();\n\n return this.signingClientPromise;\n }\n\n /**\n * Get the wallet address\n */\n async getAddress(): Promise<string> {\n return this.walletProvider.getAddress();\n }\n\n /**\n * Get the configuration\n */\n getConfig(): ManifestMCPConfig {\n return this.config;\n }\n\n /**\n * Acquire a rate limit token before making an RPC request.\n * This will wait if the rate limit has been exceeded.\n */\n async acquireRateLimit(): Promise<void> {\n await this.rateLimiter.removeTokens(1);\n }\n\n /**\n * Disconnect the signing client and release query client references.\n * The query client's underlying HTTP transport is stateless and does not\n * require an explicit disconnect.\n */\n disconnect(): void {\n if (this.signingClient) {\n this.signingClient.disconnect();\n this.signingClient = null;\n }\n this.signingClientPromise = null;\n this.queryClient = null;\n this.queryClientPromise = null;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manifest-network/manifest-mcp-core",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Shared library for Cosmos SDK blockchain logic, tool functions, and server utilities (Manifest Network)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",