@manifest-network/manifest-mcp-browser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/.claude/settings.local.json +17 -0
  2. package/.github/workflows/ci.yml +37 -0
  3. package/.github/workflows/publish.yml +51 -0
  4. package/CLAUDE.md +104 -0
  5. package/LICENSE +21 -0
  6. package/README.md +298 -0
  7. package/dist/browser.d.ts.map +1 -0
  8. package/dist/browser.js.map +1 -0
  9. package/dist/client.d.ts +44 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +131 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/config.d.ts +21 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +98 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/config.test.d.ts +2 -0
  18. package/dist/config.test.d.ts.map +1 -0
  19. package/dist/config.test.js +123 -0
  20. package/dist/config.test.js.map +1 -0
  21. package/dist/cosmos.d.ts +11 -0
  22. package/dist/cosmos.d.ts.map +1 -0
  23. package/dist/cosmos.js +112 -0
  24. package/dist/cosmos.js.map +1 -0
  25. package/dist/index.d.ts +70 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +382 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/modules.d.ts +30 -0
  30. package/dist/modules.d.ts.map +1 -0
  31. package/dist/modules.js +221 -0
  32. package/dist/modules.js.map +1 -0
  33. package/dist/modules.test.d.ts +2 -0
  34. package/dist/modules.test.d.ts.map +1 -0
  35. package/dist/modules.test.js +100 -0
  36. package/dist/modules.test.js.map +1 -0
  37. package/dist/queries/auth.d.ts +6 -0
  38. package/dist/queries/auth.d.ts.map +1 -0
  39. package/dist/queries/auth.js +93 -0
  40. package/dist/queries/auth.js.map +1 -0
  41. package/dist/queries/bank.d.ts +6 -0
  42. package/dist/queries/bank.d.ts.map +1 -0
  43. package/dist/queries/bank.js +83 -0
  44. package/dist/queries/bank.js.map +1 -0
  45. package/dist/queries/billing.d.ts +6 -0
  46. package/dist/queries/billing.d.ts.map +1 -0
  47. package/dist/queries/billing.js +115 -0
  48. package/dist/queries/billing.js.map +1 -0
  49. package/dist/queries/distribution.d.ts +6 -0
  50. package/dist/queries/distribution.d.ts.map +1 -0
  51. package/dist/queries/distribution.js +102 -0
  52. package/dist/queries/distribution.js.map +1 -0
  53. package/dist/queries/gov.d.ts +6 -0
  54. package/dist/queries/gov.d.ts.map +1 -0
  55. package/dist/queries/gov.js +92 -0
  56. package/dist/queries/gov.js.map +1 -0
  57. package/dist/queries/index.d.ts +8 -0
  58. package/dist/queries/index.d.ts.map +1 -0
  59. package/dist/queries/index.js +8 -0
  60. package/dist/queries/index.js.map +1 -0
  61. package/dist/queries/manifest.d.ts +10 -0
  62. package/dist/queries/manifest.d.ts.map +1 -0
  63. package/dist/queries/manifest.js +14 -0
  64. package/dist/queries/manifest.js.map +1 -0
  65. package/dist/queries/staking.d.ts +6 -0
  66. package/dist/queries/staking.d.ts.map +1 -0
  67. package/dist/queries/staking.js +141 -0
  68. package/dist/queries/staking.js.map +1 -0
  69. package/dist/queries/utils.d.ts +22 -0
  70. package/dist/queries/utils.d.ts.map +1 -0
  71. package/dist/queries/utils.js +32 -0
  72. package/dist/queries/utils.js.map +1 -0
  73. package/dist/queries/utils.test.d.ts +2 -0
  74. package/dist/queries/utils.test.d.ts.map +1 -0
  75. package/dist/queries/utils.test.js +57 -0
  76. package/dist/queries/utils.test.js.map +1 -0
  77. package/dist/transactions/bank.d.ts +7 -0
  78. package/dist/transactions/bank.d.ts.map +1 -0
  79. package/dist/transactions/bank.js +76 -0
  80. package/dist/transactions/bank.js.map +1 -0
  81. package/dist/transactions/billing.d.ts +7 -0
  82. package/dist/transactions/billing.d.ts.map +1 -0
  83. package/dist/transactions/billing.js +108 -0
  84. package/dist/transactions/billing.js.map +1 -0
  85. package/dist/transactions/distribution.d.ts +7 -0
  86. package/dist/transactions/distribution.d.ts.map +1 -0
  87. package/dist/transactions/distribution.js +63 -0
  88. package/dist/transactions/distribution.js.map +1 -0
  89. package/dist/transactions/gov.d.ts +7 -0
  90. package/dist/transactions/gov.d.ts.map +1 -0
  91. package/dist/transactions/gov.js +132 -0
  92. package/dist/transactions/gov.js.map +1 -0
  93. package/dist/transactions/index.d.ts +8 -0
  94. package/dist/transactions/index.d.ts.map +1 -0
  95. package/dist/transactions/index.js +8 -0
  96. package/dist/transactions/index.js.map +1 -0
  97. package/dist/transactions/manifest.d.ts +7 -0
  98. package/dist/transactions/manifest.d.ts.map +1 -0
  99. package/dist/transactions/manifest.js +58 -0
  100. package/dist/transactions/manifest.js.map +1 -0
  101. package/dist/transactions/staking.d.ts +7 -0
  102. package/dist/transactions/staking.d.ts.map +1 -0
  103. package/dist/transactions/staking.js +72 -0
  104. package/dist/transactions/staking.js.map +1 -0
  105. package/dist/transactions/utils.d.ts +40 -0
  106. package/dist/transactions/utils.d.ts.map +1 -0
  107. package/dist/transactions/utils.js +114 -0
  108. package/dist/transactions/utils.js.map +1 -0
  109. package/dist/transactions/utils.test.d.ts +2 -0
  110. package/dist/transactions/utils.test.d.ts.map +1 -0
  111. package/dist/transactions/utils.test.js +121 -0
  112. package/dist/transactions/utils.test.js.map +1 -0
  113. package/dist/types.d.ts +110 -0
  114. package/dist/types.d.ts.map +1 -0
  115. package/dist/types.js +55 -0
  116. package/dist/types.js.map +1 -0
  117. package/dist/wallet/index.d.ts +3 -0
  118. package/dist/wallet/index.d.ts.map +1 -0
  119. package/dist/wallet/index.js +2 -0
  120. package/dist/wallet/index.js.map +1 -0
  121. package/dist/wallet/keplr.d.ts.map +1 -0
  122. package/dist/wallet/keplr.js.map +1 -0
  123. package/dist/wallet/mnemonic.d.ts +40 -0
  124. package/dist/wallet/mnemonic.d.ts.map +1 -0
  125. package/dist/wallet/mnemonic.js +87 -0
  126. package/dist/wallet/mnemonic.js.map +1 -0
  127. package/package.json +40 -0
  128. package/src/client.ts +178 -0
  129. package/src/config.test.ts +143 -0
  130. package/src/config.ts +122 -0
  131. package/src/cosmos.ts +196 -0
  132. package/src/index.ts +484 -0
  133. package/src/modules.test.ts +127 -0
  134. package/src/modules.ts +278 -0
  135. package/src/queries/auth.ts +136 -0
  136. package/src/queries/bank.ts +117 -0
  137. package/src/queries/billing.ts +164 -0
  138. package/src/queries/distribution.ts +138 -0
  139. package/src/queries/gov.ts +128 -0
  140. package/src/queries/index.ts +7 -0
  141. package/src/queries/staking.ts +190 -0
  142. package/src/queries/utils.test.ts +61 -0
  143. package/src/queries/utils.ts +38 -0
  144. package/src/transactions/bank.ts +110 -0
  145. package/src/transactions/billing.ts +160 -0
  146. package/src/transactions/distribution.ts +98 -0
  147. package/src/transactions/gov.ts +185 -0
  148. package/src/transactions/index.ts +7 -0
  149. package/src/transactions/manifest.ts +89 -0
  150. package/src/transactions/staking.ts +107 -0
  151. package/src/transactions/utils.test.ts +129 -0
  152. package/src/transactions/utils.ts +156 -0
  153. package/src/types.ts +152 -0
  154. package/src/wallet/index.ts +2 -0
  155. package/src/wallet/mnemonic.ts +122 -0
  156. package/tsconfig.json +23 -0
