create-awarizon-app 1.0.9 → 1.0.11

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.
Files changed (33) hide show
  1. package/dist/index.js +0 -0
  2. package/package.json +8 -8
  3. package/templates/expo/app/(tabs)/_layout.tsx +4 -3
  4. package/templates/expo/app/(tabs)/index.tsx +40 -64
  5. package/templates/expo/app/(tabs)/nft.tsx +142 -0
  6. package/templates/expo/app/(tabs)/wallet.tsx +127 -0
  7. package/templates/expo/babel.config.js +6 -0
  8. package/templates/expo/package.json +22 -23
  9. package/templates/expo-js/app/(tabs)/_layout.jsx +4 -3
  10. package/templates/expo-js/app/(tabs)/index.jsx +40 -66
  11. package/templates/expo-js/app/(tabs)/nft.jsx +141 -0
  12. package/templates/expo-js/app/(tabs)/wallet.jsx +128 -0
  13. package/templates/expo-js/package.json +19 -20
  14. package/templates/nextjs/app/layout.tsx +5 -1
  15. package/templates/nextjs/app/nft/page.tsx +140 -0
  16. package/templates/nextjs/app/page.tsx +37 -155
  17. package/templates/nextjs/app/wallet/page.tsx +116 -0
  18. package/templates/nextjs/components/Header.tsx +56 -0
  19. package/templates/nextjs/package.json +15 -16
  20. package/templates/nextjs-js/app/layout.jsx +5 -1
  21. package/templates/nextjs-js/app/nft/page.jsx +139 -0
  22. package/templates/nextjs-js/app/page.jsx +37 -128
  23. package/templates/nextjs-js/app/wallet/page.jsx +116 -0
  24. package/templates/nextjs-js/components/Header.jsx +56 -0
  25. package/templates/nextjs-js/package.json +11 -12
  26. package/templates/react/package.json +14 -15
  27. package/templates/react/src/App.tsx +88 -145
  28. package/templates/react/src/pages/NftPage.tsx +138 -0
  29. package/templates/react/src/pages/WalletPage.tsx +114 -0
  30. package/templates/react-js/package.json +11 -12
  31. package/templates/react-js/src/App.jsx +86 -118
  32. package/templates/react-js/src/pages/NftPage.jsx +137 -0
  33. package/templates/react-js/src/pages/WalletPage.jsx +114 -0
@@ -1,64 +1,41 @@
1
1
  import { ScrollView, View, Text, StyleSheet, ActivityIndicator } from 'react-native'
