@layr-labs/ecloud-sdk 0.2.0 → 0.2.1-dev

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/dist/billing.js CHANGED
@@ -1,7 +1,514 @@
1
- import {
2
- createBillingModule
3
- } from "./chunk-ZDXN2WKP.js";
4
- import "./chunk-CA5Y4OVI.js";
1
+ // src/client/common/utils/billingapi.ts
2
+ import axios from "axios";
3
+
4
+ // src/client/common/utils/auth.ts
5
+ import { parseAbi } from "viem";
6
+ var APP_CONTROLLER_ABI = parseAbi([
7
+ "function calculateApiPermissionDigestHash(bytes4 permission, uint256 expiry) view returns (bytes32)"
8
+ ]);
9
+ var generateBillingSigData = (product, expiry) => {
10
+ return {
11
+ domain: {
12
+ name: "EigenCloud Billing API",
13
+ version: "1"
14
+ },
15
+ types: {
16
+ BillingAuth: [
17
+ { name: "product", type: "string" },
18
+ { name: "expiry", type: "uint256" }
19
+ ]
20
+ },
21
+ primaryType: "BillingAuth",
22
+ message: {
23
+ product,
24
+ expiry
25
+ }
26
+ };
27
+ };
28
+ async function calculateBillingAuthSignature(options) {
29
+ const { walletClient, product, expiry } = options;
30
+ const account = walletClient.account;
31
+ if (!account) {
32
+ throw new Error("WalletClient must have an account attached");
33
+ }
34
+ const signature = await walletClient.signTypedData({
35
+ account,
36
+ ...generateBillingSigData(product, expiry)
37
+ });
38
+ return { signature, expiry };
39
+ }
40
+
41
+ // src/client/common/utils/billingapi.ts
42
+ var BillingApiClient = class {
43
+ constructor(config, walletClient) {
44
+ this.config = config;
45
+ this.walletClient = walletClient;
46
+ }
47
+ /**
48
+ * Get the address of the connected wallet
49
+ */
50
+ get address() {
51
+ const account = this.walletClient.account;
52
+ if (!account) {
53
+ throw new Error("WalletClient must have an account attached");
54
+ }
55
+ return account.address;
56
+ }
57
+ async createSubscription(productId = "compute") {
58
+ const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
59
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId);
60
+ return resp.json();
61
+ }
62
+ async getSubscription(productId = "compute") {
63
+ const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
64
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", productId);
65
+ return resp.json();
66
+ }
67
+ async cancelSubscription(productId = "compute") {
68
+ const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
69
+ await this.makeAuthenticatedRequest(endpoint, "DELETE", productId);
70
+ }
71
+ /**
72
+ * Make an authenticated request to the billing API
73
+ */
74
+ async makeAuthenticatedRequest(url, method, productId) {
75
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
76
+ const { signature } = await calculateBillingAuthSignature({
77
+ walletClient: this.walletClient,
78
+ product: productId,
79
+ expiry
80
+ });
81
+ const headers = {
82
+ Authorization: `Bearer ${signature}`,
83
+ "X-Account": this.address,
84
+ "X-Expiry": expiry.toString()
85
+ };
86
+ try {
87
+ const response = await axios({
88
+ method,
89
+ url,
90
+ headers,
91
+ timeout: 3e4,
92
+ maxRedirects: 0,
93
+ validateStatus: () => true
94
+ // Don't throw on any status
95
+ });
96
+ const status = response.status;
97
+ const statusText = status >= 200 && status < 300 ? "OK" : "Error";
98
+ if (status < 200 || status >= 300) {
99
+ const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
100
+ throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body}`);
101
+ }
102
+ return {
103
+ json: async () => response.data,
104
+ text: async () => typeof response.data === "string" ? response.data : JSON.stringify(response.data)
105
+ };
106
+ } catch (error) {
107
+ if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ENOTFOUND") || error.cause) {
108
+ const cause = error.cause?.message || error.cause || error.message;
109
+ throw new Error(
110
+ `Failed to connect to BillingAPI at ${url}: ${cause}
111
+ Please check:
112
+ 1. Your internet connection
113
+ 2. The API server is accessible: ${this.config.billingApiServerURL}
114
+ 3. Firewall/proxy settings`
115
+ );
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+ };
121
+
122
+ // src/client/common/config/environment.ts
123
+ var SEPOLIA_CHAIN_ID = 11155111;
124
+ var MAINNET_CHAIN_ID = 1;
125
+ var CommonAddresses = {
126
+ ERC7702Delegator: "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b"
127
+ };
128
+ var ChainAddresses = {
129
+ [MAINNET_CHAIN_ID]: {
130
+ PermissionController: "0x25E5F8B1E7aDf44518d35D5B2271f114e081f0E5"
131
+ },
132
+ [SEPOLIA_CHAIN_ID]: {
133
+ PermissionController: "0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37"
134
+ }
135
+ };
136
+ var BILLING_ENVIRONMENTS = {
137
+ dev: {
138
+ billingApiServerURL: "https://billingapi-dev.eigencloud.xyz"
139
+ },
140
+ prod: {
141
+ billingApiServerURL: "https://billingapi.eigencloud.xyz"
142
+ }
143
+ };
144
+ var ENVIRONMENTS = {
145
+ "sepolia-dev": {
146
+ name: "sepolia",
147
+ build: "dev",
148
+ appControllerAddress: "0xa86DC1C47cb2518327fB4f9A1627F51966c83B92",
149
+ permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
150
+ erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
151
+ kmsServerURL: "http://10.128.0.57:8080",
152
+ userApiServerURL: "https://userapi-compute-sepolia-dev.eigencloud.xyz",
153
+ defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com"
154
+ },
155
+ sepolia: {
156
+ name: "sepolia",
157
+ build: "prod",
158
+ appControllerAddress: "0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2",
159
+ permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
160
+ erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
161
+ kmsServerURL: "http://10.128.15.203:8080",
162
+ userApiServerURL: "https://userapi-compute-sepolia-prod.eigencloud.xyz",
163
+ defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com"
164
+ },
165
+ "mainnet-alpha": {
166
+ name: "mainnet-alpha",
167
+ build: "prod",
168
+ appControllerAddress: "0xc38d35Fc995e75342A21CBd6D770305b142Fbe67",
169
+ permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,
170
+ erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
171
+ kmsServerURL: "http://10.128.0.2:8080",
172
+ userApiServerURL: "https://userapi-compute.eigencloud.xyz",
173
+ defaultRPCURL: "https://ethereum-rpc.publicnode.com"
174
+ }
175
+ };
176
+ var CHAIN_ID_TO_ENVIRONMENT = {
177
+ [SEPOLIA_CHAIN_ID.toString()]: "sepolia",
178
+ [MAINNET_CHAIN_ID.toString()]: "mainnet-alpha"
179
+ };
180
+ function getBillingEnvironmentConfig(build) {
181
+ const config = BILLING_ENVIRONMENTS[build];
182
+ if (!config) {
183
+ throw new Error(`Unknown billing environment: ${build}`);
184
+ }
185
+ return config;
186
+ }
187
+ function getBuildType() {
188
+ const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
189
+ const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
190
+ const buildType = buildTimeType || runtimeType;
191
+ if (buildType === "dev") {
192
+ return "dev";
193
+ }
194
+ return "prod";
195
+ }
196
+
197
+ // src/client/common/utils/logger.ts
198
+ var getLogger = (verbose) => ({
199
+ info: (...args) => console.info(...args),
200
+ warn: (...args) => console.warn(...args),
201
+ error: (...args) => console.error(...args),
202
+ debug: (...args) => verbose && console.debug(...args)
203
+ });
204
+
205
+ // src/client/common/utils/userapi.ts
206
+ import axios2 from "axios";
207
+
208
+ // src/client/common/utils/helpers.ts
209
+ import { extractChain, createPublicClient, createWalletClient, http } from "viem";
210
+ import { sepolia as sepolia2 } from "viem/chains";
211
+ import { privateKeyToAccount } from "viem/accounts";
212
+
213
+ // src/client/common/constants.ts
214
+ import { sepolia, mainnet } from "viem/chains";
215
+
216
+ // src/client/common/utils/billing.ts
217
+ function isSubscriptionActive(status) {
218
+ return status === "active" || status === "trialing";
219
+ }
220
+
221
+ // src/client/common/telemetry/noop.ts
222
+ var NoopClient = class {
223
+ /**
224
+ * AddMetric implements the TelemetryClient interface
225
+ */
226
+ async addMetric(_metric) {
227
+ }
228
+ /**
229
+ * Close implements the TelemetryClient interface
230
+ */
231
+ async close() {
232
+ }
233
+ };
234
+ function isNoopClient(client) {
235
+ return client instanceof NoopClient;
236
+ }
237
+
238
+ // src/client/common/telemetry/posthog.ts
239
+ import { PostHog } from "posthog-node";
240
+ var PostHogClient = class {
241
+ constructor(environment, namespace, apiKey, endpoint) {
242
+ this.namespace = namespace;
243
+ this.appEnvironment = environment;
244
+ const host = endpoint || "https://us.i.posthog.com";
245
+ this.client = new PostHog(apiKey, {
246
+ host,
247
+ flushAt: 1,
248
+ // Flush immediately for CLI/SDK usage
249
+ flushInterval: 0
250
+ // Disable interval flushing
251
+ });
252
+ this.client.identify({
253
+ distinctId: environment.userUUID,
254
+ properties: {
255
+ os: environment.os,
256
+ arch: environment.arch,
257
+ ...environment.cliVersion ? { cliVersion: environment.cliVersion } : {}
258
+ }
259
+ });
260
+ }
261
+ /**
262
+ * AddMetric implements the TelemetryClient interface
263
+ */
264
+ async addMetric(metric) {
265
+ try {
266
+ const props = {
267
+ name: metric.name,
268
+ value: metric.value
269
+ };
270
+ for (const [k, v] of Object.entries(metric.dimensions)) {
271
+ props[k] = v;
272
+ }
273
+ this.client.capture({
274
+ distinctId: this.appEnvironment.userUUID,
275
+ event: this.namespace,
276
+ properties: props
277
+ });
278
+ } catch {
279
+ }
280
+ }
281
+ /**
282
+ * Close implements the TelemetryClient interface
283
+ */
284
+ async close() {
285
+ try {
286
+ this.client.shutdown();
287
+ } catch {
288
+ }
289
+ }
290
+ };
291
+ function getPostHogAPIKey() {
292
+ if (process.env.ECLOUD_POSTHOG_KEY) {
293
+ return process.env.ECLOUD_POSTHOG_KEY;
294
+ }
295
+ return true ? "phc_BiKfywNft5iBI8N7MxmuVCkb4GGZj4mDFXYPmOPUAI8" : void 0;
296
+ }
297
+ function getPostHogEndpoint() {
298
+ return process.env.ECLOUD_POSTHOG_ENDPOINT || "https://us.i.posthog.com";
299
+ }
300
+
301
+ // src/client/common/telemetry/index.ts
302
+ import * as os from "os";
303
+
304
+ // src/client/common/telemetry/metricsContext.ts
305
+ function createMetricsContext() {
306
+ return {
307
+ startTime: /* @__PURE__ */ new Date(),
308
+ metrics: [],
309
+ properties: {}
310
+ };
311
+ }
312
+ function addMetric(context, name, value) {
313
+ addMetricWithDimensions(context, name, value, {});
314
+ }
315
+ function addMetricWithDimensions(context, name, value, dimensions) {
316
+ context.metrics.push({
317
+ name,
318
+ value,
319
+ dimensions
320
+ });
321
+ }
322
+
323
+ // src/client/common/telemetry/index.ts
324
+ function createTelemetryClient(environment, namespace, options) {
325
+ const telemetryEnabled = options?.telemetryEnabled === true;
326
+ if (!telemetryEnabled) {
327
+ return new NoopClient();
328
+ }
329
+ const resolvedApiKey = options?.apiKey || getPostHogAPIKey();
330
+ if (!resolvedApiKey) {
331
+ return new NoopClient();
332
+ }
333
+ const endpoint = options?.endpoint || getPostHogEndpoint();
334
+ try {
335
+ return new PostHogClient(environment, namespace, resolvedApiKey, endpoint);
336
+ } catch {
337
+ return new NoopClient();
338
+ }
339
+ }
340
+ function createAppEnvironment(userUUID, cliVersion, osOverride, archOverride) {
341
+ return {
342
+ userUUID,
343
+ cliVersion,
344
+ os: osOverride || os.platform(),
345
+ arch: archOverride || os.arch()
346
+ };
347
+ }
348
+ async function emitMetrics(client, context) {
349
+ if (isNoopClient(client)) {
350
+ return;
351
+ }
352
+ for (const metric of context.metrics) {
353
+ const dimensions = {
354
+ ...metric.dimensions,
355
+ ...context.properties
356
+ };
357
+ const metricWithProperties = {
358
+ ...metric,
359
+ dimensions
360
+ };
361
+ try {
362
+ await client.addMetric(metricWithProperties);
363
+ } catch {
364
+ }
365
+ }
366
+ }
367
+
368
+ // src/client/common/telemetry/wrapper.ts
369
+ import { randomUUID } from "crypto";
370
+ function generateRandomUUID() {
371
+ return randomUUID();
372
+ }
373
+ async function withSDKTelemetry(options, action) {
374
+ if (options.skipTelemetry) {
375
+ return action();
376
+ }
377
+ const userUUID = options.userUUID || generateRandomUUID();
378
+ const environment = createAppEnvironment(userUUID);
379
+ const client = createTelemetryClient(environment, "ecloud-sdk", {
380
+ telemetryEnabled: options.telemetryEnabled,
381
+ apiKey: options.apiKey,
382
+ endpoint: options.endpoint
383
+ });
384
+ const metrics = createMetricsContext();
385
+ metrics.properties["source"] = "ecloud-sdk";
386
+ metrics.properties["function"] = options.functionName;
387
+ if (options.properties) {
388
+ Object.assign(metrics.properties, options.properties);
389
+ }
390
+ addMetric(metrics, "Count", 1);
391
+ let actionError;
392
+ let result;
393
+ try {
394
+ result = await action();
395
+ return result;
396
+ } catch (err) {
397
+ actionError = err instanceof Error ? err : new Error(String(err));
398
+ throw err;
399
+ } finally {
400
+ const resultValue = actionError ? "Failure" : "Success";
401
+ const dimensions = {};
402
+ if (actionError) {
403
+ dimensions["error"] = actionError.message;
404
+ }
405
+ addMetricWithDimensions(metrics, resultValue, 1, dimensions);
406
+ const duration = Date.now() - metrics.startTime.getTime();
407
+ addMetric(metrics, "DurationMilliseconds", duration);
408
+ try {
409
+ await emitMetrics(client, metrics);
410
+ await client.close();
411
+ } catch {
412
+ }
413
+ }
414
+ }
415
+
416
+ // src/client/modules/billing/index.ts
417
+ function createBillingModule(config) {
418
+ const { verbose = false, skipTelemetry = false, walletClient } = config;
419
+ if (!walletClient.account) {
420
+ throw new Error("WalletClient must have an account attached");
421
+ }
422
+ const address = walletClient.account.address;
423
+ const logger = getLogger(verbose);
424
+ const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());
425
+ const billingApi = new BillingApiClient(billingEnvConfig, walletClient);
426
+ return {
427
+ address,
428
+ async subscribe(opts) {
429
+ return withSDKTelemetry(
430
+ {
431
+ functionName: "subscribe",
432
+ skipTelemetry,
433
+ // Skip if called from CLI
434
+ properties: { productId: opts?.productId || "compute" }
435
+ },
436
+ async () => {
437
+ const productId = opts?.productId || "compute";
438
+ logger.debug(`Checking existing subscription for ${productId}...`);
439
+ const currentStatus = await billingApi.getSubscription(productId);
440
+ if (isSubscriptionActive(currentStatus.subscriptionStatus)) {
441
+ logger.debug(`Subscription already active: ${currentStatus.subscriptionStatus}`);
442
+ return {
443
+ type: "already_active",
444
+ status: currentStatus.subscriptionStatus
445
+ };
446
+ }
447
+ if (currentStatus.subscriptionStatus === "past_due" || currentStatus.subscriptionStatus === "unpaid") {
448
+ logger.debug(`Subscription has payment issue: ${currentStatus.subscriptionStatus}`);
449
+ return {
450
+ type: "payment_issue",
451
+ status: currentStatus.subscriptionStatus,
452
+ portalUrl: currentStatus.portalUrl
453
+ };
454
+ }
455
+ logger.debug(`Creating subscription for ${productId}...`);
456
+ const result = await billingApi.createSubscription(productId);
457
+ logger.debug(`Checkout URL: ${result.checkoutUrl}`);
458
+ return {
459
+ type: "checkout_created",
460
+ checkoutUrl: result.checkoutUrl
461
+ };
462
+ }
463
+ );
464
+ },
465
+ async getStatus(opts) {
466
+ return withSDKTelemetry(
467
+ {
468
+ functionName: "getStatus",
469
+ skipTelemetry,
470
+ // Skip if called from CLI
471
+ properties: { productId: opts?.productId || "compute" }
472
+ },
473
+ async () => {
474
+ const productId = opts?.productId || "compute";
475
+ logger.debug(`Fetching subscription status for ${productId}...`);
476
+ const result = await billingApi.getSubscription(productId);
477
+ logger.debug(`Subscription status: ${result.subscriptionStatus}`);
478
+ return result;
479
+ }
480
+ );
481
+ },
482
+ async cancel(opts) {
483
+ return withSDKTelemetry(
484
+ {
485
+ functionName: "cancel",
486
+ skipTelemetry,
487
+ // Skip if called from CLI
488
+ properties: { productId: opts?.productId || "compute" }
489
+ },
490
+ async () => {
491
+ const productId = opts?.productId || "compute";
492
+ logger.debug(`Checking subscription status for ${productId}...`);
493
+ const currentStatus = await billingApi.getSubscription(productId);
494
+ if (!isSubscriptionActive(currentStatus.subscriptionStatus)) {
495
+ logger.debug(`No active subscription to cancel: ${currentStatus.subscriptionStatus}`);
496
+ return {
497
+ type: "no_active_subscription",
498
+ status: currentStatus.subscriptionStatus
499
+ };
500
+ }
501
+ logger.debug(`Canceling subscription for ${productId}...`);
502
+ await billingApi.cancelSubscription(productId);
503
+ logger.debug(`Subscription canceled successfully`);
504
+ return {
505
+ type: "canceled"
506
+ };
507
+ }
508
+ );
509
+ }
510
+ };
511
+ }
5
512
  export {
6
513
  createBillingModule
7
514
  };