package/src/client.ts ADDED
@@ -0,0 +1,178 @@
1
+ import { SigningStargateClient, GasPrice, HttpEndpoint } from '@cosmjs/stargate';
2
+ import {
3
+ cosmosProtoRegistry,
4
+ cosmosAminoConverters,
5
+ liftedinitProtoRegistry,
6
+ liftedinitAminoConverters,
7
+ strangeloveVenturesProtoRegistry,
8
+ strangeloveVenturesAminoConverters,
9
+ osmosisProtoRegistry,
10
+ osmosisAminoConverters,
11
+ liftedinit,
12
+ } from '@manifest-network/manifestjs';
13
+ import { Registry } from '@cosmjs/proto-signing';
14
+ import { AminoTypes } from '@cosmjs/stargate';
15
+ import { ManifestMCPConfig, WalletProvider, ManifestMCPError, ManifestMCPErrorCode } from './types.js';
16
+
17
+ // Type for the RPC query client from manifestjs liftedinit bundle
18
+ // This includes cosmos modules + liftedinit-specific modules (billing, manifest, sku)
19
+ export type ManifestQueryClient = Awaited<ReturnType<typeof liftedinit.ClientFactory.createRPCQueryClient>>;
20
+
21
+ /** Default timeout for transaction broadcast (60 seconds) */
22
+ const DEFAULT_BROADCAST_TIMEOUT_MS = 60_000;
23
+
24
+ /** Default polling interval for transaction confirmation (3 seconds) */
25
+ const DEFAULT_BROADCAST_POLL_INTERVAL_MS = 3_000;
26
+
27
+ /**
28
+ * Get combined signing client options with all Manifest registries
29
+ */
30
+ function getSigningManifestClientOptions() {
31
+ const registry = new Registry([
32
+ ...cosmosProtoRegistry,
33
+ ...liftedinitProtoRegistry,
34
+ ...strangeloveVenturesProtoRegistry,
35
+ ...osmosisProtoRegistry,
36
+ ]);
37
+
38
+ const aminoTypes = new AminoTypes({
39
+ ...cosmosAminoConverters,
40
+ ...liftedinitAminoConverters,
41
+ ...strangeloveVenturesAminoConverters,
42
+ ...osmosisAminoConverters,
43
+ });
44
+
45
+ return { registry, aminoTypes };
46
+ }
47
+
48
+ /**
49
+ * Manages CosmJS client instances with lazy initialization and singleton pattern
50
+ */
51
+ export class CosmosClientManager {
52
+ private static instances: Map<string, CosmosClientManager> = new Map();
53
+
54
+ private config: ManifestMCPConfig;
55
+ private walletProvider: WalletProvider;
56
+ private queryClient: ManifestQueryClient | null = null;
57
+ private signingClient: SigningStargateClient | null = null;
58
+
59
+ private constructor(config: ManifestMCPConfig, walletProvider: WalletProvider) {
60
+ this.config = config;
61
+ this.walletProvider = walletProvider;
62
+ }
63
+
64
+ /**
65
+ * Get or create a singleton instance for the given config
66
+ */
67
+ static getInstance(
68
+ config: ManifestMCPConfig,
69
+ walletProvider: WalletProvider
70
+ ): CosmosClientManager {
71
+ const key = `${config.chainId}:${config.rpcUrl}`;
72
+ let instance = CosmosClientManager.instances.get(key);
73
+
74
+ if (!instance) {
75
+ instance = new CosmosClientManager(config, walletProvider);
76
+ CosmosClientManager.instances.set(key, instance);
77
+ }
78
+
79
+ return instance;
80
+ }
81
+
82
+ /**
83
+ * Clear all cached instances (useful for testing or reconnection)
84
+ */
85
+ static clearInstances(): void {
86
+ CosmosClientManager.instances.clear();
87
+ }
88
+
89
+ /**
90
+ * Get the manifestjs RPC query client with all module extensions
91
+ */
92
+ async getQueryClient(): Promise<ManifestQueryClient> {
93
+ if (!this.queryClient) {
94
+ try {
95
+ // Use liftedinit ClientFactory which includes cosmos + liftedinit modules
96
+ this.queryClient = await liftedinit.ClientFactory.createRPCQueryClient({
97
+ rpcEndpoint: this.config.rpcUrl,
98
+ });
99
+ } catch (error) {
100
+ throw new ManifestMCPError(
101
+ ManifestMCPErrorCode.RPC_CONNECTION_FAILED,
102
+ `Failed to connect to RPC endpoint: ${error instanceof Error ? error.message : String(error)}`,
103
+ { rpcUrl: this.config.rpcUrl }
104
+ );
105
+ }
106
+ }
107
+ return this.queryClient;
108
+ }
109
+
110
+ /**
111
+ * Get a signing client with all Manifest registries (for transactions)
112
+ */
113
+ async getSigningClient(): Promise<SigningStargateClient> {
114
+ if (!this.signingClient) {
115
+ try {
116
+ const signer = await this.walletProvider.getSigner();
117
+ const gasPrice = GasPrice.fromString(this.config.gasPrice);
118
+ const { registry, aminoTypes } = getSigningManifestClientOptions();
119
+
120
+ // Configure endpoint with HTTP timeout
121
+ const endpoint: HttpEndpoint = {
122
+ url: this.config.rpcUrl,
123
+ headers: {},
124
+ };
125
+
126
+ // Note: Registry type from @cosmjs/proto-signing doesn't perfectly match
127
+ // SigningStargateClientOptions due to telescope-generated proto types.
128
+ // This is a known limitation with custom cosmos-sdk module registries.
129
+ this.signingClient = await SigningStargateClient.connectWithSigner(
130
+ endpoint,
131
+ signer,
132
+ {
133
+ registry: registry as Parameters<typeof SigningStargateClient.connectWithSigner>[2] extends { registry?: infer R } ? R : never,
134
+ aminoTypes,
135
+ gasPrice,
136
+ broadcastTimeoutMs: DEFAULT_BROADCAST_TIMEOUT_MS,
137
+ broadcastPollIntervalMs: DEFAULT_BROADCAST_POLL_INTERVAL_MS,
138
+ }
139
+ );
140
+ } catch (error) {
141
+ if (error instanceof ManifestMCPError) {
142
+ throw error;
143
+ }
144
+ throw new ManifestMCPError(
145
+ ManifestMCPErrorCode.RPC_CONNECTION_FAILED,
146
+ `Failed to connect signing client: ${error instanceof Error ? error.message : String(error)}`,
147
+ { rpcUrl: this.config.rpcUrl }
148
+ );
149
+ }
150
+ }
151
+ return this.signingClient;
152
+ }
153
+
154
+ /**
155
+ * Get the wallet address
156
+ */
157
+ async getAddress(): Promise<string> {
158
+ return this.walletProvider.getAddress();
159
+ }
160
+
161
+ /**
162
+ * Get the configuration
163
+ */
164
+ getConfig(): ManifestMCPConfig {
165
+ return this.config;
166
+ }
167
+
168
+ /**
169
+ * Disconnect all clients
170
+ */
171
+ disconnect(): void {
172
+ if (this.signingClient) {
173
+ this.signingClient.disconnect();
174
+ this.signingClient = null;
175
+ }
176
+ this.queryClient = null;
177
+ }
178
+ }
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createConfig, validateConfig, createValidatedConfig } from './config.js';
3
+ import { ManifestMCPError, ManifestMCPErrorCode } from './types.js';
4
+
5
+ describe('createConfig', () => {
6
+ it('should apply default values', () => {
7
+ const config = createConfig({
8
+ chainId: 'test-chain',
9
+ rpcUrl: 'https://example.com',
10
+ gasPrice: '1.0umfx',
11
+ });
12
+
13
+ expect(config.chainId).toBe('test-chain');
14
+ expect(config.rpcUrl).toBe('https://example.com');
15
+ expect(config.gasPrice).toBe('1.0umfx');
16
+ expect(config.gasAdjustment).toBe(1.3);
17
+ expect(config.addressPrefix).toBe('manifest');
18
+ });
19
+
20
+ it('should preserve provided optional values', () => {
21
+ const config = createConfig({
22
+ chainId: 'test-chain',
23
+ rpcUrl: 'https://example.com',
24
+ gasPrice: '1.0umfx',
25
+ gasAdjustment: 2.0,
26
+ addressPrefix: 'custom',
27
+ });
28
+
29
+ expect(config.gasAdjustment).toBe(2.0);
30
+ expect(config.addressPrefix).toBe('custom');
31
+ });
32
+ });
33
+
34
+ describe('validateConfig', () => {
35
+ it('should return valid for correct config', () => {
36
+ const result = validateConfig({
37
+ chainId: 'manifest-testnet',
38
+ rpcUrl: 'https://rpc.example.com',
39
+ gasPrice: '1.0umfx',
40
+ });
41
+
42
+ expect(result.valid).toBe(true);
43
+ expect(result.errors).toHaveLength(0);
44
+ });
45
+
46
+ it('should detect missing required fields', () => {
47
+ const result = validateConfig({});
48
+
49
+ expect(result.valid).toBe(false);
50
+ expect(result.errors).toContain('chainId is required');
51
+ expect(result.errors).toContain('rpcUrl is required');
52
+ expect(result.errors).toContain('gasPrice is required');
53
+ });
54
+
55
+ it('should validate chainId format', () => {
56
+ const result = validateConfig({
57
+ chainId: '-invalid',
58
+ rpcUrl: 'https://example.com',
59
+ gasPrice: '1.0umfx',
60
+ });
61
+
62
+ expect(result.valid).toBe(false);
63
+ expect(result.errors.some(e => e.includes('chainId'))).toBe(true);
64
+ });
65
+
66
+ it('should validate rpcUrl format', () => {
67
+ const result = validateConfig({
68
+ chainId: 'test',
69
+ rpcUrl: 'not-a-url',
70
+ gasPrice: '1.0umfx',
71
+ });
72
+
73
+ expect(result.valid).toBe(false);
74
+ expect(result.errors.some(e => e.includes('rpcUrl'))).toBe(true);
75
+ });
76
+
77
+ it('should validate gasPrice format', () => {
78
+ const result = validateConfig({
79
+ chainId: 'test',
80
+ rpcUrl: 'https://example.com',
81
+ gasPrice: 'invalid',
82
+ });
83
+
84
+ expect(result.valid).toBe(false);
85
+ expect(result.errors.some(e => e.includes('gasPrice'))).toBe(true);
86
+ });
87
+
88
+ it('should validate optional gasAdjustment', () => {
89
+ const result = validateConfig({
90
+ chainId: 'test',
91
+ rpcUrl: 'https://example.com',
92
+ gasPrice: '1.0umfx',
93
+ gasAdjustment: -1,
94
+ });
95
+
96
+ expect(result.valid).toBe(false);
97
+ expect(result.errors.some(e => e.includes('gasAdjustment'))).toBe(true);
98
+ });
99
+
100
+ it('should validate optional addressPrefix', () => {
101
+ const result = validateConfig({
102
+ chainId: 'test',
103
+ rpcUrl: 'https://example.com',
104
+ gasPrice: '1.0umfx',
105
+ addressPrefix: 'INVALID',
106
+ });
107
+
108
+ expect(result.valid).toBe(false);
109
+ expect(result.errors.some(e => e.includes('addressPrefix'))).toBe(true);
110
+ });
111
+ });
112
+
113
+ describe('createValidatedConfig', () => {
114
+ it('should return config for valid input', () => {
115
+ const config = createValidatedConfig({
116
+ chainId: 'test-chain',
117
+ rpcUrl: 'https://example.com',
118
+ gasPrice: '1.0umfx',
119
+ });
120
+
121
+ expect(config.chainId).toBe('test-chain');
122
+ });
123
+
124
+ it('should throw ManifestMCPError for invalid input', () => {
125
+ expect(() => createValidatedConfig({
126
+ chainId: '',
127
+ rpcUrl: '',
128
+ gasPrice: '',
129
+ })).toThrow(ManifestMCPError);
130
+ });
131
+
132
+ it('should have INVALID_CONFIG error code', () => {
133
+ try {
134
+ createValidatedConfig({
135
+ chainId: '',
136
+ rpcUrl: '',
137
+ gasPrice: '',
138
+ });
139
+ } catch (error) {
140
+ expect((error as ManifestMCPError).code).toBe(ManifestMCPErrorCode.INVALID_CONFIG);
141
+ }
142
+ });
143
+ });
package/src/config.ts ADDED
@@ -0,0 +1,122 @@
1
+ import { ManifestMCPConfig, ManifestMCPError, ManifestMCPErrorCode } from './types.js';
2
+
3
+ /**
4
+ * Default gas adjustment multiplier
5
+ */
6
+ const DEFAULT_GAS_ADJUSTMENT = 1.3;
7
+
8
+ /**
9
+ * Default address prefix for Manifest Network
10
+ */
11
+ const DEFAULT_ADDRESS_PREFIX = 'manifest';
12
+
13
+ /**
14
+ * Validate a URL string
15
+ */
16
+ function isValidUrl(url: string): boolean {
17
+ try {
18
+ new URL(url);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Validate gas price format (e.g., "1.0umfx")
27
+ */
28
+ function isValidGasPrice(gasPrice: string): boolean {
29
+ // Gas price should be a number followed by a denomination
30
+ return /^\d+(\.\d+)?[a-zA-Z]+$/.test(gasPrice);
31
+ }
32
+
33
+ /**
34
+ * Validate chain ID format
35
+ */
36
+ function isValidChainId(chainId: string): boolean {
37
+ // Chain ID should be alphanumeric with hyphens
38
+ return /^[a-zA-Z0-9][\w-]*$/.test(chainId);
39
+ }
40
+
41
+ /**
42
+ * Create a configuration object with defaults applied
43
+ */
44
+ export function createConfig(input: ManifestMCPConfig): ManifestMCPConfig {
45
+ return {
46
+ chainId: input.chainId,
47
+ rpcUrl: input.rpcUrl,
48
+ gasPrice: input.gasPrice,
49
+ gasAdjustment: input.gasAdjustment ?? DEFAULT_GAS_ADJUSTMENT,
50
+ addressPrefix: input.addressPrefix ?? DEFAULT_ADDRESS_PREFIX,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Validation result
56
+ */
57
+ export interface ValidationResult {
58
+ valid: boolean;
59
+ errors: string[];
60
+ }
61
+
62
+ /**
63
+ * Validate a configuration object
64
+ */
65
+ export function validateConfig(config: Partial<ManifestMCPConfig>): ValidationResult {
66
+ const errors: string[] = [];
67
+
68
+ // Required fields
69
+ if (!config.chainId) {
70
+ errors.push('chainId is required');
71
+ } else if (!isValidChainId(config.chainId)) {
72
+ errors.push('chainId must be alphanumeric with hyphens (e.g., "manifest-ledger-testnet")');
73
+ }
74
+
75
+ if (!config.rpcUrl) {
76
+ errors.push('rpcUrl is required');
77
+ } else if (!isValidUrl(config.rpcUrl)) {
78
+ errors.push('rpcUrl must be a valid URL');
79
+ }
80
+
81
+ if (!config.gasPrice) {
82
+ errors.push('gasPrice is required');
83
+ } else if (!isValidGasPrice(config.gasPrice)) {
84
+ errors.push('gasPrice must be a number followed by denomination (e.g., "1.0umfx")');
85
+ }
86
+
87
+ // Optional fields
88
+ if (config.gasAdjustment !== undefined) {
89
+ if (typeof config.gasAdjustment !== 'number' || config.gasAdjustment <= 0) {
90
+ errors.push('gasAdjustment must be a positive number');
91
+ }
92
+ }
93
+
94
+ if (config.addressPrefix !== undefined) {
95
+ if (!/^[a-z]+$/.test(config.addressPrefix)) {
96
+ errors.push('addressPrefix must be lowercase letters only');
97
+ }
98
+ }
99
+
100
+ return {
101
+ valid: errors.length === 0,
102
+ errors,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Create and validate a configuration, throwing on invalid config
108
+ */
109
+ export function createValidatedConfig(input: ManifestMCPConfig): ManifestMCPConfig {
110
+ const validation = validateConfig(input);
111
+
112
+ if (!validation.valid) {
113
+ throw new ManifestMCPError(
114
+ ManifestMCPErrorCode.INVALID_CONFIG,
115
+ `Invalid configuration: ${validation.errors.join(', ')}`,
116
+ { errors: validation.errors }
117
+ );
118
+ }
119
+
120
+ return createConfig(input);
121
+ }
122
+
package/src/cosmos.ts ADDED
@@ -0,0 +1,196 @@
1
+ import { CosmosClientManager } from './client.js';
2
+ import { CosmosQueryResult, CosmosTxResult, ManifestMCPError, ManifestMCPErrorCode } from './types.js';
3
+ import { getSupportedModules } from './modules.js';
4
+ import { routeBankQuery } from './queries/bank.js';
5
+ import { routeStakingQuery } from './queries/staking.js';
6
+ import { routeDistributionQuery } from './queries/distribution.js';
7
+ import { routeGovQuery } from './queries/gov.js';
8
+ import { routeAuthQuery } from './queries/auth.js';
9
+ import { routeBillingQuery } from './queries/billing.js';
10
+ import { routeBankTransaction } from './transactions/bank.js';
11
+ import { routeStakingTransaction } from './transactions/staking.js';
12
+ import { routeDistributionTransaction } from './transactions/distribution.js';
13
+ import { routeGovTransaction } from './transactions/gov.js';
14
+ import { routeBillingTransaction } from './transactions/billing.js';
15
+ import { routeManifestTransaction } from './transactions/manifest.js';
16
+
17
+ // Validation pattern for module/subcommand names (alphanumeric, hyphens, underscores)
18
+ // First character must not be a hyphen to prevent potential issues
19
+ const VALID_NAME_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/;
20
+
21
+ /**
22
+ * Validate that a string is safe for use as a module or subcommand name
23
+ */
24
+ function validateName(name: string, field: string): void {
25
+ if (!name || !VALID_NAME_PATTERN.test(name)) {
26
+ throw new ManifestMCPError(
27
+ ManifestMCPErrorCode.QUERY_FAILED,
28
+ `Invalid ${field}: "${name}". Only alphanumeric characters, hyphens, and underscores are allowed.`
29
+ );
30
+ }
31
+ }
32
+
33
+ // Get module lists from the authoritative registry
34
+ const supportedModules = getSupportedModules();
35
+ const QUERY_MODULES = Object.keys(supportedModules.query);
36
+ const TX_MODULES = Object.keys(supportedModules.tx);
37
+
38
+ /**
39
+ * Execute a Cosmos query via manifestjs RPC client
40
+ */
41
+ export async function cosmosQuery(
42
+ clientManager: CosmosClientManager,
43
+ module: string,
44
+ subcommand: string,
45
+ args: string[] = []
46
+ ): Promise<CosmosQueryResult> {
47
+ validateName(module, 'module');
48
+ validateName(subcommand, 'subcommand');
49
+
50
+ const queryClient = await clientManager.getQueryClient();
51
+
52
+ let result: Record<string, unknown>;
53
+
54
+ try {
55
+ switch (module) {
56
+ case 'bank':
57
+ result = await routeBankQuery(queryClient, subcommand, args);
58
+ break;
59
+ case 'staking':
60
+ result = await routeStakingQuery(queryClient, subcommand, args);
61
+ break;
62
+ case 'distribution':
63
+ result = await routeDistributionQuery(queryClient, subcommand, args);
64
+ break;
65
+ case 'gov':
66
+ result = await routeGovQuery(queryClient, subcommand, args);
67
+ break;
68
+ case 'auth':
69
+ result = await routeAuthQuery(queryClient, subcommand, args);
70
+ break;
71
+ case 'billing':
72
+ result = await routeBillingQuery(queryClient, subcommand, args);
73
+ break;
74
+ default:
75
+ throw new ManifestMCPError(
76
+ ManifestMCPErrorCode.UNKNOWN_MODULE,
77
+ `Unknown query module: ${module}`,
78
+ { availableModules: QUERY_MODULES }
79
+ );
80
+ }
81
+
82
+ return {
83
+ module,
84
+ subcommand,
85
+ result,
86
+ };
87
+ } catch (error) {
88
+ if (error instanceof ManifestMCPError) {
89
+ throw error;
90
+ }
91
+ throw new ManifestMCPError(
92
+ ManifestMCPErrorCode.QUERY_FAILED,
93
+ `Query ${module} ${subcommand} failed: ${error instanceof Error ? error.message : String(error)}`
94
+ );
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Execute a Cosmos transaction via manifestjs signing client
100
+ */
101
+ export async function cosmosTx(
102
+ clientManager: CosmosClientManager,
103
+ module: string,
104
+ subcommand: string,
105
+ args: string[] = [],
106
+ waitForConfirmation: boolean = false
107
+ ): Promise<CosmosTxResult> {
108
+ validateName(module, 'module');
109
+ validateName(subcommand, 'subcommand');
110
+
111
+ const signingClient = await clientManager.getSigningClient();
112
+ const senderAddress = await clientManager.getAddress();
113
+ const config = clientManager.getConfig();
114
+
115
+ try {
116
+ switch (module) {
117
+ case 'bank':
118
+ return await routeBankTransaction(
119
+ signingClient,
120
+ senderAddress,
121
+ subcommand,
122
+ args,
123
+ config,
124
+ waitForConfirmation
125
+ );
126
+ case 'staking':
127
+ return await routeStakingTransaction(
128
+ signingClient,
129
+ senderAddress,
130
+ subcommand,
131
+ args,
132
+ config,
133
+ waitForConfirmation
134
+ );
135
+ case 'distribution':
136
+ return await routeDistributionTransaction(
137
+ signingClient,
138
+ senderAddress,
139
+ subcommand,
140
+ args,
141
+ config,
142
+ waitForConfirmation
143
+ );
144
+ case 'gov':
145
+ return await routeGovTransaction(
146
+ signingClient,
147
+ senderAddress,
148
+ subcommand,
149
+ args,
150
+ config,
151
+ waitForConfirmation
152
+ );
153
+ case 'billing':
154
+ return await routeBillingTransaction(
155
+ signingClient,
156
+ senderAddress,
157
+ subcommand,
158
+ args,
159
+ config,
160
+ waitForConfirmation
161
+ );
162
+ case 'manifest':
163
+ return await routeManifestTransaction(
164
+ signingClient,
165
+ senderAddress,
166
+ subcommand,
167
+ args,
168
+ config,
169
+ waitForConfirmation
170
+ );
171
+ default:
172
+ throw new ManifestMCPError(
173
+ ManifestMCPErrorCode.UNKNOWN_MODULE,
174
+ `Unknown tx module: ${module}`,
175
+ { availableModules: TX_MODULES }
176
+ );
177
+ }
178
+ } catch (error) {
179
+ if (error instanceof ManifestMCPError) {
180
+ // Re-throw with enriched context if not already present
181
+ if (!error.details?.module) {
182
+ throw new ManifestMCPError(
183
+ error.code,
184
+ error.message,
185
+ { ...error.details, module, subcommand, args }
186
+ );
187
+ }
188
+ throw error;
189
+ }
190
+ throw new ManifestMCPError(
191
+ ManifestMCPErrorCode.TX_FAILED,
192
+ `Tx ${module} ${subcommand} failed: ${error instanceof Error ? error.message : String(error)}`,
193
+ { module, subcommand, args }
194
+ );
195
+ }
196
+ }