UNPKG

httpay

Version:

HTTPay SDK for interacting with HTTPay smart contracts on Neutron

1,319 lines (1,302 loc) 59.3 kB
'use strict'; var reactQuery = require('@tanstack/react-query'); var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); var cosmwasmStargate = require('@cosmjs/cosmwasm-stargate'); var z = require('zod'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z); /** * This file was automatically generated by @cosmwasm/ts-codegen@1.12.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ const escrowQueryKeys = { contract: [ { contract: "escrow", }, ], address: (contractAddress) => [ { ...escrowQueryKeys.contract[0], address: contractAddress, }, ], getEscrow: (contractAddress, args) => [ { ...escrowQueryKeys.address(contractAddress)[0], method: "get_escrow", args, }, ], getCollectedFees: (contractAddress, args) => [ { ...escrowQueryKeys.address(contractAddress)[0], method: "get_collected_fees", args, }, ], getEscrows: (contractAddress, args) => [ { ...escrowQueryKeys.address(contractAddress)[0], method: "get_escrows", args, }, ], }; function useEscrowGetEscrowsQuery({ client, args, options, }) { return reactQuery.useQuery(escrowQueryKeys.getEscrows(client?.contractAddress, args), () => client ? client.getEscrows({ caller: args.caller, limit: args.limit, provider: args.provider, startAfter: args.startAfter, }) : Promise.reject(new Error("Invalid client")), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true), }); } function useEscrowGetCollectedFeesQuery({ client, options, }) { return reactQuery.useQuery(escrowQueryKeys.getCollectedFees(client?.contractAddress), () => client ? client.getCollectedFees() : Promise.reject(new Error("Invalid client")), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true), }); } function useEscrowGetEscrowQuery({ client, args, options, }) { return reactQuery.useQuery(escrowQueryKeys.getEscrow(client?.contractAddress, args), () => client ? client.getEscrow({ escrowId: args.escrowId, }) : Promise.reject(new Error("Invalid client")), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true), }); } function useEscrowClaimFeesMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.claimFees(msg, fee, memo, funds), options); } function useEscrowRefundExpiredMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.refundExpired(msg, fee, memo, funds), options); } function useEscrowReleaseMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.release(msg, fee, memo, funds), options); } function useEscrowLockFundsMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.lockFunds(msg, fee, memo, funds), options); } var _2$1 = /*#__PURE__*/Object.freeze({ __proto__: null, escrowQueryKeys: escrowQueryKeys, useEscrowClaimFeesMutation: useEscrowClaimFeesMutation, useEscrowGetCollectedFeesQuery: useEscrowGetCollectedFeesQuery, useEscrowGetEscrowQuery: useEscrowGetEscrowQuery, useEscrowGetEscrowsQuery: useEscrowGetEscrowsQuery, useEscrowLockFundsMutation: useEscrowLockFundsMutation, useEscrowRefundExpiredMutation: useEscrowRefundExpiredMutation, useEscrowReleaseMutation: useEscrowReleaseMutation }); /** * This file was automatically generated by @cosmwasm/ts-codegen@1.12.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ const registryQueryKeys = { contract: [ { contract: "registry", }, ], address: (contractAddress) => [ { ...registryQueryKeys.contract[0], address: contractAddress, }, ], getTool: (contractAddress, args) => [ { ...registryQueryKeys.address(contractAddress)[0], method: "get_tool", args, }, ], getTools: (contractAddress, args) => [ { ...registryQueryKeys.address(contractAddress)[0], method: "get_tools", args, }, ], }; function useRegistryGetToolsQuery({ client, options, }) { return reactQuery.useQuery(registryQueryKeys.getTools(client?.contractAddress), () => client ? client.getTools() : Promise.reject(new Error("Invalid client")), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true), }); } function useRegistryGetToolQuery({ client, args, options, }) { return reactQuery.useQuery(registryQueryKeys.getTool(client?.contractAddress, args), () => client ? client.getTool({ toolId: args.toolId, }) : Promise.reject(new Error("Invalid client")), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true), }); } function useRegistryResumeToolMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.resumeTool(msg, fee, memo, funds), options); } function useRegistryPauseToolMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.pauseTool(msg, fee, memo, funds), options); } function useRegistryUpdateEndpointMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.updateEndpoint(msg, fee, memo, funds), options); } function useRegistryUpdateDenomMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.updateDenom(msg, fee, memo, funds), options); } function useRegistryUpdatePriceMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.updatePrice(msg, fee, memo, funds), options); } function useRegistryRegisterToolMutation(options) { return reactQuery.useMutation(({ client, msg, args: { fee, memo, funds } = {} }) => client.registerTool(msg, fee, memo, funds), options); } var _2 = /*#__PURE__*/Object.freeze({ __proto__: null, registryQueryKeys: registryQueryKeys, useRegistryGetToolQuery: useRegistryGetToolQuery, useRegistryGetToolsQuery: useRegistryGetToolsQuery, useRegistryPauseToolMutation: useRegistryPauseToolMutation, useRegistryRegisterToolMutation: useRegistryRegisterToolMutation, useRegistryResumeToolMutation: useRegistryResumeToolMutation, useRegistryUpdateDenomMutation: useRegistryUpdateDenomMutation, useRegistryUpdateEndpointMutation: useRegistryUpdateEndpointMutation, useRegistryUpdatePriceMutation: useRegistryUpdatePriceMutation }); /** * This file was automatically generated by @cosmwasm/ts-codegen@1.12.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ class RegistryQueryClient { constructor(client, contractAddress) { this.getTool = async ({ toolId }) => { return this.client.queryContractSmart(this.contractAddress, { get_tool: { tool_id: toolId, }, }); }; this.getTools = async () => { return this.client.queryContractSmart(this.contractAddress, { get_tools: {}, }); }; this.client = client; this.contractAddress = contractAddress; this.getTool = this.getTool.bind(this); this.getTools = this.getTools.bind(this); } } class RegistryClient extends RegistryQueryClient { constructor(client, sender, contractAddress) { super(client, contractAddress); this.registerTool = async ({ denom, description, endpoint, price, toolId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { register_tool: { denom, description, endpoint, price, tool_id: toolId, }, }, fee_, memo_, funds_); }; this.updatePrice = async ({ price, toolId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { update_price: { price, tool_id: toolId, }, }, fee_, memo_, funds_); }; this.updateDenom = async ({ denom, toolId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { update_denom: { denom, tool_id: toolId, }, }, fee_, memo_, funds_); }; this.updateEndpoint = async ({ endpoint, toolId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { update_endpoint: { endpoint, tool_id: toolId, }, }, fee_, memo_, funds_); }; this.pauseTool = async ({ toolId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { pause_tool: { tool_id: toolId, }, }, fee_, memo_, funds_); }; this.resumeTool = async ({ toolId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { resume_tool: { tool_id: toolId, }, }, fee_, memo_, funds_); }; this.client = client; this.sender = sender; this.contractAddress = contractAddress; this.registerTool = this.registerTool.bind(this); this.updatePrice = this.updatePrice.bind(this); this.updateDenom = this.updateDenom.bind(this); this.updateEndpoint = this.updateEndpoint.bind(this); this.pauseTool = this.pauseTool.bind(this); this.resumeTool = this.resumeTool.bind(this); } } var _1$1 = /*#__PURE__*/Object.freeze({ __proto__: null, RegistryClient: RegistryClient, RegistryQueryClient: RegistryQueryClient }); /** * This file was automatically generated by @cosmwasm/ts-codegen@1.12.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ class EscrowQueryClient { constructor(client, contractAddress) { this.getEscrow = async ({ escrowId, }) => { return this.client.queryContractSmart(this.contractAddress, { get_escrow: { escrow_id: escrowId, }, }); }; this.getCollectedFees = async () => { return this.client.queryContractSmart(this.contractAddress, { get_collected_fees: {}, }); }; this.getEscrows = async ({ caller, limit, provider, startAfter, }) => { return this.client.queryContractSmart(this.contractAddress, { get_escrows: { caller, limit, provider, start_after: startAfter, }, }); }; this.client = client; this.contractAddress = contractAddress; this.getEscrow = this.getEscrow.bind(this); this.getCollectedFees = this.getCollectedFees.bind(this); this.getEscrows = this.getEscrows.bind(this); } } class EscrowClient extends EscrowQueryClient { constructor(client, sender, contractAddress) { super(client, contractAddress); this.lockFunds = async ({ authToken, expires, maxFee, toolId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { lock_funds: { auth_token: authToken, expires, max_fee: maxFee, tool_id: toolId, }, }, fee_, memo_, funds_); }; this.release = async ({ escrowId, usageFee, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { release: { escrow_id: escrowId, usage_fee: usageFee, }, }, fee_, memo_, funds_); }; this.refundExpired = async ({ escrowId, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { refund_expired: { escrow_id: escrowId, }, }, fee_, memo_, funds_); }; this.claimFees = async ({ denom, }, fee_ = "auto", memo_, funds_) => { return await this.client.execute(this.sender, this.contractAddress, { claim_fees: { denom, }, }, fee_, memo_, funds_); }; this.client = client; this.sender = sender; this.contractAddress = contractAddress; this.lockFunds = this.lockFunds.bind(this); this.release = this.release.bind(this); this.refundExpired = this.refundExpired.bind(this); this.claimFees = this.claimFees.bind(this); } } var _1 = /*#__PURE__*/Object.freeze({ __proto__: null, EscrowClient: EscrowClient, EscrowQueryClient: EscrowQueryClient }); /** * Create read-only (query) clients for HTTPay contracts */ async function createQueryClients(config) { const cosmWasmClient = await cosmwasmStargate.CosmWasmClient.connect(config.rpcEndpoint); const registryQuery = new RegistryQueryClient(cosmWasmClient, config.registryAddress); const escrowQuery = new EscrowQueryClient(cosmWasmClient, config.escrowAddress); return { cosmWasmClient, registryQuery, escrowQuery, }; } /** * Create signing clients for HTTPay contracts */ function createSigningClients(signingClient, walletAddress, config) { const registry = new RegistryClient(signingClient, walletAddress, config.registryAddress); const escrow = new EscrowClient(signingClient, walletAddress, config.escrowAddress); return { registry, escrow, }; } /** * Create empty clients object */ function createEmptyClients() { return { registryQuery: null, escrowQuery: null, registry: null, escrow: null, cosmWasmClient: null, signingClient: null, }; } /** * Handle and normalize errors from SDK operations */ function handleSDKError(error, operation) { if (error instanceof Error) { return new Error(`${operation}: ${error.message}`); } if (typeof error === "string") { return new Error(`${operation}: ${error}`); } if (error && typeof error === "object" && "message" in error) { return new Error(`${operation}: ${String(error.message)}`); } return new Error(`${operation}: Unknown error occurred`); } /** * Extract escrow ID from transaction attributes */ function extractEscrowIdFromTx(txHash, events) { try { for (const event of events) { if (event.type === "wasm") { for (const attr of event.attributes) { if (attr.key === "escrow_id") { return parseInt(attr.value, 10); } } } } } catch (error) { console.warn("Failed to extract escrow ID from transaction:", error); } return null; } /** * Convert base64 string to regular string */ function fromBase64(base64String) { try { return Buffer.from(base64String, "base64").toString("utf-8"); } catch { return base64String; // Return as-is if not valid base64 } } /** * Convert string to base64 */ function toBase64(input) { return Buffer.from(input, "utf-8").toString("base64"); } /** * Validate wallet address format */ function isValidAddress(address, prefix = "neutron") { const regex = new RegExp(`^${prefix}1[a-z0-9]{38,58}$`); return regex.test(address); } /** * Format token amount for display */ function formatTokenAmount(amount, decimals = 6) { const num = Number(amount) / Math.pow(10, decimals); return new Intl.NumberFormat("en-US", { minimumFractionDigits: 0, maximumFractionDigits: decimals, }).format(num); } /** * Parse token amount from user input to smallest unit */ function parseTokenAmount(amount, decimals = 6) { const num = Number(amount); if (isNaN(num) || num < 0) { throw new Error("Invalid amount"); } return Math.floor(num * Math.pow(10, decimals)).toString(); } /** * Get short address for display (e.g., "neutron1abc...xyz") */ function getShortAddress(address, startChars = 10, endChars = 6) { if (address.length <= startChars + endChars) { return address; } return `${address.slice(0, startChars)}...${address.slice(-endChars)}`; } /** * Calculate blocks until expiration */ function getBlocksUntilExpiration(expirationBlock, currentBlock) { return Math.max(0, expirationBlock - currentBlock); } /** * Estimate time until expiration (assuming ~6 second block time) */ function estimateTimeUntilExpiration(expirationBlock, currentBlock) { const blocks = getBlocksUntilExpiration(expirationBlock, currentBlock); const seconds = blocks * 6; // Approximate block time if (seconds < 60) { return `${seconds}s`; } const minutes = Math.floor(seconds / 60); if (minutes < 60) { return `${minutes}m`; } const hours = Math.floor(minutes / 60); if (hours < 24) { return `${hours}h ${minutes % 60}m`; } const days = Math.floor(hours / 24); return `${days}d ${hours % 24}h`; } function useRegistry({ clients, walletAddress, isWalletConnected, hasSigningCapabilities, loading, setLoadingState, toast, }) { const [tools, setTools] = react.useState([]); const handleError = react.useCallback((error, operation) => { const normalizedError = handleSDKError(error, operation); console.error(normalizedError); toast({ title: `Error in ${operation}`, description: normalizedError.message, variant: "destructive", }); }, [toast]); const validateSigningRequirements = react.useCallback(() => { if (!walletAddress || !isWalletConnected) { toast({ title: "Error", description: "Please connect your wallet first", variant: "destructive", }); return false; } if (!hasSigningCapabilities || !clients.registry) { toast({ title: "Error", description: "SDK does not have signing capabilities. Please reconnect your wallet.", variant: "destructive", }); return false; } return true; }, [walletAddress, isWalletConnected, hasSigningCapabilities, clients.registry, toast]); const loadTools = react.useCallback(async () => { if (!clients.registryQuery) { console.warn("Registry query client not available"); return; } try { setLoadingState("tools", true); const response = await clients.registryQuery.getTools(); setTools(response.tools); } catch (error) { handleError(error, "loading tools"); } finally { setLoadingState("tools", false); } }, [clients.registryQuery, setLoadingState, handleError]); const registerTool = react.useCallback(async (toolData) => { if (!validateSigningRequirements() || !clients.registry) return; try { setLoadingState("registerTool", true); const result = await clients.registry.registerTool({ toolId: toolData.toolId, price: toolData.price, description: toolData.description, endpoint: toolData.endpoint, denom: toolData.denom, }); toast({ title: "Tool Registered", description: `Tool ${toolData.toolId} registered successfully. TX: ${result.transactionHash}`, }); // Reload tools to get the updated list await loadTools(); } catch (error) { handleError(error, "tool registration"); } finally { setLoadingState("registerTool", false); } }, [validateSigningRequirements, clients.registry, setLoadingState, toast, handleError, loadTools]); const updateEndpoint = react.useCallback(async (toolId, endpoint) => { if (!validateSigningRequirements() || !clients.registry) return; try { setLoadingState("updateEndpoint", true); const result = await clients.registry.updateEndpoint({ toolId, endpoint, }); toast({ title: "Endpoint Updated", description: `Endpoint for ${toolId} updated successfully. TX: ${result.transactionHash}`, }); await loadTools(); } catch (error) { handleError(error, "endpoint update"); } finally { setLoadingState("updateEndpoint", false); } }, [validateSigningRequirements, clients.registry, setLoadingState, toast, handleError, loadTools]); const updatePrice = react.useCallback(async (toolId, price) => { if (!validateSigningRequirements() || !clients.registry) return; try { setLoadingState("updatePrice", true); const result = await clients.registry.updatePrice({ toolId, price, }); toast({ title: "Price Updated", description: `Price for ${toolId} updated successfully. TX: ${result.transactionHash}`, }); await loadTools(); } catch (error) { handleError(error, "price update"); } finally { setLoadingState("updatePrice", false); } }, [validateSigningRequirements, clients.registry, setLoadingState, toast, handleError, loadTools]); const updateDenom = react.useCallback(async (toolId, denom) => { if (!validateSigningRequirements() || !clients.registry) return; try { setLoadingState("updateDenom", true); const result = await clients.registry.updateDenom({ toolId, denom, }); toast({ title: "Denomination Updated", description: `Denomination for ${toolId} updated successfully. TX: ${result.transactionHash}`, }); await loadTools(); } catch (error) { handleError(error, "denomination update"); } finally { setLoadingState("updateDenom", false); } }, [validateSigningRequirements, clients.registry, setLoadingState, toast, handleError, loadTools]); const pauseTool = react.useCallback(async (toolId) => { if (!validateSigningRequirements() || !clients.registry) return; try { setLoadingState("pauseTool", true); const result = await clients.registry.pauseTool({ toolId, }); toast({ title: "Tool Paused", description: `Tool ${toolId} paused successfully. TX: ${result.transactionHash}`, }); await loadTools(); } catch (error) { handleError(error, "tool pause"); } finally { setLoadingState("pauseTool", false); } }, [validateSigningRequirements, clients.registry, setLoadingState, toast, handleError, loadTools]); const resumeTool = react.useCallback(async (toolId) => { if (!validateSigningRequirements() || !clients.registry) return; try { setLoadingState("resumeTool", true); const result = await clients.registry.resumeTool({ toolId, }); toast({ title: "Tool Resumed", description: `Tool ${toolId} resumed successfully. TX: ${result.transactionHash}`, }); await loadTools(); } catch (error) { handleError(error, "tool resume"); } finally { setLoadingState("resumeTool", false); } }, [validateSigningRequirements, clients.registry, setLoadingState, toast, handleError, loadTools]); return { tools, loadTools, registerTool, updateEndpoint, updatePrice, updateDenom, pauseTool, resumeTool, }; } function useEscrow({ clients, walletAddress, isWalletConnected, hasSigningCapabilities, loading, setLoadingState, getCurrentBlockHeight, toast, }) { const [escrows, setEscrows] = react.useState([]); const [escrowsFilter, setEscrowsFilter] = react.useState({}); const [hasMoreEscrows, setHasMoreEscrows] = react.useState(false); const handleError = react.useCallback((error, operation) => { const normalizedError = handleSDKError(error, operation); console.error(normalizedError); toast({ title: `Error in ${operation}`, description: normalizedError.message, variant: "destructive", }); }, [toast]); const validateSigningRequirements = react.useCallback(() => { if (!walletAddress || !isWalletConnected) { toast({ title: "Error", description: "Please connect your wallet first", variant: "destructive", }); return false; } if (!hasSigningCapabilities || !clients.escrow) { toast({ title: "Error", description: "SDK does not have signing capabilities. Please reconnect your wallet.", variant: "destructive", }); return false; } return true; }, [walletAddress, isWalletConnected, hasSigningCapabilities, clients.escrow, toast]); const loadEscrows = react.useCallback(async (filter) => { if (!clients.escrowQuery) { console.warn("Escrow query client not available"); return; } try { setLoadingState("escrows", true); // If a new filter is provided, update the current filter and reset pagination if (filter !== undefined) { setEscrowsFilter(filter); setEscrows([]); // Clear existing escrows when applying new filter } // Use provided filter or current filter const activeFilter = filter !== undefined ? filter : escrowsFilter; // Query escrows with current filter const result = await clients.escrowQuery.getEscrows({ caller: activeFilter.caller, provider: activeFilter.provider, startAfter: activeFilter.startAfter, limit: activeFilter.limit || 10, // Default limit of 10 }); // Update escrows state - replace if new filter, append if pagination if (filter !== undefined) { setEscrows(result.escrows); } else { setEscrows(prev => [...prev, ...result.escrows]); } // Check if there are more escrows available setHasMoreEscrows(result.escrows.length >= (activeFilter.limit || 10)); } catch (error) { handleError(error, "loading escrows"); } finally { setLoadingState("escrows", false); } }, [clients.escrowQuery, escrowsFilter, setLoadingState, handleError]); const loadMoreEscrows = react.useCallback(async () => { if (!hasMoreEscrows || escrows.length === 0) return; // Set the startAfter to the last escrow's ID for pagination const lastEscrowId = escrows[escrows.length - 1].escrow_id; const paginationFilter = { ...escrowsFilter, startAfter: lastEscrowId, }; await loadEscrows(paginationFilter); }, [hasMoreEscrows, escrows, escrowsFilter, loadEscrows]); const resetEscrowsFilter = react.useCallback(async () => { const emptyFilter = {}; setEscrowsFilter(emptyFilter); setEscrows([]); setHasMoreEscrows(false); await loadEscrows(emptyFilter); }, [loadEscrows]); const lockFunds = react.useCallback(async (escrowData) => { if (!validateSigningRequirements() || !clients.escrow) return; try { setLoadingState("lockFunds", true); const currentBlockHeight = await getCurrentBlockHeight(); const expires = currentBlockHeight + parseInt(escrowData.ttl); // Convert authToken to base64 if needed const authToken = toBase64(escrowData.authToken); // Prepare funds to send with the transaction const funds = [{ denom: "untrn", amount: escrowData.maxFee }]; const result = await clients.escrow.lockFunds({ toolId: escrowData.toolId, maxFee: escrowData.maxFee, authToken, expires, }, "auto", undefined, funds); // Extract escrow ID from transaction events if available const escrowId = extractEscrowIdFromTx(result.transactionHash, [...(result.events || [])]) || 0; const lockResult = { transactionHash: result.transactionHash, escrowId, denom: "untrn", }; toast({ title: "Funds Locked", description: `Escrow ${escrowId} created successfully. TX: ${result.transactionHash}`, }); await loadEscrows(); return lockResult; } catch (error) { handleError(error, "locking funds"); } finally { setLoadingState("lockFunds", false); } }, [validateSigningRequirements, clients.escrow, getCurrentBlockHeight, setLoadingState, toast, handleError, loadEscrows]); const verifyEscrow = react.useCallback(async (verificationData) => { if (!clients.escrowQuery) { const error = "Please initialize SDK first"; toast({ title: "Error", description: error, variant: "destructive", }); return { isValid: false, error }; } try { setLoadingState("verifyEscrow", true); const escrowId = parseInt(verificationData.escrowId, 10); const escrow = await clients.escrowQuery.getEscrow({ escrowId }); // Get current block height const blockHeight = await getCurrentBlockHeight(); // Check if escrow is expired if (escrow.expires < blockHeight) { return { isValid: false, error: "Escrow is expired", escrow, blockHeight, }; } // Check if provider address matches if (escrow.provider !== verificationData.providerAddr) { return { isValid: false, error: "Provider address mismatch", escrow, blockHeight, }; } // Check if auth token matches if (escrow.auth_token !== verificationData.authToken) { return { isValid: false, error: "Auth token mismatch", escrow, blockHeight, }; } // All checks passed const result = { isValid: true, escrow, blockHeight, }; toast({ title: "Escrow Verification", description: "Escrow is valid", }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; handleError(error, "escrow verification"); return { isValid: false, error: errorMessage, }; } finally { setLoadingState("verifyEscrow", false); } }, [clients.escrowQuery, getCurrentBlockHeight, setLoadingState, toast, handleError]); const postUsage = react.useCallback(async (usageData) => { if (!validateSigningRequirements() || !clients.escrow) return; try { setLoadingState("postUsage", true); const result = await clients.escrow.release({ escrowId: parseInt(usageData.escrowId, 10), usageFee: usageData.usageFee, }); toast({ title: "Usage Posted", description: `Usage reported successfully. TX: ${result.transactionHash}`, }); await loadEscrows(); } catch (error) { handleError(error, "posting usage"); } finally { setLoadingState("postUsage", false); } }, [validateSigningRequirements, clients.escrow, setLoadingState, toast, handleError, loadEscrows]); const refundExpired = react.useCallback(async (escrowId) => { if (!validateSigningRequirements() || !clients.escrow) return; try { setLoadingState("refundEscrow", true); const result = await clients.escrow.refundExpired({ escrowId, }); toast({ title: "Refund Processed", description: `Escrow ${escrowId} refunded successfully. TX: ${result.transactionHash}`, }); await loadEscrows(); } catch (error) { handleError(error, "refunding escrow"); } finally { setLoadingState("refundEscrow", false); } }, [validateSigningRequirements, clients.escrow, setLoadingState, toast, handleError, loadEscrows]); const claimFees = react.useCallback(async (denom) => { if (!validateSigningRequirements() || !clients.escrow) return; try { setLoadingState("claimFees", true); const result = await clients.escrow.claimFees({ denom, }); toast({ title: "Fees Claimed", description: `Fees claimed successfully. TX: ${result.transactionHash}`, }); } catch (error) { handleError(error, "claiming fees"); } finally { setLoadingState("claimFees", false); } }, [validateSigningRequirements, clients.escrow, setLoadingState, toast, handleError]); return { escrows, hasMoreEscrows, loadEscrows, loadMoreEscrows, resetEscrowsFilter, lockFunds, verifyEscrow, postUsage, refundExpired, claimFees, }; } function useWalletIntegration({ config, setLoadingState, chainName, toast, walletAddress, isWalletConnected = false, isWalletConnecting = false, isWalletDisconnected = true, isWalletError = false, walletStatus = 'disconnected', walletMessage = '', getSigningCosmWasmClient, connectWallet = async () => { console.warn('connectWallet not provided to useWalletIntegration'); }, disconnectWallet = async () => { console.warn('disconnectWallet not provided to useWalletIntegration'); } }) { const [clients, setClients] = react.useState(createEmptyClients()); const [isConnected, setIsConnected] = react.useState(false); const [hasSigningCapabilities, setHasSigningCapabilities] = react.useState(false); const handleError = react.useCallback((error, operation) => { const normalizedError = handleSDKError(error, operation); console.error(normalizedError); toast({ title: `Error in ${operation}`, description: normalizedError.message, variant: "destructive", }); }, [toast]); // Initialize read-only SDK connection const initializeSDK = react.useCallback(async () => { try { setLoadingState("connecting", true); const queryClients = await createQueryClients(config); setClients(prev => ({ ...prev, cosmWasmClient: queryClients.cosmWasmClient, registryQuery: queryClients.registryQuery, escrowQuery: queryClients.escrowQuery, })); setIsConnected(true); setHasSigningCapabilities(false); toast({ title: "SDK Initialized", description: "Connected to the blockchain successfully", }); } catch (error) { handleError(error, "SDK initialization"); setIsConnected(false); setHasSigningCapabilities(false); } finally { setLoadingState("connecting", false); } }, [config, setLoadingState, handleError, toast]); // Initialize SDK with wallet signing capabilities const initializeWalletSDK = react.useCallback(async () => { console.log("initializeWalletSDK called with:", { walletAddress, isWalletConnected }); if (!walletAddress || !isWalletConnected) { console.warn("Cannot initialize SDK with wallet - wallet not ready:", { walletAddress, isWalletConnected }); toast({ title: "Error", description: "Please connect your wallet first", variant: "destructive", }); return; } try { setLoadingState("wallet", true); console.log("Initializing SDK with wallet...", { walletAddress, isWalletConnected }); // Get the signing client from CosmosKit const signingClient = await getSigningCosmWasmClient(); console.log("Got signing client from CosmosKit:", !!signingClient); // Create query clients if not already created const queryClients = clients.cosmWasmClient ? { cosmWasmClient: clients.cosmWasmClient, registryQuery: clients.registryQuery, escrowQuery: clients.escrowQuery, } : await createQueryClients(config); // Create signing clients const signingClientsResult = createSigningClients(signingClient, walletAddress, config); console.log("SDK connected with signing capabilities"); setClients({ cosmWasmClient: queryClients.cosmWasmClient, registryQuery: queryClients.registryQuery, escrowQuery: queryClients.escrowQuery, signingClient, registry: signingClientsResult.registry, escrow: signingClientsResult.escrow, }); setIsConnected(true); setHasSigningCapabilities(true); console.log("State updated: clients set, isConnected=true, hasSigningCapabilities=true"); toast({ title: "SDK Connected with Wallet", description: `Using wallet address: ${walletAddress}`, }); } catch (error) { console.error("Failed to initialize SDK with wallet:", error); setHasSigningCapabilities(false); handleError(error, "wallet connection"); } finally { setLoadingState("wallet", false); } }, [walletAddress, isWalletConnected, config, clients, getSigningCosmWasmClient, setLoadingState, handleError, toast]); const forceReconnectWallet = react.useCallback(async () => { console.log("Force reconnecting wallet..."); if (isWalletConnected && walletAddress) { await initializeWalletSDK(); } else { console.warn("Cannot force reconnect - wallet not connected:", { isWalletConnected, walletAddress }); } }, [isWalletConnected, walletAddress, initializeWalletSDK]); // Monitor wallet status changes react.useEffect(() => { console.log("Wallet status changed:", { isWalletConnected, walletAddress, isConnected, hasSigningCapabilities }); if (isWalletConnected && walletAddress) { // Always reinitialize SDK with wallet when wallet connects console.log("Wallet connected, initializing SDK with signing capabilities..."); const initSdk = async () => { try { await initializeWalletSDK(); console.log("SDK initialization result: success"); } catch (error) { console.error("Failed to initialize SDK with wallet in useEffect:", error); } }; // Add a small delay to ensure the wallet is fully ready setTimeout(initSdk, 100); } else if (!isWalletConnected && isConnected) { console.log("Wallet disconnected, removing signing capabilities..."); setHasSigningCapabilities(false); // Keep query capabilities but remove signing clients setClients(prev => ({ ...prev, signingClient: null, registry: null, escrow: null, })); } }, [isWalletConnected, walletAddress]); // Removed initializeWalletSDK from dependencies to prevent recreation loops return { clients, isConnected, hasSigningCapabilities, walletAddress, isWalletConnected, isWalletConnecting, isWalletDisconnected, isWalletError, walletStatus, walletMessage, initializeSDK, initializeWalletSDK, forceReconnectWallet, connectWallet, disconnectWallet, }; } function useBlockHeight({ clients, isConnected }) { const [currentBlockHeight, setCurrentBlockHeight] = react.useState(null); const getCurrentBlockHeight = react.useCallback(async () => { if (!clients.cosmWasmClient) { throw new Error("SDK not initialized"); } const height = await clients.cosmWasmClient.getHeight(); return height; }, [clients.cosmWasmClient]); const updateBlockHeight = react.useCallback(async () => { if (!isConnected || !clients.cosmWasmClient) { return; } try { const height = await getCurrentBlockHeight(); setCurrentBlockHeight(height); } catch (error) { // Silently fail for block height updates to avoid spam console.warn("Failed to fetch block height:", error); } }, [isConnected, clients.cosmWasmClient, getCurrentBlockHeight]); // Set up interval to update block height every second when connected react.useEffect(() => { if (!isConnected || !clients.cosmWasmClient) { setCurrentBlockHeight(null); return; } // Initial fetch updateBlockHeight(); // Set up interval const interval = setInterval(updateBlockHeight, 1000); // 1 second return () => clearInterval(interval); }, [isConnected, clients.cosmWasmClient, updateBlockHeight]); return { currentBlockHeight, getCurrentBlockHeight, updateBlockHeight, }; } // Create the context const HTTPaySDKContext = react.createContext(undefined); // Default configuration const defaultConfig = { rpcEndpoint: "https://rpc-falcron.pion-1.ntrn.tech", chainId: "pion-1", registryAddress: "neutron1y3sukd6exjkmhu3sqdh7efl7gx3qthm4y9gadgaxuu5xckydnwesr6mev0", escrowAddress: "neutron1e9taftylxzdqvtcwscddznmy5ualhcx30xrrrttxznme0jsrm0msxkm6xn", gasPrice: "0.0053untrn", gasAdjustment: 1.3, }; // Default loading states const defaultLoadingStates = { connecting: false, wallet: false, tools: false, escrows: false, registerTool: false, updateEndpoint: false, lockFunds: false, verifyEscrow: false, postUsage: false, pauseTool: false, resumeTool: false, updatePrice: false, updateDenom: false, refundEscrow: false, claimFees: false, }; function HTTPaySDKProvider({ children, initialConfig, chainName, toast, walletAddress, isWalletConnected = false, getSigningCosmWasmClient }) { // Configuration state const [config, setConfig] = react.useState({ ...defaultConfig, ...initialConfig, }); // Client state const [clients, setClients] = react.useState(createEmptyClients()); // Connection state const [connection, setConnection] = react.useState({ isConnected: false, hasSigningCapabilities: false, walletAddress: null, currentBlockHeight: null, }); // Loading states const [loading, setLoading] = react.useState(defaultLoadingStates); const [escrowsFilter, setEscrowsFilter] = react.useState({}); // Loading state helper const setLoadingState = react.useCallback((key, value) => { setLoading(prev => ({ ...prev, [key]: value })); }, []); // Use wallet integration hook const { clients: walletClients, isConnected: walletIsConnected, hasSigningCapabilities: walletHasSigningCapabilities, initializeSDK: initializeSDKFromHook, initializeWalletSDK, forceReconnectWallet, // Other wallet-related properties provided by the hook } = useWalletIntegration({ config, setLoadingState, chainName, toast, // Pass the injected wallet properties walletAddress, isWalletConnected, getSigningCosmWasmClient, }); // Block height tracking const { currentBlockHeight, getCurrentBlockHeight } = useBlockHeight({ clients, isConnected: connection.isConnected }); // Update connection state when wallet or block height changes react.useEffect(() => { setConnection(prev => ({ ...prev, walletAddress: walletAddress || null, currentBlockHeight, })); }, [walletAddress, currentBlockHeight]); // Update clients when wallet clients change react.useEffect(() => { if (walletClients.signingClient) { setClients(prev => ({ ...prev, ...walletClients, })); setConnection(prev => ({ ...prev, isConnected: walletIsConnected, hasSigningCapabilities: walletHasSigningCapabilities, })); } }, [walletClients, walletIsConnected, walletHasSigningCapabilities]); // Configuration update react.useCallback((newConfig) => { setConfig(prev => ({ ...prev, ...newConfig })); }, []); // Use the hook's functions for SDK initialization const initializeSDK = react.useCallback(async () => { await initializeSDKFromHook(); }, [initializeSDKFromHook]); // SDK initialization with wallet (signing capabilities) const initSDKWithWallet = react.useCallback(async () => { try { await initializeWalletSDK(); return true; } catch (error) { console.error("Error in initSDKWithWallet:", error); return false; } }, [initializeWalletSDK]); // Disconnect react.useCallback(() => { setClients(createEmptyClients()); setConnection({ isConnected: false, hasSigningCapabilities: false, walletAddress: null, currentBlockHeight: null, }); setEscrowsFilter({}); }, []); // Initialize specialized hooks with dependency injection const { tools, ..