// AUTO-GENERATED by scaffold-stacks. DO NOT EDIT.
// Re-run `stacksdapp generate` to update.
"use client";
import { useState, useCallback, useEffect } from 'react';
import { ClarityValue } from '@stacks/transactions';
import * as contracts from './contracts';
import { scaffoldConfig } from '../scaffold.config';
type TxLifecycleStatus = 'pending' | 'success' | 'abort_by_response' | 'error';
const TX_POLL_INITIAL_DELAY_MS = 1500;
const TX_POLL_INTERVAL_MS = 2500;
const TX_POLL_MAX_TRANSIENT_ERRORS = 20;
/** Hiro may return 404 until the tx is indexed — not a failure. */
function isTransientTxPollHttpStatus(status: number): boolean {
return status === 404 || status === 408 || status === 429 || status === 502 || status === 503 || status === 504;
}
{% for contract in contracts %}
{% for fn in contract.functions %}
{% if fn.access == "public" or fn.access == "read_only" %}
export function use{{ contract.contract_name | upper_camel }}_{{ fn.name | upper_camel }}() {
const [data, setData] = useState<unknown>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [txid, setTxid] = useState<string | null>(null);
const [txStatus, setTxStatus] = useState<TxLifecycleStatus | null>(null);
const [txStatusError, setTxStatusError] = useState<string | null>(null);
const isReadOnly = {% if fn.access == "read_only" %}true{% else %}false{% endif %};
const call = useCallback(async (functionArgs: ClarityValue[] = []) => {
setLoading(true);
setError(null);
setTxid(null);
setTxStatus(null);
setTxStatusError(null);
try {
// Use the generated contract function from the index
const fn = (contracts as any).{{ contract.contract_name | camel }}_{{ fn.name | camel }};
const result = isReadOnly
? await fn(functionArgs)
: await fn(functionArgs, []); // Default empty post-conditions for public
if (!isReadOnly && result?.txid) {
setTxid(result.txid);
setTxStatus('pending');
}
setData(result ?? null);
return result;
} catch (e) {
console.error(e);
setError(e as Error);
throw e; // Re-throw so the caller can handle it if needed
} finally {
setLoading(false);
}
// Dependency array is stable because isReadOnly is a constant literal
}, [isReadOnly]);
useEffect(() => {
if (isReadOnly || !txid || !scaffoldConfig.nodeUrl) return;
let mounted = true;
let timer: ReturnType<typeof setTimeout> | null = null;
let transientErrors = 0;
const headers: Record<string, string> = {};
if (scaffoldConfig.hiroApiKey) {
headers['x-api-key'] = scaffoldConfig.hiroApiKey;
}
const scheduleRetry = () => {
timer = setTimeout(() => {
void poll();
}, TX_POLL_INTERVAL_MS);
};
const poll = async () => {
try {
const response = await fetch(
`${scaffoldConfig.nodeUrl}/extended/v1/tx/${txid}`,
{
headers,
cache: 'no-store',
},
);
if (!response.ok) {
if (isTransientTxPollHttpStatus(response.status)) {
transientErrors += 1;
if (!mounted) return;
if (transientErrors >= TX_POLL_MAX_TRANSIENT_ERRORS) {
setTxStatus('error');
setTxStatusError(
`Transaction status unavailable (HTTP ${response.status}) after ${transientErrors} attempts.`,
);
return;
}
setTxStatus('pending');
setTxStatusError(null);
scheduleRetry();
return;
}
throw new Error(`tx status request failed with ${response.status}`);
}
transientErrors = 0;
const payload = await response.json();
const status = String(payload?.tx_status ?? '').toLowerCase();
if (!mounted) return;
if (status.includes('success')) {
setTxStatus('success');
setTxStatusError(null);
return;
}
if (status.includes('abort')) {
setTxStatus('abort_by_response');
setTxStatusError(String(payload?.tx_result?.repr ?? 'Transaction aborted.'));
return;
}
if (status.includes('pending')) {
setTxStatus('pending');
setTxStatusError(null);
scheduleRetry();
return;
}
setTxStatus('error');
setTxStatusError(payload?.tx_status ? `Unexpected tx status: ${payload.tx_status}` : 'Unknown tx status.');
} catch (err) {
if (!mounted) return;
transientErrors += 1;
if (transientErrors >= TX_POLL_MAX_TRANSIENT_ERRORS) {
setTxStatus('error');
setTxStatusError(
err instanceof Error ? err.message : 'Failed to poll transaction status.',
);
return;
}
setTxStatus('pending');
setTxStatusError(null);
scheduleRetry();
}
};
timer = setTimeout(() => {
void poll();
}, TX_POLL_INITIAL_DELAY_MS);
return () => {
mounted = false;
if (timer) clearTimeout(timer);
};
}, [isReadOnly, txid]);
const explorerUrl = txid
? `${scaffoldConfig.explorerBaseUrl}${txid}${scaffoldConfig.explorerChainQuery}`
: null;
return { data, loading, error, txid, txStatus, txStatusError, explorerUrl, call };
}
{% endif %}
{% endfor %}
{% endfor %}