httpay
Version:
HTTPay SDK for interacting with HTTPay smart contracts on Neutron
1,319 lines (1,302 loc) • 59.3 kB
JavaScript
'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, ..