@kadi.build/deploy-ability 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +523 -0
- package/dist/constants.d.ts +82 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +82 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors/certificate-error.d.ts +95 -0
- package/dist/errors/certificate-error.d.ts.map +1 -0
- package/dist/errors/certificate-error.js +111 -0
- package/dist/errors/certificate-error.js.map +1 -0
- package/dist/errors/deployment-error.d.ts +122 -0
- package/dist/errors/deployment-error.d.ts.map +1 -0
- package/dist/errors/deployment-error.js +185 -0
- package/dist/errors/deployment-error.js.map +1 -0
- package/dist/errors/index.d.ts +13 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +18 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/profile-error.d.ts +106 -0
- package/dist/errors/profile-error.d.ts.map +1 -0
- package/dist/errors/profile-error.js +127 -0
- package/dist/errors/profile-error.js.map +1 -0
- package/dist/errors/provider-error.d.ts +104 -0
- package/dist/errors/provider-error.d.ts.map +1 -0
- package/dist/errors/provider-error.js +120 -0
- package/dist/errors/provider-error.js.map +1 -0
- package/dist/errors/wallet-error.d.ts +131 -0
- package/dist/errors/wallet-error.d.ts.map +1 -0
- package/dist/errors/wallet-error.js +154 -0
- package/dist/errors/wallet-error.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/targets/akash/bid-selectors.d.ts +251 -0
- package/dist/targets/akash/bid-selectors.d.ts.map +1 -0
- package/dist/targets/akash/bid-selectors.js +322 -0
- package/dist/targets/akash/bid-selectors.js.map +1 -0
- package/dist/targets/akash/bid-types.d.ts +297 -0
- package/dist/targets/akash/bid-types.d.ts.map +1 -0
- package/dist/targets/akash/bid-types.js +89 -0
- package/dist/targets/akash/bid-types.js.map +1 -0
- package/dist/targets/akash/blockchain-client.d.ts +577 -0
- package/dist/targets/akash/blockchain-client.d.ts.map +1 -0
- package/dist/targets/akash/blockchain-client.js +803 -0
- package/dist/targets/akash/blockchain-client.js.map +1 -0
- package/dist/targets/akash/certificate-manager.d.ts +228 -0
- package/dist/targets/akash/certificate-manager.d.ts.map +1 -0
- package/dist/targets/akash/certificate-manager.js +395 -0
- package/dist/targets/akash/certificate-manager.js.map +1 -0
- package/dist/targets/akash/constants.d.ts +231 -0
- package/dist/targets/akash/constants.d.ts.map +1 -0
- package/dist/targets/akash/constants.js +225 -0
- package/dist/targets/akash/constants.js.map +1 -0
- package/dist/targets/akash/deployer.d.ts +136 -0
- package/dist/targets/akash/deployer.d.ts.map +1 -0
- package/dist/targets/akash/deployer.js +599 -0
- package/dist/targets/akash/deployer.js.map +1 -0
- package/dist/targets/akash/environment.d.ts +241 -0
- package/dist/targets/akash/environment.d.ts.map +1 -0
- package/dist/targets/akash/environment.js +245 -0
- package/dist/targets/akash/environment.js.map +1 -0
- package/dist/targets/akash/index.d.ts +1113 -0
- package/dist/targets/akash/index.d.ts.map +1 -0
- package/dist/targets/akash/index.js +909 -0
- package/dist/targets/akash/index.js.map +1 -0
- package/dist/targets/akash/lease-monitor.d.ts +51 -0
- package/dist/targets/akash/lease-monitor.d.ts.map +1 -0
- package/dist/targets/akash/lease-monitor.js +110 -0
- package/dist/targets/akash/lease-monitor.js.map +1 -0
- package/dist/targets/akash/logs.d.ts +71 -0
- package/dist/targets/akash/logs.d.ts.map +1 -0
- package/dist/targets/akash/logs.js +311 -0
- package/dist/targets/akash/logs.js.map +1 -0
- package/dist/targets/akash/logs.types.d.ts +102 -0
- package/dist/targets/akash/logs.types.d.ts.map +1 -0
- package/dist/targets/akash/logs.types.js +9 -0
- package/dist/targets/akash/logs.types.js.map +1 -0
- package/dist/targets/akash/pricing.d.ts +247 -0
- package/dist/targets/akash/pricing.d.ts.map +1 -0
- package/dist/targets/akash/pricing.js +246 -0
- package/dist/targets/akash/pricing.js.map +1 -0
- package/dist/targets/akash/provider-client.d.ts +114 -0
- package/dist/targets/akash/provider-client.d.ts.map +1 -0
- package/dist/targets/akash/provider-client.js +318 -0
- package/dist/targets/akash/provider-client.js.map +1 -0
- package/dist/targets/akash/provider-metadata.d.ts +228 -0
- package/dist/targets/akash/provider-metadata.d.ts.map +1 -0
- package/dist/targets/akash/provider-metadata.js +14 -0
- package/dist/targets/akash/provider-metadata.js.map +1 -0
- package/dist/targets/akash/provider-service.d.ts +133 -0
- package/dist/targets/akash/provider-service.d.ts.map +1 -0
- package/dist/targets/akash/provider-service.js +391 -0
- package/dist/targets/akash/provider-service.js.map +1 -0
- package/dist/targets/akash/query-client.d.ts +125 -0
- package/dist/targets/akash/query-client.d.ts.map +1 -0
- package/dist/targets/akash/query-client.js +332 -0
- package/dist/targets/akash/query-client.js.map +1 -0
- package/dist/targets/akash/sdl-generator.d.ts +31 -0
- package/dist/targets/akash/sdl-generator.d.ts.map +1 -0
- package/dist/targets/akash/sdl-generator.js +279 -0
- package/dist/targets/akash/sdl-generator.js.map +1 -0
- package/dist/targets/akash/types.d.ts +285 -0
- package/dist/targets/akash/types.d.ts.map +1 -0
- package/dist/targets/akash/types.js +54 -0
- package/dist/targets/akash/types.js.map +1 -0
- package/dist/targets/akash/wallet-manager.d.ts +526 -0
- package/dist/targets/akash/wallet-manager.d.ts.map +1 -0
- package/dist/targets/akash/wallet-manager.js +953 -0
- package/dist/targets/akash/wallet-manager.js.map +1 -0
- package/dist/targets/local/compose-generator.d.ts +244 -0
- package/dist/targets/local/compose-generator.d.ts.map +1 -0
- package/dist/targets/local/compose-generator.js +324 -0
- package/dist/targets/local/compose-generator.js.map +1 -0
- package/dist/targets/local/deployer.d.ts +82 -0
- package/dist/targets/local/deployer.d.ts.map +1 -0
- package/dist/targets/local/deployer.js +367 -0
- package/dist/targets/local/deployer.js.map +1 -0
- package/dist/targets/local/engine-manager.d.ts +155 -0
- package/dist/targets/local/engine-manager.d.ts.map +1 -0
- package/dist/targets/local/engine-manager.js +250 -0
- package/dist/targets/local/engine-manager.js.map +1 -0
- package/dist/targets/local/index.d.ts +40 -0
- package/dist/targets/local/index.d.ts.map +1 -0
- package/dist/targets/local/index.js +43 -0
- package/dist/targets/local/index.js.map +1 -0
- package/dist/targets/local/network-manager.d.ts +160 -0
- package/dist/targets/local/network-manager.d.ts.map +1 -0
- package/dist/targets/local/network-manager.js +337 -0
- package/dist/targets/local/network-manager.js.map +1 -0
- package/dist/targets/local/types.d.ts +327 -0
- package/dist/targets/local/types.d.ts.map +1 -0
- package/dist/targets/local/types.js +9 -0
- package/dist/targets/local/types.js.map +1 -0
- package/dist/types/common.d.ts +585 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +13 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/options.d.ts +329 -0
- package/dist/types/options.d.ts.map +1 -0
- package/dist/types/options.js +10 -0
- package/dist/types/options.js.map +1 -0
- package/dist/types/profiles.d.ts +329 -0
- package/dist/types/profiles.d.ts.map +1 -0
- package/dist/types/profiles.js +27 -0
- package/dist/types/profiles.js.map +1 -0
- package/dist/types/results.d.ts +443 -0
- package/dist/types/results.d.ts.map +1 -0
- package/dist/types/results.js +64 -0
- package/dist/types/results.js.map +1 -0
- package/dist/types/validators.d.ts +118 -0
- package/dist/types/validators.d.ts.map +1 -0
- package/dist/types/validators.js +198 -0
- package/dist/types/validators.js.map +1 -0
- package/dist/utils/command-runner.d.ts +128 -0
- package/dist/utils/command-runner.d.ts.map +1 -0
- package/dist/utils/command-runner.js +210 -0
- package/dist/utils/command-runner.js.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +68 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +93 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/profile-loader.d.ts +76 -0
- package/dist/utils/profile-loader.d.ts.map +1 -0
- package/dist/utils/profile-loader.js +194 -0
- package/dist/utils/profile-loader.js.map +1 -0
- package/dist/utils/registry/index.d.ts +27 -0
- package/dist/utils/registry/index.d.ts.map +1 -0
- package/dist/utils/registry/index.js +29 -0
- package/dist/utils/registry/index.js.map +1 -0
- package/dist/utils/registry/manager.d.ts +319 -0
- package/dist/utils/registry/manager.d.ts.map +1 -0
- package/dist/utils/registry/manager.js +671 -0
- package/dist/utils/registry/manager.js.map +1 -0
- package/dist/utils/registry/setup.d.ts +135 -0
- package/dist/utils/registry/setup.d.ts.map +1 -0
- package/dist/utils/registry/setup.js +207 -0
- package/dist/utils/registry/setup.js.map +1 -0
- package/dist/utils/registry/transformer.d.ts +92 -0
- package/dist/utils/registry/transformer.d.ts.map +1 -0
- package/dist/utils/registry/transformer.js +131 -0
- package/dist/utils/registry/transformer.js.map +1 -0
- package/dist/utils/registry/types.d.ts +241 -0
- package/dist/utils/registry/types.d.ts.map +1 -0
- package/dist/utils/registry/types.js +10 -0
- package/dist/utils/registry/types.js.map +1 -0
- package/docs/EXAMPLES.md +293 -0
- package/docs/PLACEMENT.md +433 -0
- package/docs/STORAGE.md +318 -0
- package/docs/building-provider-reliability-tracker.md +2581 -0
- package/package.json +109 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Akash Network Wallet Connection Module
|
|
3
|
+
*
|
|
4
|
+
* Provides a clean, step-by-step API for connecting to Keplr wallet via WalletConnect.
|
|
5
|
+
* This is a LIBRARY - no QR code display, no prompts, just pure wallet operations.
|
|
6
|
+
*
|
|
7
|
+
* Key Improvements from kadi-deploy:
|
|
8
|
+
* - ✅ ZERO CLI dependencies (no QRCode, chalk, enquirer)
|
|
9
|
+
* - ✅ ZERO `any` types (was 5 any types)
|
|
10
|
+
* - ✅ Split into 4 clear steps for composability
|
|
11
|
+
* - ✅ Result types for all operations
|
|
12
|
+
* - ✅ Natural, intuitive API design
|
|
13
|
+
*
|
|
14
|
+
* Architecture:
|
|
15
|
+
* ```
|
|
16
|
+
* Step 1: initWalletConnect() → Create SignClient
|
|
17
|
+
* ↓
|
|
18
|
+
* Step 2: generateConnectionUri() → Get URI for QR code
|
|
19
|
+
* ↓
|
|
20
|
+
* Step 3: waitForApproval() → Poll for user approval
|
|
21
|
+
* ↓
|
|
22
|
+
* Step 4: createWalletContext() → Get ready-to-use wallet
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Design Philosophy:
|
|
26
|
+
* Each step is independent and composable. Callers can:
|
|
27
|
+
* - Display QR codes however they want (terminal, web, mobile)
|
|
28
|
+
* - Add their own timeout logic
|
|
29
|
+
* - Implement custom retry mechanisms
|
|
30
|
+
* - Show progress however they prefer
|
|
31
|
+
*
|
|
32
|
+
* @module targets/akash/wallet
|
|
33
|
+
*/
|
|
34
|
+
import { SignClient } from '@walletconnect/sign-client';
|
|
35
|
+
import { KeplrWalletConnectV2 } from '@keplr-wallet/wc-client';
|
|
36
|
+
import { fromHex } from '@cosmjs/encoding';
|
|
37
|
+
import { StargateClient } from '@cosmjs/stargate';
|
|
38
|
+
import { WalletError, WalletErrorCodes } from '../../errors/index.js';
|
|
39
|
+
import { getNetworkConfig } from './environment.js';
|
|
40
|
+
/**
|
|
41
|
+
* Step 1: Initialize WalletConnect client
|
|
42
|
+
*
|
|
43
|
+
* Creates a WalletConnect SignClient with proper configuration.
|
|
44
|
+
* This is a one-time setup that can be reused for multiple connections.
|
|
45
|
+
*
|
|
46
|
+
* @param projectId - WalletConnect Cloud project ID
|
|
47
|
+
* @param metadata - Optional app metadata (defaults to KADI Deploy)
|
|
48
|
+
* @returns Result with WalletConnect client or error
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Initialize with your project ID
|
|
53
|
+
* const clientResult = await initWalletConnect('your-project-id-here');
|
|
54
|
+
*
|
|
55
|
+
* if (!clientResult.success) {
|
|
56
|
+
* console.error('Failed to initialize:', clientResult.error);
|
|
57
|
+
* return;
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* const wcClient = clientResult.data;
|
|
61
|
+
* console.log('WalletConnect ready!');
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export async function initWalletConnect(projectId, metadata) {
|
|
65
|
+
try {
|
|
66
|
+
// Step 1: Validate project ID
|
|
67
|
+
if (!projectId || projectId.trim().length === 0) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: new WalletError('WalletConnect project ID is required', WalletErrorCodes.MISSING_PROJECT_ID, { projectId })
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Step 2: Prepare metadata (use defaults if not provided)
|
|
74
|
+
const appMetadata = metadata || {
|
|
75
|
+
name: 'KADI Deploy',
|
|
76
|
+
description: 'Deploy to Akash Network',
|
|
77
|
+
url: 'https://kadi.build',
|
|
78
|
+
icons: ['https://kadi.build/icon.png']
|
|
79
|
+
};
|
|
80
|
+
// Step 3: Initialize SignClient
|
|
81
|
+
// This creates the WalletConnect client that manages all connections
|
|
82
|
+
const client = await SignClient.init({
|
|
83
|
+
projectId,
|
|
84
|
+
metadata: appMetadata
|
|
85
|
+
});
|
|
86
|
+
// Step 4: Return initialized client
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
data: {
|
|
90
|
+
client,
|
|
91
|
+
metadata: appMetadata
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
// Step 5: Handle initialization errors
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: new WalletError(`Failed to initialize WalletConnect: ${error}`, WalletErrorCodes.INIT_FAILED, { error: String(error) })
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Step 2: Generate connection URI for QR code display
|
|
105
|
+
*
|
|
106
|
+
* Creates a new pairing and generates the WalletConnect URI.
|
|
107
|
+
* The caller is responsible for displaying this URI as a QR code.
|
|
108
|
+
*
|
|
109
|
+
* @param wcClient - Initialized WalletConnect client from step 1
|
|
110
|
+
* @param network - Akash network to connect to
|
|
111
|
+
* @returns Result with URI and pairing info or error
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* // Generate URI for QR code
|
|
116
|
+
* const uriResult = await generateConnectionUri(wcClient, 'mainnet');
|
|
117
|
+
*
|
|
118
|
+
* if (!uriResult.success) {
|
|
119
|
+
* console.error('Failed to generate URI:', uriResult.error);
|
|
120
|
+
* return;
|
|
121
|
+
* }
|
|
122
|
+
*
|
|
123
|
+
* const { uri, pairingTopic, approval } = uriResult.data;
|
|
124
|
+
*
|
|
125
|
+
* // Caller displays QR code however they want:
|
|
126
|
+
* // - Terminal: QRCode.generate(uri)
|
|
127
|
+
* // - Web: <QRCode value={uri} />
|
|
128
|
+
* // - Or just show as text: console.log(uri)
|
|
129
|
+
* console.log('Scan this QR code:', uri);
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export async function generateConnectionUri(wcClient, network) {
|
|
133
|
+
try {
|
|
134
|
+
// Step 1: Get network configuration
|
|
135
|
+
const networkConfig = getNetworkConfig(network);
|
|
136
|
+
const chainId = `cosmos:${networkConfig.chainId}`;
|
|
137
|
+
// Step 2: Define optional namespaces for Akash
|
|
138
|
+
// This tells the wallet what permissions we need
|
|
139
|
+
// Using optionalNamespaces (new API) instead of deprecated requiredNamespaces
|
|
140
|
+
const optionalNamespaces = {
|
|
141
|
+
cosmos: {
|
|
142
|
+
chains: [chainId],
|
|
143
|
+
methods: [
|
|
144
|
+
'cosmos_signDirect', // For transaction signing
|
|
145
|
+
'cosmos_signAmino', // Legacy signing support
|
|
146
|
+
'cosmos_getAccounts' // To get wallet address
|
|
147
|
+
],
|
|
148
|
+
events: []
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
// Step 3: Create connection request
|
|
152
|
+
// This generates the URI and starts waiting for approval
|
|
153
|
+
const { uri, approval } = await wcClient.client.connect({
|
|
154
|
+
optionalNamespaces
|
|
155
|
+
});
|
|
156
|
+
// Step 4: Validate URI was generated
|
|
157
|
+
if (!uri) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
error: new WalletError('Failed to generate connection URI', WalletErrorCodes.URI_GENERATION_FAILED, { network })
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Step 5: Return URI and approval promise
|
|
164
|
+
// This matches the original kadi-deploy behavior
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
data: {
|
|
168
|
+
uri,
|
|
169
|
+
approval
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
// Step 7: Handle connection errors
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: new WalletError(`Failed to generate connection URI: ${error}`, WalletErrorCodes.CONNECTION_FAILED, { error: String(error) })
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Step 3: Wait for user to approve connection
|
|
183
|
+
*
|
|
184
|
+
* Polls for wallet approval with configurable timeout.
|
|
185
|
+
* Returns immediately when user approves or rejects.
|
|
186
|
+
*
|
|
187
|
+
* @param approval - Approval promise from step 2
|
|
188
|
+
* @param timeoutMs - Maximum wait time in milliseconds (default: 5 minutes)
|
|
189
|
+
* @returns Result with approved session or error
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* // Wait for user to scan QR and approve
|
|
194
|
+
* console.log('Waiting for approval...');
|
|
195
|
+
*
|
|
196
|
+
* const approvalResult = await waitForApproval(
|
|
197
|
+
* approval,
|
|
198
|
+
* 60000 // 60 second timeout
|
|
199
|
+
* );
|
|
200
|
+
*
|
|
201
|
+
* if (!approvalResult.success) {
|
|
202
|
+
* if (approvalResult.error.code === 'APPROVAL_TIMEOUT') {
|
|
203
|
+
* console.log('User did not approve in time');
|
|
204
|
+
* } else if (approvalResult.error.code === 'APPROVAL_REJECTED') {
|
|
205
|
+
* console.log('User rejected connection');
|
|
206
|
+
* }
|
|
207
|
+
* return;
|
|
208
|
+
* }
|
|
209
|
+
*
|
|
210
|
+
* const { session, address } = approvalResult.data;
|
|
211
|
+
* console.log(`Connected to ${address}!`);
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export async function waitForApproval(approval, timeoutMs = 300000 // 5 minutes default
|
|
215
|
+
) {
|
|
216
|
+
try {
|
|
217
|
+
// Step 1: Create timeout promise
|
|
218
|
+
// This races against the approval to implement timeout
|
|
219
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
reject(new Error('Connection timeout'));
|
|
222
|
+
}, timeoutMs);
|
|
223
|
+
});
|
|
224
|
+
// Step 2: Race approval against timeout
|
|
225
|
+
// Whichever resolves first wins
|
|
226
|
+
const session = await Promise.race([
|
|
227
|
+
approval(),
|
|
228
|
+
timeoutPromise
|
|
229
|
+
]);
|
|
230
|
+
// Step 3: Extract account info from session
|
|
231
|
+
// The session contains the connected accounts
|
|
232
|
+
const accounts = session.namespaces.cosmos?.accounts || [];
|
|
233
|
+
if (accounts.length === 0) {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
error: new WalletError('No accounts found in session', WalletErrorCodes.NO_ACCOUNTS, { session })
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Step 4: Parse account address and chain
|
|
240
|
+
// Format: "cosmos:chainid:address"
|
|
241
|
+
const [_namespace, chainId, address] = accounts[0].split(':');
|
|
242
|
+
// Validate parsed values
|
|
243
|
+
if (!chainId || !address) {
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
error: new WalletError('Invalid account format in session', WalletErrorCodes.ACCOUNT_NOT_FOUND, { account: accounts[0] })
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
// Step 5: Return approval result
|
|
250
|
+
return {
|
|
251
|
+
success: true,
|
|
252
|
+
data: {
|
|
253
|
+
session,
|
|
254
|
+
address,
|
|
255
|
+
chainId
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
// Step 6: Handle timeout or rejection
|
|
261
|
+
const errorMessage = String(error);
|
|
262
|
+
if (errorMessage.includes('timeout')) {
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
error: new WalletError('Connection approval timed out', WalletErrorCodes.APPROVAL_TIMEOUT, { timeoutMs })
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (errorMessage.includes('rejected') || errorMessage.includes('cancelled')) {
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
error: new WalletError('User rejected connection', WalletErrorCodes.APPROVAL_REJECTED, {})
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// Step 7: Handle other errors
|
|
275
|
+
return {
|
|
276
|
+
success: false,
|
|
277
|
+
error: new WalletError(`Approval failed: ${error}`, WalletErrorCodes.APPROVAL_FAILED, { error: errorMessage })
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Step 4: Create complete wallet context for Akash operations
|
|
283
|
+
*
|
|
284
|
+
* Finalizes the connection by creating signers and querying account data.
|
|
285
|
+
* Returns a ready-to-use WalletContext for all Akash operations.
|
|
286
|
+
*
|
|
287
|
+
* @param wcClient - WalletConnect client from step 1
|
|
288
|
+
* @param approvalResult - Approval result from step 3
|
|
289
|
+
* @param network - Akash network being used
|
|
290
|
+
* @returns Result with complete wallet context or error
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* // Create final wallet context
|
|
295
|
+
* const walletResult = await createWalletContext(
|
|
296
|
+
* wcClient,
|
|
297
|
+
* approvalResult.data,
|
|
298
|
+
* 'mainnet'
|
|
299
|
+
* );
|
|
300
|
+
*
|
|
301
|
+
* if (!walletResult.success) {
|
|
302
|
+
* console.error('Failed to create wallet:', walletResult.error);
|
|
303
|
+
* return;
|
|
304
|
+
* }
|
|
305
|
+
*
|
|
306
|
+
* const wallet = walletResult.data;
|
|
307
|
+
* console.log('Wallet ready!');
|
|
308
|
+
* console.log('Address:', wallet.address);
|
|
309
|
+
* console.log('Chain:', wallet.chainId);
|
|
310
|
+
*
|
|
311
|
+
* // Now ready for Akash operations
|
|
312
|
+
* const deployment = await deployToAkash({ wallet, ... });
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
export async function createWalletContext(wcClient, approvalResult, network) {
|
|
316
|
+
try {
|
|
317
|
+
// Step 1: Get network configuration
|
|
318
|
+
const networkConfig = getNetworkConfig(network);
|
|
319
|
+
const chainId = networkConfig.chainId;
|
|
320
|
+
// Step 2: Create Keplr wallet instance via WalletConnect
|
|
321
|
+
// This wraps the WalletConnect session in a Keplr-compatible interface
|
|
322
|
+
const keplr = new KeplrWalletConnectV2(wcClient.client, approvalResult.session);
|
|
323
|
+
// Step 3: Get offline signer for transaction signing
|
|
324
|
+
// This is the main interface for signing transactions
|
|
325
|
+
// Note: There's a known type incompatibility between @keplr-wallet/types and @cosmjs/proto-signing
|
|
326
|
+
// (SignDoc.accountNumber: Long vs bigint). The runtime objects work fine - this is TypeScript only.
|
|
327
|
+
// We use 'as unknown as' to force the cast since TS knows the types are incompatible.
|
|
328
|
+
const offlineSigner = keplr.getOfflineSigner(chainId);
|
|
329
|
+
// Step 4: Get accounts from signer
|
|
330
|
+
// This should match the approved account
|
|
331
|
+
const accounts = await offlineSigner.getAccounts();
|
|
332
|
+
if (accounts.length === 0) {
|
|
333
|
+
return {
|
|
334
|
+
success: false,
|
|
335
|
+
error: new WalletError('No accounts available from signer', WalletErrorCodes.NO_SIGNER_ACCOUNTS, { chainId })
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// Step 5: Connect to blockchain to get account details
|
|
339
|
+
// We need account number and sequence for transactions
|
|
340
|
+
const stargateClient = await StargateClient.connect(networkConfig.rpc);
|
|
341
|
+
let accountData;
|
|
342
|
+
try {
|
|
343
|
+
const account = await stargateClient.getAccount(accounts[0].address);
|
|
344
|
+
if (account) {
|
|
345
|
+
accountData = {
|
|
346
|
+
address: account.address,
|
|
347
|
+
pubkey: account.pubkey ? fromHex(account.pubkey.value) : null,
|
|
348
|
+
accountNumber: account.accountNumber,
|
|
349
|
+
sequence: account.sequence
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
// Account might not exist on chain yet (new account)
|
|
355
|
+
// This is OK - account will be created with first transaction
|
|
356
|
+
accountData = undefined;
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
// Step 6: Always disconnect Stargate client
|
|
360
|
+
stargateClient.disconnect();
|
|
361
|
+
}
|
|
362
|
+
// Step 7: Create complete wallet context
|
|
363
|
+
const walletContext = {
|
|
364
|
+
address: accounts[0].address,
|
|
365
|
+
signer: offlineSigner,
|
|
366
|
+
offlineSigner: offlineSigner,
|
|
367
|
+
signClient: wcClient.client,
|
|
368
|
+
session: approvalResult.session,
|
|
369
|
+
chainId: chainId,
|
|
370
|
+
account: accountData
|
|
371
|
+
};
|
|
372
|
+
// Step 8: Return ready-to-use wallet
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
data: walletContext
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
// Step 9: Handle creation errors
|
|
380
|
+
return {
|
|
381
|
+
success: false,
|
|
382
|
+
error: new WalletError(`Failed to create wallet context: ${error}`, WalletErrorCodes.CONTEXT_CREATION_FAILED, { error: String(error) })
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Create wallet context from any offline signer
|
|
388
|
+
*
|
|
389
|
+
* Use this for **automated deployments** where you have direct access to a signer
|
|
390
|
+
* (e.g., agent wallet, CI/CD, hardware wallet, KMS) instead of interactive wallet
|
|
391
|
+
* connection via browser or WalletConnect.
|
|
392
|
+
*
|
|
393
|
+
* **Security Model:**
|
|
394
|
+
* This function accepts a **signer interface**, NOT a private key or mnemonic!
|
|
395
|
+
* The signer can sign transactions without exposing the underlying key.
|
|
396
|
+
*
|
|
397
|
+
* **Common Use Cases:**
|
|
398
|
+
* - Self-deploying agents (signer from encrypted storage)
|
|
399
|
+
* - CI/CD pipelines (signer from secrets manager)
|
|
400
|
+
* - Hardware wallets (Ledger - key never leaves device)
|
|
401
|
+
* - Cloud KMS (AWS/GCP - key stays in cloud)
|
|
402
|
+
* - Multi-signature wallets (threshold signing)
|
|
403
|
+
*
|
|
404
|
+
* @param signer - Any offline signer (must implement OfflineAminoSigner & OfflineDirectSigner)
|
|
405
|
+
* @param network - Akash network to connect to
|
|
406
|
+
* @returns Result with wallet context ready for deployments
|
|
407
|
+
*
|
|
408
|
+
* @example Agent with encrypted wallet
|
|
409
|
+
* ```typescript
|
|
410
|
+
* import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
|
411
|
+
* import { createWalletContextFromSigner } from 'deploy-ability/akash';
|
|
412
|
+
*
|
|
413
|
+
* // Agent loads signer from secure storage (mnemonic never exposed to caller)
|
|
414
|
+
* const agentSigner = await myAgent.wallet.getSigner();
|
|
415
|
+
*
|
|
416
|
+
* // Create wallet context
|
|
417
|
+
* const walletCtx = await createWalletContextFromSigner(agentSigner, 'mainnet');
|
|
418
|
+
*
|
|
419
|
+
* if (walletCtx.success) {
|
|
420
|
+
* // Ready to deploy - signer will handle signing internally
|
|
421
|
+
* await deployToAkash({
|
|
422
|
+
* wallet: walletCtx.data,
|
|
423
|
+
* certificate: cert,
|
|
424
|
+
* projectRoot: './',
|
|
425
|
+
* profile: 'prod'
|
|
426
|
+
* });
|
|
427
|
+
* }
|
|
428
|
+
* ```
|
|
429
|
+
*
|
|
430
|
+
* @example CI/CD with secrets manager
|
|
431
|
+
* ```typescript
|
|
432
|
+
* // Load signer from AWS Secrets Manager / GitHub Secrets
|
|
433
|
+
* const mnemonic = await loadFromSecretsManager('deploy-wallet-key');
|
|
434
|
+
* const signer = await DirectSecp256k1Wallet.fromMnemonic(mnemonic, {
|
|
435
|
+
* prefix: 'akash'
|
|
436
|
+
* });
|
|
437
|
+
*
|
|
438
|
+
* const walletCtx = await createWalletContextFromSigner(signer, 'testnet');
|
|
439
|
+
* // Now deploy without any user interaction
|
|
440
|
+
* ```
|
|
441
|
+
*
|
|
442
|
+
* @example Hardware wallet (Ledger)
|
|
443
|
+
* ```typescript
|
|
444
|
+
* import Ledger from '@cosmjs/ledger-amino';
|
|
445
|
+
*
|
|
446
|
+
* // Connect to Ledger - key never leaves device
|
|
447
|
+
* const transport = await TransportWebUSB.create();
|
|
448
|
+
* const ledgerSigner = new Ledger(transport, {
|
|
449
|
+
* hdPaths: [makeCosmoshubPath(0)],
|
|
450
|
+
* prefix: 'akash'
|
|
451
|
+
* });
|
|
452
|
+
*
|
|
453
|
+
* const walletCtx = await createWalletContextFromSigner(ledgerSigner, 'mainnet');
|
|
454
|
+
* // Ledger will prompt for approval when signing
|
|
455
|
+
* ```
|
|
456
|
+
*
|
|
457
|
+
* @example Cloud KMS
|
|
458
|
+
* ```typescript
|
|
459
|
+
* // Custom signer using AWS KMS / GCP KMS
|
|
460
|
+
* class KmsSigner implements OfflineSigner {
|
|
461
|
+
* constructor(private keyId: string) {}
|
|
462
|
+
*
|
|
463
|
+
* async signDirect(address: string, signDoc: SignDoc) {
|
|
464
|
+
* // Sign using KMS - key never leaves cloud
|
|
465
|
+
* return await kms.sign(this.keyId, signDoc);
|
|
466
|
+
* }
|
|
467
|
+
* }
|
|
468
|
+
*
|
|
469
|
+
* const kmsSigner = new KmsSigner('arn:aws:kms:...');
|
|
470
|
+
* const walletCtx = await createWalletContextFromSigner(kmsSigner, 'mainnet');
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
export async function createWalletContextFromSigner(signer, network) {
|
|
474
|
+
try {
|
|
475
|
+
// Step 1: Get network configuration
|
|
476
|
+
const networkConfig = getNetworkConfig(network);
|
|
477
|
+
// Step 2: Get accounts from signer
|
|
478
|
+
// The signer provides accounts without exposing private keys
|
|
479
|
+
const accounts = await signer.getAccounts();
|
|
480
|
+
if (accounts.length === 0) {
|
|
481
|
+
return {
|
|
482
|
+
success: false,
|
|
483
|
+
error: new WalletError('No accounts available from signer', WalletErrorCodes.NO_SIGNER_ACCOUNTS, { network }),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const address = accounts[0].address;
|
|
487
|
+
// Step 3: Connect to blockchain to get account details
|
|
488
|
+
// We need account number and sequence for transactions
|
|
489
|
+
const stargateClient = await StargateClient.connect(networkConfig.rpc);
|
|
490
|
+
let accountData;
|
|
491
|
+
try {
|
|
492
|
+
const account = await stargateClient.getAccount(address);
|
|
493
|
+
if (account) {
|
|
494
|
+
accountData = {
|
|
495
|
+
address: account.address,
|
|
496
|
+
pubkey: account.pubkey ? fromHex(Buffer.from(account.pubkey.value).toString('hex')) : null,
|
|
497
|
+
accountNumber: account.accountNumber,
|
|
498
|
+
sequence: account.sequence,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
// Always disconnect the query client
|
|
504
|
+
stargateClient.disconnect();
|
|
505
|
+
}
|
|
506
|
+
// Step 4: Create wallet context
|
|
507
|
+
// Note: No WalletConnect client or session - this is for direct signer usage
|
|
508
|
+
const walletContext = {
|
|
509
|
+
address,
|
|
510
|
+
signer,
|
|
511
|
+
offlineSigner: signer,
|
|
512
|
+
chainId: networkConfig.chainId,
|
|
513
|
+
account: accountData,
|
|
514
|
+
// signClient and session are undefined - only used for WalletConnect
|
|
515
|
+
};
|
|
516
|
+
return {
|
|
517
|
+
success: true,
|
|
518
|
+
data: walletContext,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
return {
|
|
523
|
+
success: false,
|
|
524
|
+
error: new WalletError(`Failed to create wallet context from signer: ${error}`, WalletErrorCodes.CONTEXT_CREATION_FAILED, { error: String(error), network }),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Disconnect an active WalletConnect session
|
|
530
|
+
*
|
|
531
|
+
* Cleanly disconnects from the wallet, clearing the session and cleaning up
|
|
532
|
+
* all internal resources (relay transport, heartbeat, event listeners).
|
|
533
|
+
*
|
|
534
|
+
* This ensures the Node.js event loop can exit by releasing all timers and
|
|
535
|
+
* connections. Without this cleanup, the WalletConnect client keeps the
|
|
536
|
+
* process alive indefinitely.
|
|
537
|
+
*
|
|
538
|
+
* Always call this when done with wallet operations to prevent hanging.
|
|
539
|
+
*
|
|
540
|
+
* Note: This only applies to WalletConnect sessions. Wallets created
|
|
541
|
+
* with `createWalletContextFromSigner()` don't need disconnection.
|
|
542
|
+
*
|
|
543
|
+
* @param wallet - Wallet context to disconnect
|
|
544
|
+
* @returns Result indicating success or error
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* ```typescript
|
|
548
|
+
* // Disconnect when done
|
|
549
|
+
* const result = await disconnectWallet(wallet);
|
|
550
|
+
*
|
|
551
|
+
* if (result.success) {
|
|
552
|
+
* console.log('Wallet disconnected');
|
|
553
|
+
* } else {
|
|
554
|
+
* console.warn('Disconnect failed:', result.error);
|
|
555
|
+
* }
|
|
556
|
+
* ```
|
|
557
|
+
*/
|
|
558
|
+
export async function disconnectWallet(wallet) {
|
|
559
|
+
try {
|
|
560
|
+
// Step 1: Check if WalletConnect session exists
|
|
561
|
+
if (!wallet.signClient || !wallet.session) {
|
|
562
|
+
// Not a WalletConnect wallet (maybe browser extension)
|
|
563
|
+
return { success: true, data: undefined };
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
// Step 2: Disconnect the session
|
|
567
|
+
await wallet.signClient.disconnect({
|
|
568
|
+
topic: wallet.session.topic,
|
|
569
|
+
reason: {
|
|
570
|
+
code: 1,
|
|
571
|
+
message: 'User disconnected'
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
finally {
|
|
576
|
+
// Step 3: Clean up internal WalletConnect resources
|
|
577
|
+
// This is critical to allow Node.js event loop to exit
|
|
578
|
+
try {
|
|
579
|
+
// Close relay WebSocket transport
|
|
580
|
+
await wallet.signClient.core.relayer.transportClose();
|
|
581
|
+
}
|
|
582
|
+
catch (err) {
|
|
583
|
+
// Ignore transport close errors
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
// Stop heartbeat timer
|
|
587
|
+
wallet.signClient.core.heartbeat?.stop();
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
// Ignore heartbeat stop errors
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
// Remove all event listeners to prevent memory leaks
|
|
594
|
+
// Pass undefined to remove all listeners for all events
|
|
595
|
+
if (wallet.signClient.core.events.removeAllListeners) {
|
|
596
|
+
wallet.signClient.core.events.removeAllListeners(undefined);
|
|
597
|
+
}
|
|
598
|
+
if (wallet.signClient.removeAllListeners) {
|
|
599
|
+
wallet.signClient.removeAllListeners(undefined);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch (err) {
|
|
603
|
+
// Ignore listener removal errors
|
|
604
|
+
}
|
|
605
|
+
try {
|
|
606
|
+
// ================================================================
|
|
607
|
+
// STORAGE CLEANUP - THE RUSSIAN DOLL PROBLEM
|
|
608
|
+
// ================================================================
|
|
609
|
+
//
|
|
610
|
+
// WHY IS THIS NECESSARY?
|
|
611
|
+
// WalletConnect persists session data to disk using the "unstorage" library.
|
|
612
|
+
// Unstorage uses a filesystem driver that creates a "chokidar" file watcher
|
|
613
|
+
// to monitor the storage files for changes. File watchers keep Node.js alive
|
|
614
|
+
// indefinitely - the process will NEVER exit until they're explicitly closed.
|
|
615
|
+
//
|
|
616
|
+
// WHY THE WEIRD NESTING?
|
|
617
|
+
// WalletConnect uses a "wrapper pattern" (like Russian dolls 🪆):
|
|
618
|
+
//
|
|
619
|
+
// Layer 1: storage (wrapper with high-level API)
|
|
620
|
+
// └─ Has methods: getItem(), setItem(), initialize()
|
|
621
|
+
// └─ DOES NOT have cleanup methods!
|
|
622
|
+
//
|
|
623
|
+
// Layer 2: storage.database (queue manager)
|
|
624
|
+
// └─ Manages write operations with a queue
|
|
625
|
+
// └─ Still DOES NOT have cleanup methods!
|
|
626
|
+
//
|
|
627
|
+
// Layer 3: storage.database.database (ACTUAL DRIVER)
|
|
628
|
+
// └─ This is the real filesystem driver (unstorage/fs)
|
|
629
|
+
// └─ THIS is where unwatch() and dispose() live!
|
|
630
|
+
// └─ THIS is where the chokidar file watcher exists!
|
|
631
|
+
//
|
|
632
|
+
// WHY DESIGN IT THIS WAY?
|
|
633
|
+
// Each wrapper adds functionality:
|
|
634
|
+
// - Outer wrapper: Simple API for WalletConnect to use
|
|
635
|
+
// - Middle wrapper: Batches writes for better performance
|
|
636
|
+
// - Inner driver: Actually talks to the filesystem
|
|
637
|
+
//
|
|
638
|
+
// It's like a restaurant:
|
|
639
|
+
// - Customer (WalletConnect) → talks to waiter (storage wrapper)
|
|
640
|
+
// - Waiter → gives orders to kitchen manager (database queue)
|
|
641
|
+
// - Kitchen manager → tells chef (actual fs driver) to cook
|
|
642
|
+
//
|
|
643
|
+
// To turn off the stove (file watcher), you need to tell the chef directly,
|
|
644
|
+
// not the waiter! That's why we must drill down to the innermost layer.
|
|
645
|
+
//
|
|
646
|
+
// ================================================================
|
|
647
|
+
const storage = wallet.signClient.core?.storage;
|
|
648
|
+
// DRILL DOWN: storage → database → database (innermost layer)
|
|
649
|
+
// This is where the actual filesystem driver lives with the file watcher
|
|
650
|
+
const innerDb = storage?.database?.database;
|
|
651
|
+
if (innerDb) {
|
|
652
|
+
// Call unwatch() to stop the chokidar file watcher
|
|
653
|
+
// Without this, the file watcher keeps Node.js event loop alive forever!
|
|
654
|
+
if (typeof innerDb.unwatch === 'function') {
|
|
655
|
+
await innerDb.unwatch().catch(() => { });
|
|
656
|
+
}
|
|
657
|
+
// Call dispose() to clean up any other resources (file handles, caches, etc.)
|
|
658
|
+
if (typeof innerDb.dispose === 'function') {
|
|
659
|
+
await innerDb.dispose().catch(() => { });
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
// Silently ignore storage cleanup errors during shutdown
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
// Step 4: Return success
|
|
668
|
+
return { success: true, data: undefined };
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
// Step 5: Handle disconnect errors (non-fatal)
|
|
672
|
+
return {
|
|
673
|
+
success: false,
|
|
674
|
+
error: new WalletError(`Failed to disconnect wallet: ${error}`, WalletErrorCodes.DISCONNECT_FAILED, { error: String(error) })
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Complete wallet connection flow (convenience function)
|
|
680
|
+
*
|
|
681
|
+
* Combines all 4 steps into a single function for simple use cases.
|
|
682
|
+
* For more control, use the individual step functions.
|
|
683
|
+
*
|
|
684
|
+
* @param projectId - WalletConnect project ID
|
|
685
|
+
* @param network - Akash network to connect to
|
|
686
|
+
* @param options - Optional configuration
|
|
687
|
+
* @returns Result with wallet context or error
|
|
688
|
+
*
|
|
689
|
+
* @example
|
|
690
|
+
* ```typescript
|
|
691
|
+
* // Simple one-call connection
|
|
692
|
+
* const walletResult = await connectWallet(
|
|
693
|
+
* 'your-project-id',
|
|
694
|
+
* 'mainnet',
|
|
695
|
+
* {
|
|
696
|
+
* onUriGenerated: (uri) => {
|
|
697
|
+
* // Display QR code
|
|
698
|
+
* QRCode.generate(uri);
|
|
699
|
+
* console.log('Scan QR code with Keplr');
|
|
700
|
+
* },
|
|
701
|
+
* timeoutMs: 60000
|
|
702
|
+
* }
|
|
703
|
+
* );
|
|
704
|
+
*
|
|
705
|
+
* if (!walletResult.success) {
|
|
706
|
+
* console.error('Connection failed:', walletResult.error);
|
|
707
|
+
* return;
|
|
708
|
+
* }
|
|
709
|
+
*
|
|
710
|
+
* const wallet = walletResult.data;
|
|
711
|
+
* console.log('Connected to:', wallet.address);
|
|
712
|
+
* ```
|
|
713
|
+
*/
|
|
714
|
+
export async function connectWallet(projectId, network, options) {
|
|
715
|
+
// Step 1: Initialize WalletConnect
|
|
716
|
+
const clientResult = await initWalletConnect(projectId, options?.metadata);
|
|
717
|
+
if (!clientResult.success) {
|
|
718
|
+
return clientResult;
|
|
719
|
+
}
|
|
720
|
+
// Step 2: Generate connection URI
|
|
721
|
+
const uriResult = await generateConnectionUri(clientResult.data, network);
|
|
722
|
+
if (!uriResult.success) {
|
|
723
|
+
return uriResult;
|
|
724
|
+
}
|
|
725
|
+
// Step 3: Notify caller of URI (for QR display)
|
|
726
|
+
if (options?.onUriGenerated) {
|
|
727
|
+
options.onUriGenerated(uriResult.data.uri);
|
|
728
|
+
}
|
|
729
|
+
// Step 4: Wait for approval
|
|
730
|
+
const approvalResult = await waitForApproval(uriResult.data.approval, options?.timeoutMs);
|
|
731
|
+
if (!approvalResult.success) {
|
|
732
|
+
return approvalResult;
|
|
733
|
+
}
|
|
734
|
+
// Step 5: Create wallet context
|
|
735
|
+
return createWalletContext(clientResult.data, approvalResult.data, network);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Create wallet context from mnemonic (for agent-controlled wallets)
|
|
739
|
+
*
|
|
740
|
+
* ⚠️ **CRITICAL SECURITY WARNING** ⚠️
|
|
741
|
+
*
|
|
742
|
+
* This function is ONLY for scenarios where YOU control the mnemonic:
|
|
743
|
+
* - ✅ Your own automation (CI/CD, personal scripts)
|
|
744
|
+
* - ✅ Your own agent (running on infrastructure you control)
|
|
745
|
+
* - ✅ Custodial services YOU operate (creating wallets for your users)
|
|
746
|
+
*
|
|
747
|
+
* **NEVER use this if:**
|
|
748
|
+
* - ❌ Someone else's agent asks for your mnemonic
|
|
749
|
+
* - ❌ Third-party service requests your mnemonic
|
|
750
|
+
* - ❌ You don't fully control where the mnemonic is stored/used
|
|
751
|
+
*
|
|
752
|
+
* **For third-party agents/services:**
|
|
753
|
+
* Use `connectWallet()` with WalletConnect instead! This lets you approve
|
|
754
|
+
* transactions on YOUR device without exposing your mnemonic.
|
|
755
|
+
*
|
|
756
|
+
* **Security Model:**
|
|
757
|
+
* ```
|
|
758
|
+
* ┌─────────────────────────────────────────────────────────────┐
|
|
759
|
+
* │ WHO CONTROLS THE AGENT? │
|
|
760
|
+
* ├─────────────────────────────────────────────────────────────┤
|
|
761
|
+
* │ │
|
|
762
|
+
* │ YOU control it: │
|
|
763
|
+
* │ ✅ createWalletFromMnemonic() - You trust your own code │
|
|
764
|
+
* │ │
|
|
765
|
+
* │ SOMEONE ELSE controls it: │
|
|
766
|
+
* │ ✅ connectWallet() - WalletConnect for approval │
|
|
767
|
+
* │ ❌ createWalletFromMnemonic() - NEVER share mnemonic! │
|
|
768
|
+
* │ │
|
|
769
|
+
* └─────────────────────────────────────────────────────────────┘
|
|
770
|
+
* ```
|
|
771
|
+
*
|
|
772
|
+
* **Common Use Cases:**
|
|
773
|
+
*
|
|
774
|
+
* @example 1. Your own CI/CD pipeline
|
|
775
|
+
* ```typescript
|
|
776
|
+
* // Mnemonic stored in GitHub Secrets / GitLab CI Variables
|
|
777
|
+
* const mnemonic = process.env.DEPLOYMENT_WALLET_MNEMONIC!;
|
|
778
|
+
* const wallet = await createWalletFromMnemonic(mnemonic, 'mainnet');
|
|
779
|
+
*
|
|
780
|
+
* if (wallet.success) {
|
|
781
|
+
* // Automated deployment from CI/CD
|
|
782
|
+
* await deployToAkash({
|
|
783
|
+
* wallet: wallet.data,
|
|
784
|
+
* projectRoot: './',
|
|
785
|
+
* profile: 'production'
|
|
786
|
+
* });
|
|
787
|
+
* }
|
|
788
|
+
* ```
|
|
789
|
+
*
|
|
790
|
+
* @example 2. Your own agent with encrypted storage
|
|
791
|
+
* ```typescript
|
|
792
|
+
* // Agent loads its OWN mnemonic (not user's!)
|
|
793
|
+
* class DeploymentAgent {
|
|
794
|
+
* async deploy(projectConfig: Config) {
|
|
795
|
+
* // Agent uses its own wallet to pay for deployments
|
|
796
|
+
* const mnemonic = await this.secrets.getEncryptedMnemonic();
|
|
797
|
+
* const wallet = await createWalletFromMnemonic(mnemonic, 'mainnet');
|
|
798
|
+
*
|
|
799
|
+
* if (wallet.success) {
|
|
800
|
+
* // Agent deploys using its own funds
|
|
801
|
+
* return deployToAkash({
|
|
802
|
+
* wallet: wallet.data,
|
|
803
|
+
* ...projectConfig
|
|
804
|
+
* });
|
|
805
|
+
* }
|
|
806
|
+
* }
|
|
807
|
+
* }
|
|
808
|
+
* ```
|
|
809
|
+
*
|
|
810
|
+
* @example 3. Custodial wallet service (you operate the service)
|
|
811
|
+
* ```typescript
|
|
812
|
+
* // Service creates and manages wallets FOR users
|
|
813
|
+
* class CustodialWalletService {
|
|
814
|
+
* async createUserWallet(userId: string) {
|
|
815
|
+
* // Generate new wallet for user
|
|
816
|
+
* const { DirectSecp256k1HdWallet } = await import('@cosmjs/proto-signing');
|
|
817
|
+
* const newWallet = await DirectSecp256k1HdWallet.generate(24);
|
|
818
|
+
* const mnemonic = newWallet.mnemonic;
|
|
819
|
+
*
|
|
820
|
+
* // Store encrypted mnemonic in your secure database
|
|
821
|
+
* await this.db.storeEncryptedMnemonic(userId, mnemonic);
|
|
822
|
+
*
|
|
823
|
+
* return { userId, address: (await newWallet.getAccounts())[0].address };
|
|
824
|
+
* }
|
|
825
|
+
*
|
|
826
|
+
* async deployForUser(userId: string, projectConfig: Config) {
|
|
827
|
+
* // Load user's mnemonic from secure storage
|
|
828
|
+
* const mnemonic = await this.db.getDecryptedMnemonic(userId);
|
|
829
|
+
* const wallet = await createWalletFromMnemonic(mnemonic, 'mainnet');
|
|
830
|
+
*
|
|
831
|
+
* if (wallet.success) {
|
|
832
|
+
* // Deploy on user's behalf
|
|
833
|
+
* return deployToAkash({
|
|
834
|
+
* wallet: wallet.data,
|
|
835
|
+
* ...projectConfig
|
|
836
|
+
* });
|
|
837
|
+
* }
|
|
838
|
+
* }
|
|
839
|
+
* }
|
|
840
|
+
* ```
|
|
841
|
+
*
|
|
842
|
+
* @example 4. WRONG - Never do this!
|
|
843
|
+
* ```typescript
|
|
844
|
+
* // ❌ DANGEROUS - Third-party agent asking for your mnemonic!
|
|
845
|
+
* const thirdPartyAgent = new SomeoneElsesAgent();
|
|
846
|
+
*
|
|
847
|
+
* // ❌ DO NOT DO THIS!
|
|
848
|
+
* await thirdPartyAgent.deploy({
|
|
849
|
+
* mnemonic: myMnemonic, // ❌ Now they can steal all your funds!
|
|
850
|
+
* project: './my-app'
|
|
851
|
+
* });
|
|
852
|
+
*
|
|
853
|
+
* // ✅ DO THIS INSTEAD - Use WalletConnect!
|
|
854
|
+
* const wallet = await connectWallet(projectId, 'mainnet', {
|
|
855
|
+
* onUriGenerated: (uri) => {
|
|
856
|
+
* console.log('Scan QR code to approve');
|
|
857
|
+
* // You approve on YOUR device, agent never gets mnemonic
|
|
858
|
+
* }
|
|
859
|
+
* });
|
|
860
|
+
* await thirdPartyAgent.deploy({ wallet: wallet.data, ... });
|
|
861
|
+
* ```
|
|
862
|
+
*
|
|
863
|
+
* **Implementation Notes for Future:**
|
|
864
|
+
*
|
|
865
|
+
* This function should:
|
|
866
|
+
* 1. Import DirectSecp256k1HdWallet from @cosmjs/proto-signing
|
|
867
|
+
* 2. Create signer with proper network prefix:
|
|
868
|
+
* - mainnet/testnet → 'akash'
|
|
869
|
+
* - sandbox → 'akash'
|
|
870
|
+
* 3. Call createWalletContextFromSigner() with the signer
|
|
871
|
+
* 4. Return Result<WalletContext, WalletError>
|
|
872
|
+
*
|
|
873
|
+
* Dependencies needed:
|
|
874
|
+
* - @cosmjs/proto-signing (DirectSecp256k1HdWallet)
|
|
875
|
+
*
|
|
876
|
+
* Error handling:
|
|
877
|
+
* - Invalid mnemonic format
|
|
878
|
+
* - Network configuration issues
|
|
879
|
+
* - Account fetch failures
|
|
880
|
+
*
|
|
881
|
+
* @param mnemonic - BIP39 mnemonic phrase (12 or 24 words)
|
|
882
|
+
* @param network - Akash network to connect to
|
|
883
|
+
* @returns Result with wallet context or error
|
|
884
|
+
*/
|
|
885
|
+
export async function createWalletFromMnemonic(_mnemonic, network) {
|
|
886
|
+
// TODO: Implement mnemonic → wallet conversion
|
|
887
|
+
//
|
|
888
|
+
// Implementation plan:
|
|
889
|
+
//
|
|
890
|
+
// 1. Validate mnemonic format (should be 12 or 24 words)
|
|
891
|
+
// - Split by whitespace and count words
|
|
892
|
+
// - Return error if invalid length
|
|
893
|
+
//
|
|
894
|
+
// 2. Import DirectSecp256k1HdWallet from @cosmjs/proto-signing
|
|
895
|
+
// - This is the standard CosmJS wallet for Cosmos chains
|
|
896
|
+
// - Handles BIP39 mnemonic → keypair derivation
|
|
897
|
+
//
|
|
898
|
+
// 3. Create signer with Akash address prefix
|
|
899
|
+
// const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
|
|
900
|
+
// prefix: 'akash' // All Akash networks use 'akash' prefix
|
|
901
|
+
// });
|
|
902
|
+
//
|
|
903
|
+
// 4. Delegate to existing createWalletContextFromSigner()
|
|
904
|
+
// return createWalletContextFromSigner(signer, network);
|
|
905
|
+
//
|
|
906
|
+
// Error cases to handle:
|
|
907
|
+
// - Invalid mnemonic (wrong word count, invalid words)
|
|
908
|
+
// - Network configuration issues (passed through from createWalletContextFromSigner)
|
|
909
|
+
// - Account fetch failures (passed through from createWalletContextFromSigner)
|
|
910
|
+
//
|
|
911
|
+
// Example implementation:
|
|
912
|
+
//
|
|
913
|
+
// try {
|
|
914
|
+
// // Validate mnemonic word count
|
|
915
|
+
// const words = mnemonic.trim().split(/\s+/);
|
|
916
|
+
// if (![12, 15, 18, 21, 24].includes(words.length)) {
|
|
917
|
+
// return {
|
|
918
|
+
// success: false,
|
|
919
|
+
// error: new WalletError(
|
|
920
|
+
// `Invalid mnemonic: expected 12, 15, 18, 21, or 24 words, got ${words.length}`,
|
|
921
|
+
// WalletErrorCodes.INVALID_MNEMONIC,
|
|
922
|
+
// { wordCount: words.length }
|
|
923
|
+
// ),
|
|
924
|
+
// };
|
|
925
|
+
// }
|
|
926
|
+
//
|
|
927
|
+
// // Import CosmJS wallet
|
|
928
|
+
// const { DirectSecp256k1HdWallet } = await import('@cosmjs/proto-signing');
|
|
929
|
+
//
|
|
930
|
+
// // Create signer from mnemonic
|
|
931
|
+
// const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
|
|
932
|
+
// prefix: 'akash'
|
|
933
|
+
// });
|
|
934
|
+
//
|
|
935
|
+
// // Delegate to existing function
|
|
936
|
+
// return createWalletContextFromSigner(signer, network);
|
|
937
|
+
// } catch (error) {
|
|
938
|
+
// return {
|
|
939
|
+
// success: false,
|
|
940
|
+
// error: new WalletError(
|
|
941
|
+
// `Failed to create wallet from mnemonic: ${error}`,
|
|
942
|
+
// WalletErrorCodes.MNEMONIC_CREATION_FAILED,
|
|
943
|
+
// { error: String(error), network }
|
|
944
|
+
// ),
|
|
945
|
+
// };
|
|
946
|
+
// }
|
|
947
|
+
// Temporary implementation - will be replaced with above
|
|
948
|
+
return {
|
|
949
|
+
success: false,
|
|
950
|
+
error: new WalletError('createWalletFromMnemonic() not yet implemented - see TODO in function body', WalletErrorCodes.NOT_IMPLEMENTED, { network }),
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
//# sourceMappingURL=wallet-manager.js.map
|