2
- import { useReadContract } from '@awarizon/react-native'
3
-
4
- const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
5
-
6
- /** @type {readonly object[]} */
7
- const ERC20_ABI = [
8
- { name: 'name', type: 'function', inputs: [], outputs: [{ type: 'string' }], stateMutability: 'view' },
9
- { name: 'symbol', type: 'function', inputs: [], outputs: [{ type: 'string' }], stateMutability: 'view' },
10
- { name: 'decimals', type: 'function', inputs: [], outputs: [{ type: 'uint8' }], stateMutability: 'view' },
11
- { name: 'totalSupply', type: 'function', inputs: [], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
2
+ import { usePrices } from '@awarizon/react-native'
3
+
4
+ const TOKENS = [
5
+ { symbol: 'ETH', name: 'Ethereum', color: '#627EEA' },
6
+ { symbol: 'BTC', name: 'Bitcoin', color: '#F7931A' },
7
+ { symbol: 'USDC', name: 'USD Coin', color: '#2775CA' },
8
+ { symbol: 'BNB', name: 'BNB', color: '#F3BA2F' },
9
+ { symbol: 'SOL', name: 'Solana', color: '#9945FF' },
12
10
  ]
13
11
 
14
- function usdc(fn) {
15
- return useReadContract({ address: USDC_BASE, abi: ERC20_ABI, method: fn })
16
- }
17
-
18
- function Row({ label, value }) {
19
- return (
20
- <View style={s.row}>
21
- <Text style={s.label}>{label}</Text>
22
- {value === undefined
23
- ? <ActivityIndicator size="small" color="#6366f1" />
24
- : <Text style={s.value}>{value}</Text>}
25
- </View>
26
- )
27
- }
28
-
29
12
  export default function HomeScreen() {
30
- const { data: name } = usdc('name')
31
- const { data: symbol } = usdc('symbol')
32
- const { data: decimals } = usdc('decimals')
33
- const { data: totalSupply } = usdc('totalSupply')
34
-
35
- const formatted =
36
- totalSupply !== undefined && decimals !== undefined
37
- ? (Number(totalSupply) / 10 ** Number(decimals)).toLocaleString(undefined, { maximumFractionDigits: 2 })
38
- : undefined
13
+ const { prices, isLoading } = usePrices(TOKENS.map(t => t.symbol))
39
14
 
40
15
  return (
41
16
  <ScrollView style={s.bg} contentContainerStyle={s.container}>
42
- <View style={s.hero}>
43
- <Text style={s.badge}>{{ProjectName}}</Text>
44
- <Text style={s.heading}>Live on-chain data</Text>
45
- <Text style={s.sub}>Reading USDC on Base — no backend required.</Text>
46
- </View>
47
-
48
- <View style={s.card}>
49
- <Text style={s.cardTitle}>USDC — Base</Text>
50
- <Row label="Name" value={name !== undefined ? String(name) : undefined} />
51
- <Row label="Symbol" value={symbol !== undefined ? String(symbol) : undefined} />
52
- <Row label="Decimals" value={decimals !== undefined ? String(decimals) : undefined} />
53
- <Row label="Total Supply" value={formatted} />
54
- </View>
55
-
56
- <View style={s.card}>
57
- <Text style={s.cardTitle}>Next steps</Text>
58
- <Text style={s.step}>1. Replace USDC_BASE with your contract address</Text>
59
- <Text style={s.step}>2. Swap ERC20_ABI for your contract ABI</Text>
60
- <Text style={s.step}>3. Change the chain in app/_layout.jsx</Text>
61
- <Text style={s.step}>4. Use <Text style={s.code}>useWriteContract</Text> for transactions</Text>
17
+ <View style={s.grid}>
18
+ {TOKENS.map(({ symbol, name, color }) => {
19
+ const price = prices[symbol]?.price
20
+ return (
21
+ <View key={symbol} style={s.card}>
22
+ <View style={[s.icon, { backgroundColor: color + '22', borderColor: color + '35' }]}>
23
+ <Text style={[s.iconText, { color }]}>{symbol.slice(0, 3)}</Text>
24
+ </View>
25
+ <Text style={s.symbol}>{symbol}</Text>
26
+ <Text style={s.name}>{name}</Text>
27
+ <View style={s.priceRow}>
28
+ {isLoading
29
+ ? <ActivityIndicator size="small" color="#FFE500" />
30
+ : <Text style={s.price}>
31
+ {price != null
32
+ ? `$${price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
33
+ : '—'}
34
+ </Text>}
35
+ </View>
36
+ </View>
37
+ )
38
+ })}
62
39
  </View>
63
40
  </ScrollView>
64
41
  )
@@ -66,16 +43,13 @@ export default function HomeScreen() {
66
43
 
67
44
  const s = StyleSheet.create({
68
45
  bg: { flex: 1, backgroundColor: '#0a0a0a' },
69
- container: { padding: 20, paddingBottom: 40 },
70
- hero: { marginBottom: 24 },
71
- badge: { color: '#6366f1', fontSize: 12, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1.5, marginBottom: 8 },
72
- heading: { color: '#fff', fontSize: 28, fontWeight: '800', marginBottom: 6 },
73
- sub: { color: '#9ca3af', fontSize: 15, lineHeight: 22 },
74
- card: { backgroundColor: '#111', borderWidth: 1, borderColor: '#1f1f1f', borderRadius: 12, padding: 16, marginBottom: 16 },
75
- cardTitle: { color: '#fff', fontWeight: '700', fontSize: 15, marginBottom: 12 },
76
- row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#1f1f1f' },
77
- label: { color: '#9ca3af', fontSize: 13 },
78
- value: { color: '#e5e7eb', fontSize: 13, fontWeight: '500', fontFamily: 'monospace' },
79
- step: { color: '#9ca3af', fontSize: 13, lineHeight: 22, marginBottom: 4 },
80
- code: { color: '#6366f1', fontFamily: 'monospace' },
46
+ container: { padding: 16, paddingBottom: 40 },
47
+ grid: { flexDirection: 'row', flexWrap: 'wrap', gap: 12 },
48
+ card: { width: '47%', backgroundColor: '#111827', borderWidth: 1, borderColor: '#1f2937', borderRadius: 16, padding: 16 },
49
+ icon: { width: 40, height: 40, borderRadius: 20, borderWidth: 1, alignItems: 'center', justifyContent: 'center', marginBottom: 12 },
50
+ iconText: { fontSize: 10, fontWeight: '700', letterSpacing: 0.5 },
51
+ symbol: { color: '#6b7280', fontSize: 11, fontWeight: '600', marginBottom: 2 },
52
+ name: { color: '#374151', fontSize: 11, marginBottom: 10 },
53
+ priceRow: { minHeight: 22, justifyContent: 'center' },
54
+ price: { color: '#f9fafb', fontSize: 16, fontWeight: '600', letterSpacing: -0.3 },
81
55
  })
@@ -0,0 +1,141 @@
1
+ import { useState } from 'react'
2
+ import { ScrollView, View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'
3
+ import { contract, useRead, useSDK } from '@awarizon/react-native'
4
+ import { ERC721_ABI } from '@awarizon/web3'
5
+
6
+ // Replace with your ERC-721 contract address on your configured chain.
7
+ const NFT_ADDRESS = '0x0000000000000000000000000000000000000000'
8
+ const NFT = contract(NFT_ADDRESS, ERC721_ABI)
9
+
10
+ function Row({ label, value }) {
11
+ return (
12
+ <View style={s.row}>
13
+ <Text style={s.label}>{label}</Text>
14
+ {value === undefined
15
+ ? <ActivityIndicator size="small" color="#FFE500" />
16
+ : <Text style={s.value}>{value}</Text>}
17
+ </View>
18
+ )
19
+ }
20
+
21
+ export default function NftScreen() {
22
+ const { data: name, isLoading: l1, error: e1 } = useRead(NFT.name())
23
+ const { data: symbol, isLoading: l2, error: e2 } = useRead(NFT.symbol())
24
+ const awarizon = useSDK()
25
+
26
+ const [tokenId, setTokenId] = useState('')
27
+ const [owner, setOwner] = useState(null)
28
+ const [uri, setUri] = useState(null)
29
+ const [lookupLoading, setLookupLoading] = useState(false)
30
+ const [lookupError, setLookupError] = useState(null)
31
+
32
+ const collectionError = e1?.message ?? e2?.message ?? null
33
+
34
+ async function handleLookup() {
35
+ if (!tokenId) return
36
+ setLookupLoading(true)
37
+ setLookupError(null)
38
+ setOwner(null)
39
+ setUri(null)
40
+ try {
41
+ const id = BigInt(tokenId)
42
+ const nft = await awarizon.contract({ address: NFT_ADDRESS, abi: ERC721_ABI })
43
+ const [o, u] = await Promise.all([nft.ownerOf(id), nft.tokenURI(id)])
44
+ setOwner(String(o))
45
+ setUri(String(u))
46
+ } catch (e) {
47
+ setLookupError(e instanceof Error ? e.message : 'Lookup failed')
48
+ } finally {
49
+ setLookupLoading(false)
50
+ }
51
+ }
52
+
53
+ return (
54
+ <ScrollView style={s.bg} contentContainerStyle={s.container}>
55
+ <View style={s.hero}>
56
+ <Text style={s.badge}>ERC-721</Text>
57
+ <Text style={s.heading}>NFT Collection</Text>
58
+ <Text style={s.sub}>
59
+ Replace NFT_ADDRESS in this file with your ERC-721 contract address.
60
+ </Text>
61
+ </View>
62
+
63
+ {collectionError && (
64
+ <View style={s.errorBox}>
65
+ <Text style={s.errorText}>{collectionError}</Text>
66
+ </View>
67
+ )}
68
+
69
+ <View style={s.card}>
70
+ <Text style={s.cardTitle}>Collection Info</Text>
71
+ <Text style={s.hint}>{NFT_ADDRESS.slice(0, 6)}…{NFT_ADDRESS.slice(-4)}</Text>
72
+ <Row label="name()" value={l1 ? undefined : name ?? '—'} />
73
+ <Row label="symbol()" value={l2 ? undefined : symbol ?? '—'} />
74
+ </View>
75
+
76
+ <View style={s.card}>
77
+ <Text style={s.cardTitle}>Token Lookup</Text>
78
+ <View style={s.inputRow}>
79
+ <TextInput
80
+ style={s.input}
81
+ value={tokenId}
82
+ onChangeText={setTokenId}
83
+ placeholder="Token ID"
84
+ placeholderTextColor="#4b5563"
85
+ keyboardType="numeric"
86
+ />
87
+ <TouchableOpacity
88
+ style={[s.button, (!tokenId || lookupLoading) && s.buttonDisabled]}
89
+ onPress={handleLookup}
90
+ disabled={!tokenId || lookupLoading}
91
+ >
92
+ <Text style={s.buttonText}>{lookupLoading ? 'Loading…' : 'Look up'}</Text>
93
+ </TouchableOpacity>
94
+ </View>
95
+ {lookupError && <Text style={s.errorText}>{lookupError}</Text>}
96
+ {owner !== null && (
97
+ <Row label="ownerOf()" value={`${owner.slice(0, 6)}…${owner.slice(-4)}`} />
98
+ )}
99
+ {uri !== null && (
100
+ <View>
101
+ <Text style={s.hint}>tokenURI()</Text>
102
+ <Text style={s.uriText}>{uri}</Text>
103
+ </View>
104
+ )}
105
+ </View>
106
+
107
+ <View style={s.card}>
108
+ <Text style={s.cardTitle}>Usage Notes</Text>
109
+ <Text style={s.step}>1. Replace NFT_ADDRESS at the top of this file</Text>
110
+ <Text style={s.step}>2. No ABI needed — ERC721_ABI is built into the SDK</Text>
111
+ <Text style={s.step}>3. Enter a token ID and tap Look up to call <Text style={s.code}>ownerOf</Text> and <Text style={s.code}>tokenURI</Text></Text>
112
+ <Text style={s.step}>4. Use <Text style={s.code}>awarizon.contract()</Text> for on-demand reads in handlers</Text>
113
+ </View>
114
+ </ScrollView>
115
+ )
116
+ }
117
+
118
+ const s = StyleSheet.create({
119
+ bg: { flex: 1, backgroundColor: '#0a0a0a' },
120
+ container: { padding: 20, paddingBottom: 40 },
121
+ hero: { marginBottom: 24 },
122
+ badge: { color: '#FFE500', fontSize: 12, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1.5, marginBottom: 8 },
123
+ heading: { color: '#fff', fontSize: 28, fontWeight: '800', marginBottom: 6 },
124
+ sub: { color: '#9ca3af', fontSize: 15, lineHeight: 22 },
125
+ card: { backgroundColor: '#111', borderWidth: 1, borderColor: '#1f1f1f', borderRadius: 12, padding: 16, marginBottom: 16 },
126
+ cardTitle: { color: '#fff', fontWeight: '700', fontSize: 15, marginBottom: 12 },
127
+ hint: { color: '#4b5563', fontSize: 11, marginBottom: 8 },
128
+ row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#1f1f1f' },
129
+ label: { color: '#9ca3af', fontSize: 13 },
130
+ value: { color: '#e5e7eb', fontSize: 13, fontWeight: '500', fontFamily: 'monospace' },
131
+ inputRow: { flexDirection: 'row', gap: 8, marginBottom: 12 },
132
+ input: { flex: 1, backgroundColor: '#1a1a1a', borderWidth: 1, borderColor: '#333', borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, color: '#e5e7eb', fontFamily: 'monospace', fontSize: 13 },
133
+ button: { backgroundColor: '#FFE500', borderRadius: 8, paddingHorizontal: 16, paddingVertical: 8, justifyContent: 'center' },
134
+ buttonDisabled: { opacity: 0.4 },
135
+ buttonText: { color: '#000', fontWeight: '700', fontSize: 12 },
136
+ errorBox: { backgroundColor: 'rgba(239,68,68,0.1)', borderWidth: 1, borderColor: 'rgba(239,68,68,0.2)', borderRadius: 8, padding: 12, marginBottom: 16 },
137
+ errorText: { color: '#f87171', fontSize: 12, fontFamily: 'monospace' },
138
+ uriText: { color: '#e5e7eb', fontSize: 12, fontFamily: 'monospace', marginTop: 4 },
139
+ step: { color: '#9ca3af', fontSize: 13, lineHeight: 22, marginBottom: 4 },
140
+ code: { color: '#FFE500', fontFamily: 'monospace' },
141
+ })
@@ -0,0 +1,128 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { ScrollView, View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'
3
+ import { useSDK, useRNWallet, usePrices } from '@awarizon/react-native'
4
+
5
+ const SYMBOLS = ['ETH', 'BTC', 'USDC']
6
+
7
+ function Row({ label, value, loading = false }) {
8
+ return (
9
+ <View style={s.row}>
10
+ <Text style={s.label}>{label}</Text>
11
+ {loading
12
+ ? <ActivityIndicator size="small" color="#FFE500" />
13
+ : <Text style={s.value}>{value ?? '—'}</Text>}
14
+ </View>
15
+ )
16
+ }
17
+
18
+ export default function WalletScreen() {
19
+ const awarizon = useSDK()
20
+ const { address, isConnected, isLoading: walletLoading, create } = useRNWallet({ awarizon })
21
+ const { prices, isLoading: pricesLoading } = usePrices(SYMBOLS)
22
+
23
+ const [nativeBalance, setNativeBalance] = useState(null)
24
+ const [balanceLoading, setBalanceLoading] = useState(false)
25
+
26
+ useEffect(() => {
27
+ if (!address) { setNativeBalance(null); return }
28
+ setBalanceLoading(true)
29
+ awarizon.publicClient
30
+ .getBalance({ address })
31
+ .then(bal => {
32
+ const eth = Number(bal) / 1e18
33
+ setNativeBalance(eth.toFixed(6))
34
+ })
35
+ .catch(() => setNativeBalance(null))
36
+ .finally(() => setBalanceLoading(false))
37
+ }, [address, awarizon])
38
+
39
+ const fmtPrice = (sym) =>
40
+ prices[sym]?.price != null
41
+ ? `$${prices[sym].price.toLocaleString(undefined, { maximumFractionDigits: 2 })}`
42
+ : '—'
43
+
44
+ const ethFiatValue =
45
+ nativeBalance != null && prices['ETH']?.price != null
46
+ ? `$${(parseFloat(nativeBalance) * prices['ETH'].price).toLocaleString(undefined, { maximumFractionDigits: 2 })}`
47
+ : null
48
+
49
+ return (
50
+ <ScrollView style={s.bg} contentContainerStyle={s.container}>
51
+ <View style={s.hero}>
52
+ <Text style={s.badge}>Portfolio</Text>
53
+ <Text style={s.heading}>Wallet</Text>
54
+ <Text style={s.sub}>
55
+ Native balance and market prices via <Text style={s.code}>useRNWallet</Text> and <Text style={s.code}>usePrices</Text>.
56
+ </Text>
57
+ </View>
58
+
59
+ {!isConnected ? (
60
+ <View style={s.card}>
61
+ <Text style={s.cardTitle}>No Wallet</Text>
62
+ <Text style={s.sub}>Create a wallet to view your balance and portfolio.</Text>
63
+ <TouchableOpacity
64
+ style={[s.button, walletLoading && s.buttonDisabled]}
65
+ onPress={create}
66
+ disabled={walletLoading}
67
+ >
68
+ {walletLoading
69
+ ? <ActivityIndicator size="small" color="#000" />
70
+ : <Text style={s.buttonText}>Create Wallet</Text>}
71
+ </TouchableOpacity>
72
+ </View>
73
+ ) : (
74
+ <>
75
+ <View style={s.card}>
76
+ <Text style={s.cardTitle}>Account</Text>
77
+ <Row label="Address" value={address ? `${address.slice(0, 6)}…${address.slice(-4)}` : undefined} />
78
+ </View>
79
+
80
+ <View style={s.card}>
81
+ <Text style={s.cardTitle}>Balance</Text>
82
+ <Row
83
+ label="Native"
84
+ value={nativeBalance != null ? `${nativeBalance} ETH` : undefined}
85
+ loading={balanceLoading}
86
+ />
87
+ <Row
88
+ label="Est. Value"
89
+ value={ethFiatValue ?? undefined}
90
+ loading={balanceLoading || pricesLoading}
91
+ />
92
+ <Text style={s.hint}>via publicClient.getBalance · ETH × market price</Text>
93
+ </View>
94
+
95
+ <View style={s.card}>
96
+ <Text style={s.cardTitle}>Market Prices</Text>
97
+ {pricesLoading
98
+ ? <ActivityIndicator size="small" color="#FFE500" style={{ marginVertical: 8 }} />
99
+ : SYMBOLS.map(sym => (
100
+ <Row key={sym} label={sym} value={fmtPrice(sym)} />
101
+ ))
102
+ }
103
+ <Text style={s.hint}>via usePrices · Chainlink + CoinGecko fallback</Text>
104
+ </View>
105
+ </>
106
+ )}
107
+ </ScrollView>
108
+ )
109
+ }
110
+
111
+ const s = StyleSheet.create({
112
+ bg: { flex: 1, backgroundColor: '#0a0a0a' },
113
+ container: { padding: 20, paddingBottom: 40 },
114
+ hero: { marginBottom: 24 },
115
+ badge: { color: '#FFE500', fontSize: 12, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1.5, marginBottom: 8 },
116
+ heading: { color: '#fff', fontSize: 28, fontWeight: '800', marginBottom: 6 },
117
+ sub: { color: '#9ca3af', fontSize: 15, lineHeight: 22 },
118
+ card: { backgroundColor: '#111', borderWidth: 1, borderColor: '#1f1f1f', borderRadius: 12, padding: 16, marginBottom: 16 },
119
+ cardTitle: { color: '#fff', fontWeight: '700', fontSize: 15, marginBottom: 12 },
120
+ hint: { color: '#4b5563', fontSize: 11, marginTop: 8 },
121
+ row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#1f1f1f' },
122
+ label: { color: '#9ca3af', fontSize: 13 },
123
+ value: { color: '#e5e7eb', fontSize: 13, fontWeight: '500', fontFamily: 'monospace' },
124
+ button: { backgroundColor: '#FFE500', borderRadius: 8, paddingHorizontal: 16, paddingVertical: 10, alignItems: 'center', marginTop: 16 },
125
+ buttonDisabled: { opacity: 0.4 },
126
+ buttonText: { color: '#000', fontWeight: '700', fontSize: 13 },
127
+ code: { color: '#FFE500', fontFamily: 'monospace' },
128
+ })
@@ -3,32 +3,31 @@
3
3
  "version": "1.0.0",
4
4
  "main": "expo-router/entry",
5
5
  "scripts": {
6
- "start": "expo start",
6
+ "start": "expo start",
7
7
  "android": "expo start --android",
8
- "ios": "expo start --ios",
9
- "web": "expo start --web"
8
+ "ios": "expo start --ios",
9
+ "web": "expo start --web"
10
10
  },
11
11
  "dependencies": {
12
- "@awarizon/react-native": "^1.2.0",
13
- "@awarizon/web3": "^1.3.4",
14
- "@expo/vector-icons": "^14.0.2",
15
- "expo": "~51.0.0",
16
- "expo-constants": "~16.0.2",
17
- "expo-font": "~12.0.9",
18
- "expo-linking": "~6.3.1",
19
- "expo-router": "~3.5.17",
20
- "expo-secure-store": "~13.0.2",
21
- "expo-splash-screen": "~0.27.5",
22
- "expo-status-bar": "~1.12.1",
23
- "expo-system-ui": "~3.0.7",
24
- "react": "18.2.0",
25
- "react-dom": "18.2.0",
26
- "react-native": "0.74.5",
12
+ "@awarizon/react-native": "^1.2.1",
13
+ "@awarizon/web3": "^1.3.5",
14
+ "@expo/vector-icons": "^14.0.2",
15
+ "expo": "~51.0.0",
16
+ "expo-constants": "~16.0.2",
17
+ "expo-font": "~12.0.9",
18
+ "expo-linking": "~6.3.1",
19
+ "expo-router": "~3.5.17",
20
+ "expo-secure-store": "~13.0.2",
21
+ "expo-splash-screen": "~0.27.5",
22
+ "expo-status-bar": "~1.12.1",
23
+ "expo-system-ui": "~3.0.7",
24
+ "react": "18.2.0",
25
+ "react-dom": "18.2.0",
26
+ "react-native": "0.74.5",
27
27
  "react-native-safe-area-context": "4.10.5",
28
- "react-native-screens": "3.31.1"
28
+ "react-native-screens": "3.31.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@babel/core": "^7.24.0"
32
32
  }
33
33
  }
34
-
@@ -1,5 +1,6 @@
1
1
  import type { Metadata } from 'next'
2
2
  import { Providers } from '@/components/Providers'
3
+ import { Header } from '@/components/Header'
3
4
  import './globals.css'
4
5
 
5
6
  export const metadata: Metadata = {
@@ -11,7 +12,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
11
12
  return (
12
13
  <html lang="en">
13
14
  <body>
14
- <Providers>{children}</Providers>
15
+ <Providers>
16
+ <Header />
17
+ {children}
18
+ </Providers>
15
19
  </body>
16
20
  </html>
17
21
  )
@@ -0,0 +1,140 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useNFT } from '@awarizon/react'
5
+ import type { Address } from '@awarizon/web3'
6
+ import { Badge } from '@/components/ui/Badge'
7
+ import { Button } from '@/components/ui/Button'
8
+ import { Card } from '@/components/ui/Card'
9
+ import { Skeleton } from '@/components/ui/Skeleton'
10
+
11
+ // Replace with your ERC-721 contract address on your configured chain.
12
+ const NFT_ADDRESS = '0x0000000000000000000000000000000000000000' as Address
13
+
14
+ function DataRow({ label, value, loading }: { label: string; value?: string; loading: boolean }) {
15
+ return (
16
+ <div className="flex items-center justify-between py-3 border-b border-zinc-800/50 last:border-0">
17
+ <code className="text-[11px] text-yellow-400/70 font-mono">{label}</code>
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
+ }
24
+
25
+ export default function NftPage() {
26
+ const { name, symbol, isLoading, error, ownerOf, tokenURI } = useNFT(NFT_ADDRESS)
27
+
28
+ const [tokenId, setTokenId] = useState('')
29
+ const [owner, setOwner] = useState<string | null>(null)
30
+ const [uri, setUri] = useState<string | null>(null)
31
+ const [lookupLoading, setLookupLoading] = useState(false)
32
+ const [lookupError, setLookupError] = useState<string | null>(null)
33
+
34
+ async function handleLookup() {
35
+ if (!tokenId) return
36
+ setLookupLoading(true)
37
+ setLookupError(null)
38
+ setOwner(null)
39
+ setUri(null)
40
+ try {
41
+ const id = BigInt(tokenId)
42
+ const [o, u] = await Promise.all([ownerOf(id), tokenURI(id)])
43
+ setOwner(o)
44
+ setUri(u)
45
+ } catch (e) {
46
+ setLookupError(e instanceof Error ? e.message : 'Lookup failed')
47
+ } finally {
48
+ setLookupLoading(false)
49
+ }
50
+ }
51
+
52
+ return (
53
+ <main className="max-w-4xl mx-auto px-6 py-12">
54
+
55
+ {/* ── Hero ───────────────────────────────────────── */}
56
+ <div className="mb-10">
57
+ <Badge variant="outline" className="mb-5">ERC-721</Badge>
58
+ <h1 className="text-4xl font-bold tracking-tight text-white mb-3">NFT Collection</h1>
59
+ <p className="text-zinc-400 text-base max-w-lg leading-relaxed">
60
+ Zero-ABI reads from any ERC-721 contract using{' '}
61
+ <code className="font-mono text-[11px] text-yellow-400/80">useNFT</code>.
62
+ Replace <code className="font-mono text-[11px] text-yellow-400/80">NFT_ADDRESS</code> at the top of this file.
63
+ </p>
64
+ </div>
65
+
66
+ {error && (
67
+ <div className="mb-4 px-4 py-3 bg-red-500/10 border border-red-500/20 rounded-xl">
68
+ <code className="font-mono text-xs text-red-400">{error.message}</code>
69
+ </div>
70
+ )}
71
+
72
+ {/* ── Cards ──────────────────────────────────────── */}
73
+ <div className="grid md:grid-cols-2 gap-4 mb-4">
74
+
75
+ <Card>
76
+ <div className="mb-4">
77
+ <p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-1.5">Collection Info</p>
78
+ <code className="font-mono text-xs text-zinc-500">
79
+ {NFT_ADDRESS.slice(0, 6)}…{NFT_ADDRESS.slice(-4)}
80
+ </code>
81
+ </div>
82
+ <DataRow label="name()" value={name ?? undefined} loading={isLoading} />
83
+ <DataRow label="symbol()" value={symbol ?? undefined} loading={isLoading} />
84
+ </Card>
85
+
86
+ <Card>
87
+ <p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-4">Token Lookup</p>
88
+ <div className="flex gap-2 mb-4">
89
+ <input
90
+ type="number"
91
+ min="0"
92
+ value={tokenId}
93
+ onChange={e => setTokenId(e.target.value)}
94
+ placeholder="Token ID"
95
+ 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"
96
+ />
97
+ <Button onClick={handleLookup} disabled={!tokenId || lookupLoading}>
98
+ {lookupLoading ? 'Loading…' : 'Look up'}
99
+ </Button>
100
+ </div>
101
+ {lookupError && (
102
+ <p className="font-mono text-[11px] text-red-400 mb-3">{lookupError}</p>
103
+ )}
104
+ {owner !== null && (
105
+ <DataRow
106
+ label="ownerOf()"
107
+ value={`${owner.slice(0, 6)}…${owner.slice(-4)}`}
108
+ loading={false}
109
+ />
110
+ )}
111
+ {uri !== null && (
112
+ <div className="pt-3">
113
+ <p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-1.5">tokenURI()</p>
114
+ <p className="font-mono text-xs text-zinc-300 break-all">{uri}</p>
115
+ </div>
116
+ )}
117
+ </Card>
118
+ </div>
119
+
120
+ {/* ── Notes ──────────────────────────────────────── */}
121
+ <Card>
122
+ <p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase mb-4">Usage Notes</p>
123
+ <ul className="space-y-3">
124
+ {[
125
+ ['01', 'Replace NFT_ADDRESS with any ERC-721 contract on your configured chain'],
126
+ ['02', 'useNFT requires no ABI — the SDK uses the built-in ERC-721 interface'],
127
+ ['03', 'ownerOf(tokenId) returns the owner address of a specific token'],
128
+ ['04', 'tokenURI(tokenId) returns the metadata URI (IPFS, Arweave, HTTP, etc.)'],
129
+ ['05', 'balanceOf(address) counts how many tokens a given address holds'],
130
+ ].map(([n, s]) => (
131
+ <li key={n} className="flex items-start gap-3">
132
+ <span className="font-mono text-[10px] text-yellow-400/50 mt-0.5 shrink-0 select-none">{n}</span>
133
+ <span className="text-sm text-zinc-400 leading-relaxed">{s}</span>
134
+ </li>
135
+ ))}
136
+ </ul>
137
+ </Card>
138
+ </main>
139
+ )
140
+ }