@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.
- package/.claude/settings.local.json +17 -0
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/publish.yml +51 -0
- package/CLAUDE.md +104 -0
- package/LICENSE +21 -0
- package/README.md +298 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js.map +1 -0
- package/dist/client.d.ts +44 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +131 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +98 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +123 -0
- package/dist/config.test.js.map +1 -0
- package/dist/cosmos.d.ts +11 -0
- package/dist/cosmos.d.ts.map +1 -0
- package/dist/cosmos.js +112 -0
- package/dist/cosmos.js.map +1 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +382 -0
- package/dist/index.js.map +1 -0
- package/dist/modules.d.ts +30 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +221 -0
- package/dist/modules.js.map +1 -0
- package/dist/modules.test.d.ts +2 -0
- package/dist/modules.test.d.ts.map +1 -0
- package/dist/modules.test.js +100 -0
- package/dist/modules.test.js.map +1 -0
- package/dist/queries/auth.d.ts +6 -0
- package/dist/queries/auth.d.ts.map +1 -0
- package/dist/queries/auth.js +93 -0
- package/dist/queries/auth.js.map +1 -0
- package/dist/queries/bank.d.ts +6 -0
- package/dist/queries/bank.d.ts.map +1 -0
- package/dist/queries/bank.js +83 -0
- package/dist/queries/bank.js.map +1 -0
- package/dist/queries/billing.d.ts +6 -0
- package/dist/queries/billing.d.ts.map +1 -0
- package/dist/queries/billing.js +115 -0
- package/dist/queries/billing.js.map +1 -0
- package/dist/queries/distribution.d.ts +6 -0
- package/dist/queries/distribution.d.ts.map +1 -0
- package/dist/queries/distribution.js +102 -0
- package/dist/queries/distribution.js.map +1 -0
- package/dist/queries/gov.d.ts +6 -0
- package/dist/queries/gov.d.ts.map +1 -0
- package/dist/queries/gov.js +92 -0
- package/dist/queries/gov.js.map +1 -0
- package/dist/queries/index.d.ts +8 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +8 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/manifest.d.ts +10 -0
- package/dist/queries/manifest.d.ts.map +1 -0
- package/dist/queries/manifest.js +14 -0
- package/dist/queries/manifest.js.map +1 -0
- package/dist/queries/staking.d.ts +6 -0
- package/dist/queries/staking.d.ts.map +1 -0
- package/dist/queries/staking.js +141 -0
- package/dist/queries/staking.js.map +1 -0
- package/dist/queries/utils.d.ts +22 -0
- package/dist/queries/utils.d.ts.map +1 -0
- package/dist/queries/utils.js +32 -0
- package/dist/queries/utils.js.map +1 -0
- package/dist/queries/utils.test.d.ts +2 -0
- package/dist/queries/utils.test.d.ts.map +1 -0
- package/dist/queries/utils.test.js +57 -0
- package/dist/queries/utils.test.js.map +1 -0
- package/dist/transactions/bank.d.ts +7 -0
- package/dist/transactions/bank.d.ts.map +1 -0
- package/dist/transactions/bank.js +76 -0
- package/dist/transactions/bank.js.map +1 -0
- package/dist/transactions/billing.d.ts +7 -0
- package/dist/transactions/billing.d.ts.map +1 -0
- package/dist/transactions/billing.js +108 -0
- package/dist/transactions/billing.js.map +1 -0
- package/dist/transactions/distribution.d.ts +7 -0
- package/dist/transactions/distribution.d.ts.map +1 -0
- package/dist/transactions/distribution.js +63 -0
- package/dist/transactions/distribution.js.map +1 -0
- package/dist/transactions/gov.d.ts +7 -0
- package/dist/transactions/gov.d.ts.map +1 -0
- package/dist/transactions/gov.js +132 -0
- package/dist/transactions/gov.js.map +1 -0
- package/dist/transactions/index.d.ts +8 -0
- package/dist/transactions/index.d.ts.map +1 -0
- package/dist/transactions/index.js +8 -0
- package/dist/transactions/index.js.map +1 -0
- package/dist/transactions/manifest.d.ts +7 -0
- package/dist/transactions/manifest.d.ts.map +1 -0
- package/dist/transactions/manifest.js +58 -0
- package/dist/transactions/manifest.js.map +1 -0
- package/dist/transactions/staking.d.ts +7 -0
- package/dist/transactions/staking.d.ts.map +1 -0
- package/dist/transactions/staking.js +72 -0
- package/dist/transactions/staking.js.map +1 -0
- package/dist/transactions/utils.d.ts +40 -0
- package/dist/transactions/utils.d.ts.map +1 -0
- package/dist/transactions/utils.js +114 -0
- package/dist/transactions/utils.js.map +1 -0
- package/dist/transactions/utils.test.d.ts +2 -0
- package/dist/transactions/utils.test.d.ts.map +1 -0
- package/dist/transactions/utils.test.js +121 -0
- package/dist/transactions/utils.test.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +55 -0
- package/dist/types.js.map +1 -0
- package/dist/wallet/index.d.ts +3 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +2 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/keplr.d.ts.map +1 -0
- package/dist/wallet/keplr.js.map +1 -0
- package/dist/wallet/mnemonic.d.ts +40 -0
- package/dist/wallet/mnemonic.d.ts.map +1 -0
- package/dist/wallet/mnemonic.js +87 -0
- package/dist/wallet/mnemonic.js.map +1 -0
- package/package.json +40 -0
- package/src/client.ts +178 -0
- package/src/config.test.ts +143 -0
- package/src/config.ts +122 -0
- package/src/cosmos.ts +196 -0
- package/src/index.ts +484 -0
- package/src/modules.test.ts +127 -0
- package/src/modules.ts +278 -0
- package/src/queries/auth.ts +136 -0
- package/src/queries/bank.ts +117 -0
- package/src/queries/billing.ts +164 -0
- package/src/queries/distribution.ts +138 -0
- package/src/queries/gov.ts +128 -0
- package/src/queries/index.ts +7 -0
- package/src/queries/staking.ts +190 -0
- package/src/queries/utils.test.ts +61 -0
- package/src/queries/utils.ts +38 -0
- package/src/transactions/bank.ts +110 -0
- package/src/transactions/billing.ts +160 -0
- package/src/transactions/distribution.ts +98 -0
- package/src/transactions/gov.ts +185 -0
- package/src/transactions/index.ts +7 -0
- package/src/transactions/manifest.ts +89 -0
- package/src/transactions/staking.ts +107 -0
- package/src/transactions/utils.test.ts +129 -0
- package/src/transactions/utils.ts +156 -0
- package/src/types.ts +152 -0
- package/src/wallet/index.ts +2 -0
- package/src/wallet/mnemonic.ts +122 -0
- 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
|
+
}
|