Skip to content

Commit a7a85f8

Browse files
committed
contract interactions, read balance and mint tokens
1 parent ab3ca00 commit a7a85f8

File tree

5 files changed

+737
-0
lines changed

5 files changed

+737
-0
lines changed

quick-starts/react-quick-start/src/App.css

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,125 @@ input::placeholder {
263263
.container {
264264
max-width: 1024px;
265265
}
266+
}
267+
268+
/* Contract section styles */
269+
.contract-section {
270+
margin-top: 24px;
271+
padding: 20px;
272+
background-color: var(--bg-light);
273+
border: 1px solid var(--border-color);
274+
border-radius: var(--radius);
275+
box-shadow: var(--shadow-sm);
276+
}
277+
278+
.contract-section h3 {
279+
margin-top: 0;
280+
color: var(--text-color);
281+
}
282+
283+
.font-bold {
284+
font-weight: 600;
285+
}
286+
287+
.border {
288+
border: 1px solid var(--border-color);
289+
}
290+
291+
.rounded-md {
292+
border-radius: var(--radius);
293+
}
294+
295+
.my-5 {
296+
margin: 20px 0;
297+
}
298+
299+
.p-2 {
300+
padding: 8px;
301+
}
302+
303+
.text-lg {
304+
font-size: 18px;
305+
}
306+
307+
.text-left {
308+
text-align: left;
309+
}
310+
311+
.text-right {
312+
text-align: right;
313+
}
314+
315+
.grid-cols-2 {
316+
display: grid;
317+
grid-template-columns: 1fr 1fr;
318+
gap: 8px;
319+
}
320+
321+
.w-full {
322+
width: 100%;
323+
}
324+
325+
.w-fit {
326+
width: fit-content;
327+
}
328+
329+
.inline-block {
330+
display: inline-block;
331+
}
332+
333+
.block {
334+
display: block;
335+
}
336+
337+
.mb-2 {
338+
margin-bottom: 8px;
339+
}
340+
341+
.mx-2 {
342+
margin: 0 8px;
343+
}
344+
345+
.mx-3 {
346+
margin: 0 12px;
347+
}
348+
349+
.my-0 {
350+
margin: 12px 0;
351+
}
352+
353+
.my-2 {
354+
margin: 8px 0;
355+
}
356+
357+
.px-2 {
358+
padding: 0 8px;
359+
}
360+
361+
.pl-2 {
362+
padding-left: 8px;
363+
}
364+
365+
.h-10 {
366+
height: 40px;
367+
}
368+
369+
.w-96, .w-400 {
370+
width: 400px;
371+
}
372+
373+
.padding-1 {
374+
padding: 4px;
375+
}
376+
377+
.focus\:ring-2:focus {
378+
box-shadow: 0 0 0 2px var(--primary-color);
379+
}
380+
381+
.focus\:ring-inset:focus {
382+
box-shadow: inset 0 0 0 2px var(--primary-color);
383+
}
384+
385+
.focus\:ring-indigo-600:focus {
386+
box-shadow: 0 0 0 2px var(--primary-color);
266387
}

quick-starts/react-quick-start/src/App.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { SendTransaction } from "./components/sendTransaction";
99
import { Balance } from "./components/getBalance";
1010
import { SwitchChain } from "./components/switchNetwork";
1111
import { ExportPrivateKey } from "./components/exportPrivateKey";
12+
import { ContractData } from "./components/ContractData";
13+
import { myTokenModuleMyTokenAddress } from "./generated";
1214

