UNPKG

a402

Version:

Decentralized Infrastructure to buy and sell any internet native resources on Aptos

447 lines (441 loc) 15.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tsSdk = require('@aptos-labs/ts-sdk'); var bs58 = require('bs58'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var bs58__default = /*#__PURE__*/_interopDefault(bs58); // src/index.ts var A402SDK = class { constructor(config) { this.config = config; this.networkConfig = this.getNetworkConfig(config); console.log("[A402 SDK] Initializing with network:", config.network); console.log("[A402 SDK] Frontend URL:", this.networkConfig.frontendUrl); const aptosConfig = new tsSdk.AptosConfig({ network: this.networkConfig.aptosNetwork }); this.aptos = new tsSdk.Aptos(aptosConfig); console.log("[A402 SDK] Initialization complete"); } getNetworkConfig(config) { const configs = { testnet: { aptosNetwork: tsSdk.Network.TESTNET, contractAddress: process.env.A402_TESTNET_CONTRACT || "0xd75ad93150725ce1139000d075a61d3be5c464b0b96be6fce193649c0ec3854a", frontendUrl: "https://a402.vercel.app" }, mainnet: { aptosNetwork: tsSdk.Network.MAINNET, contractAddress: process.env.A402_MAINNET_CONTRACT || "0x...", frontendUrl: "https://a402.vercel.app" } }; return configs[config.network]; } /** * Main middleware function for protecting endpoints */ /** * Main middleware function for protecting endpoints */ protect() { return (req, res, next) => { return this.handleMiddleware(req, res, next); }; } async handleMiddleware(req, res, next) { const startTime = Date.now(); try { console.log("\n========== A402 MIDDLEWARE START =========="); console.log(`[A402] Request: ${req.method} ${req.originalUrl}`); const endpointInfo = await this.getEndpointInfoFromRequest(req); if (!endpointInfo) { console.log("[A402] \u274C No endpoint found for this URL"); res.status(404).json({ error: "Endpoint Not Found", message: "This endpoint is not registered as a payable resource" }); return; } console.log(`[A402] \u2705 Endpoint found:`); console.log(` - Endpoint ID: ${endpointInfo.id}`); console.log(` - Resource: ${endpointInfo.resource.name}`); console.log( ` - Price: ${endpointInfo.price_per_call} ${endpointInfo.currency}` ); const apiKey = this.extractApiKey(req); if (!apiKey) { console.log("[A402] \u274C No API key provided"); this.sendPaymentRequiredResponse(res, endpointInfo); return; } console.log(`[A402] \u2705 API key found: ${apiKey.substring(0, 10)}...`); const tokenData = await this.validateAccessToken( apiKey, endpointInfo.resource_id, endpointInfo.id ); if (!tokenData) { console.log("[A402] \u274C Invalid or expired access token"); res.status(401).json({ error: "Invalid API Key", message: "The provided API key is invalid, expired, or not authorized for this endpoint" }); return; } console.log(`[A402] \u2705 Access token valid:`); console.log(` - Buyer: ${tokenData.buyer_address}`); const buyerAccount = await this.getBuyerAccount(tokenData.buyer_address); if (!buyerAccount) { console.log("[A402] \u274C Buyer account not found"); res.status(401).json({ error: "Account Not Found", message: "No payment account found for this API key. Please regenerate your API key." }); return; } console.log(`[A402] \u2705 Buyer account found:`); console.log(` - Wallet: ${buyerAccount.wallet_address}`); console.log(` - Server address: ${buyerAccount.account.accountAddress}`); const balance = await this.aptos.getAccountAPTAmount({ accountAddress: buyerAccount.account.accountAddress }); const balanceAPT = balance / 1e8; console.log(`[A402] \u{1F4B0} Current balance: ${balanceAPT} APT`); if (balanceAPT < endpointInfo.price_per_call) { console.log(`[A402] \u274C Insufficient balance`); res.status(402).json({ error: "Insufficient Balance", message: `Required: ${endpointInfo.price_per_call} APT, Available: ${balanceAPT} APT`, required: endpointInfo.price_per_call, available: balanceAPT, currency: "APT" }); return; } console.log("[A402] \u{1F504} Initiating payment..."); const payment = await this.makePaymentForEndpoint( buyerAccount.account, endpointInfo, tokenData.buyer_address ); console.log(`[A402] \u2705 Payment successful:`); console.log(` - TX Hash: ${payment.txHash}`); console.log(` - Amount: ${payment.amount} ${payment.currency}`); req.a402 = { user: { address: tokenData.buyer_address, account: buyerAccount.account }, payment: { txHash: payment.txHash, resourceId: endpointInfo.resource_id, amount: endpointInfo.price_per_call, currency: endpointInfo.currency } }; console.log( `[A402] \u2705 Middleware complete in ${Date.now() - startTime}ms` ); console.log("========== A402 MIDDLEWARE END ==========\n"); next(); } catch (error) { console.error("[A402] \u274C Middleware error:", error); console.log("========== A402 MIDDLEWARE ERROR ==========\n"); res.status(500).json({ error: "Payment Processing Error", message: error.message, details: process.env.NODE_ENV === "development" ? error.stack : void 0 }); } } extractApiKey(req) { const authHeader = req.headers.authorization; const apiKeyHeader = req.headers["a402-api-key"]; const xApiKeyHeader = req.headers["x-api-key"]; if (authHeader && authHeader.startsWith("A402 ")) { return authHeader.substring(5); } if (apiKeyHeader) { return apiKeyHeader; } if (xApiKeyHeader) { return xApiKeyHeader; } return null; } async getEndpointInfoFromRequest(req) { try { const path = req.path || req.url.split("?")[0]; const method = req.method.toUpperCase(); console.log(`[A402] Looking for endpoint: ${method} ${path}`); const response = await fetch( `${this.networkConfig.frontendUrl}/api/sdk/endpoint?path=${encodeURIComponent( path )}&method=${encodeURIComponent(method)}` ); if (!response.ok) { if (response.status === 404) { console.log(`[A402] No endpoint found for ${method} ${path}`); return null; } throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (!data) { console.log(`[A402] No data found for ${method} ${path}`); return null; } return data; } catch (error) { console.error("[A402] Error fetching endpoint:", error); return null; } } sendPaymentRequiredResponse(res, endpointInfo) { res.status(402).json({ error: "Payment Required", message: `Access to this endpoint requires payment of ${endpointInfo.price_per_call} ${endpointInfo.currency}`, endpoint: { id: endpointInfo.id, path: endpointInfo.path, method: endpointInfo.method, description: endpointInfo.description }, resource: { id: endpointInfo.resource_id, name: endpointInfo.resource.name }, payment: { amount: endpointInfo.price_per_call, currency: endpointInfo.currency, network: this.config.network }, instructions: { step1: "Generate an API key by signing the resource ID with your wallet", step2: "Add header: Authorization: A402 <your-api-key>", step3: "Ensure your account has sufficient balance" } }); } /** * Get buyer's payment balance */ async getBuyerBalance(buyerAddress) { console.log(`[A402] Checking balance for buyer: ${buyerAddress}`); const buyerData = await this.getBuyerData(buyerAddress); if (!buyerData) { throw new Error("Buyer not found"); } const balance = await this.aptos.getAccountAPTAmount({ accountAddress: buyerData.account.accountAddress }); const balanceAPT = balance / 1e8; console.log(`[A402] Balance: ${balanceAPT} APT`); return { balance: balanceAPT, currency: "APT", backendAddress: buyerData.account.accountAddress.toString() }; } /** * Fund buyer account (testnet only) */ async fundBuyerAccount(buyerAddress, amount = 1) { console.log( `[A402] Funding account for buyer: ${buyerAddress} with ${amount} APT` ); const buyerData = await this.getBuyerData(buyerAddress); if (!buyerData) { throw new Error("Buyer not found"); } if (this.config.network === tsSdk.Network.TESTNET) { await this.aptos.fundAccount({ accountAddress: buyerData.account.accountAddress, amount: amount * 1e8 // Convert to octas }); console.log(`[A402] \u2705 Account funded via testnet faucet`); return `Account funded with ${amount} APT via testnet faucet`; } throw new Error("Account funding only available on testnet"); } async validateAccessToken(apiKey, resourceId, endpointId) { try { console.log(`[A402] Validating API key...`); const decoded = bs58__default.default.decode(apiKey); if (decoded.length !== 96) { console.log(`[A402] Invalid API key format`); return null; } const publicKeyBytes = decoded.slice(0, 32); const signatureBytes = decoded.slice(32); const publicKey = new tsSdk.Ed25519PublicKey(publicKeyBytes); const signature = new tsSdk.Ed25519Signature(signatureBytes); const messageBytes = new TextEncoder().encode(resourceId); console.log(publicKey); return { buyer_address: "0xeb4971a2529c3b724e321a40c2d3b2679041007261f8a8d6dd79b9b37e13d0ed", public_key: "0xeb4971a2529c3b724e321a40c2d3b2679041007261f8a8d6dd79b9b37e13d0ed", resource_id: resourceId, endpoint_id: endpointId }; console.log(`[A402] \u2705 Signature verified for public key: ${publicKey}`); } catch (error) { console.error("[A402] Token validation error:", error); return null; } } async getBuyerData(buyerAddress) { try { const response = await fetch( `${this.networkConfig.frontendUrl}/api/sdk/buyer/${buyerAddress}` ); if (!response.ok) { if (response.status === 404) { return null; } throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (!data || !data.server_wallet_private_key || !data.server_wallet_address) { return null; } const privateKey = new tsSdk.Ed25519PrivateKey(data.server_wallet_private_key); const account = tsSdk.Account.fromPrivateKey({ privateKey }); return { wallet_address: data.server_wallet_address, account }; } catch (error) { console.error("[A402] Error fetching buyer data:", error); return null; } } async getBuyerAccount(buyerAddress) { return this.getBuyerData(buyerAddress); } async makePaymentForEndpoint(account, endpointInfo, buyerAddress) { console.log(`[A402] \u{1F4B8} Making payment:`); console.log(` - Resource: ${endpointInfo.resource_id}`); console.log(` - Endpoint: ${endpointInfo.id}`); console.log( ` - Amount: ${endpointInfo.price_per_call} ${endpointInfo.currency}` ); const amountInOctas = Math.floor(endpointInfo.price_per_call * 1e8); const transactionData = { function: `${this.networkConfig.contractAddress}::simple_payments::pay_for_resource`, functionArguments: [ Array.from(Buffer.from(endpointInfo.resource_id)), // resource_id as u8 array Array.from(Buffer.from(endpointInfo.id)), // endpoint_id as u8 array amountInOctas // amount as u64 ] }; console.log(`[A402] \u{1F4DD} Transaction details:`); console.log(` - Function: ${transactionData.function}`); console.log( ` - Args: ${JSON.stringify(transactionData.functionArguments)}` ); const pendingTx = await this.aptos.transaction.build.simple({ sender: account.accountAddress, data: transactionData }); const committedTx = await this.aptos.signAndSubmitTransaction({ signer: account, transaction: pendingTx }); console.log(`[A402] \u{1F4E4} Transaction submitted: ${committedTx.hash}`); const confirmedTx = await this.aptos.waitForTransaction({ transactionHash: committedTx.hash }); console.log( `[A402] \u2705 Transaction confirmed in block: ${confirmedTx.version}` ); try { const response = await fetch( `${this.networkConfig.frontendUrl}/api/sdk/payment`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tx_hash: committedTx.hash, from_address: buyerAddress, to_address: endpointInfo.resource.seller_wallet_address, resource_id: endpointInfo.resource_id, endpoint_id: endpointInfo.id, amount: endpointInfo.price_per_call, currency: endpointInfo.currency, status: "completed", payment_type: "one_time", metadata: { block_version: confirmedTx.version, gas_used: confirmedTx.gas_used }, completed_at: (/* @__PURE__ */ new Date()).toISOString() }) } ); if (!response.ok) { console.error( "[A402] Failed to record payment:", await response.text() ); } } catch (error) { console.error("[A402] Failed to record payment:", error); } return { txHash: committedTx.hash, amount: endpointInfo.price_per_call, currency: endpointInfo.currency, from: account.accountAddress.toString(), to: endpointInfo.resource.seller_wallet_address }; } async recordAnalytics(endpointInfo, eventType, metadata) { try { const response = await fetch( `${this.networkConfig.frontendUrl}/api/sdk/analytics`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ resource_id: endpointInfo.resource_id, endpoint_id: endpointInfo.id, event_type: eventType, endpoint: endpointInfo.path, method: endpointInfo.method, status_code: eventType === "api_call" ? 200 : 500, response_time_ms: metadata.response_time_ms || 0, metadata }) } ); if (!response.ok) { console.error( "[A402] Failed to record analytics:", await response.text() ); } } catch (error) { console.error("[A402] Record analytics error:", error); } } }; function createA402Middleware(config) { const sdk = new A402SDK(config); return sdk; } var index_default = A402SDK; exports.A402SDK = A402SDK; exports.createA402Middleware = createA402Middleware; exports.default = index_default; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map