diff --git a/packages/cli/src/commands/billing/cancel.ts b/packages/cli/src/commands/billing/cancel.ts index c81276e..1f8b15a 100644 --- a/packages/cli/src/commands/billing/cancel.ts +++ b/packages/cli/src/commands/billing/cancel.ts @@ -16,7 +16,7 @@ export default class BillingCancel extends Command { required: false, description: "Product ID", default: "compute", - options: ["compute"], + options: ["compute", "eigenai"], env: "ECLOUD_PRODUCT_ID", }), force: Flags.boolean({ @@ -34,7 +34,7 @@ export default class BillingCancel extends Command { // Check subscription status first this.debug(`\nChecking subscription status for ${flags.product}...`); const status = await billing.getStatus({ - productId: flags.product as "compute", + productId: flags.product as "compute" | "eigenai", }); // Check if there's an active subscription to cancel @@ -58,7 +58,7 @@ export default class BillingCancel extends Command { this.log(`\nCanceling subscription for ${flags.product}...`); const result = await billing.cancel({ - productId: flags.product as "compute", + productId: flags.product as "compute" | "eigenai", }); // Handle response (defensive - should always be canceled at this point) diff --git a/packages/cli/src/commands/billing/status.ts b/packages/cli/src/commands/billing/status.ts index 0d92a9d..8388e67 100644 --- a/packages/cli/src/commands/billing/status.ts +++ b/packages/cli/src/commands/billing/status.ts @@ -14,7 +14,7 @@ export default class BillingStatus extends Command { required: false, description: "Product ID", default: "compute", - options: ["compute"], + options: ["compute", "eigenai"], env: "ECLOUD_PRODUCT_ID", }), }; @@ -25,7 +25,7 @@ export default class BillingStatus extends Command { const billing = await createBillingClient(flags); const result = await billing.getStatus({ - productId: flags.product as "compute", + productId: flags.product as "compute" | "eigenai", }); const formatExpiry = (timestamp?: number) => diff --git a/packages/cli/src/commands/billing/subscribe.ts b/packages/cli/src/commands/billing/subscribe.ts index c8a332a..1b17077 100644 --- a/packages/cli/src/commands/billing/subscribe.ts +++ b/packages/cli/src/commands/billing/subscribe.ts @@ -1,5 +1,6 @@ import { Command, Flags } from "@oclif/core"; import { isSubscriptionActive } from "@layr-labs/ecloud-sdk"; +import type { ProductID, ChainID } from "@layr-labs/ecloud-sdk"; import { createBillingClient } from "../../client"; import { commonFlags } from "../../flags"; import chalk from "chalk"; @@ -19,9 +20,15 @@ export default class BillingSubscribe extends Command { required: false, description: "Product ID", default: "compute", - options: ["compute"], + options: ["compute", "eigenai"], env: "ECLOUD_PRODUCT_ID", }), + "chain-id": Flags.string({ + required: false, + description: "Chain ID for EigenAI subscription (required when product is eigenai)", + options: ["ethereum-mainnet", "ethereum-sepolia"], + env: "ECLOUD_CHAIN_ID", + }), }; async run() { @@ -31,69 +38,171 @@ export default class BillingSubscribe extends Command { this.debug(`\nChecking subscription status for ${flags.product}...`); - const result = await billing.subscribe({ - productId: flags.product as "compute", - }); - - // Handle already active subscription - if (result.type === "already_active") { - this.log( - `\n${chalk.green("✓")} Wallet ${chalk.bold(billing.address)} is already subscribed to ${flags.product}.`, - ); - this.log(chalk.gray("Run 'ecloud billing status' for details.")); + // Handle compute subscription + if (flags.product === "compute") { + await this.handleComputeSubscription(billing); return; } - // Handle payment issue - if (result.type === "payment_issue") { - this.log( - `\n${chalk.yellow("⚠")} You already have a subscription on ${flags.product}, but it has a payment issue.`, - ); - this.log("Please update your payment method to restore access."); - - if (result.portalUrl) { - this.log(`\n${chalk.bold("Update payment method:")}`); - this.log(` ${result.portalUrl}`); + // Handle eigenai subscription + if (flags.product === "eigenai") { + // Validate chain-id is provided for eigenai product + if (!flags["chain-id"]) { + this.error( + `${chalk.red("Error:")} --chain-id is required when subscribing to eigenai.\n` + + ` Use: ecloud billing subscribe --product eigenai --chain-id ethereum-mainnet`, + ); } + await this.handleEigenAISubscription(billing, flags["chain-id"] as ChainID); return; } + }); + } - // Open checkout URL in browser - this.log(`\nOpening checkout for wallet ${chalk.bold(billing.address)}...`); - this.log(chalk.gray(`\nURL: ${result.checkoutUrl}`)); - await open(result.checkoutUrl); - - // Poll for subscription status - this.log(`\n${chalk.gray("Waiting for payment confirmation...")}`); - - const startTime = Date.now(); - - while (Date.now() - startTime < PAYMENT_TIMEOUT_MS) { - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); - - try { - const status = await billing.getStatus({ - productId: flags.product as "compute", - }); - - // Check if subscription is now active or trialing - if (isSubscriptionActive(status.subscriptionStatus)) { - this.log( - `\n${chalk.green("✓")} Subscription activated successfully for ${flags.product}!`, - ); - this.log(`\n${chalk.gray("Start deploying with:")} ecloud compute app deploy`); - return; - } - } catch (error) { - this.debug(`Error polling for subscription status: ${error}`); - } - } + /** + * Handle subscription for compute product + */ + private async handleComputeSubscription( + billing: Awaited>, + ) { + const result = await billing.subscribe({ + productId: "compute", + }); - // Timeout reached + // Handle already active subscription + if (result.type === "already_active") { + this.log( + `\n${chalk.green("✓")} Wallet ${chalk.bold(billing.address)} is already subscribed to compute.`, + ); + this.log(chalk.gray("Run 'ecloud billing status' for details.")); + return; + } + + // Handle payment issue + if (result.type === "payment_issue") { + this.log( + `\n${chalk.yellow("⚠")} You already have a subscription on compute, but it has a payment issue.`, + ); + this.log("Please update your payment method to restore access."); + + if (result.portalUrl) { + this.log(`\n${chalk.bold("Update payment method:")}`); + this.log(` ${result.portalUrl}`); + } + return; + } + + // Open checkout URL in browser + this.log(`\nOpening checkout for wallet ${chalk.bold(billing.address)}...`); + this.log(chalk.gray(`\nURL: ${result.checkoutUrl}`)); + await open(result.checkoutUrl); + + // Poll for subscription status + this.log(`\n${chalk.gray("Waiting for payment confirmation...")}`); + const activated = await this.pollForSubscriptionStatus(billing, "compute"); + + if (activated) { + this.log(`\n${chalk.green("✓")} Subscription activated successfully for compute!`); + this.log(`\n${chalk.gray("Start deploying with:")} ecloud compute app deploy`); + } else { this.log(`\n${chalk.yellow("⚠")} Payment confirmation timed out after 5 minutes.`); this.log( chalk.gray(`If you completed payment, run 'ecloud billing status' to check status.`), ); + } + } + + /** + * Handle subscription for eigenai product + */ + private async handleEigenAISubscription( + billing: Awaited>, + chainId: ChainID, + ) { + const result = await billing.subscribeEigenAI({ + productId: "eigenai", + chainId, }); + + // Handle already active subscription + if (result.type === "already_active") { + this.log( + `\n${chalk.green("✓")} Wallet ${chalk.bold(billing.address)} is already subscribed to eigenai.`, + ); + this.log(chalk.gray("Run 'ecloud billing status --product eigenai' for details.")); + return; + } + + // Handle payment issue + if (result.type === "payment_issue") { + this.log( + `\n${chalk.yellow("⚠")} You already have a subscription on eigenai, but it has a payment issue.`, + ); + this.log("Please update your payment method to restore access."); + + if (result.portalUrl) { + this.log(`\n${chalk.bold("Update payment method:")}`); + this.log(` ${result.portalUrl}`); + } + return; + } + + // Display the API key prominently - this is shown only once! + this.log(`\n${chalk.bgYellow.black(" IMPORTANT ")} ${chalk.yellow("Save your API key now!")}`); + this.log(chalk.yellow("This key will only be shown once and cannot be recovered.\n")); + this.log(`${chalk.bold("Your EigenAI API Key:")}`); + this.log(` ${chalk.cyan(result.apiKey)}\n`); + this.log(chalk.gray("Store this key securely. You will need it to authenticate API requests.")); + + // Open checkout URL in browser + this.log(`\n${chalk.bold("Opening checkout for wallet")} ${chalk.bold(billing.address)}...`); + this.log(chalk.gray(`\nURL: ${result.checkoutUrl}`)); + await open(result.checkoutUrl); + + // Poll for subscription status + this.log(`\n${chalk.gray("Waiting for payment confirmation...")}`); + const activated = await this.pollForSubscriptionStatus(billing, "eigenai"); + + if (activated) { + this.log(`\n${chalk.green("✓")} Subscription activated successfully for eigenai!`); + this.log( + `\n${chalk.gray("Your EigenAI subscription is now active. Use your API key to make requests.")}`, + ); + } else { + this.log(`\n${chalk.yellow("⚠")} Payment confirmation timed out after 5 minutes.`); + this.log( + chalk.gray( + `If you completed payment, run 'ecloud billing status --product eigenai' to check status.`, + ), + ); + } + } + + /** + * Poll for subscription activation status + * @returns true if subscription became active, false if timed out + */ + private async pollForSubscriptionStatus( + billing: Awaited>, + productId: ProductID, + ): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < PAYMENT_TIMEOUT_MS) { + await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); + + try { + const status = await billing.getStatus({ productId }); + + // Check if subscription is now active or trialing + if (isSubscriptionActive(status.subscriptionStatus)) { + return true; + } + } catch (error) { + this.debug(`Error polling for subscription status: ${error}`); + } + } + + return false; } } diff --git a/packages/cli/src/commands/compute/app/deploy.ts b/packages/cli/src/commands/compute/app/deploy.ts index d043d02..638c00f 100644 --- a/packages/cli/src/commands/compute/app/deploy.ts +++ b/packages/cli/src/commands/compute/app/deploy.ts @@ -494,12 +494,9 @@ async function fetchAvailableInstanceTypes( rpcUrl, environment: environmentConfig.name, }); - const userApiClient = new UserApiClient( - environmentConfig, - walletClient, - publicClient, - { clientId: getClientId() }, - ); + const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient, { + clientId: getClientId(), + }); const skuList = await userApiClient.getSKUs(); if (skuList.skus.length === 0) { diff --git a/packages/cli/src/commands/compute/app/upgrade.ts b/packages/cli/src/commands/compute/app/upgrade.ts index 7a2ef5a..4007239 100644 --- a/packages/cli/src/commands/compute/app/upgrade.ts +++ b/packages/cli/src/commands/compute/app/upgrade.ts @@ -288,12 +288,9 @@ export default class AppUpgrade extends Command { rpcUrl, environment: environmentConfig.name, }); - const userApiClient = new UserApiClient( - environmentConfig, - walletClient, - publicClient, - { clientId: getClientId() }, - ); + const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient, { + clientId: getClientId(), + }); const infos = await userApiClient.getInfos([appID], 1); if (infos.length > 0) { currentInstanceType = infos[0].machineType || ""; @@ -398,7 +395,9 @@ async function fetchAvailableInstanceTypes( rpcUrl, environment: environmentConfig.name, }); - const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient, { clientId: getClientId() }); + const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient, { + clientId: getClientId(), + }); const skuList = await userApiClient.getSKUs(); if (skuList.skus.length === 0) { diff --git a/packages/cli/src/commands/compute/build/submit.ts b/packages/cli/src/commands/compute/build/submit.ts index c0d924d..af85405 100644 --- a/packages/cli/src/commands/compute/build/submit.ts +++ b/packages/cli/src/commands/compute/build/submit.ts @@ -111,9 +111,13 @@ export default class BuildSubmit extends Command { `\nUse ${chalk.yellow(`ecloud compute build logs ${buildId} --follow`)} to watch progress`, ); this.log(""); - this.log(chalk.gray("After the build completes, deploy or upgrade your app with the digest:")); + this.log( + chalk.gray("After the build completes, deploy or upgrade your app with the digest:"), + ); this.log(chalk.gray(" ecloud compute app deploy --verifiable --image-ref ")); - this.log(chalk.gray(" ecloud compute app upgrade --verifiable --image-ref ")); + this.log( + chalk.gray(" ecloud compute app upgrade --verifiable --image-ref "), + ); } return; } @@ -182,7 +186,9 @@ export default class BuildSubmit extends Command { this.log(` ecloud compute app deploy --verifiable --image-ref ${build.imageDigest}`); this.log(""); this.log(chalk.yellow(" Upgrade an existing app:")); - this.log(` ecloud compute app upgrade --verifiable --image-ref ${build.imageDigest}`); + this.log( + ` ecloud compute app upgrade --verifiable --image-ref ${build.imageDigest}`, + ); } else { this.log(chalk.red(`Build failed: ${build.errorMessage ?? "Unknown error"}`)); } diff --git a/packages/cli/src/utils/appResolver.ts b/packages/cli/src/utils/appResolver.ts index c92aaea..8842179 100644 --- a/packages/cli/src/utils/appResolver.ts +++ b/packages/cli/src/utils/appResolver.ts @@ -243,12 +243,9 @@ export class AppResolver { } // Fetch info for all apps to get profile names - const userApiClient = new UserApiClient( - this.environmentConfig, - walletClient, - publicClient, - { clientId: getClientId() }, - ); + const userApiClient = new UserApiClient(this.environmentConfig, walletClient, publicClient, { + clientId: getClientId(), + }); const appInfos = await getAppInfosChunked(userApiClient, apps); // Build profile names map diff --git a/packages/cli/src/utils/prompts.ts b/packages/cli/src/utils/prompts.ts index 6436962..50ea052 100644 --- a/packages/cli/src/utils/prompts.ts +++ b/packages/cli/src/utils/prompts.ts @@ -1052,12 +1052,9 @@ async function getAppIDInteractive(options: GetAppIDOptions): Promise
{ // If cache is empty/expired, fetch fresh profile names from API if (!cachedProfiles) { try { - const userApiClient = new UserApiClient( - environmentConfig, - walletClient, - publicClient, - { clientId: getClientId() }, - ); + const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient, { + clientId: getClientId(), + }); const appInfos = await getAppInfosChunked(userApiClient, apps); // Build and cache profile names diff --git a/packages/sdk/src/browser.ts b/packages/sdk/src/browser.ts index 363f925..a9e71a6 100644 --- a/packages/sdk/src/browser.ts +++ b/packages/sdk/src/browser.ts @@ -225,11 +225,7 @@ export { // ============================================================================= // Helper Utilities (browser-safe) // ============================================================================= -export { - getChainFromID, - addHexPrefix, - stripHexPrefix, -} from "./client/common/utils/helpers"; +export { getChainFromID, addHexPrefix, stripHexPrefix } from "./client/common/utils/helpers"; // ============================================================================= // No-op Logger (browser-safe) @@ -239,10 +235,7 @@ export { noopLogger } from "./client/common/types"; // ============================================================================= // KMS Encryption Utilities (browser-safe - uses jose library) // ============================================================================= -export { - encryptRSAOAEPAndAES256GCM, - getAppProtectedHeaders, -} from "./client/common/encryption/kms"; +export { encryptRSAOAEPAndAES256GCM, getAppProtectedHeaders } from "./client/common/encryption/kms"; export { getKMSKeysForEnvironment } from "./client/common/utils/keys"; diff --git a/packages/sdk/src/client/common/contract/eip7702.ts b/packages/sdk/src/client/common/contract/eip7702.ts index 95eb0b8..49f29e8 100644 --- a/packages/sdk/src/client/common/contract/eip7702.ts +++ b/packages/sdk/src/client/common/contract/eip7702.ts @@ -136,7 +136,10 @@ export async function checkERC7702Delegation( /** * Execute batch of operations via EIP-7702 delegator */ -export async function executeBatch(options: ExecuteBatchOptions, logger: Logger = noopLogger): Promise { +export async function executeBatch( + options: ExecuteBatchOptions, + logger: Logger = noopLogger, +): Promise { const { walletClient, publicClient, environmentConfig, executions, pendingMessage, gas } = options; diff --git a/packages/sdk/src/client/common/hooks/index.ts b/packages/sdk/src/client/common/hooks/index.ts index e2ca9e4..d8fa081 100644 --- a/packages/sdk/src/client/common/hooks/index.ts +++ b/packages/sdk/src/client/common/hooks/index.ts @@ -4,4 +4,8 @@ * These hooks require React 18+ as a peer dependency. */ -export { useComputeSession, type UseComputeSessionConfig, type UseComputeSessionReturn } from "./useComputeSession"; +export { + useComputeSession, + type UseComputeSessionConfig, + type UseComputeSessionReturn, +} from "./useComputeSession"; diff --git a/packages/sdk/src/client/common/types/index.ts b/packages/sdk/src/client/common/types/index.ts index edf2c71..1166e14 100644 --- a/packages/sdk/src/client/common/types/index.ts +++ b/packages/sdk/src/client/common/types/index.ts @@ -307,7 +307,7 @@ export interface AppProfileResponse { } // Billing types -export type ProductID = "compute"; +export type ProductID = "compute" | "eigenai"; export type ChainID = "ethereum-mainnet" | "ethereum-sepolia"; export type SubscriptionStatus = @@ -336,6 +336,14 @@ export interface CreateSubscriptionOptions { cancelUrl?: string; } +/** Options for creating an EigenAI subscription */ +export interface CreateEigenAISubscriptionOptions extends CreateSubscriptionOptions { + /** Chain ID for EigenAI subscription */ + chainId: ChainID; + /** Keccak256 hash of the API key (hex string with 0x prefix) */ + apiKeyHash: string; +} + export interface CreateSubscriptionResponse { checkoutUrl: string; } @@ -396,6 +404,26 @@ export interface SubscriptionOpts { cancelUrl?: string; } +/** Options specific to EigenAI subscription */ +export interface EigenAISubscriptionOpts extends SubscriptionOpts { + productId: "eigenai"; + /** Chain ID for EigenAI subscription (required) */ + chainId: ChainID; +} + +/** Response for EigenAI subscription that includes the API key */ +export interface EigenAICheckoutCreatedResponse { + type: "checkout_created"; + checkoutUrl: string; + /** The generated API key - ONLY shown once, must be saved by user */ + apiKey: string; +} + +export type EigenAISubscribeResponse = + | EigenAICheckoutCreatedResponse + | AlreadyActiveResponse + | PaymentIssueResponse; + // Billing environment configuration export interface BillingEnvironmentConfig { billingApiServerURL: string; @@ -410,11 +438,7 @@ export type DeployProgressCallback = (step: DeployStep, txHash?: Hex) => void; /** * Steps in sequential deployment flow */ -export type DeployStep = - | "createApp" - | "acceptAdmin" - | "setPublicLogs" - | "complete"; +export type DeployStep = "createApp" | "acceptAdmin" | "setPublicLogs" | "complete"; /** * Result from sequential deployment diff --git a/packages/sdk/src/client/common/utils/apikey.ts b/packages/sdk/src/client/common/utils/apikey.ts new file mode 100644 index 0000000..2a032c4 --- /dev/null +++ b/packages/sdk/src/client/common/utils/apikey.ts @@ -0,0 +1,35 @@ +/** + * API Key generation utilities for EigenAI + * + * Generates API keys similar to OpenAI's format: + * - sk-{64 hex characters from 32 random bytes} + */ + +import { keccak256, stringToHex } from "viem"; +import { randomBytes } from "crypto"; + +export interface GeneratedApiKey { + /** The API key in format "sk-{hex}" - must be saved, only shown once */ + apiKey: string; + /** Keccak256 hash of the API key (hex string with 0x prefix) */ + apiKeyHash: string; +} + +/** + * Generates a cryptographically secure API key with format "sk-{64 hex chars}" + * and returns both the key and its keccak256 hash. + * + * @returns The generated API key and its hash + */ +export function generateApiKey(): GeneratedApiKey { + // Generate 32 bytes of cryptographically secure randomness + const bytes = randomBytes(32); + + // Create API key with "sk-" prefix (similar to OpenAI's convention) + const apiKey = `sk-${bytes.toString("hex")}`; + + // Hash the API key using keccak256 + const apiKeyHash = keccak256(stringToHex(apiKey)); + + return { apiKey, apiKeyHash }; +} diff --git a/packages/sdk/src/client/common/utils/billingapi.ts b/packages/sdk/src/client/common/utils/billingapi.ts index 49f2cd4..08947af 100644 --- a/packages/sdk/src/client/common/utils/billingapi.ts +++ b/packages/sdk/src/client/common/utils/billingapi.ts @@ -12,7 +12,13 @@ import axios, { AxiosResponse } from "axios"; import { Address, type WalletClient } from "viem"; -import { ProductID, CreateSubscriptionOptions, CreateSubscriptionResponse, ProductSubscriptionResponse } from "../types"; +import { + ProductID, + CreateSubscriptionOptions, + CreateEigenAISubscriptionOptions, + CreateSubscriptionResponse, + ProductSubscriptionResponse, +} from "../types"; import { calculateBillingAuthSignature } from "./auth"; import { BillingEnvironmentConfig } from "../types"; import { @@ -138,10 +144,30 @@ export class BillingApiClient { async createSubscription(productId: ProductID = "compute", options?: CreateSubscriptionOptions): Promise { const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`; - const body = options ? { + const body = options + ? { + success_url: options.successUrl, + cancel_url: options.cancelUrl, + } + : undefined; + const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId, body); + return resp.json(); + } + + /** + * Create an EigenAI subscription with API key hash and chain ID + */ + async createEigenAISubscription( + options: CreateEigenAISubscriptionOptions, + ): Promise { + const productId: ProductID = "eigenai"; + const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`; + const body = { + chainId: options.chainId, + apiKeyHash: options.apiKeyHash, success_url: options.successUrl, cancel_url: options.cancelUrl, - } : undefined; + }; const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId, body); return resp.json(); } diff --git a/packages/sdk/src/client/common/utils/index.ts b/packages/sdk/src/client/common/utils/index.ts index 1dcb8ea..b270e85 100644 --- a/packages/sdk/src/client/common/utils/index.ts +++ b/packages/sdk/src/client/common/utils/index.ts @@ -9,3 +9,4 @@ export * from "./billing"; export * from "./helpers"; export * from "./billingapi"; export * from "./auth"; +export * from "./apikey"; \ No newline at end of file diff --git a/packages/sdk/src/client/modules/billing/index.ts b/packages/sdk/src/client/modules/billing/index.ts index 1c95033..26e4d3c 100644 --- a/packages/sdk/src/client/modules/billing/index.ts +++ b/packages/sdk/src/client/modules/billing/index.ts @@ -19,11 +19,15 @@ import type { SubscribeResponse, CancelResponse, ProductSubscriptionResponse, + EigenAISubscriptionOpts, + EigenAISubscribeResponse, } from "../../common/types"; +import { generateApiKey } from "../../common/utils/apikey"; export interface BillingModule { address: Address; subscribe: (opts?: SubscriptionOpts) => Promise; + subscribeEigenAI: (opts: EigenAISubscriptionOpts) => Promise; getStatus: (opts?: SubscriptionOpts) => Promise; cancel: (opts?: SubscriptionOpts) => Promise; } @@ -105,6 +109,63 @@ export function createBillingModule(config: BillingModuleConfig): BillingModule ); }, + async subscribeEigenAI(opts: EigenAISubscriptionOpts) { + return withSDKTelemetry( + { + functionName: "subscribeEigenAI", + skipTelemetry: skipTelemetry, + properties: { productId: "eigenai", chainId: opts.chainId }, + }, + async () => { + // Check existing subscription status first + logger.debug(`Checking existing subscription for eigenai...`); + const currentStatus = await billingApi.getSubscription("eigenai"); + + // If already active or trialing, don't create new checkout + if (isSubscriptionActive(currentStatus.subscriptionStatus)) { + logger.debug(`Subscription already active: ${currentStatus.subscriptionStatus}`); + return { + type: "already_active" as const, + status: currentStatus.subscriptionStatus, + }; + } + + // If subscription has payment issues, return portal URL instead + if ( + currentStatus.subscriptionStatus === "past_due" || + currentStatus.subscriptionStatus === "unpaid" + ) { + logger.debug(`Subscription has payment issue: ${currentStatus.subscriptionStatus}`); + return { + type: "payment_issue" as const, + status: currentStatus.subscriptionStatus, + portalUrl: currentStatus.portalUrl, + }; + } + + // Generate API key and hash + logger.debug(`Generating API key for EigenAI subscription...`); + const { apiKey, apiKeyHash } = generateApiKey(); + + // Create new checkout session with EigenAI-specific parameters + logger.debug(`Creating EigenAI subscription with chainId: ${opts.chainId}...`); + const result = await billingApi.createEigenAISubscription({ + chainId: opts.chainId, + apiKeyHash, + successUrl: opts.successUrl, + cancelUrl: opts.cancelUrl, + }); + + logger.debug(`Checkout URL: ${result.checkoutUrl}`); + return { + type: "checkout_created" as const, + checkoutUrl: result.checkoutUrl, + apiKey, // Return the API key to the caller - only shown once! + }; + }, + ); + }, + async getStatus(opts) { return withSDKTelemetry( {