// AUTO-GENERATED by scaffold-stacks. DO NOT EDIT.
// Re-run `stacksdapp generate` to update.
"use client";
import { useState } from 'react';
import { Cl } from '@stacks/transactions';
import Message from '@/components/Message';
{% for contract in contracts %}
{% for fn in contract.functions %}
{% if fn.access == "public" or fn.access == "read_only" %}
import { use{{ contract.contract_name | upper_camel }}_{{ fn.name | upper_camel }} } from './hooks';
{% endif %}
{% endfor %}
{% endfor %}
{% raw %}
const S = {
page: { padding: '24px', display: 'flex', flexDirection: 'column' as const, gap: '24px', alignItems: 'center'},
header: { display: 'flex', alignItems: 'center', gap: '25px', paddingBottom: '16px', width: '428px' },
headerDot: { width: '8px', height: '8px', borderRadius: '50%', background: '#34d399' },
headerTitle: { fontSize: '12px', fontWeight: 500, margin: 0, fontFamily: 'JetBrains Mono' },
tab: {
fontSize: '12px',
margin: 0,
fontFamily: 'JetBrains Mono',
background: 'transparent',
border: '1px solid transparent',
padding: 0,
cursor: 'pointer',
transition: 'all 0.15s ease',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
tabActive: {
background: '#434242',
width: '135px',
height: '40px',
borderRadius: '25px',
border: '1px solid #1F1E1F',
color: '#F4F3EF',
lineHeight: '1',
fontWeight: 400 // font-mono usually maps to normal weight unless specified
},
tabIdle: {
color: '#908E8E',
width: '135px', // Keep width consistent so tabs don't jump when switching
height: '40px'
},
tabHover: {
color: '#FFFFFF'
},
headerTag: { fontSize: '12px', color: '#6b7280', fontFamily: 'monospace', marginLeft: 'auto' },
panel: { borderRadius: '12px', border: '1px solid #1f2937', overflow: 'hidden', width: '428px', marginBottom: '12px' },
panelBtn: { width: '100%', textAlign: 'left' as const, padding: '16px 20px', background: '#1F1F1F', display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer', border: 'none' },
panelBtnL: { display: 'flex', alignItems: 'center' },
panelBar: { width: '6px', height: '20px', borderRadius: '3px', background: '#FF550E', marginRight: '12px' },
panelName: { fontWeight: 500, color: '#F4F3EF', fontSize: '14px', fontFamily: 'Instrument Sans' },
panelExt: { fontSize: '12px', color: '#374151', fontFamily: 'monospace' },
panelGrid: { padding: '20px', width:'100%' },
resLabel: { fontSize: '11px', color: '#9ca3af', fontWeight: 600, textTransform: 'uppercase' as const, letterSpacing: '0.05em', marginBottom: '4px' },
resPre: { fontSize: '12px', color: '#F4F3EF', overflowX: 'auto' as const, whiteSpace: 'pre-wrap' as const, wordBreak: 'break-word' as const, margin: 0 },
txBox: { borderRadius: '8px', background: 'rgba(6,78,59,0.2)', border: '1px solid #2E2D2E', padding: '8px 12px' },
txLabel: { fontSize: '11px', color: '#34d399', fontWeight: 600, marginBottom: '4px' },
txHash: { fontSize: '12px', color: '#F4F3EF', fontFamily: 'monospace', wordBreak: 'break-all' as const, margin: 0 },
fnNumber: { fontFamily: 'JetBrains Mono', fontSize: '12px', color: '#4B4B4B', marginRight: '12px' },
card: { borderRadius: '12px', border: '1px solid #1F1E1F', background: '#1A1B1E', padding: '24px', display: 'flex', flexDirection: 'column' as const, transition: 'all 0.3s ease' },
formContainer: { overflow: 'hidden', transition: 'max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease' },
cardHead: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '4px'
},
fnName: {
fontFamily: 'Instrument Sans',
fontSize: '12px',
fontWeight: 500,
color: '#908E8E'
},
badgeRead: {
fontSize: '10px',
padding: '4px 10px',
borderRadius: '4px',
fontFamily: 'JetBrains Mono',
color: '#908E8E',
display: 'flex',
alignItems: 'center',
gap: '4px'
},
badgeWrite: {
fontSize: '10px',
padding: '4px 10px',
borderRadius: '4px',
fontFamily: 'JetBrains Mono',
color: '#908E8E',
display: 'flex',
alignItems: 'center',
gap: '4px'
},
form: { display: 'flex', flexDirection: 'column' as const, gap: '16px' },
argLabel: {
fontSize: '11px',
color: '#908E8E',
fontFamily: 'JetBrains Mono',
marginBottom: '8px',
display: 'block'
},
argType: {
fontSize: '11px',
color: '#4B4B4B',
marginLeft: '8px'
},
argInput: {
width: '100%',
borderRadius: '8px',
background: '#131416', // Deep dark input
border: '1px solid #2C2C2C',
padding: '12px 16px',
fontSize: '14px',
fontFamily: 'JetBrains Mono',
color: '#FFFFFF',
outline: 'none',
boxSizing: 'border-box' as const
},
btnRead: {
width: '100%',
padding: '12px',
borderRadius: '8px',
fontSize: '14px',
fontWeight: 600,
border: 'none',
background: '#FF550E',
color: '#fff',
fontFamily: 'Instrument Sans',
marginTop: '8px'
},
btnWrite: {
width: '100%',
padding: '12px',
borderRadius: '8px',
fontSize: '14px',
fontWeight: 600,
border: 'none',
background: '#FF550E',
color: '#fff',
fontFamily: 'Instrument Sans',
marginTop: '8px'
},
resBox: {
marginTop: '12px',
borderRadius: '8px',
background: '#131416',
border: '1px solid #3B82F6', // Outline matches the action type
padding: '12px'
},
badgeDot: {
width: '6px',
height: '6px',
borderRadius: '50%',
background: 'currentColor',
marginRight: '6px'
},
} as const;
type AbiType = any;
function isObjectLike(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
/** Clarity uint/int decode to JS BigInt — JSON.stringify cannot serialize them by default. */
function formatResult(value: unknown): string {
return JSON.stringify(
value,
(_key, v) => (typeof v === 'bigint' ? v.toString() : v),
2
);
}
function sampleValueForAbi(typeDef: AbiType): unknown {
if (typeof typeDef === 'string') {
const t = typeDef.toLowerCase();
if (t === 'bool') return true;
if (t.startsWith('uint') || t.startsWith('int')) return 1;
if (t === 'principal') return 'ST000000000000000000002AMW42H';
if (t.startsWith('string')) return 'example';
if (t.startsWith('buff')) return 'ff';
return 'example';
}
if (!isObjectLike(typeDef)) return 'example';
if (Object.prototype.hasOwnProperty.call(typeDef, 'string-ascii')) return 'example';
if (Object.prototype.hasOwnProperty.call(typeDef, 'string-utf8')) return 'example';
if (Object.prototype.hasOwnProperty.call(typeDef, 'buffer') || Object.prototype.hasOwnProperty.call(typeDef, 'buff')) return 'ff';
if (Object.prototype.hasOwnProperty.call(typeDef, 'optional')) return null;
if (Object.prototype.hasOwnProperty.call(typeDef, 'response')) return { ok: 1 };
if (Object.prototype.hasOwnProperty.call(typeDef, 'list')) {
const listDef = (typeDef as any).list ?? {};
return [sampleValueForAbi(listDef.type)];
}
if (Object.prototype.hasOwnProperty.call(typeDef, 'tuple')) {
const entries = Array.isArray((typeDef as any).tuple) ? (typeDef as any).tuple : [];
const out: Record<string, unknown> = {};
for (const entry of entries) {
if (entry && typeof entry.name === 'string') out[entry.name] = sampleValueForAbi(entry.type);
}
return out;
}
return 'example';
}
function sampleJsonForAbi(typeDef: AbiType): string {
return JSON.stringify(sampleValueForAbi(typeDef), null, 2);
}
function parseJsonArg(value: string, label: string): unknown {
try {
return JSON.parse(value);
} catch {
throw new Error(`Invalid JSON for argument "${label}".`);
}
}
function valueToClarityFromJson(value: unknown, typeDef: AbiType, label: string): import('@stacks/transactions').ClarityValue {
if (typeof typeDef === 'string') {
const t = typeDef.toLowerCase();
if (t === 'bool') return Cl.bool(Boolean(value));
if (t.startsWith('uint')) return Cl.uint(BigInt(String(value)));
if (t.startsWith('int')) return Cl.int(BigInt(String(value)));
if (t === 'principal') return Cl.principal(String(value));
if (t.startsWith('string-utf8')) return Cl.stringUtf8(String(value));
if (t.startsWith('string-ascii')) return Cl.stringAscii(String(value));
if (t.startsWith('buff')) return Cl.bufferFromHex(String(value));
if (/^\d+$/.test(String(value))) return Cl.uint(BigInt(String(value)));
return Cl.stringAscii(String(value));
}
return toClarityValue(JSON.stringify(value), typeDef, label);
}
// Convert a raw string value to the appropriate ClarityValue based on ABI type.
function toClarityValue(value: string, typeDef: AbiType, argLabel = 'arg'): import('@stacks/transactions').ClarityValue {
const v = value.trim();
if (typeof typeDef === 'string') {
const t = typeDef.toLowerCase();
if (t === 'bool') return Cl.bool(v === 'true' || v === '1');
if (t.startsWith('uint')) return Cl.uint(BigInt(v));
if (t.startsWith('int')) return Cl.int(BigInt(v));
if (t === 'principal') return Cl.principal(v);
if (t.startsWith('string-utf8')) return Cl.stringUtf8(v);
if (t.startsWith('string-ascii')) return Cl.stringAscii(v);
if (t.startsWith('buff')) return Cl.bufferFromHex(v);
if (/^\d+$/.test(v)) return Cl.uint(BigInt(v));
return Cl.stringAscii(v);
}
if (!isObjectLike(typeDef)) throw new Error(`Unsupported ABI type for argument "${argLabel}".`);
if (Object.prototype.hasOwnProperty.call(typeDef, 'string-ascii')) return Cl.stringAscii(v);
if (Object.prototype.hasOwnProperty.call(typeDef, 'string-utf8')) return Cl.stringUtf8(v);
if (Object.prototype.hasOwnProperty.call(typeDef, 'buffer') || Object.prototype.hasOwnProperty.call(typeDef, 'buff')) return Cl.bufferFromHex(v);
if (Object.prototype.hasOwnProperty.call(typeDef, 'tuple')) {
const parsed = parseJsonArg(v, argLabel);
if (!isObjectLike(parsed)) throw new Error(`Tuple argument "${argLabel}" must be a JSON object.`);
const entries = Array.isArray((typeDef as any).tuple) ? (typeDef as any).tuple : [];
const converted: Record<string, import('@stacks/transactions').ClarityValue> = {};
for (const entry of entries) {
const field = entry?.name;
if (typeof field !== 'string') continue;
if (!(field in parsed)) throw new Error(`Tuple argument "${argLabel}" is missing field "${field}".`);
converted[field] = valueToClarityFromJson((parsed as any)[field], entry.type, `${argLabel}.${field}`);
}
return Cl.tuple(converted);
}
if (Object.prototype.hasOwnProperty.call(typeDef, 'list')) {
const parsed = parseJsonArg(v, argLabel);
if (!Array.isArray(parsed)) throw new Error(`List argument "${argLabel}" must be a JSON array.`);
const listDef = (typeDef as any).list ?? {};
const innerType = listDef.type;
const maxLen = Number(listDef.length ?? 0);
if (maxLen > 0 && parsed.length > maxLen) throw new Error(`List argument "${argLabel}" exceeds max length ${maxLen}.`);
return Cl.list(parsed.map((item, idx) => valueToClarityFromJson(item, innerType, `${argLabel}[${idx}]`)));
}
if (Object.prototype.hasOwnProperty.call(typeDef, 'optional')) {
const parsed = parseJsonArg(v, argLabel);
if (parsed === null) return Cl.none();
return Cl.some(valueToClarityFromJson(parsed, (typeDef as any).optional, `${argLabel}.some`));
}
if (Object.prototype.hasOwnProperty.call(typeDef, 'response')) {
const parsed = parseJsonArg(v, argLabel);
if (!isObjectLike(parsed)) throw new Error(`Response argument "${argLabel}" must be {"ok": ...} or {"error": ...}.`);
const hasOk = Object.prototype.hasOwnProperty.call(parsed, 'ok');
const hasErr = Object.prototype.hasOwnProperty.call(parsed, 'error');
if (hasOk === hasErr) throw new Error(`Response argument "${argLabel}" must include exactly one of "ok" or "error".`);
const responseDef = (typeDef as any).response ?? {};
if (hasOk) return Cl.ok(valueToClarityFromJson((parsed as any).ok, responseDef.ok, `${argLabel}.ok`));
return Cl.error(valueToClarityFromJson((parsed as any).error, responseDef.error, `${argLabel}.error`));
}
throw new Error(`Unsupported complex ABI type for argument "${argLabel}".`);
}
{% endraw %}
export default function DebugContracts() {
const [activeTab, setActiveTab] = useState<'write' | 'read'>('write');
const [hoveredTab, setHoveredTab] = useState<'write' | 'read' | null>(null);
// Initialize with the first contract name to drop it down on load
const [openContract, setOpenContract] = useState<string | null>({% if contracts | length > 0 %}"{{ contracts[0].contract_name }}"{% else %}null{% endif %});
const [openFunction, setOpenFunction] = useState<string | null>(null);
return (
<div style={S.page}>
<div style={S.header}>
<button type="button" onMouseEnter={() => setHoveredTab('write')}
onMouseLeave={() => setHoveredTab(null)} style={Object.assign({}, S.tab, activeTab === 'write' ? S.tabActive : S.tabIdle, (hoveredTab === 'write' && activeTab !== 'write') ? S.tabHover : {})} onClick={() => setActiveTab('write')}>
Write Contracts
</button>
<button onMouseEnter={() => setHoveredTab('read')}
onMouseLeave={() => setHoveredTab(null)} type="button" style={Object.assign({}, S.tab, activeTab === 'read' ? S.tabActive : S.tabIdle, (hoveredTab === 'read' && activeTab !== 'read') ? S.tabHover : {})} onClick={() => setActiveTab('read')}>
Read Contracts
</button>
</div>
{% for contract in contracts %}
<div style={S.panel}>
<button
style={S.panelBtn}
onClick={() => {
setOpenContract(openContract === "{{ contract.contract_name }}" ? null : "{{ contract.contract_name }}");
setOpenFunction(null); // Close functions when switching contracts
}}
>
<div style={Object.assign({ display: 'flex', alignItems: 'center' })}>
<div style={S.panelBar} />
<span style={S.panelName}>{{ contract.contract_name | capitalize }}</span>
<span style={S.panelExt}>.clar</span>
</div>
<span style={S.headerTag}>{openContract === "{{ contract.contract_name }}" ? '▲' : '▼'}</span>
</button>
{openContract === "{{ contract.contract_name }}" && (
<div style={Object.assign({ padding: '20px', width:'100%' })} className='space-y-4'>
{% for fn in contract.functions %}
{% if fn.access == "public" or fn.access == "read_only" %}
{% set fn_id = contract.contract_name ~ "_" ~ fn.name %}
{% set current_idx = loop.index0 %}
{# Manual padding logic #}
{% if current_idx < 10 %}
{% set padded_idx = "0" ~ current_idx %}
{% else %}
{% set padded_idx = current_idx %}
{% endif %}
{% if fn.access == "read_only" %}
{activeTab === 'read' && (
<FunctionCard_{{ contract.contract_name | upper_camel }}_{{ fn.name | upper_camel }}
num="{{ padded_idx }}"
isOpen={openFunction === "{{ fn_id }}"}
onToggle={() => setOpenFunction(openFunction === "{{ fn_id }}" ? null : "{{ fn_id }}")}
/>
)}
{% else %}
{activeTab === 'write' && (
<FunctionCard_{{ contract.contract_name | upper_camel }}_{{ fn.name | upper_camel }}
num="{{ padded_idx }}"
isOpen={openFunction === "{{ fn_id }}"}
onToggle={() => setOpenFunction(openFunction === "{{ fn_id }}" ? null : "{{ fn_id }}")}
/>
)}
{% endif %}
{% endif %}
{% endfor %}
</div>
)}
</div>
{% endfor %}
<Message />
</div>
);
}
function ContractPanel({ name, children }: { name: string; children: React.ReactNode }) {
const [open, setOpen] = useState(true);
return (
<div style={S.panel}>
<button style={S.panelBtn} onClick={() => setOpen(o => !o)}>
<div style={S.panelBtnL}>
<div style={S.panelBar} />
<span style={S.panelName}>{name}</span>
<span style={S.panelExt}>.clar</span>
</div>
<span style={S.headerTag}>{open ? '▲' : '▼'}</span>
</button>
{open && <div style={S.panelGrid} className='space-y-4'>{children}</div>}
</div>
);
}
{% for contract in contracts %}
{% for fn in contract.functions %}
{% if fn.access == "public" or fn.access == "read_only" %}
function FunctionCard_{{ contract.contract_name | upper_camel }}_{{ fn.name | upper_camel }}({ num, isOpen, onToggle }: { num: string, isOpen: boolean, onToggle: () => void }) {
const { data, loading, txid, txStatus, txStatusError, explorerUrl, call } = use{{ contract.contract_name | upper_camel }}_{{ fn.name | upper_camel }}();
const isReadOnly = {% if fn.access == "read_only" %}true{% else %}false{% endif %};
{% if fn.args %}
const [args, setArgs] = useState<Record<string, string>>({
{% for arg in fn.args %}'{{ arg.name }}': {% if arg.type_str == "tuple" or arg.type_str == "list" or arg.type_str == "optional" or arg.type_str == "response" %}sampleJsonForAbi({{ arg.type | json_encode | safe }}){% else %}''{% endif %}, {% endfor %}
});
const setArg = (name: string, value: string) => setArgs(a => Object.assign({}, a, { [name]: value }));
{% endif %}
return (
<div style={Object.assign({}, S.card, { border: isOpen ? '1px solid #FF550E' : S.card.border })}>
<div style={Object.assign({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer' })} onClick={onToggle}>
<div style={Object.assign({ display: 'flex', alignItems: 'center' })}>
<span style={S.fnNumber}>{num}</span>
<span style={S.fnName}>{{ fn.name | upper }}</span>
</div>
<div style={isReadOnly ? S.badgeRead : S.badgeWrite}>
<div style={S.badgeDot} />
{isReadOnly ? 'READ' : 'WRITE'}
<span style={Object.assign({ marginLeft: '8px', transition: '0.3s' }, { transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)' })}>▼</span>
</div>
</div>
<div style={Object.assign({}, S.formContainer, { maxHeight: isOpen ? '1000px' : '0px', opacity: isOpen ? 1 : 0, marginTop: isOpen ? '20px' : '0px' })}>
<form style={S.form} onSubmit={async (e) => { e.preventDefault(); await call([{% if fn.args %}{% for arg in fn.args %}toClarityValue(args['{{ arg.name }}'] ?? '', {{ arg.type | json_encode | safe }}, '{{ arg.name }}'),{% endfor %}{% endif %}]); }}>
{% if fn.args %}
{% for arg in fn.args %}
<div>
<label style={S.argLabel}>{{ arg.name }} <span style={S.argType}>{{ arg.type_str | upper }}</span></label>
{% if arg.type_str == "tuple" or arg.type_str == "list" or arg.type_str == "optional" or arg.type_str == "response" %}
<textarea
style={Object.assign({}, S.argInput, { minHeight: '120px', resize: 'vertical' as const })}
value={args['{{ arg.name }}']}
onChange={e => setArg('{{ arg.name }}', e.target.value)}
/>
<p style={Object.assign({}, S.argType, { marginLeft: 0, marginTop: '6px', display: 'block' })}>JSON input expected. Example prefilled from ABI.</p>
{% else %}
<input style={S.argInput} value={args['{{ arg.name }}']} onChange={e => setArg('{{ arg.name }}', e.target.value)} />
{% endif %}
</div>
{% endfor %}
{% endif %}
<button type="submit" disabled={loading} style={Object.assign({}, isReadOnly ? S.btnRead : S.btnWrite, { opacity: loading ? 0.5 : 1 })}>
{loading ? 'PROCESSING...' : (isReadOnly ? 'READ' : 'CALL')}
</button>
{!!data && (
<div style={Object.assign({}, S.resBox, { borderColor: isReadOnly ? '#2E2D2E' : '#2E2D2E' })}>
<p style={S.resLabel}>RESULT</p>
<pre style={S.resPre}>{formatResult(data)}</pre>
</div>
)}
{!isReadOnly && !!txid && (
<div style={Object.assign({}, S.resBox, { borderColor: '#2E2D2E' })}>
<p style={S.resLabel}>TRANSACTION</p>
<pre style={S.resPre}>{txid}</pre>
{!!txStatus && (
<p style={Object.assign({}, S.resPre, { marginTop: '8px' })}>
status: {txStatus}
</p>
)}
{!!txStatusError && (
<p style={Object.assign({}, S.resPre, { marginTop: '8px', color: '#fca5a5' })}>
{txStatusError}
</p>
)}
{!!explorerUrl && (
<a
href={explorerUrl}
target="_blank"
rel="noreferrer"
style={Object.assign({}, S.resPre, { display: 'inline-block', marginTop: '8px', color: '#93c5fd', textDecoration: 'underline' })}
>
Open in explorer
</a>
)}
</div>
)}
</form>
</div>
</div>
);
}
{% endif %}
{% endfor %}
{% endfor %}