1315
function App() {
1416
const {
@@ -26,6 +28,8 @@ function App() {
2628
const { userInfo } = useWeb3AuthUser();
2729
const { address } = useAccount();
2830

31+
const contractAddress = myTokenModuleMyTokenAddress[420420422];
32+
2933
function uiConsole(...args: any[]): void {
3034
const el = document.querySelector("#console>p");
3135
if (el) {
@@ -58,6 +62,14 @@ function App() {
5862
<Balance />
5963
<SwitchChain />
6064
<ExportPrivateKey />
65+
66+
<div className="contract-section">
67+
<h3>Contract Interactions</h3>
68+
<ContractData
69+
contractAddress={contractAddress}
70+
userAddresses={address ? [address] : undefined}
71+
/>
72+
</div>
6173
</div>
6274
);
6375

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { myTokenModuleMyTokenAbi } from "../generated";
2+
import { useReadContracts } from "wagmi";
3+
import { Mint } from "./Mint";
4+
5+
export function ContractData(params: {
6+
contractAddress: `0x${string}`,
7+
userAddresses?: readonly `0x${string}`[],
8+
}) {
9+
const userAddressesSet = new Set(params.userAddresses);
10+
11+
const myTokenContract = {
12+
address: params.contractAddress,
13+
abi: myTokenModuleMyTokenAbi
14+
} as const;
15+
16+
const contractData = useReadContracts({
17+
contracts: [{
18+
...myTokenContract,
19+
functionName: "minter"
20+
}, {
21+
...myTokenContract,
22+
functionName: "totalSupply"
23+
}, {
24+
...myTokenContract,
25+
functionName: "symbol"
26+
}, {
27+
...myTokenContract,
28+
functionName: "decimals"
29+
}, ...(params.userAddresses ?? []).map(addr => ({
30+
...myTokenContract,
31+
abi: myTokenModuleMyTokenAbi,
32+
functionName: "balanceOf",
33+
args: [addr]
34+
}))
35+
]
36+
});
37+
38+
let error: string | null = null;
39+
40+
if (contractData.error !== null) {
41+
error = contractData.error.toString();
42+
} else {
43+
error = contractData.data?.find(el => el.error !== undefined)?.toString() || null;
44+
}
45+
46+
if (error !== null) {
47+
return (
48+
<p>
49+
Loading contract data for <span className="font-bold">{params.contractAddress}</span> failed!<br />
50+
<code style={{ whiteSpace: "pre-wrap" }}>{error}</code>
51+
</p>
52+
);
53+
}
54+
55+
if (contractData.isLoading || contractData?.data === undefined || contractData.data.some(el => el === undefined)) {
56+
return (
57+
<p>
58+
Loading contract data for <span className="font-bold">{params.contractAddress}</span>...
59+
</p>
60+
);
61+
}
62+
63+
const owner = contractData.data[0].result as `0x${string}`;
64+
const isOwner = owner && (userAddressesSet.has(owner));
65+
const totalSupply = contractData.data[1].result as bigint;
66+
const tokenName = contractData.data[2].result as string;
67+
const decimals = contractData.data[3].result as number;
68+
const balances = contractData.data.slice(4).map(el => el.result as bigint);
69+
70+
const formatMoney = (amount: bigint): string => (
71+
String(Number(amount / 10n ** (BigInt(decimals) - 3n)) / 1000)
72+
+ " "
73+
+ tokenName
74+
);
75+
76+
return (
77+
<>
78+
<p>
79+
Smart contract address: <span className="font-bold">{params.contractAddress}</span>
80+
</p>
81+
<p>
82+
Smart contract owner: <span className="font-bold">{owner}</span>
83+
{isOwner && (<> (that's you!!)</>)}
84+
</p>
85+
<p>
86+
Total supply: <span className="font-bold">{formatMoney(totalSupply)}</span>
87+
</p>
88+
89+
{(params.userAddresses && params.userAddresses.length > 0) && (
90+
<div className="border rounded-md my-5 p-2 w-full align-top">
91+
<h3 className="font-bold text-lg">Balances</h3>
92+
<div className="w-full grid grid-cols-2">
93+
{balances.map((val, index) => [
94+
<div key={index.toString() + "_addr"} className="text-left">{params.userAddresses![index]}</div>,
95+
<div key={index.toString() + "_value"} className="text-right">{formatMoney(val)}</div>
96+
]).flat()}
97+
</div>
98+
</div>
99+
)}
100+
101+
<Mint contractAddress={params.contractAddress} ownerAddress={owner} decimals={decimals} symbol={tokenName} />
102+
103+
{!isOwner && (
104+
<p style={{ color: 'orange', fontSize: '14px', marginTop: '8px' }}>
105+
Note: You are not the contract owner. Minting may fail unless you have the MINTER_ROLE.
106+
</p>
107+
)}
108+
</>
109+
);
110+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { myTokenModuleMyTokenAbi } from "../generated";
2+
import { useWriteContract, useAccount } from "wagmi";
3+
4+
import { useState, useEffect } from "react";
5+
6+
export function Mint(params: {
7+
contractAddress: `0x${string}`,
8+
ownerAddress: `0x${string}`,
9+
decimals: number,
10+
symbol: string,
11+
}) {
12+
const { address: userAddress } = useAccount();
13+
const [amount, setAmount] = useState(0);
14+
const [address, setAddress] = useState<`0x${string}`>(userAddress || "0x932217f9faf715808c1f76eA9EeAb7026806C963");
15+
16+
useEffect(() => {
17+
if (userAddress) {
18+
setAddress(userAddress);
19+
}
20+
}, [userAddress]);
21+
22+
const { writeContract, status, data, error } = useWriteContract();
23+
24+
return (
25+
<div className="border rounded-md my-5 mx-2 p-2 w-fit inline-block">
26+
<h3 className="px-2 block mb-2 font-bold text-lg">Mint {params.symbol}s</h3>
27+
<div className="text-right my-2">
28+
<label htmlFor="address" className="px-2 block mb-2 inline-block">Address</label>
29+
<input
30+
id="address"
31+
value={address}
32+
placeholder="0x..."
33+
onChange={(e) => setAddress(e.target.value as `0x${string}`)}
34+
disabled={status === "pending"}
35+
className="
36+
border rounded-md padding-1 pl-2 h-10 w-400
37+
focus:ring-2 focus:ring-inset focus:ring-indigo-600
38+
" />
39+
</div>
40+
<div className="text-right my-2">
41+
<label htmlFor="amount" className="px-2 block mb-2 inline-block">Amount</label>
42+
<input
43+
id="amount"
44+
type="number"
45+
placeholder="0"
46+
onChange={(e) => setAmount(Number(e.target.value))}
47+
disabled={status === "pending"}
48+
className="
49+
border rounded-md padding-1 pl-2 h-10 w-400
50+
focus:ring-2 focus:ring-inset focus:ring-indigo-600
51+
" />
52+
</div>
53+
54+
<button onClick={() => writeContract({
55+
address: params.contractAddress,
56+
abi: myTokenModuleMyTokenAbi,
57+
functionName: "mint",
58+
args: [address, BigInt(amount) * (10n ** BigInt(params.decimals))]
59+
})} disabled={status === "pending" || amount <= 0} className="
60+
my-0 mx-3 h-10 py-0
61+
focus:ring-2 focus:ring-inset focus:ring-indigo-600
62+
">Mint {
63+
status === "pending" ? "⏳"
64+
: status === "success" ? "✅"
65+
: status === "error" ? "❌" : ""
66+
}
67+
</button>
68+
69+
{status === "error" && error && (
70+
<div style={{ color: 'red', fontSize: '14px', marginTop: '8px', padding: '8px' }}>
71+
Error: {error.message}
72+
</div>
73+
)}
74+
75+
{status === "success" && data && (
76+
<div style={{ color: 'green', fontSize: '14px', marginTop: '8px', padding: '8px' }}>
77+
Transaction successful! Hash: {data}
78+
</div>
79+
)}
80+
</div>
81+
);
82+
}

0 commit comments

Comments
 (0)