> ## Documentation Index
> Fetch the complete documentation index at: https://web3docs.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend Integration

> Read balances and run approve / deposit / withdraw / mint from React

# Frontend Integration

Now connect the React app to the deployed contracts using wagmi hooks.

## 1. Contract addresses & ABIs

Keep addresses and minimal ABIs in one place:

```ts title="src/lib/contracts.ts" theme={null}
export const RUPIAH_TOKEN_ADDRESS =
  "0x7C78582DEa2a03d982D5fb0975fb4895E731dD05" as const // Sepolia
export const VAULT_ADDRESS =
  "0x8E81aB5f111670D732D961e892E94aCFcD593b07" as const // Sepolia

export const TOKEN_SYMBOL = "IDRT"
export const TOKEN_DECIMALS = 18

export const rupiahTokenAbi = [
  { type: "function", name: "balanceOf", stateMutability: "view",
    inputs: [{ name: "account", type: "address" }], outputs: [{ type: "uint256" }] },
  { type: "function", name: "allowance", stateMutability: "view",
    inputs: [{ type: "address" }, { type: "address" }], outputs: [{ type: "uint256" }] },
  { type: "function", name: "approve", stateMutability: "nonpayable",
    inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] },
  { type: "function", name: "mint", stateMutability: "nonpayable",
    inputs: [{ type: "address" }, { type: "uint256" }], outputs: [] },
] as const

export const vaultAbi = [
  { type: "function", name: "balances", stateMutability: "view",
    inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] },
  { type: "function", name: "deposit", stateMutability: "nonpayable",
    inputs: [{ type: "uint256" }], outputs: [] },
  { type: "function", name: "withdraw", stateMutability: "nonpayable",
    inputs: [{ type: "uint256" }], outputs: [] },
] as const
```

## 2. Read balances

`useReadContract` reads on-chain view functions. Enable each only when a wallet
is connected:

```tsx theme={null}
const { address } = useAccount()
const opts = { query: { enabled: !!address } }

const wallet = useReadContract({
  address: RUPIAH_TOKEN_ADDRESS, abi: rupiahTokenAbi,
  functionName: "balanceOf", args: address ? [address] : undefined, ...opts,
})
const savings = useReadContract({
  address: VAULT_ADDRESS, abi: vaultAbi,
  functionName: "balances", args: address ? [address] : undefined, ...opts,
})
const allowance = useReadContract({
  address: RUPIAH_TOKEN_ADDRESS, abi: rupiahTokenAbi,
  functionName: "allowance", args: address ? [address, VAULT_ADDRESS] : undefined, ...opts,
})
```

## 3. Write transactions

`useWriteContract` sends a transaction; `useWaitForTransactionReceipt` watches it.
After success, refetch the reads so balances update:

```tsx theme={null}
const { writeContract, data: hash, isPending, error: writeError } = useWriteContract()
const { isLoading, isSuccess, isError, error: receiptError } =
  useWaitForTransactionReceipt({ hash })

useEffect(() => {
  if (isSuccess) {
    wallet.refetch()
    savings.refetch()
    allowance.refetch()
  }
}, [isSuccess]) // eslint-disable-line react-hooks/exhaustive-deps
```

The four actions, all via the same `writeContract`:

```tsx theme={null}
// 1) Mint (open faucet) — to your own address
writeContract({ address: RUPIAH_TOKEN_ADDRESS, abi: rupiahTokenAbi,
  functionName: "mint", args: [address, wei] })

// 2) Approve the Vault
writeContract({ address: RUPIAH_TOKEN_ADDRESS, abi: rupiahTokenAbi,
  functionName: "approve", args: [VAULT_ADDRESS, wei] })

// 3) Deposit into the Vault
writeContract({ address: VAULT_ADDRESS, abi: vaultAbi,
  functionName: "deposit", args: [wei] })

// 4) Withdraw back to your wallet
writeContract({ address: VAULT_ADDRESS, abi: vaultAbi,
  functionName: "withdraw", args: [wei] })
```

`wei` is the amount parsed to 18 decimals with `parseUnits(amount, 18)`.

## 4. Friendly numbers

Balances and the input are easier to read with thousand separators:

```ts title="src/lib/helpers.ts" theme={null}
import { formatUnits } from "viem"

const numberFmt = new Intl.NumberFormat("en-US", { maximumFractionDigits: 4 })

// 1_000_000n → "1,000,000"
export const formatAmount = (value: bigint | undefined, decimals: number) =>
  numberFmt.format(Number(formatUnits(value ?? 0n, decimals)))

// group the input while typing: "1000000" → "1,000,000"
export const groupDigits = (raw: string) => {
  if (!raw) return ""
  const [int, dec] = raw.split(".")
  const grouped = (int || "0").replace(/\B(?=(\d{3})+(?!\d))/g, ",")
  return dec === undefined ? grouped : `${grouped}.${dec}`
}
```

## 5. Loading & transaction status

While a tx is in flight, show the clicked button as `Loading…`, and after it
settles show a link to Etherscan:

```tsx theme={null}
const busy = isPending || isLoading
const label = (text: string) =>
  lastAction === text && busy ? "Loading…" : text

// derive status without setState-in-effect
let status: { ok: boolean; hash?: `0x${string}`; msg?: string } | null = null
if (!busy) {
  if (isSuccess && hash) status = { ok: true, hash }
  else if (writeError || isError)
    status = { ok: false, hash, msg: shortError(writeError ?? receiptError) }
}
```

```tsx theme={null}
{status && (
  <p>
    Transaksi {lastAction} {status.ok ? "sukses" : "gagal"}:{" "}
    {status.hash
      ? <a href={`https://sepolia.etherscan.io/tx/${status.hash}`} target="_blank" rel="noopener noreferrer">{shortHash(status.hash)} ↗</a>
      : <span>{status.msg}</span>}
  </p>
)}
```

## 6. Guard deposit on allowance

`deposit` reverts if you haven't approved enough — and a failed gas estimate
surfaces as a confusing **"gas limit too high"**. Catch it before sending: if the
allowance is too low, show a hint instead of a doomed transaction.

```tsx theme={null}
const needApprove = wei !== undefined && (allowance.data ?? 0n) < wei

// on Deposit click:
if (needApprove) {
  setShowApproveNote(true) // "Allowance kurang — Approve dulu"
  return                   // don't send a tx that will revert
}
```

After **Approve** succeeds, `allowance` refetches → `needApprove` becomes false →
**Deposit** works.

## The full flow

<Steps>
  <Step title="Connect">Connect MetaMask on Sepolia.</Step>
  <Step title="Mint">Click **Mint** to get IDRT (open faucet).</Step>
  <Step title="Approve">Enter an amount, click **Approve**.</Step>
  <Step title="Deposit">Click **Deposit** — tokens move into the Vault.</Step>
  <Step title="Withdraw">Click **Withdraw** to pull them back out.</Step>
</Steps>

<Check>
  That's the full loop: a neo-brutalism dApp reading and writing real contracts on
  Sepolia. 🎉
</Check>

<Card title="Full Source" icon="arrow-right" href="/evm/workshop-amikom/06-full-code" horizontal>
  Copy every file in full from one page.
</Card>
