Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/web-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ npm run build
```

This app currently uses an in-browser mock transport so flows can be exercised without a wallet during development.

## Included Flows

- Create guild with SDK `createGuild` and display resolved guild/token/governor addresses.
- Governance proposal todo list using `governanceAction` and per-item vote actions via `vote`.
- Treasury send money and get approval actions via `treasuryAction` (`transfer` and `approve` modes).
124 changes: 123 additions & 1 deletion examples/web-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ interface ProposalTodo {
lastTxHash: string;
}

interface TreasuryActivity {
id: number;
mode: 'transfer' | 'approve';
target: string;
token: string;
amount: string;
txHash: string;
}

function isStarknetAddress(value: string): value is `0x${string}` {
return /^0x[0-9a-fA-F]+$/.test(value);
}
Expand Down Expand Up @@ -43,6 +52,15 @@ export function App() {
const [proposalForm, setProposalForm] = useState({ title: 'Fund toolchain grant', description: 'Approve monthly ops budget.' });
const [proposalTodos, setProposalTodos] = useState<ProposalTodo[]>([]);
const [governanceError, setGovernanceError] = useState<string | null>(null);
const [treasuryForm, setTreasuryForm] = useState({
guild: '0x111',
target: '0x777',
token: '0x222',
amount: '50',
mode: 'transfer' as 'transfer' | 'approve',
});
const [treasuryActivities, setTreasuryActivities] = useState<TreasuryActivity[]>([]);
const [treasuryError, setTreasuryError] = useState<string | null>(null);

const bundle = useMemo(() => createBrowserClientBundle(network, factory), [network, factory]);
const invokes = bundle.transport.getState().invokes;
Expand Down Expand Up @@ -75,6 +93,7 @@ export function App() {
governor: addresses.governor,
});
setGovernorAddress(addresses.governor);
setTreasuryForm((prev) => ({ ...prev, guild: addresses.guild, token: addresses.token }));
} catch (error) {
setCreateError(error instanceof Error ? error.message : 'Create guild failed');
}
Expand Down Expand Up @@ -149,6 +168,37 @@ export function App() {
}
}

async function onTreasurySubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setTreasuryError(null);

try {
const actionType = treasuryForm.mode === 'transfer' ? 1 : 2;
const tx = await bundle.client.treasuryAction({
guild: treasuryForm.guild as `0x${string}`,
actionType,
target: treasuryForm.target as `0x${string}`,
token: treasuryForm.token as `0x${string}`,
amount: BigInt(treasuryForm.amount),
calldata: [],
});

setTreasuryActivities((prev) => [
{
id: prev.length + 1,
mode: treasuryForm.mode,
target: treasuryForm.target,
token: treasuryForm.token,
amount: treasuryForm.amount,
txHash: tx.transactionHash,
},
...prev,
]);
} catch (error) {
setTreasuryError(error instanceof Error ? error.message : 'Treasury action failed');
}
}

return (
<div className="page-shell">
<header className="hero">
Expand Down Expand Up @@ -207,7 +257,7 @@ export function App() {
</article>
<article>
<h3>Treasury Actions</h3>
<p>Send money and approval actions land in slice 4.</p>
<p>Send money and approve spender actions now available below.</p>
</article>
</div>
</section>
Expand Down Expand Up @@ -270,6 +320,78 @@ export function App() {
{governanceError ? <p className="error">{governanceError}</p> : null}
</section>

<section className="panel">
<h2>Treasury: Send Money / Approve</h2>
<form className="grid three" onSubmit={onTreasurySubmit}>
<label>
<span>Guild Address</span>
<input
value={treasuryForm.guild}
onChange={(event) => setTreasuryForm((prev) => ({ ...prev, guild: event.target.value }))}
/>
</label>
<label>
<span>Action</span>
<select
value={treasuryForm.mode}
onChange={(event) =>
setTreasuryForm((prev) => ({ ...prev, mode: event.target.value as 'transfer' | 'approve' }))
}
>
<option value="transfer">Send Money (transfer)</option>
<option value="approve">Get Approval (approve)</option>
</select>
</label>
<label>
<span>Target</span>
<input
value={treasuryForm.target}
onChange={(event) => setTreasuryForm((prev) => ({ ...prev, target: event.target.value }))}
/>
</label>
<label>
<span>Token</span>
<input
value={treasuryForm.token}
onChange={(event) => setTreasuryForm((prev) => ({ ...prev, token: event.target.value }))}
/>
</label>
<label>
<span>Amount</span>
<input
value={treasuryForm.amount}
onChange={(event) => setTreasuryForm((prev) => ({ ...prev, amount: event.target.value }))}
/>
</label>
<div className="actions">
<button type="submit">Run Treasury Action</button>
</div>
</form>

{treasuryActivities.length === 0 ? (
<p className="empty">No treasury actions submitted yet.</p>
) : (
<ul className="treasury-list">
{treasuryActivities.map((item) => (
<li key={item.id}>
<div>
<h3>{item.mode === 'transfer' ? 'Transfer' : 'Approve'}</h3>
<p>
Target: <code>{item.target}</code>
</p>
<p>
Token: <code>{item.token}</code>
</p>
<p>Amount: {item.amount}</p>
</div>
<code>{item.txHash}</code>
</li>
))}
</ul>
)}
{treasuryError ? <p className="error">{treasuryError}</p> : null}
</section>

<section className="panel">
<h2>Create Guild</h2>
<form className="grid three" onSubmit={onCreateGuildSubmit}>
Expand Down
18 changes: 18 additions & 0 deletions examples/web-client/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,24 @@ button:hover {
gap: 0.65rem;
}

.treasury-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.55rem;
}

.treasury-list li {
border: 1px solid #dbece1;
border-radius: 0.75rem;
padding: 0.7rem;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 0.75rem;
}

.todo-list li {
border: 1px solid #dbece1;
border-radius: 0.75rem;
Expand Down