create-awarizon-app 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/expo/app/(tabs)/_layout.tsx +3 -1
- package/templates/expo/app/(tabs)/index.tsx +39 -68
- package/templates/expo/app/(tabs)/nft.tsx +142 -0
- package/templates/expo/app/(tabs)/wallet.tsx +151 -0
- package/templates/expo/package.json +22 -23
- package/templates/expo-js/app/(tabs)/_layout.jsx +3 -1
- package/templates/expo-js/app/(tabs)/index.jsx +39 -68
- package/templates/expo-js/app/(tabs)/nft.jsx +141 -0
- package/templates/expo-js/app/(tabs)/wallet.jsx +152 -0
- package/templates/expo-js/package.json +19 -20
- package/templates/nextjs/app/layout.tsx +5 -1
- package/templates/nextjs/app/nft/page.tsx +140 -0
- package/templates/nextjs/app/page.tsx +34 -142
- package/templates/nextjs/app/wallet/page.tsx +145 -0
- package/templates/nextjs/components/Header.tsx +60 -0
- package/templates/nextjs/package.json +15 -16
- package/templates/nextjs-js/app/layout.jsx +5 -1
- package/templates/nextjs-js/app/nft/page.jsx +139 -0
- package/templates/nextjs-js/app/page.jsx +35 -130
- package/templates/nextjs-js/app/wallet/page.jsx +145 -0
- package/templates/nextjs-js/components/Header.jsx +60 -0
- package/templates/nextjs-js/package.json +11 -12
- package/templates/react/package.json +14 -15
- package/templates/react/src/App.tsx +88 -145
- package/templates/react/src/pages/NftPage.tsx +138 -0
- package/templates/react/src/pages/WalletPage.tsx +143 -0
- package/templates/react-js/package.json +11 -12
- package/templates/react-js/src/App.jsx +86 -119
- package/templates/react-js/src/pages/NftPage.jsx +137 -0
- package/templates/react-js/src/pages/WalletPage.jsx +143 -0
|
@@ -1,155 +1,47 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { ERC20_ABI } from '@awarizon/web3'
|
|
5
|
-
import { Badge } from '@/components/ui/Badge'
|
|
6
|
-
import { Button } from '@/components/ui/Button'
|
|
7
|
-
import { Card } from '@/components/ui/Card'
|
|
3
|
+
import { usePrices, TokenIcon } from '@awarizon/react'
|
|
8
4
|
import { Skeleton } from '@/components/ui/Skeleton'
|
|
9
|
-
import { StatCard } from '@/components/ui/StatCard'
|
|
10
5
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{loading
|
|
19
|
-
? <Skeleton className="h-3.5 w-28" />
|
|
20
|
-
: <span className="font-mono text-xs text-zinc-300">{value ?? '—'}</span>}
|
|
21
|
-
</div>
|
|
22
|
-
)
|
|
23
|
-
}
|
|
6
|
+
const TOKENS = [
|
|
7
|
+
{ symbol: 'ETH', name: 'Ethereum' },
|
|
8
|
+
{ symbol: 'BTC', name: 'Bitcoin' },
|
|
9
|
+
{ symbol: 'USDC', name: 'USD Coin' },
|
|
10
|
+
{ symbol: 'BNB', name: 'BNB' },
|
|
11
|
+
{ symbol: 'HYPE', name: 'Hyperliquid' },
|
|
12
|
+
]
|
|
24
13
|
|
|
25
14
|
export default function Home() {
|
|
26
|
-
const {
|
|
27
|
-
const { data: symbol, isLoading: l2 } = useRead<string>(USDC.symbol())
|
|
28
|
-
const { data: decimals, isLoading: l3 } = useRead<bigint>(USDC.decimals())
|
|
29
|
-
const { data: totalSupply, isLoading: l4 } = useRead<bigint>(USDC.totalSupply())
|
|
30
|
-
const { price: ethPrice, isLoading: lp } = usePrice('ETH')
|
|
31
|
-
|
|
32
|
-
const formatted =
|
|
33
|
-
totalSupply !== undefined && decimals !== undefined
|
|
34
|
-
? (Number(totalSupply) / 10 ** Number(decimals))
|
|
35
|
-
.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
36
|
-
: undefined
|
|
37
|
-
|
|
38
|
-
const synced = !l1 && !l2 && !l3 && !l4
|
|
15
|
+
const { prices, isLoading } = usePrices(TOKENS.map(t => t.symbol))
|
|
39
16
|
|
|
40
17
|
return (
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<div className="flex items-center gap-2.5">
|
|
47
|
-
<div className="w-5 h-5 rounded-[4px] bg-yellow-400" />
|
|
48
|
-
<span className="font-mono text-[11px] tracking-[0.2em] text-zinc-400 uppercase">
|
|
49
|
-
{'{{project-name}}'}
|
|
50
|
-
</span>
|
|
51
|
-
</div>
|
|
52
|
-
<div className="flex items-center gap-3">
|
|
53
|
-
<Badge variant="outline">Base</Badge>
|
|
54
|
-
<Badge variant={synced ? 'success' : 'loading'}>
|
|
55
|
-
{synced ? '● Live' : '○ Loading'}
|
|
56
|
-
</Badge>
|
|
57
|
-
{process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID && <ConnectButton />}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
</header>
|
|
61
|
-
|
|
62
|
-
<main className="max-w-4xl mx-auto px-6 py-12">
|
|
63
|
-
|
|
64
|
-
{/* ── Hero ───────────────────────────────────────── */}
|
|
65
|
-
<div className="mb-10">
|
|
66
|
-
<Badge variant="outline" className="mb-5">Awarizon Web3 SDK · Next.js</Badge>
|
|
67
|
-
<h1 className="text-4xl font-bold tracking-tight text-white mb-3">{'{{ProjectName}}'}</h1>
|
|
68
|
-
<p className="text-zinc-400 text-base max-w-lg leading-relaxed">
|
|
69
|
-
Live on-chain reads from Base — no backend required. Built with{' '}
|
|
70
|
-
<code className="font-mono text-[11px] text-yellow-400/80">@awarizon/react</code>.
|
|
71
|
-
</p>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
{/* ── Data grid ──────────────────────────────────── */}
|
|
75
|
-
<div className="grid md:grid-cols-3 gap-4 mb-4">
|
|
76
|
-
|
|
77
|
-
<Card className="md:col-span-2">
|
|
78
|
-
<div className="flex items-start justify-between mb-4">
|
|
79
|
-
<div>
|
|
80
|
-
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-1.5">
|
|
81
|
-
Contract · Live Read
|
|
82
|
-
</p>
|
|
83
|
-
<code className="font-mono text-xs text-zinc-500">0x8335…2913 · Base</code>
|
|
84
|
-
</div>
|
|
85
|
-
<Badge variant={synced ? 'success' : 'loading'}>
|
|
86
|
-
{synced ? 'Synced' : 'Fetching…'}
|
|
87
|
-
</Badge>
|
|
88
|
-
</div>
|
|
89
|
-
<DataRow label="name()" value={name} loading={l1} />
|
|
90
|
-
<DataRow label="symbol()" value={symbol} loading={l2} />
|
|
91
|
-
<DataRow label="decimals()" value={String(decimals ?? '')} loading={l3} />
|
|
92
|
-
<DataRow label="totalSupply()" value={formatted} loading={l4} />
|
|
93
|
-
</Card>
|
|
94
|
-
|
|
95
|
-
<div className="flex flex-col gap-4">
|
|
96
|
-
<StatCard
|
|
97
|
-
label="ETH Price"
|
|
98
|
-
value={ethPrice != null ? `$${ethPrice.toLocaleString(undefined, { maximumFractionDigits: 2 })}` : undefined}
|
|
99
|
-
loading={lp}
|
|
100
|
-
sub="via usePrice"
|
|
101
|
-
className="flex-1"
|
|
102
|
-
/>
|
|
103
|
-
<StatCard
|
|
104
|
-
label="Total Supply"
|
|
105
|
-
value={formatted}
|
|
106
|
-
loading={l4}
|
|
107
|
-
sub="USDC · formatted"
|
|
108
|
-
className="flex-1"
|
|
109
|
-
/>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
{/* ── Network info ────────────────────────────────── */}
|
|
114
|
-
<div className="grid sm:grid-cols-3 gap-4 mb-4">
|
|
115
|
-
{[
|
|
116
|
-
{ label: 'Network', value: 'Base Mainnet' },
|
|
117
|
-
{ label: 'Chain ID', value: '8453' },
|
|
118
|
-
{ label: 'SDK', value: '@awarizon/react' },
|
|
119
|
-
].map(({ label, value }) => (
|
|
18
|
+
<main className="max-w-5xl mx-auto px-6 py-10">
|
|
19
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
20
|
+
{TOKENS.map(({ symbol, name }) => {
|
|
21
|
+
const price = prices[symbol]?.price
|
|
22
|
+
return (
|
|
120
23
|
<div
|
|
121
|
-
key={
|
|
122
|
-
className="bg-zinc-900/
|
|
24
|
+
key={symbol}
|
|
25
|
+
className="bg-zinc-900/40 border border-zinc-800/40 rounded-2xl p-5 hover:bg-zinc-900/60 hover:border-zinc-700/50 transition-all cursor-default"
|
|
123
26
|
>
|
|
124
|
-
<
|
|
125
|
-
|
|
27
|
+
<div className="mb-4">
|
|
28
|
+
<div className="mb-3">
|
|
29
|
+
<TokenIcon symbol={symbol} size={40} radiusFraction={0.28} />
|
|
30
|
+
</div>
|
|
31
|
+
<p className="text-[11px] text-zinc-500 font-medium mb-0.5">{symbol}</p>
|
|
32
|
+
<p className="text-xs text-zinc-600">{name}</p>
|
|
33
|
+
</div>
|
|
34
|
+
<div className="text-lg font-semibold text-white tracking-tight">
|
|
35
|
+
{isLoading
|
|
36
|
+
? <Skeleton className="h-5 w-20" />
|
|
37
|
+
: price != null
|
|
38
|
+
? `$${price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
|
39
|
+
: '—'}
|
|
40
|
+
</div>
|
|
126
41
|
</div>
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<Card>
|
|
132
|
-
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-4">Next Steps</p>
|
|
133
|
-
<ul className="space-y-3 mb-6">
|
|
134
|
-
{[
|
|
135
|
-
['01', 'Replace USDC_BASE with your contract address'],
|
|
136
|
-
['02', 'Swap ERC20_ABI for your contract ABI'],
|
|
137
|
-
['03', 'Change chain="base" in components/Providers.tsx to your target network'],
|
|
138
|
-
['04', 'Use useWrite(USDC.transfer) to send transactions'],
|
|
139
|
-
['05', 'Use usePrice("ETH") or usePrices(["ETH","BTC"]) for live token prices'],
|
|
140
|
-
].map(([n, s]) => (
|
|
141
|
-
<li key={n} className="flex items-start gap-3">
|
|
142
|
-
<span className="font-mono text-[10px] text-yellow-400/50 mt-0.5 shrink-0 select-none">{n}</span>
|
|
143
|
-
<span className="text-sm text-zinc-400 leading-relaxed">{s}</span>
|
|
144
|
-
</li>
|
|
145
|
-
))}
|
|
146
|
-
</ul>
|
|
147
|
-
<div className="flex flex-wrap gap-2">
|
|
148
|
-
<Button href="https://awarizon.com/docs" target="_blank" rel="noreferrer">Read the docs →</Button>
|
|
149
|
-
<Button href="https://awarizon.com/dashboard" target="_blank" rel="noreferrer" variant="secondary">Dashboard</Button>
|
|
150
|
-
</div>
|
|
151
|
-
</Card>
|
|
152
|
-
</main>
|
|
153
|
-
</div>
|
|
42
|
+
)
|
|
43
|
+
})}
|
|
44
|
+
</div>
|
|
45
|
+
</main>
|
|
154
46
|
)
|
|
155
47
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useWallet, useNativeBalance, usePrices, ConnectButton, TokenIcon, ChainIcon, getChainNativeSymbol, getChainName } from '@awarizon/react'
|
|
4
|
+
import { Badge } from '@/components/ui/Badge'
|
|
5
|
+
import { Card } from '@/components/ui/Card'
|
|
6
|
+
import { Skeleton } from '@/components/ui/Skeleton'
|
|
7
|
+
|
|
8
|
+
const ASSETS = [
|
|
9
|
+
{ symbol: 'ETH', name: 'Ethereum' },
|
|
10
|
+
{ symbol: 'BTC', name: 'Bitcoin' },
|
|
11
|
+
{ symbol: 'USDC', name: 'USD Coin' },
|
|
12
|
+
{ symbol: 'BNB', name: 'BNB' },
|
|
13
|
+
{ symbol: 'HYPE', name: 'Hyperliquid' },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
function DataRow({ label, value, loading }: { label: string; value?: string; loading: boolean }) {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex items-center justify-between py-3 border-b border-zinc-800/50 last:border-0">
|
|
19
|
+
<span className="font-mono text-[10px] text-zinc-600 uppercase tracking-widest">{label}</span>
|
|
20
|
+
{loading
|
|
21
|
+
? <Skeleton className="h-3.5 w-32" />
|
|
22
|
+
: <span className="font-mono text-xs text-zinc-300">{value ?? '—'}</span>}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function WalletPage() {
|
|
28
|
+
const { address, isConnected, isChainMismatch, chainId, connectorInfo } = useWallet()
|
|
29
|
+
const nativeSymbol = chainId != null ? getChainNativeSymbol(chainId) : 'ETH'
|
|
30
|
+
const { formatted: nativeBalance, isLoading: balanceLoading } = useNativeBalance(
|
|
31
|
+
address ?? undefined,
|
|
32
|
+
12_000,
|
|
33
|
+
)
|
|
34
|
+
const { prices, isLoading: pricesLoading } = usePrices(ASSETS.map(a => a.symbol))
|
|
35
|
+
|
|
36
|
+
const ethFiatValue =
|
|
37
|
+
nativeBalance != null && prices['ETH']?.price != null
|
|
38
|
+
? `$${(parseFloat(nativeBalance) * prices['ETH'].price).toLocaleString(undefined, { maximumFractionDigits: 2 })}`
|
|
39
|
+
: undefined
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<main className="max-w-4xl mx-auto px-6 py-12">
|
|
43
|
+
|
|
44
|
+
{/* ── Hero ───────────────────────────────────────── */}
|
|
45
|
+
<div className="mb-10">
|
|
46
|
+
<Badge variant="outline" className="mb-5">Portfolio</Badge>
|
|
47
|
+
<h1 className="text-4xl font-bold tracking-tight text-white mb-3">Wallet</h1>
|
|
48
|
+
<p className="text-zinc-400 text-base max-w-lg leading-relaxed">
|
|
49
|
+
Live balance and portfolio data via{' '}
|
|
50
|
+
<code className="font-mono text-[11px] text-yellow-400/80">useWallet</code>,{' '}
|
|
51
|
+
<code className="font-mono text-[11px] text-yellow-400/80">useNativeBalance</code>, and{' '}
|
|
52
|
+
<code className="font-mono text-[11px] text-yellow-400/80">usePrices</code>.
|
|
53
|
+
</p>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{!isConnected ? (
|
|
57
|
+
<Card className="py-12 flex flex-col items-center text-center">
|
|
58
|
+
<p className="font-mono text-[10px] tracking-widest text-zinc-600 uppercase mb-3">
|
|
59
|
+
No Wallet Connected
|
|
60
|
+
</p>
|
|
61
|
+
<p className="text-zinc-400 text-sm mb-6 max-w-sm leading-relaxed">
|
|
62
|
+
Connect a wallet to view your address, native balance, and real-time portfolio value.
|
|
63
|
+
</p>
|
|
64
|
+
<ConnectButton />
|
|
65
|
+
</Card>
|
|
66
|
+
) : (
|
|
67
|
+
<>
|
|
68
|
+
{isChainMismatch && (
|
|
69
|
+
<div className="mb-4 px-4 py-3 bg-amber-500/10 border border-amber-500/20 rounded-xl">
|
|
70
|
+
<span className="font-mono text-xs text-amber-400">
|
|
71
|
+
Wrong network — switch to the chain configured in components/Providers.tsx
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{/* ── Account ──────────────────────────────────── */}
|
|
77
|
+
<Card className="mb-4">
|
|
78
|
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-4">Account</p>
|
|
79
|
+
<DataRow label="Address" value={address ?? undefined} loading={false} />
|
|
80
|
+
<DataRow label="Chain" value={chainId != null ? getChainName(chainId) : undefined} loading={false} />
|
|
81
|
+
<DataRow label="Connector" value={connectorInfo?.name ?? connectorInfo?.type} loading={false} />
|
|
82
|
+
</Card>
|
|
83
|
+
|
|
84
|
+
{/* ── Balance ──────────────────────────────────── */}
|
|
85
|
+
<Card className="mb-4">
|
|
86
|
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-3">Balance</p>
|
|
87
|
+
<div className="flex items-center gap-3 py-2">
|
|
88
|
+
{chainId != null
|
|
89
|
+
? <ChainIcon chainId={chainId} size={36} radiusFraction={0.5} />
|
|
90
|
+
: <TokenIcon symbol={nativeSymbol} size={36} radiusFraction={0.5} />
|
|
91
|
+
}
|
|
92
|
+
<div className="flex-1 min-w-0">
|
|
93
|
+
<p className="text-sm font-medium text-white">{nativeSymbol}</p>
|
|
94
|
+
<p className="text-xs text-zinc-500">{chainId != null ? getChainName(chainId) : 'Native'}</p>
|
|
95
|
+
</div>
|
|
96
|
+
<div className="text-right">
|
|
97
|
+
{balanceLoading
|
|
98
|
+
? <Skeleton className="h-4 w-28" />
|
|
99
|
+
: <>
|
|
100
|
+
<p className="text-sm font-medium text-white">
|
|
101
|
+
{nativeBalance != null ? `${parseFloat(nativeBalance).toFixed(6)} ${nativeSymbol}` : '—'}
|
|
102
|
+
</p>
|
|
103
|
+
{ethFiatValue && (
|
|
104
|
+
<p className="text-xs text-zinc-500">{ethFiatValue}</p>
|
|
105
|
+
)}
|
|
106
|
+
</>
|
|
107
|
+
}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</Card>
|
|
111
|
+
|
|
112
|
+
{/* ── Assets ───────────────────────────────────── */}
|
|
113
|
+
<Card>
|
|
114
|
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-3">Assets</p>
|
|
115
|
+
<div className="divide-y divide-zinc-800/50">
|
|
116
|
+
{ASSETS.map(({ symbol, name }) => {
|
|
117
|
+
const price = prices[symbol]?.price
|
|
118
|
+
return (
|
|
119
|
+
<div key={symbol} className="flex items-center gap-3 py-3 first:pt-0 last:pb-0">
|
|
120
|
+
<TokenIcon symbol={symbol} size={36} radiusFraction={0.28} />
|
|
121
|
+
<div className="flex-1 min-w-0">
|
|
122
|
+
<p className="text-sm font-medium text-white">{symbol}</p>
|
|
123
|
+
<p className="text-xs text-zinc-500">{name}</p>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="text-right">
|
|
126
|
+
{pricesLoading
|
|
127
|
+
? <Skeleton className="h-4 w-20" />
|
|
128
|
+
: <p className="text-sm font-medium text-white">
|
|
129
|
+
{price != null
|
|
130
|
+
? `$${price.toLocaleString(undefined, { maximumFractionDigits: 2 })}`
|
|
131
|
+
: '—'}
|
|
132
|
+
</p>
|
|
133
|
+
}
|
|
134
|
+
<p className="text-xs text-zinc-600">USD · usePrices</p>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
)
|
|
138
|
+
})}
|
|
139
|
+
</div>
|
|
140
|
+
</Card>
|
|
141
|
+
</>
|
|
142
|
+
)}
|
|
143
|
+
</main>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { usePathname } from 'next/navigation'
|
|
5
|
+
import { ConnectButton, ChainIcon, useWallet, getChainName } from '@awarizon/react'
|
|
6
|
+
|
|
7
|
+
const NAV = [
|
|
8
|
+
{ href: '/', label: 'Markets' },
|
|
9
|
+
{ href: '/nft', label: 'NFT' },
|
|
10
|
+
{ href: '/wallet', label: 'Wallet' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export function Header() {
|
|
14
|
+
const pathname = usePathname()
|
|
15
|
+
const { chainId } = useWallet()
|
|
16
|
+
const displayChainId = chainId ?? 8453
|
|
17
|
+
const chainName = getChainName(displayChainId)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<header className="sticky top-0 z-50 border-b border-white/[0.06] bg-zinc-950/70 backdrop-blur-2xl">
|
|
21
|
+
<div className="max-w-5xl mx-auto px-6 h-16 flex items-center justify-between gap-8">
|
|
22
|
+
|
|
23
|
+
<div className="flex items-center gap-2.5 shrink-0">
|
|
24
|
+
<div className="w-7 h-7 rounded-lg bg-yellow-400" />
|
|
25
|
+
<span className="text-sm font-semibold text-white">{'{{project-name}}'}</span>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<nav className="flex items-center">
|
|
29
|
+
{NAV.map(({ href, label }) => {
|
|
30
|
+
const active = pathname === href
|
|
31
|
+
return (
|
|
32
|
+
<Link
|
|
33
|
+
key={href}
|
|
34
|
+
href={href}
|
|
35
|
+
className={[
|
|
36
|
+
'relative px-4 py-2 text-sm font-medium transition-colors',
|
|
37
|
+
active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300',
|
|
38
|
+
].join(' ')}
|
|
39
|
+
>
|
|
40
|
+
{label}
|
|
41
|
+
{active && (
|
|
42
|
+
<span className="absolute bottom-0 left-4 right-4 h-px bg-yellow-400" />
|
|
43
|
+
)}
|
|
44
|
+
</Link>
|
|
45
|
+
)
|
|
46
|
+
})}
|
|
47
|
+
</nav>
|
|
48
|
+
|
|
49
|
+
<div className="flex items-center gap-3 shrink-0">
|
|
50
|
+
<div className="hidden sm:flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-zinc-800 bg-zinc-900/60 text-xs text-zinc-400 font-medium">
|
|
51
|
+
<ChainIcon chainId={displayChainId} size={16} radiusFraction={0.5} />
|
|
52
|
+
{chainName}
|
|
53
|
+
</div>
|
|
54
|
+
{process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID && <ConnectButton />}
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
</div>
|
|
58
|
+
</header>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -3,28 +3,27 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev":
|
|
6
|
+
"dev": "next dev",
|
|
7
7
|
"build": "next build",
|
|
8
8
|
"start": "next start",
|
|
9
|
-
"lint":
|
|
9
|
+
"lint": "next lint"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@awarizon/react": "^1.6.
|
|
13
|
-
"@awarizon/web3":
|
|
14
|
-
"next":
|
|
15
|
-
"react":
|
|
16
|
-
"react-dom":
|
|
12
|
+
"@awarizon/react": "^1.6.4",
|
|
13
|
+
"@awarizon/web3": "^1.3.7",
|
|
14
|
+
"next": "14.2.5",
|
|
15
|
+
"react": "^18",
|
|
16
|
+
"react-dom": "^18"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@types/node":
|
|
20
|
-
"@types/react":
|
|
21
|
-
"@types/react-dom":
|
|
22
|
-
"typescript":
|
|
23
|
-
"eslint":
|
|
19
|
+
"@types/node": "^20",
|
|
20
|
+
"@types/react": "^18",
|
|
21
|
+
"@types/react-dom": "^18",
|
|
22
|
+
"typescript": "^5",
|
|
23
|
+
"eslint": "^8",
|
|
24
24
|
"eslint-config-next": "14.2.5",
|
|
25
|
-
"autoprefixer":
|
|
26
|
-
"postcss":
|
|
27
|
-
"tailwindcss":
|
|
25
|
+
"autoprefixer": "^10.4.19",
|
|
26
|
+
"postcss": "^8.4.38",
|
|
27
|
+
"tailwindcss": "^3.4.4"
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Providers } from '@/components/Providers'
|
|
2
|
+
import { Header } from '@/components/Header'
|
|
2
3
|
import './globals.css'
|
|
3
4
|
|
|
4
5
|
/** @type {import('next').Metadata} */
|
|
@@ -11,7 +12,10 @@ export default function RootLayout({ children }) {
|
|
|
11
12
|
return (
|
|
12
13
|
<html lang="en">
|
|
13
14
|
<body>
|
|
14
|
-
<Providers>
|
|
15
|
+
<Providers>
|
|
16
|
+
<Header />
|
|
17
|
+
{children}
|
|
18
|
+
</Providers>
|
|
15
19
|
</body>
|
|
16
20
|
</html>
|
|
17
21
|
)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useNFT } from '@awarizon/react'
|
|
5
|
+
import { Badge } from '@/components/ui/Badge'
|
|
6
|
+
import { Button } from '@/components/ui/Button'
|
|
7
|
+
import { Card } from '@/components/ui/Card'
|
|
8
|
+
import { Skeleton } from '@/components/ui/Skeleton'
|
|
9
|
+
|
|
10
|
+
// Replace with your ERC-721 contract address on your configured chain.
|
|
11
|
+
const NFT_ADDRESS = '0x0000000000000000000000000000000000000000'
|
|
12
|
+
|
|
13
|
+
function DataRow({ label, value, loading }) {
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex items-center justify-between py-3 border-b border-zinc-800/50 last:border-0">
|
|
16
|
+
<code className="text-[11px] text-yellow-400/70 font-mono">{label}</code>
|
|
17
|
+
{loading
|
|
18
|
+
? <Skeleton className="h-3.5 w-28" />
|
|
19
|
+
: <span className="font-mono text-xs text-zinc-300">{value ?? '—'}</span>}
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function NftPage() {
|
|
25
|
+
const { name, symbol, isLoading, error, ownerOf, tokenURI } = useNFT(NFT_ADDRESS)
|
|
26
|
+
|
|
27
|
+
const [tokenId, setTokenId] = useState('')
|
|
28
|
+
const [owner, setOwner] = useState(null)
|
|
29
|
+
const [uri, setUri] = useState(null)
|
|
30
|
+
const [lookupLoading, setLookupLoading] = useState(false)
|
|
31
|
+
const [lookupError, setLookupError] = useState(null)
|
|
32
|
+
|
|
33
|
+
async function handleLookup() {
|
|
34
|
+
if (!tokenId) return
|
|
35
|
+
setLookupLoading(true)
|
|
36
|
+
setLookupError(null)
|
|
37
|
+
setOwner(null)
|
|
38
|
+
setUri(null)
|
|
39
|
+
try {
|
|
40
|
+
const id = BigInt(tokenId)
|
|
41
|
+
const [o, u] = await Promise.all([ownerOf(id), tokenURI(id)])
|
|
42
|
+
setOwner(o)
|
|
43
|
+
setUri(u)
|
|
44
|
+
} catch (e) {
|
|
45
|
+
setLookupError(e instanceof Error ? e.message : 'Lookup failed')
|
|
46
|
+
} finally {
|
|
47
|
+
setLookupLoading(false)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<main className="max-w-4xl mx-auto px-6 py-12">
|
|
53
|
+
|
|
54
|
+
{/* ── Hero ───────────────────────────────────────── */}
|
|
55
|
+
<div className="mb-10">
|
|
56
|
+
<Badge variant="outline" className="mb-5">ERC-721</Badge>
|
|
57
|
+
<h1 className="text-4xl font-bold tracking-tight text-white mb-3">NFT Collection</h1>
|
|
58
|
+
<p className="text-zinc-400 text-base max-w-lg leading-relaxed">
|
|
59
|
+
Zero-ABI reads from any ERC-721 contract using{' '}
|
|
60
|
+
<code className="font-mono text-[11px] text-yellow-400/80">useNFT</code>.
|
|
61
|
+
Replace <code className="font-mono text-[11px] text-yellow-400/80">NFT_ADDRESS</code> at the top of this file.
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{error && (
|
|
66
|
+
<div className="mb-4 px-4 py-3 bg-red-500/10 border border-red-500/20 rounded-xl">
|
|
67
|
+
<code className="font-mono text-xs text-red-400">{error.message}</code>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{/* ── Cards ──────────────────────────────────────── */}
|
|
72
|
+
<div className="grid md:grid-cols-2 gap-4 mb-4">
|
|
73
|
+
|
|
74
|
+
<Card>
|
|
75
|
+
<div className="mb-4">
|
|
76
|
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-1.5">Collection Info</p>
|
|
77
|
+
<code className="font-mono text-xs text-zinc-500">
|
|
78
|
+
{NFT_ADDRESS.slice(0, 6)}…{NFT_ADDRESS.slice(-4)}
|
|
79
|
+
</code>
|
|
80
|
+
</div>
|
|
81
|
+
<DataRow label="name()" value={name ?? undefined} loading={isLoading} />
|
|
82
|
+
<DataRow label="symbol()" value={symbol ?? undefined} loading={isLoading} />
|
|
83
|
+
</Card>
|
|
84
|
+
|
|
85
|
+
<Card>
|
|
86
|
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-4">Token Lookup</p>
|
|
87
|
+
<div className="flex gap-2 mb-4">
|
|
88
|
+
<input
|
|
89
|
+
type="number"
|
|
90
|
+
min="0"
|
|
91
|
+
value={tokenId}
|
|
92
|
+
onChange={e => setTokenId(e.target.value)}
|
|
93
|
+
placeholder="Token ID"
|
|
94
|
+
className="flex-1 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 font-mono text-xs text-zinc-200 placeholder-zinc-600 outline-none focus:border-yellow-400/40"
|
|
95
|
+
/>
|
|
96
|
+
<Button onClick={handleLookup} disabled={!tokenId || lookupLoading}>
|
|
97
|
+
{lookupLoading ? 'Loading…' : 'Look up'}
|
|
98
|
+
</Button>
|
|
99
|
+
</div>
|
|
100
|
+
{lookupError && (
|
|
101
|
+
<p className="font-mono text-[11px] text-red-400 mb-3">{lookupError}</p>
|
|
102
|
+
)}
|
|
103
|
+
{owner !== null && (
|
|
104
|
+
<DataRow
|
|
105
|
+
label="ownerOf()"
|
|
106
|
+
value={`${owner.slice(0, 6)}…${owner.slice(-4)}`}
|
|
107
|
+
loading={false}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
{uri !== null && (
|
|
111
|
+
<div className="pt-3">
|
|
112
|
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-1.5">tokenURI()</p>
|
|
113
|
+
<p className="font-mono text-xs text-zinc-300 break-all">{uri}</p>
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
</Card>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{/* ── Notes ──────────────────────────────────────── */}
|
|
120
|
+
<Card>
|
|
121
|
+
<p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-4">Usage Notes</p>
|
|
122
|
+
<ul className="space-y-3">
|
|
123
|
+
{[
|
|
124
|
+
['01', 'Replace NFT_ADDRESS with any ERC-721 contract on your configured chain'],
|
|
125
|
+
['02', 'useNFT requires no ABI — the SDK uses the built-in ERC-721 interface'],
|
|
126
|
+
['03', 'ownerOf(tokenId) returns the owner address of a specific token'],
|
|
127
|
+
['04', 'tokenURI(tokenId) returns the metadata URI (IPFS, Arweave, HTTP, etc.)'],
|
|
128
|
+
['05', 'balanceOf(address) counts how many tokens a given address holds'],
|
|
129
|
+
].map(([n, s]) => (
|
|
130
|
+
<li key={n} className="flex items-start gap-3">
|
|
131
|
+
<span className="font-mono text-[10px] text-yellow-400/50 mt-0.5 shrink-0 select-none">{n}</span>
|
|
132
|
+
<span className="text-sm text-zinc-400 leading-relaxed">{s}</span>
|
|
133
|
+
</li>
|
|
134
|
+
))}
|
|
135
|
+
</ul>
|
|
136
|
+
</Card>
|
|
137
|
+
</main>
|
|
138
|
+
)
|
|
139
|
+
}
|