@kadi.build/deploy-ability 0.0.2 → 0.0.4

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 (103) hide show
  1. package/dist/targets/akash/bids.d.ts +183 -0
  2. package/dist/targets/akash/bids.d.ts.map +1 -0
  3. package/dist/targets/akash/bids.js +247 -0
  4. package/dist/targets/akash/bids.js.map +1 -0
  5. package/dist/targets/akash/certificate-manager.d.ts +89 -167
  6. package/dist/targets/akash/certificate-manager.d.ts.map +1 -1
  7. package/dist/targets/akash/certificate-manager.js +193 -301
  8. package/dist/targets/akash/certificate-manager.js.map +1 -1
  9. package/dist/targets/akash/client.d.ts +644 -0
  10. package/dist/targets/akash/client.d.ts.map +1 -0
  11. package/dist/targets/akash/client.js +972 -0
  12. package/dist/targets/akash/client.js.map +1 -0
  13. package/dist/targets/akash/constants.d.ts +12 -149
  14. package/dist/targets/akash/constants.d.ts.map +1 -1
  15. package/dist/targets/akash/constants.js +14 -136
  16. package/dist/targets/akash/constants.js.map +1 -1
  17. package/dist/targets/akash/deployer.d.ts +3 -82
  18. package/dist/targets/akash/deployer.d.ts.map +1 -1
  19. package/dist/targets/akash/deployer.js +122 -160
  20. package/dist/targets/akash/deployer.js.map +1 -1
  21. package/dist/targets/akash/environment.d.ts +16 -214
  22. package/dist/targets/akash/environment.d.ts.map +1 -1
  23. package/dist/targets/akash/environment.js +20 -210
  24. package/dist/targets/akash/environment.js.map +1 -1
  25. package/dist/targets/akash/index.d.ts +95 -189
  26. package/dist/targets/akash/index.d.ts.map +1 -1
  27. package/dist/targets/akash/index.js +69 -197
  28. package/dist/targets/akash/index.js.map +1 -1
  29. package/dist/targets/akash/lease-monitor.d.ts +3 -21
  30. package/dist/targets/akash/lease-monitor.d.ts.map +1 -1
  31. package/dist/targets/akash/lease-monitor.js +39 -56
  32. package/dist/targets/akash/lease-monitor.js.map +1 -1
  33. package/dist/targets/akash/logs.d.ts +103 -4
  34. package/dist/targets/akash/logs.d.ts.map +1 -1
  35. package/dist/targets/akash/logs.js +12 -3
  36. package/dist/targets/akash/logs.js.map +1 -1
  37. package/dist/targets/akash/pricing.d.ts +12 -191
  38. package/dist/targets/akash/pricing.d.ts.map +1 -1
  39. package/dist/targets/akash/pricing.js +12 -188
  40. package/dist/targets/akash/pricing.js.map +1 -1
  41. package/dist/targets/akash/provider-manager.d.ts +120 -0
  42. package/dist/targets/akash/provider-manager.d.ts.map +1 -0
  43. package/dist/targets/akash/provider-manager.js +574 -0
  44. package/dist/targets/akash/provider-manager.js.map +1 -0
  45. package/dist/targets/akash/sdl-generator.d.ts +2 -2
  46. package/dist/targets/akash/sdl-generator.d.ts.map +1 -1
  47. package/dist/targets/akash/sdl-generator.js +6 -39
  48. package/dist/targets/akash/sdl-generator.js.map +1 -1
  49. package/dist/targets/akash/types.d.ts +66 -243
  50. package/dist/targets/akash/types.d.ts.map +1 -1
  51. package/dist/targets/akash/types.js +4 -41
  52. package/dist/targets/akash/types.js.map +1 -1
  53. package/dist/targets/akash/wallet-manager.d.ts +35 -352
  54. package/dist/targets/akash/wallet-manager.d.ts.map +1 -1
  55. package/dist/targets/akash/wallet-manager.js +37 -439
  56. package/dist/targets/akash/wallet-manager.js.map +1 -1
  57. package/dist/targets/local/deployer.js +4 -4
  58. package/dist/targets/local/deployer.js.map +1 -1
  59. package/dist/types/index.d.ts +1 -1
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/types/index.js.map +1 -1
  62. package/dist/types/options.d.ts +45 -4
  63. package/dist/types/options.d.ts.map +1 -1
  64. package/dist/utils/registry/manager.js +6 -6
  65. package/dist/utils/registry/manager.js.map +1 -1
  66. package/dist/utils/registry/setup.js +4 -4
  67. package/dist/utils/registry/setup.js.map +1 -1
  68. package/package.json +8 -11
  69. package/dist/targets/akash/bid-selectors.d.ts +0 -251
  70. package/dist/targets/akash/bid-selectors.d.ts.map +0 -1
  71. package/dist/targets/akash/bid-selectors.js +0 -322
  72. package/dist/targets/akash/bid-selectors.js.map +0 -1
  73. package/dist/targets/akash/bid-types.d.ts +0 -297
  74. package/dist/targets/akash/bid-types.d.ts.map +0 -1
  75. package/dist/targets/akash/bid-types.js +0 -89
  76. package/dist/targets/akash/bid-types.js.map +0 -1
  77. package/dist/targets/akash/blockchain-client.d.ts +0 -577
  78. package/dist/targets/akash/blockchain-client.d.ts.map +0 -1
  79. package/dist/targets/akash/blockchain-client.js +0 -803
  80. package/dist/targets/akash/blockchain-client.js.map +0 -1
  81. package/dist/targets/akash/logs.types.d.ts +0 -102
  82. package/dist/targets/akash/logs.types.d.ts.map +0 -1
  83. package/dist/targets/akash/logs.types.js +0 -9
  84. package/dist/targets/akash/logs.types.js.map +0 -1
  85. package/dist/targets/akash/provider-client.d.ts +0 -114
  86. package/dist/targets/akash/provider-client.d.ts.map +0 -1
  87. package/dist/targets/akash/provider-client.js +0 -318
  88. package/dist/targets/akash/provider-client.js.map +0 -1
  89. package/dist/targets/akash/provider-metadata.d.ts +0 -228
  90. package/dist/targets/akash/provider-metadata.d.ts.map +0 -1
  91. package/dist/targets/akash/provider-metadata.js +0 -14
  92. package/dist/targets/akash/provider-metadata.js.map +0 -1
  93. package/dist/targets/akash/provider-service.d.ts +0 -133
  94. package/dist/targets/akash/provider-service.d.ts.map +0 -1
  95. package/dist/targets/akash/provider-service.js +0 -391
  96. package/dist/targets/akash/provider-service.js.map +0 -1
  97. package/dist/targets/akash/query-client.d.ts +0 -125
  98. package/dist/targets/akash/query-client.d.ts.map +0 -1
  99. package/dist/targets/akash/query-client.js +0 -332
  100. package/dist/targets/akash/query-client.js.map +0 -1
  101. package/docs/EXAMPLES.md +0 -293
  102. package/docs/PLACEMENT.md +0 -433
  103. package/docs/STORAGE.md +0 -318
@@ -0,0 +1,972 @@
1
+ /**
2
+ * Akash Blockchain Client
3
+ *
4
+ * Single SDK instance manager for all Akash blockchain operations.
5
+ * Eliminates SDK duplication by creating one instance reused across all methods.
6
+ *
7
+ * **Usage Pattern:**
8
+ * ```typescript
9
+ * // Create client (read-only or with signer)
10
+ * const client = new AkashClient({ network: 'mainnet', signer });
11
+ *
12
+ * // Perform operations
13
+ * const bids = await client.getBids(wallet, dseq);
14
+ * const lease = await client.acceptBid(bids.data[0].bid);
15
+ *
16
+ * // Cleanup
17
+ * await client.disconnect();
18
+ * ```
19
+ *
20
+ * @module targets/akash/client
21
+ */
22
+ import Long from 'long';
23
+ import { createChainNodeWebSDK } from '@akashnetwork/chain-sdk/web';
24
+ import { createStargateClient } from '@akashnetwork/chain-sdk';
25
+ import { success, failure } from '../../types/index.js';
26
+ import { DeploymentError } from '../../errors/index.js';
27
+ import { getNetworkConfig } from './environment.js';
28
+ import { createBidPricing } from './bids.js';
29
+ import { fetchAllProviders } from './provider-manager.js';
30
+ import { CertificateManager } from './certificate-manager.js';
31
+ // ============================================================================
32
+ // AkashClient Class
33
+ // ============================================================================
34
+ /**
35
+ * Akash blockchain client with managed SDK lifecycle
36
+ *
37
+ * @example Query Operations (No Signer)
38
+ * ```typescript
39
+ * const client = new AkashClient({ network: 'mainnet' });
40
+ *
41
+ * const deployment = await client.getDeployment('akash1...', 12345);
42
+ * const bids = await client.getBids({ address: 'akash1...' }, 12345);
43
+ *
44
+ * await client.disconnect();
45
+ * ```
46
+ *
47
+ * @example Write Operations (Signer Required)
48
+ * ```typescript
49
+ * const client = new AkashClient({
50
+ * network: 'mainnet',
51
+ * signer: keplrSigner
52
+ * });
53
+ *
54
+ * const deployment = await client.createDeployment(sdl, 5);
55
+ * const bids = await client.awaitBids({ address }, deployment.data.dseq);
56
+ * const lease = await client.acceptBid(bids.data[0].bid);
57
+ *
58
+ * await client.disconnect();
59
+ * ```
60
+ */
61
+ export class AkashClient {
62
+ /**
63
+ * Chain SDK instance (created once, reused for all operations)
64
+ * @private
65
+ */
66
+ sdk;
67
+ /**
68
+ * Network configuration
69
+ * @private
70
+ */
71
+ network;
72
+ config;
73
+ /**
74
+ * Optional signer for write operations
75
+ * @private
76
+ */
77
+ signer;
78
+ /**
79
+ * Gas multiplier for transaction safety
80
+ * @private
81
+ */
82
+ gasMultiplier;
83
+ /**
84
+ * Lazy-initialized certificate manager
85
+ * @private
86
+ */
87
+ certManager;
88
+ // ==========================================================================
89
+ // Constructor
90
+ // ==========================================================================
91
+ /**
92
+ * Create new Akash blockchain client
93
+ *
94
+ * Initializes a single SDK instance that will be reused for all operations.
95
+ * This is the key improvement over the old implementation which created
96
+ * 7+ SDK instances per deployment.
97
+ *
98
+ * @param options - Client configuration
99
+ *
100
+ * @example Read-Only Client
101
+ * ```typescript
102
+ * const client = new AkashClient({ network: 'mainnet' });
103
+ * // Can only perform queries (getBids, getDeployment, etc.)
104
+ * ```
105
+ *
106
+ * @example Full Client (Read + Write)
107
+ * ```typescript
108
+ * const client = new AkashClient({
109
+ * network: 'mainnet',
110
+ * signer: keplrSigner,
111
+ * gasMultiplier: 1.6
112
+ * });
113
+ * // Can perform queries AND transactions
114
+ * ```
115
+ */
116
+ constructor(options) {
117
+ this.network = options.network;
118
+ this.config = getNetworkConfig(options.network);
119
+ this.signer = options.signer;
120
+ this.gasMultiplier = options.gasMultiplier ?? 1.6;
121
+ // Create SDK once - this is the key optimization!
122
+ // Old code created this 7+ times per deployment
123
+ //
124
+ // SDK SELECTION: Why We Use createChainNodeWebSDK
125
+ // ================================================
126
+ // chain-sdk provides TWO SDK creation functions:
127
+ //
128
+ // 1. createChainNodeSDK (Node.js) - Uses gRPC protocol (binary)
129
+ // - Requires: gRPC endpoint (e.g., http://grpc.akashnet.net:9090)
130
+ // - Problem: Public gRPC endpoints are NOT accessible (firewall/proxy issues)
131
+ // - Use case: Internal/private networks with gRPC access
132
+ //
133
+ // 2. createChainNodeWebSDK (Web) - Uses gRPC Gateway protocol (HTTP/JSON)
134
+ // - Requires: REST endpoint (e.g., https://api.akashnet.net:443)
135
+ // - Benefit: REST endpoints ARE publicly accessible
136
+ // - Use case: Public networks, browsers, CLI tools
137
+ //
138
+ // Both return the SAME API - only the transport layer differs.
139
+ // We use the Web SDK because Akash's public infrastructure provides REST/LCD endpoints,
140
+ // not raw gRPC endpoints.
141
+ this.sdk = createChainNodeWebSDK({
142
+ query: {
143
+ baseUrl: this.config.rest // REST API (gRPC Gateway): https://api.akashnet.net:443
144
+ },
145
+ tx: options.signer ? {
146
+ signer: createStargateClient({
147
+ baseUrl: this.config.rpc, // RPC for transactions: https://rpc.akashnet.net:443
148
+ signer: options.signer,
149
+ gasMultiplier: this.gasMultiplier
150
+ })
151
+ } : undefined
152
+ });
153
+ }
154
+ // ==========================================================================
155
+ // Query Methods (Read-Only - No Signer Required)
156
+ // ==========================================================================
157
+ /**
158
+ * Fetch provider bids for a deployment
159
+ *
160
+ * Queries the Akash marketplace for open bids on a specific deployment.
161
+ * Automatically filters blacklisted providers and optionally enriches
162
+ * with provider metadata (uptime, location, audit status).
163
+ *
164
+ * **Performance**: ~200ms (first call), ~50ms (subsequent calls with warm SDK)
165
+ *
166
+ * @param wallet - Wallet context (for filtering bids by owner)
167
+ * @param dseq - Deployment sequence number
168
+ * @param options - Optional filtering and enhancement options
169
+ * @returns Array of enhanced bids with pricing and provider info
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * const bids = await client.getBids(
174
+ * { address: 'akash1...' },
175
+ * 12345,
176
+ * { filterOffline: true, blacklist: ['akash1bad...'] }
177
+ * );
178
+ *
179
+ * if (bids.success) {
180
+ * console.log(`Found ${bids.data.length} bids`);
181
+ * console.log(`Cheapest: ${bids.data[0].pricing.akt.perMonth} AKT/month`);
182
+ * }
183
+ * ```
184
+ */
185
+ async getBids(wallet, dseq, options) {
186
+ try {
187
+ const { blacklist = [], filterOffline = true, includeProviderMetadata = true } = options ?? {};
188
+ // Query marketplace for bids (reuses this.sdk)
189
+ const response = await this.sdk.akash.market.v1beta5.getBids({
190
+ filters: {
191
+ owner: wallet.address,
192
+ dseq: Long.fromNumber(dseq),
193
+ state: 'open'
194
+ },
195
+ pagination: {
196
+ limit: 100n
197
+ }
198
+ });
199
+ // Extract valid bids
200
+ const validBids = this.extractValidBids(response);
201
+ if (validBids.length === 0) {
202
+ return success([]);
203
+ }
204
+ // Filter blacklisted providers (user-provided only)
205
+ const filteredBids = validBids.filter(bid => {
206
+ const provider = bid.id?.provider ?? bid.bidId?.provider ?? '';
207
+ return !blacklist.includes(provider);
208
+ });
209
+ // Optionally enrich with provider metadata
210
+ let enhancedBids;
211
+ if (includeProviderMetadata) {
212
+ // Fetch provider metadata from Console API
213
+ const providersResult = await fetchAllProviders(this.network);
214
+ const providerMap = providersResult.success
215
+ ? providersResult.data
216
+ : new Map();
217
+ // Enhance bids with pricing and provider info
218
+ enhancedBids = filteredBids.map(bid => {
219
+ const bidId = bid.id ?? bid.bidId;
220
+ const providerId = bidId?.provider ?? '';
221
+ const providerInfo = providerMap.get(providerId);
222
+ // Create unique ID from bid coordinates
223
+ const id = bidId
224
+ ? `${bidId.owner}/${bidId.dseq}/${bidId.gseq}/${bidId.oseq}/${bidId.provider}`
225
+ : '';
226
+ // Convert createdAt (Long) to Date
227
+ const createdAt = bid.createdAt ? new Date(Number(bid.createdAt) * 1000) : new Date();
228
+ // Default provider info for bids without metadata
229
+ const defaultProviderInfo = {
230
+ owner: providerId,
231
+ hostUri: '',
232
+ isAudited: false
233
+ };
234
+ return {
235
+ id,
236
+ bid,
237
+ pricing: createBidPricing(bid.price ?? { denom: 'uakt', amount: '0' }),
238
+ provider: providerInfo ?? defaultProviderInfo,
239
+ createdAt
240
+ };
241
+ });
242
+ // Filter offline providers if requested
243
+ if (filterOffline) {
244
+ enhancedBids = enhancedBids.filter(bid => bid.provider?.reliability?.isOnline !== false);
245
+ }
246
+ }
247
+ else {
248
+ // Just pricing, no provider metadata
249
+ enhancedBids = filteredBids.map(bid => {
250
+ const bidId = bid.id ?? bid.bidId;
251
+ const providerId = bidId?.provider ?? '';
252
+ // Create unique ID from bid coordinates
253
+ const id = bidId
254
+ ? `${bidId.owner}/${bidId.dseq}/${bidId.gseq}/${bidId.oseq}/${bidId.provider}`
255
+ : '';
256
+ // Convert createdAt (Long) to Date
257
+ const createdAt = bid.createdAt ? new Date(Number(bid.createdAt) * 1000) : new Date();
258
+ // Default provider info
259
+ const defaultProviderInfo = {
260
+ owner: providerId,
261
+ hostUri: '',
262
+ isAudited: false
263
+ };
264
+ return {
265
+ id,
266
+ bid,
267
+ pricing: createBidPricing(bid.price ?? { denom: 'uakt', amount: '0' }),
268
+ provider: defaultProviderInfo,
269
+ createdAt
270
+ };
271
+ });
272
+ }
273
+ return success(enhancedBids);
274
+ }
275
+ catch (error) {
276
+ return failure(new DeploymentError('Failed to fetch bids from marketplace', 'BID_QUERY_FAILED', { network: this.network, dseq, error: error instanceof Error ? error.message : String(error) }, true, 'Verify network connectivity and that deployment exists'));
277
+ }
278
+ }
279
+ /**
280
+ * Extract valid bids from marketplace response
281
+ *
282
+ * Filters out invalid/malformed bids from the raw response.
283
+ *
284
+ * @param response - Raw marketplace response
285
+ * @returns Array of valid bids
286
+ * @private
287
+ */
288
+ extractValidBids(response) {
289
+ if (!response.bids || !Array.isArray(response.bids)) {
290
+ return [];
291
+ }
292
+ return response.bids
293
+ .filter((b) => b.bid !== undefined && b.bid !== null)
294
+ .map((b) => b.bid)
295
+ .filter((bid) => {
296
+ const providerId = bid.id?.provider ?? bid.bidId?.provider ?? '';
297
+ return providerId !== '';
298
+ });
299
+ }
300
+ /**
301
+ * Poll marketplace for bids with timeout
302
+ *
303
+ * Waits for at least `minBidCount` bids to appear, polling the marketplace
304
+ * at regular intervals. Returns when bids are found or timeout is reached.
305
+ *
306
+ * **Use case**: After creating a deployment, wait for providers to bid
307
+ *
308
+ * @param wallet - Wallet context
309
+ * @param dseq - Deployment sequence number
310
+ * @param options - Polling configuration
311
+ * @returns Array of bids when found, or error if timeout
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * // Wait up to 2 minutes for at least 3 bids
316
+ * const bids = await client.awaitBids(
317
+ * { address: 'akash1...' },
318
+ * 12345,
319
+ * { timeout: 120000, minBidCount: 3 }
320
+ * );
321
+ *
322
+ * if (bids.success) {
323
+ * console.log(`Received ${bids.data.length} bids`);
324
+ * }
325
+ * ```
326
+ */
327
+ async awaitBids(wallet, dseq, options) {
328
+ const { timeout = 60000, pollInterval = 5000, minBidCount = 1, ...bidOptions } = options ?? {};
329
+ const startTime = Date.now();
330
+ while (true) {
331
+ // Query for bids
332
+ const bidsResult = await this.getBids(wallet, dseq, bidOptions);
333
+ if (!bidsResult.success) {
334
+ return bidsResult;
335
+ }
336
+ // Check if we have enough bids
337
+ if (bidsResult.data.length >= minBidCount) {
338
+ return bidsResult;
339
+ }
340
+ // Check timeout
341
+ const elapsed = Date.now() - startTime;
342
+ if (elapsed >= timeout) {
343
+ return failure(new DeploymentError(`Timeout waiting for bids (waited ${elapsed}ms, found ${bidsResult.data.length}/${minBidCount} bids)`, 'BID_TIMEOUT', { dseq, timeout, minBidCount, found: bidsResult.data.length }, true, 'Try increasing timeout or reducing minBidCount. Check deployment is visible on blockchain.'));
344
+ }
345
+ // Wait before next poll
346
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
347
+ }
348
+ }
349
+ /**
350
+ * Fetch lease details by ID
351
+ *
352
+ * @param lease - Lease identifier
353
+ * @returns Lease details from blockchain
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * const lease = await client.getLeaseById({
358
+ * owner: 'akash1...',
359
+ * dseq: 12345,
360
+ * gseq: 1,
361
+ * oseq: 1,
362
+ * provider: 'akash1provider...'
363
+ * });
364
+ *
365
+ * console.log(`Lease state: ${lease.data.state}`);
366
+ * console.log(`Price: ${lease.data.price.amount} ${lease.data.price.denom}`);
367
+ * ```
368
+ */
369
+ async getLeaseById(lease) {
370
+ try {
371
+ // Query blockchain (reuses this.sdk)
372
+ const response = await this.sdk.akash.market.v1beta5.getLease({
373
+ id: {
374
+ owner: lease.owner,
375
+ dseq: Long.fromNumber(lease.dseq),
376
+ gseq: lease.gseq,
377
+ oseq: lease.oseq,
378
+ provider: lease.provider
379
+ }
380
+ });
381
+ const details = this.mapLease(response);
382
+ if (!details) {
383
+ return failure(new DeploymentError('Lease not found', 'LEASE_NOT_FOUND', { network: this.network, lease }));
384
+ }
385
+ return success(details);
386
+ }
387
+ catch (error) {
388
+ return failure(new DeploymentError('Failed to query lease', 'RPC_ERROR', { network: this.network, error: error instanceof Error ? error.message : String(error) }, true));
389
+ }
390
+ }
391
+ /**
392
+ * Fetch multiple leases with optional filtering
393
+ *
394
+ * @param wallet - Wallet context
395
+ * @param filters - Optional filters (dseq, state, provider)
396
+ * @returns Array of matching leases
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * // Get all active leases
401
+ * const leases = await client.getLeases(
402
+ * { address: 'akash1...' },
403
+ * { state: 'active' }
404
+ * );
405
+ *
406
+ * console.log(`Active leases: ${leases.data.length}`);
407
+ * ```
408
+ */
409
+ async getLeases(wallet, filters) {
410
+ try {
411
+ const queryFilters = {
412
+ owner: wallet.address
413
+ };
414
+ if (filters?.dseq !== undefined) {
415
+ queryFilters.dseq = Long.fromString(String(filters.dseq));
416
+ }
417
+ if (filters?.provider)
418
+ queryFilters.provider = filters.provider;
419
+ if (typeof filters?.gseq === 'number')
420
+ queryFilters.gseq = filters.gseq;
421
+ if (typeof filters?.oseq === 'number')
422
+ queryFilters.oseq = filters.oseq;
423
+ if (filters?.state)
424
+ queryFilters.state = filters.state;
425
+ // Query blockchain (reuses this.sdk)
426
+ const response = await this.sdk.akash.market.v1beta5.getLeases({
427
+ filters: queryFilters
428
+ });
429
+ const leases = [];
430
+ for (const entry of response.leases ?? []) {
431
+ const details = this.mapLease(entry);
432
+ if (details) {
433
+ leases.push(details);
434
+ }
435
+ }
436
+ return success(Object.freeze(leases));
437
+ }
438
+ catch (error) {
439
+ return failure(new DeploymentError('Failed to query leases', 'RPC_ERROR', { network: this.network, error: error instanceof Error ? error.message : String(error) }, true));
440
+ }
441
+ }
442
+ /**
443
+ * Map lease response to LeaseDetails
444
+ * @private
445
+ */
446
+ mapLease(response) {
447
+ if (!response?.lease) {
448
+ return null;
449
+ }
450
+ const lease = response.lease;
451
+ const id = lease.id; // FIX: Was lease.leaseId, but actual field is lease.id
452
+ if (!id) {
453
+ return null;
454
+ }
455
+ return {
456
+ owner: id.owner,
457
+ provider: id.provider,
458
+ dseq: this.longToString(id.dseq),
459
+ gseq: id.gseq,
460
+ oseq: id.oseq,
461
+ state: this.mapLeaseState(lease.state),
462
+ price: {
463
+ denom: lease.price?.denom ?? 'uakt',
464
+ amount: lease.price?.amount ?? '0'
465
+ },
466
+ createdAt: this.longToString(lease.createdAt ?? Long.ZERO)
467
+ };
468
+ }
469
+ /**
470
+ * Map lease state enum to string
471
+ * @private
472
+ */
473
+ mapLeaseState(state) {
474
+ if (typeof state === 'string') {
475
+ // Validate and return known states
476
+ if (state === 'active' || state === 'closed' || state === 'insufficient_funds') {
477
+ return state;
478
+ }
479
+ // Default to closed for unknown string states
480
+ return 'closed';
481
+ }
482
+ switch (state) {
483
+ case 1: return 'active';
484
+ case 2: return 'insufficient_funds';
485
+ case 3: return 'closed';
486
+ default: return 'closed'; // Default to closed for unknown states
487
+ }
488
+ }
489
+ /**
490
+ * Convert Long to string
491
+ * @private
492
+ */
493
+ longToString(value) {
494
+ if (Long.isLong(value)) {
495
+ return value.toString();
496
+ }
497
+ return String(value);
498
+ }
499
+ /**
500
+ * Fetch deployment details from blockchain
501
+ *
502
+ * @param owner - Wallet address that created deployment
503
+ * @param dseq - Deployment sequence number
504
+ * @returns Deployment details with groups and resources
505
+ *
506
+ * @example
507
+ * ```typescript
508
+ * const deployment = await client.getDeployment('akash1...', 12345);
509
+ *
510
+ * console.log(`State: ${deployment.data.state}`);
511
+ * console.log(`Groups: ${deployment.data.groups.length}`);
512
+ * ```
513
+ */
514
+ async getDeployment(owner, dseq) {
515
+ try {
516
+ // Query blockchain (reuses this.sdk)
517
+ const response = await this.sdk.akash.deployment.v1beta4.getDeployment({
518
+ id: {
519
+ owner,
520
+ dseq: Long.fromNumber(dseq)
521
+ }
522
+ });
523
+ if (!response.deployment) {
524
+ return failure(new DeploymentError('Deployment not found', 'DEPLOYMENT_NOT_FOUND', { network: this.network, owner, dseq }));
525
+ }
526
+ const details = this.mapDeployment(response);
527
+ return success(details);
528
+ }
529
+ catch (error) {
530
+ return failure(new DeploymentError('Failed to query deployment', 'RPC_ERROR', { network: this.network, error: error instanceof Error ? error.message : String(error) }, true));
531
+ }
532
+ }
533
+ /**
534
+ * Map deployment response to DeploymentDetails
535
+ * @private
536
+ */
537
+ mapDeployment(response) {
538
+ const deployment = response.deployment;
539
+ const owner = deployment.deploymentId?.owner ?? '';
540
+ const dseq = deployment.deploymentId?.dseq ?? '';
541
+ const state = this.mapDeploymentState(deployment.state);
542
+ const version = this.toHexString(deployment.version);
543
+ const createdAt = this.longToString(deployment.createdAt ?? Long.ZERO);
544
+ const groups = (response.groups ?? []).map((group) => {
545
+ const groupSpec = group.groupSpec;
546
+ const resources = (groupSpec?.resources ?? []).map((item) => {
547
+ const resource = item.resource;
548
+ return {
549
+ resourceId: resource?.id ?? 0,
550
+ count: item.count ?? 0,
551
+ cpuUnits: this.decodeResourceValue(resource?.cpu?.units?.val),
552
+ memoryQuantity: this.decodeResourceValue(resource?.memory?.quantity?.val),
553
+ storageQuantities: (resource?.storage ?? []).map((storage) => this.decodeResourceValue(storage.quantity?.val))
554
+ };
555
+ });
556
+ return {
557
+ name: groupSpec?.name ?? 'group',
558
+ resources: Object.freeze(resources)
559
+ };
560
+ });
561
+ return {
562
+ owner,
563
+ dseq: String(dseq),
564
+ state,
565
+ version,
566
+ createdAt,
567
+ groups: Object.freeze(groups)
568
+ };
569
+ }
570
+ /**
571
+ * Map deployment state enum to string
572
+ * @private
573
+ */
574
+ mapDeploymentState(state) {
575
+ if (typeof state === 'string') {
576
+ return state;
577
+ }
578
+ switch (state) {
579
+ case 1: return 'active';
580
+ case 2: return 'closed';
581
+ default: return 'unknown';
582
+ }
583
+ }
584
+ /**
585
+ * Decode resource value from protobuf bytes
586
+ * @private
587
+ */
588
+ decodeResourceValue(value) {
589
+ if (!value || value.length === 0) {
590
+ return '0';
591
+ }
592
+ return new TextDecoder().decode(value);
593
+ }
594
+ /**
595
+ * Convert binary version to hex string
596
+ * @private
597
+ */
598
+ toHexString(value) {
599
+ if (!value || value.length === 0) {
600
+ return '';
601
+ }
602
+ return Buffer.from(value).toString('hex');
603
+ }
604
+ /**
605
+ * Fetch provider metadata from blockchain
606
+ *
607
+ * @param providerAddress - Provider wallet address
608
+ * @returns Provider metadata (hostUri, attributes)
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * const provider = await client.getProvider('akash1provider...');
613
+ *
614
+ * console.log(`Host: ${provider.data.hostUri}`);
615
+ * console.log(`Audited: ${provider.data.attributes.some(a => a.key === 'audited-by')}`);
616
+ * ```
617
+ */
618
+ async getProvider(providerAddress) {
619
+ try {
620
+ // Query blockchain (reuses this.sdk)
621
+ const response = await this.sdk.akash.provider.v1beta4.getProvider({
622
+ owner: providerAddress
623
+ });
624
+ if (!response.provider) {
625
+ return success(undefined);
626
+ }
627
+ return success(this.mapProvider(response.provider));
628
+ }
629
+ catch (error) {
630
+ return failure(new DeploymentError('Failed to query provider', 'RPC_ERROR', { network: this.network, error: error instanceof Error ? error.message : String(error) }, true));
631
+ }
632
+ }
633
+ /**
634
+ * Map provider response to ProviderMetadata
635
+ * @private
636
+ */
637
+ mapProvider(provider) {
638
+ return {
639
+ owner: provider.owner,
640
+ hostUri: this.normalizeProviderUri(provider.hostUri),
641
+ attributes: Object.freeze((provider.attributes ?? []).map((attr) => ({
642
+ key: attr.key ?? '',
643
+ value: attr.value ?? ''
644
+ })))
645
+ };
646
+ }
647
+ /**
648
+ * Normalize provider URI
649
+ * @private
650
+ */
651
+ normalizeProviderUri(uri) {
652
+ if (!uri) {
653
+ return undefined;
654
+ }
655
+ try {
656
+ const parsed = new URL(uri);
657
+ if (parsed.protocol !== 'https:') {
658
+ return undefined;
659
+ }
660
+ parsed.pathname = parsed.pathname.replace(/\/$/, '');
661
+ return parsed.toString();
662
+ }
663
+ catch {
664
+ return undefined;
665
+ }
666
+ }
667
+ // ==========================================================================
668
+ // Transaction Methods (Write - Signer Required)
669
+ // ==========================================================================
670
+ /**
671
+ * Create deployment on Akash blockchain
672
+ *
673
+ * Submits an SDL to the blockchain, creating a new deployment that
674
+ * providers can bid on. Requires signing capability.
675
+ *
676
+ * **Gas cost**: ~0.05 AKT + deposit
677
+ *
678
+ * @param sdl - Service Definition Language document
679
+ * @param depositAkt - Initial deposit in AKT (refunded on close)
680
+ * @returns Deployment sequence number and transaction hash
681
+ *
682
+ * @example
683
+ * ```typescript
684
+ * const result = await client.createDeployment(sdl, 5);
685
+ *
686
+ * if (result.success) {
687
+ * console.log(`Deployment created: DSEQ ${result.data.dseq}`);
688
+ * console.log(`Transaction: ${result.data.transactionHash}`);
689
+ * }
690
+ * ```
691
+ */
692
+ async createDeployment(sdl, depositAkt = 5) {
693
+ // Validate signer exists
694
+ if (!this.signer) {
695
+ return failure(new DeploymentError('Signer required for creating deployments', 'SIGNER_REQUIRED', {}, false, 'Create AkashClient with a signer to perform write operations', 'error'));
696
+ }
697
+ try {
698
+ const owner = await this.getSignerAddress();
699
+ // Get current block height for dseq
700
+ const blockResult = await this.sdk.cosmos.base.tendermint.v1beta1.getLatestBlock({});
701
+ const dseq = Number(blockResult.block?.header?.height ?? 0);
702
+ // Extract SDL groups and manifest hash
703
+ const groups = sdl.groups();
704
+ const hash = await sdl.manifestVersion();
705
+ // Calculate deposit in uakt (1 AKT = 1,000,000 uakt)
706
+ const aktAmount = Number.isFinite(depositAkt) && depositAkt > 0 ? depositAkt : 5;
707
+ const depositUakt = String(Math.round(aktAmount * 1_000_000));
708
+ // Capture transaction metadata
709
+ let txHash = '';
710
+ let txHeight = 0;
711
+ // Create deployment message (reuses this.sdk)
712
+ const txResponse = await this.sdk.akash.deployment.v1beta4.createDeployment({
713
+ id: { owner, dseq: Long.fromNumber(dseq) },
714
+ groups,
715
+ hash,
716
+ deposit: {
717
+ amount: { denom: 'uakt', amount: depositUakt },
718
+ sources: [1] // Source.balance
719
+ }
720
+ }, {
721
+ afterBroadcast: (response) => {
722
+ txHash = response.transactionHash;
723
+ txHeight = response.height;
724
+ }
725
+ });
726
+ // If afterBroadcast wasn't called, extract from response
727
+ if (!txHash && txResponse) {
728
+ // The SDK might return the response directly
729
+ const response = txResponse;
730
+ txHash = response.transactionHash || response.txHash || '';
731
+ txHeight = response.height || 0;
732
+ }
733
+ return success({
734
+ dseq,
735
+ transactionHash: txHash,
736
+ height: txHeight
737
+ });
738
+ }
739
+ catch (error) {
740
+ const errorMessage = error instanceof Error ? error.message : String(error);
741
+ console.error('Deployment creation error:', errorMessage, error);
742
+ return failure(new DeploymentError(`Failed to create deployment on blockchain: ${errorMessage}`, 'TRANSACTION_FAILED', { error: errorMessage, details: error }, true, 'Check wallet balance and network connectivity'));
743
+ }
744
+ }
745
+ /**
746
+ * Get signer address
747
+ * @private
748
+ */
749
+ async getSignerAddress() {
750
+ if (!this.signer) {
751
+ throw new Error('Signer not available');
752
+ }
753
+ const accounts = await this.signer.getAccounts();
754
+ if (accounts.length === 0) {
755
+ throw new Error('No accounts found in signer');
756
+ }
757
+ const account = accounts[0];
758
+ if (!account) {
759
+ throw new Error('First account is undefined');
760
+ }
761
+ return account.address;
762
+ }
763
+ /**
764
+ * Accept a provider bid and create lease
765
+ *
766
+ * Creates a lease by accepting a provider's bid. The provider will then
767
+ * start deploying containers according to the SDL.
768
+ *
769
+ * **Gas cost**: ~0.02 AKT
770
+ *
771
+ * @param bid - Provider bid to accept (from getBids)
772
+ * @returns Lease identifier and transaction hash
773
+ *
774
+ * @example
775
+ * ```typescript
776
+ * const bids = await client.getBids(wallet, dseq);
777
+ * const cheapestBid = bids.data[0];
778
+ *
779
+ * const lease = await client.acceptBid(cheapestBid.bid);
780
+ *
781
+ * if (lease.success) {
782
+ * console.log(`Lease created with ${lease.data.lease.provider}`);
783
+ * }
784
+ * ```
785
+ */
786
+ async acceptBid(bid) {
787
+ // Validate signer exists
788
+ if (!this.signer) {
789
+ return failure(new DeploymentError('Signer required for accepting bids', 'SIGNER_REQUIRED', {}, false, 'Create AkashClient with a signer to perform write operations'));
790
+ }
791
+ try {
792
+ const bidId = bid.id ?? bid.bidId;
793
+ if (!bidId) {
794
+ return failure(new DeploymentError('Invalid bid: missing bid ID', 'INVALID_BID', { bid }));
795
+ }
796
+ // Capture transaction metadata
797
+ let txHash = '';
798
+ let txHeight = 0;
799
+ // Create lease (reuses this.sdk)
800
+ await this.sdk.akash.market.v1beta5.createLease({
801
+ bidId: {
802
+ owner: bidId.owner,
803
+ dseq: bidId.dseq,
804
+ gseq: bidId.gseq,
805
+ oseq: bidId.oseq,
806
+ provider: bidId.provider,
807
+ bseq: bidId.bseq ?? 0
808
+ }
809
+ }, {
810
+ afterBroadcast: (txResponse) => {
811
+ txHash = txResponse.transactionHash;
812
+ txHeight = txResponse.height;
813
+ }
814
+ });
815
+ const lease = {
816
+ owner: bidId.owner,
817
+ dseq: Number(bidId.dseq),
818
+ gseq: bidId.gseq,
819
+ oseq: bidId.oseq,
820
+ provider: bidId.provider
821
+ };
822
+ return success({
823
+ lease,
824
+ transactionHash: txHash,
825
+ height: txHeight
826
+ });
827
+ }
828
+ catch (error) {
829
+ return failure(new DeploymentError('Failed to create lease', 'TRANSACTION_FAILED', { error: error instanceof Error ? error.message : String(error) }, true, 'Check wallet balance and that bid is still valid'));
830
+ }
831
+ }
832
+ /**
833
+ * Close deployment on blockchain
834
+ *
835
+ * Closes the deployment and all associated leases. Remaining deposit
836
+ * is refunded to the wallet.
837
+ *
838
+ * **Gas cost**: ~0.02 AKT
839
+ *
840
+ * @param dseq - Deployment sequence number to close
841
+ * @returns Closure confirmation with refund amount
842
+ *
843
+ * @example
844
+ * ```typescript
845
+ * const result = await client.closeDeployment(12345);
846
+ *
847
+ * if (result.success) {
848
+ * console.log(`Deployment ${result.data.dseq} closed`);
849
+ * console.log(`Refunded: ${result.data.refundAmount}`);
850
+ * }
851
+ * ```
852
+ */
853
+ async closeDeployment(dseq) {
854
+ // Validate signer exists
855
+ if (!this.signer) {
856
+ return failure(new DeploymentError('Signer required for closing deployments', 'SIGNER_REQUIRED', {}, false, 'Create AkashClient with a signer to perform write operations'));
857
+ }
858
+ try {
859
+ const owner = await this.getSignerAddress();
860
+ // Capture transaction metadata
861
+ let txHash = '';
862
+ let txHeight = 0;
863
+ // Close deployment (reuses this.sdk)
864
+ await this.sdk.akash.deployment.v1beta4.closeDeployment({
865
+ id: {
866
+ owner,
867
+ dseq: Long.fromNumber(dseq)
868
+ }
869
+ }, {
870
+ afterBroadcast: (txResponse) => {
871
+ txHash = txResponse.transactionHash;
872
+ txHeight = txResponse.height;
873
+ }
874
+ });
875
+ return success({
876
+ dseq: String(dseq),
877
+ owner,
878
+ transactionHash: txHash,
879
+ height: txHeight,
880
+ closedAt: new Date(),
881
+ confirmed: true
882
+ });
883
+ }
884
+ catch (error) {
885
+ return failure(new DeploymentError('Failed to close deployment', 'TRANSACTION_FAILED', { dseq, error: error instanceof Error ? error.message : String(error) }, true, 'Verify deployment exists and is owned by this wallet'));
886
+ }
887
+ }
888
+ // ==========================================================================
889
+ // Certificate Methods
890
+ // ==========================================================================
891
+ /**
892
+ * Get certificate manager instance
893
+ *
894
+ * Returns a lazy-initialized CertificateManager that uses this client's SDK.
895
+ * The manager handles certificate generation, querying, and broadcasting.
896
+ *
897
+ * @returns Certificate manager instance
898
+ *
899
+ * @example
900
+ * ```typescript
901
+ * const certManager = client.getCertificateManager();
902
+ * const cert = await certManager.getOrCreate('akash1...');
903
+ * ```
904
+ */
905
+ getCertificateManager() {
906
+ // Lazy initialization - create manager on first access
907
+ if (!this.certManager) {
908
+ this.certManager = new CertificateManager(this);
909
+ }
910
+ return this.certManager;
911
+ }
912
+ // ==========================================================================
913
+ // Utility Methods
914
+ // ==========================================================================
915
+ /**
916
+ * Disconnect and cleanup resources
917
+ *
918
+ * Closes SDK connections and cleans up resources. Call this when
919
+ * done with the client to prevent memory leaks.
920
+ *
921
+ * **Important**: Always call this in a `finally` block
922
+ *
923
+ * @example
924
+ * ```typescript
925
+ * const client = new AkashClient({ network: 'mainnet', signer });
926
+ *
927
+ * try {
928
+ * await client.createDeployment(sdl);
929
+ * } finally {
930
+ * await client.disconnect(); // Always cleanup!
931
+ * }
932
+ * ```
933
+ */
934
+ async disconnect() {
935
+ // SDK cleanup if available
936
+ // The chain-sdk handles cleanup internally
937
+ // This method is a placeholder for future cleanup needs
938
+ }
939
+ /**
940
+ * Get current network
941
+ *
942
+ * @returns Network this client is connected to
943
+ *
944
+ * @example
945
+ * ```typescript
946
+ * console.log(`Connected to: ${client.getNetwork()}`);
947
+ * ```
948
+ */
949
+ getNetwork() {
950
+ return this.network;
951
+ }
952
+ /**
953
+ * Check if client can sign transactions
954
+ *
955
+ * Returns true if signer was provided in constructor.
956
+ * Use this to check if write operations are available.
957
+ *
958
+ * @returns True if client can sign transactions
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * if (!client.canSign()) {
963
+ * console.error('Cannot create deployment - client is read-only');
964
+ * console.log('Create client with signer for write operations');
965
+ * }
966
+ * ```
967
+ */
968
+ canSign() {
969
+ return this.signer !== undefined;
970
+ }
971
+ }
972
+ //# sourceMappingURL=client.js.map