create-sbc-app 0.2.0 → 0.3.1

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/README.md CHANGED
@@ -24,7 +24,7 @@ pnpm dev # or npm run dev
24
24
  ## CLI Options
25
25
 
26
26
  ```bash
27
- Usage: create-sbc-app [project-directory] [options]
27
+ Usage: npx create-sbc-app [project-directory] [options]
28
28
 
29
29
  Create a new SBC App Kit project with an opinionated template
30
30
 
@@ -34,21 +34,26 @@ Arguments:
34
34
  Options:
35
35
  -V, --version output the version number
36
36
  -t, --template <type> Template to use: react, react-dynamic, or react-para
37
+ -c, --chain <chain> Chain to use: baseSepolia, base, or radiusTestnet
37
38
  --api-key <apiKey> Your SBC API key for immediate configuration
38
39
  --wallet <wallet> Wallet integration (not yet implemented)
39
40
  -h, --help display help for command
40
41
 
41
42
  Examples:
42
- $ create-sbc-app my-app
43
- $ create-sbc-app my-app --template react
44
- $ create-sbc-app my-app --template react-dynamic
45
- $ create-sbc-app my-app --template react-para
46
- # Next.js template removed for now
43
+ $ npx create-sbc-app my-app
44
+ $ npx create-sbc-app my-app --template react --chain radiusTestnet
45
+ $ npx create-sbc-app my-app --template react-dynamic --chain base
46
+ $ npx create-sbc-app my-app --template react-para --api-key your-key
47
47
 
48
48
  Available Templates:
49
49
  - react React + Vite template with SBC integration
50
50
  - react-dynamic React + Vite with Dynamic wallet integration
51
51
  - react-para React + Vite with Para wallet integration
52
+
53
+ Available Chains:
54
+ - baseSepolia Base Sepolia testnet (default)
55
+ - base Base mainnet
56
+ - radiusTestnet Radius testnet (react template only)
52
57
  ```
53
58
 
54
59
  ## ✨ Features
@@ -81,7 +86,9 @@ The React template includes comprehensive, production-ready examples:
81
86
  - **Hot Reload** - Fast development iteration
82
87
  - **Production Ready** - Optimized builds and deployment preparation
83
88
 
84
- ## 🚀 React Template
89
+ ## 🚀 Templates
90
+
91
+ ### React Template (Default)
85
92
 
86
93
  **Best for:** Client-side applications, rapid prototyping, and production use
87
94
 
@@ -89,14 +96,52 @@ The React template includes comprehensive, production-ready examples:
89
96
  npx create-sbc-app my-app
90
97
  ```
91
98
 
92
- **Features:**
99
+ **Supported Chains:** Base Sepolia, Base, Radius Testnet
93
100
 
101
+ **Features:**
94
102
  - Fast development setup
95
103
  - Hot module replacement
104
+ - Direct wallet connection (MetaMask, Coinbase, WalletConnect)
96
105
  - Built-in testing framework
97
106
  - Easy deployment to static hosts
98
107
  - Modern React patterns and hooks
99
108
 
109
+ ### React + Dynamic Template
110
+
111
+ **Best for:** Applications requiring embedded wallets with social logins
112
+
113
+ ```bash
114
+ npx create-sbc-app my-app --template react-dynamic
115
+ ```
116
+
117
+ **Supported Chains:** Base Sepolia, Base (Radius Testnet not supported)
118
+
119
+ **Features:**
120
+ - Dynamic SDK integration for embedded wallets
121
+ - Social login support (Google, Twitter, Discord, etc.)
122
+ - Email/SMS wallet creation
123
+ - All standard SBC features
124
+
125
+ **Additional Requirements:** Dynamic Environment ID from [Dynamic Dashboard](https://app.dynamic.xyz/)
126
+
127
+ ### React + Para Template
128
+
129
+ **Best for:** DeFi applications leveraging EIP-2612 permits
130
+
131
+ ```bash
132
+ npx create-sbc-app my-app --template react-para
133
+ ```
134
+
135
+ **Supported Chains:** Base Sepolia, Base (Radius Testnet not supported)
136
+
137
+ **Features:**
138
+ - Para wallet integration
139
+ - EIP-2612 permit signatures
140
+ - Gasless token approvals
141
+ - All standard SBC features
142
+
143
+ **Additional Requirements:** Para API Key from [Para](https://para.xyz/)
144
+
100
145
  ## 📝 Configuration
101
146
 
102
147
  ### Environment Variables
@@ -108,7 +153,8 @@ The template includes comprehensive environment configuration:
108
153
  ```bash
109
154
  # Your SBC API key (get from SBC dashboard)
110
155
  VITE_SBC_API_KEY=your_api_key_here
111
- # "base" or "baseSepolia"
156
+
157
+ # Supported chains: "baseSepolia" | "base" | "radiusTestnet"
112
158
  VITE_CHAIN="baseSepolia"
113
159
  ```
114
160
 
@@ -139,9 +185,9 @@ cp .env.template .env
139
185
 
140
186
  # then ensure your .env has the environment variables set up
141
187
 
142
- # "base" or "baseSepolia"
143
- VITE_CHAIN="baseSepolia"
144
- # Custom RPC URL (optional) - e.g. get one from Alchemey at https://dashboard.alchemy.com/apps
188
+ # Supported chains: "baseSepolia" | "base" | "radiusTestnet"
189
+ VITE_CHAIN="baseSepolia"
190
+ # Custom RPC URL (optional) - e.g. get one from Alchemy at https://dashboard.alchemy.com/apps
145
191
  VITE_RPC_URL=
146
192
  # Get your SBC API Key at https://dashboard.stablecoin.xyz
147
193
  VITE_SBC_API_KEY=
package/bin/cli.js CHANGED
@@ -13,18 +13,24 @@ program
13
13
  .version('0.2.0')
14
14
  .argument('[project-directory]', 'Directory to create the new app in')
15
15
  .option('-t, --template <template>', 'Template to use: react, react-dynamic, or react-para')
16
+ .option('-c, --chain <chain>', 'Chain to use: baseSepolia, base, or radiusTestnet')
16
17
  .option('--api-key <apiKey>', 'Your SBC API key for immediate configuration')
17
18
  .option('--wallet <wallet>', 'Wallet integration (not yet implemented)')
18
19
  .addHelpText('after', `
19
20
  Examples:
20
21
  $ create-sbc-app my-app
21
- $ create-sbc-app my-app --template react
22
+ $ create-sbc-app my-app --template react --chain radiusTestnet
22
23
  $ create-sbc-app my-app --template react --api-key your-api-key
23
24
 
24
25
  Available Templates:
25
26
  - react React + Vite template with SBC integration
26
27
  - react-dynamic React + Vite with Dynamic wallet integration
27
28
  - react-para React + Vite with Para wallet integration
29
+
30
+ Available Chains:
31
+ - baseSepolia Base Sepolia testnet (default)
32
+ - base Base mainnet
33
+ - radiusTestnet Radius testnet
28
34
  `)
29
35
  .action(async (dir, options) => {
30
36
  if (options.wallet) {
@@ -36,6 +42,11 @@ Available Templates:
36
42
  { title: 'React (Dynamic wallet)', value: 'react-dynamic' },
37
43
  { title: 'React (Para wallet)', value: 'react-para' }
38
44
  ];
45
+ const chainChoices = [
46
+ { title: 'Base Sepolia (testnet)', value: 'baseSepolia' },
47
+ { title: 'Base (mainnet)', value: 'base' },
48
+ { title: 'Radius Testnet', value: 'radiusTestnet' }
49
+ ];
39
50
  // Use provided argument or prompt for project directory
40
51
  let projectDir = dir && dir.trim() ? dir.trim() : '';
41
52
  if (!projectDir) {
@@ -69,6 +80,26 @@ Available Templates:
69
80
  process.exit(1);
70
81
  }
71
82
  }
83
+ // Use provided option or prompt for chain
84
+ let chain = options.chain && ['baseSepolia', 'base', 'radiusTestnet'].includes(options.chain) ? options.chain : '';
85
+ if (!chain) {
86
+ const res = await prompts({
87
+ type: 'select',
88
+ name: 'chain',
89
+ message: 'Which chain?',
90
+ choices: chainChoices,
91
+ initial: 0
92
+ });
93
+ if (res.chain === undefined) {
94
+ console.log('Chain selection is required.');
95
+ process.exit(1);
96
+ }
97
+ chain = res.chain;
98
+ if (!chain || !['baseSepolia', 'base', 'radiusTestnet'].includes(chain)) {
99
+ console.log('Chain selection is required.');
100
+ process.exit(1);
101
+ }
102
+ }
72
103
  // Use provided option or prompt for API key
73
104
  let apiKey = options.apiKey && options.apiKey.trim() ? options.apiKey.trim() : '';
74
105
  if (!apiKey) {
@@ -96,7 +127,7 @@ Available Templates:
96
127
  }
97
128
  await copyTemplate(templateDir, targetDir, {
98
129
  projectName: projectDir,
99
- chain: 'baseSepolia',
130
+ chain: chain,
100
131
  apiKey: apiKey
101
132
  });
102
133
  // Ensure SBC logo exists in public/ for all templates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sbc-app",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Scaffold a new SBC App Kit project with one command.",
5
5
  "bin": {
6
6
  "create-sbc-app": "bin/cli.js"
@@ -1,17 +1,75 @@
1
- # create-sbc-app
1
+ # create-sbc-app Templates
2
2
 
3
3
  This directory contains ready-to-use templates for quickly starting new projects with the SBC App Kit. Each template is a minimal, fully functional app that demonstrates best practices for integrating @stablecoin.xyz/core and @stablecoin.xyz/react.
4
4
 
5
5
  ## Available Templates
6
6
 
7
- - **react/**Minimal React app with SBC integration (Vite)
7
+ ### react/ – React + Vite + SBC App Kit (Default)
8
+
9
+ **Best for:** Applications with custom wallet integrations, rapid prototyping, production use
10
+
11
+ **Supported Chains:** Base Sepolia, Base, Radius Testnet
12
+
13
+ **Features:**
14
+ - Direct wallet connection (MetaMask, Coinbase Wallet, WalletConnect)
15
+ - Complete SBC App Kit integration for gasless transactions
16
+ - Full control over wallet connection UI/UX
17
+ - TypeScript support
18
+ - Vite for fast development
19
+
20
+ **Use when:** You want maximum flexibility and plan to implement your own wallet connection logic or use standard Web3 wallets.
21
+
22
+ ### react-dynamic/ – React + Dynamic + SBC App Kit
23
+
24
+ **Best for:** Applications requiring embedded wallets with social logins
25
+
26
+ **Supported Chains:** Base Sepolia, Base (Radius Testnet not supported by Dynamic)
27
+
28
+ **Features:**
29
+ - Dynamic SDK integration for embedded wallets
30
+ - Social authentication (Google, Twitter, Discord, GitHub, Apple, etc.)
31
+ - Email and SMS wallet creation
32
+ - Seamless onboarding for non-crypto users
33
+ - Multi-chain support (Base and Base Sepolia)
34
+ - All SBC App Kit features
35
+
36
+ **Use when:** You want to provide the easiest possible onboarding experience with social logins and don't want users to install a wallet extension.
37
+
38
+ **Additional Requirements:**
39
+ - Dynamic Environment ID (get from https://app.dynamic.xyz/)
40
+
41
+ ### react-para/ – React + Para + SBC App Kit
42
+
43
+ **Best for:** DeFi applications leveraging EIP-2612 permits and advanced gasless patterns
44
+
45
+ **Supported Chains:** Base Sepolia, Base (Radius Testnet not supported by Para)
46
+
47
+ **Features:**
48
+ - Para wallet integration
49
+ - EIP-2612 permit signature support
50
+ - Gasless token approvals
51
+ - Single-step token operations (no separate approval transaction)
52
+ - Advanced meta-transaction patterns
53
+ - All SBC App Kit features
54
+
55
+ **Use when:** You're building DeFi applications (DEX, lending, staking) and want to optimize the user experience by eliminating approval transactions.
56
+
57
+ **Additional Requirements:**
58
+ - Para API Key (get from https://para.xyz/)
8
59
 
9
60
  ## How to Use a Template
10
61
 
11
62
  1. **Copy the template directory** you want to use:
12
63
 
13
64
  ```bash
65
+ # Plain React template
14
66
  cp -r create-sbc-app/react my-new-sbc-app
67
+
68
+ # Dynamic wallet template
69
+ cp -r create-sbc-app/react-dynamic my-dynamic-app
70
+
71
+ # Para wallet template
72
+ cp -r create-sbc-app/react-para my-para-app
15
73
  ```
16
74
 
17
75
  2. **Install dependencies:**
@@ -32,8 +90,8 @@ This directory contains ready-to-use templates for quickly starting new projects
32
90
  ```
33
91
 
34
92
  4. **Customize as needed:**
35
- - Update the API key and config in `src/App.tsx` or `app/page.tsx`.
36
- - Follow the template’s README for more details.
93
+ - Update the API key and config in `src/App.tsx`.
94
+ - Follow each template’s README for specific details and environment variables.
37
95
 
38
96
  ## Keeping Templates Up to Date
39
97
 
@@ -1,5 +1,6 @@
1
1
  # SBC App Kit Configuration
2
- VITE_CHAIN="baseSepolia"
2
+ # Supported chains: "baseSepolia" | "base" | "radiusTestnet"
3
+ VITE_CHAIN="{{chain}}"
3
4
  # Custom RPC URL (optional) - e.g. get one from Alchemey at https://dashboard.alchemy.com/apps
4
5
  VITE_RPC_URL=
5
6
  # Get your SBC API Key at https://dashboard.stablecoin.xyz
@@ -18,12 +18,12 @@ cp .env.example .env # Optional: for local overrides
18
18
 
19
19
  Edit `.env` and add your SBC API key and Chain, with the optional for a custom RPC:
20
20
  ```bash
21
- # "base" or "baseSepolia"
22
- VITE_CHAIN="baseSepolia"
23
- # Custom RPC URL (optional) - e.g. get one from Alchemey at https://dashboard.alchemy.com/apps
21
+ # Supported chains: "baseSepolia" | "base" | "radiusTestnet"
22
+ VITE_CHAIN={{chain}}
23
+ # Custom RPC URL (optional) - e.g. get one from Alchemy at https://dashboard.alchemy.com/apps
24
24
  VITE_RPC_URL=
25
25
  # Get your SBC API Key at https://dashboard.stablecoin.xyz
26
- VITE_SBC_API_KEY=
26
+ VITE_SBC_API_KEY={{apiKey}}
27
27
  ```
28
28
 
29
29
  > **Get your API key:** Visit the [SBC Dashboard](https://dashboard.stablecoin.xyz) to create an account and get your API key.
@@ -108,13 +108,12 @@ This app is configured for **{{chain}}**. To use a different chain:
108
108
 
109
109
  1. Update `.env`:
110
110
  ```bash
111
- VITE_SBC_CHAIN=your_chain_here
111
+ VITE_CHAIN=base # Base mainnet
112
+ VITE_CHAIN=baseSepolia # Base Sepolia testnet
113
+ VITE_CHAIN=radiusTestnet # Radius testnet
112
114
  ```
113
115
 
114
- 2. Update the import in `App.tsx`:
115
- ```typescript
116
- import { yourChain } from 'viem/chains';
117
- ```
116
+ 2. Restart your development server after changing the chain
118
117
 
119
118
  ## 📖 Learn More
120
119
 
@@ -1,19 +1,32 @@
1
1
  import { useState, useEffect, useRef, createContext, useContext } from 'react';
2
2
  import { SbcProvider, WalletButton, useSbcApp, useUserOperation } from '@stablecoin.xyz/react';
3
+ import { radiusTestnet, TestSBC_CONTRACT_ADDRESS } from '@stablecoin.xyz/core';
3
4
  import { base, baseSepolia } from 'viem/chains';
4
5
  import { createPublicClient, http, getAddress, parseSignature, WalletClient, PublicClient } from 'viem';
5
6
  import { parseUnits, encodeFunctionData, erc20Abi } from 'viem';
6
7
  import './App.css';
7
8
 
8
9
  // Chain selection helpers
9
- const chain = (import.meta.env.VITE_CHAIN === 'base') ? base : baseSepolia;
10
+ const getChain = () => {
11
+ const chainEnv = import.meta.env.VITE_CHAIN;
12
+ if (chainEnv === 'base') return base;
13
+ if (chainEnv === 'radiusTestnet') return radiusTestnet;
14
+ return baseSepolia;
15
+ };
16
+
17
+ const chain = getChain();
10
18
  const rpcUrl = import.meta.env.VITE_RPC_URL;
11
19
 
20
+ // Radius testnet uses TestSBC (a test token for development)
21
+ const TEST_SBC_DECIMALS = 6;
22
+
12
23
  const SBC_TOKEN_ADDRESS = (chain) => {
13
24
  if (chain.id === baseSepolia.id) {
14
25
  return '0xf9FB20B8E097904f0aB7d12e9DbeE88f2dcd0F16';
15
26
  } else if (chain.id === base.id) {
16
27
  return '0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798';
28
+ } else if (chain.id === radiusTestnet.id) {
29
+ return TestSBC_CONTRACT_ADDRESS;
17
30
  }
18
31
  throw new Error('Unsupported chain');
19
32
  };
@@ -23,15 +36,26 @@ const SBC_DECIMALS = (chain) => {
23
36
  return 6;
24
37
  } else if (chain.id === base.id) {
25
38
  return 18;
39
+ } else if (chain.id === radiusTestnet.id) {
40
+ return TEST_SBC_DECIMALS;
26
41
  }
27
42
  throw new Error('Unsupported chain');
28
43
  };
29
44
 
45
+ const getTokenSymbol = (chain) => {
46
+ if (chain.id === radiusTestnet.id) {
47
+ return 'TestSBC';
48
+ }
49
+ return 'SBC';
50
+ };
51
+
30
52
  const chainExplorer = (chain) => {
31
53
  if (chain.id === baseSepolia.id) {
32
54
  return 'https://sepolia.basescan.org';
33
55
  } else if (chain.id === base.id) {
34
56
  return 'https://basescan.org';
57
+ } else if (chain.id === radiusTestnet.id) {
58
+ return 'https://testnet.radiustech.xyz/testnet/explorer';
35
59
  }
36
60
  throw new Error('Unsupported chain');
37
61
  };
@@ -73,6 +97,54 @@ const permitAbi = [
73
97
 
74
98
  function WalletStatus({ onDisconnect }: { onDisconnect: () => void }) {
75
99
  const { ownerAddress } = useSbcApp();
100
+ const [eoaBalances, setEoaBalances] = useState<{ eth: string | null; sbc: string | null }>({ eth: null, sbc: null });
101
+ const [isLoadingEoaBalances, setIsLoadingEoaBalances] = useState(false);
102
+
103
+ // Fetch ETH and TestSBC balances for EOA wallet
104
+ useEffect(() => {
105
+ if (!ownerAddress) return;
106
+
107
+ const fetchEoaBalances = async () => {
108
+ setIsLoadingEoaBalances(true);
109
+ try {
110
+ const [ethBalance, sbcBalance] = await Promise.all([
111
+ publicClient.getBalance({ address: ownerAddress as `0x${string}` }),
112
+ publicClient.readContract({
113
+ address: SBC_TOKEN_ADDRESS(chain) as `0x${string}`,
114
+ abi: erc20Abi,
115
+ functionName: 'balanceOf',
116
+ args: [ownerAddress as `0x${string}`],
117
+ })
118
+ ]);
119
+ setEoaBalances({ eth: ethBalance.toString(), sbc: (sbcBalance as bigint).toString() });
120
+ } catch (error) {
121
+ console.error('Failed to fetch EOA balances:', error);
122
+ setEoaBalances({ eth: '0', sbc: '0' });
123
+ } finally {
124
+ setIsLoadingEoaBalances(false);
125
+ }
126
+ };
127
+
128
+ fetchEoaBalances();
129
+ }, [ownerAddress]);
130
+
131
+ const formatEthBalance = (balance: string | null): string => {
132
+ if (!balance) return '0.0000';
133
+ try {
134
+ return (Number(balance) / 1e18).toFixed(4);
135
+ } catch {
136
+ return '0.0000';
137
+ }
138
+ };
139
+
140
+ const formatSbcBalance = (balance: string | null): string => {
141
+ if (!balance) return '0.00';
142
+ try {
143
+ return (Number(balance) / Math.pow(10, SBC_DECIMALS(chain))).toFixed(2);
144
+ } catch {
145
+ return '0.00';
146
+ }
147
+ };
76
148
 
77
149
  if (!ownerAddress) return null;
78
150
 
@@ -94,6 +166,18 @@ function WalletStatus({ onDisconnect }: { onDisconnect: () => void }) {
94
166
  <label>Chain:</label>
95
167
  <div className="value">{chain.name}</div>
96
168
  </div>
169
+ <div className="info-row">
170
+ <label>EOA ETH Balance:</label>
171
+ <div className="value">
172
+ {isLoadingEoaBalances ? 'Loading...' : `${formatEthBalance(eoaBalances.eth)} ETH`}
173
+ </div>
174
+ </div>
175
+ <div className="info-row">
176
+ <label>EOA {getTokenSymbol(chain)} Balance:</label>
177
+ <div className="value">
178
+ {isLoadingEoaBalances ? 'Loading...' : `${formatSbcBalance(eoaBalances.sbc)} ${getTokenSymbol(chain)}`}
179
+ </div>
180
+ </div>
97
181
  </div>
98
182
  );
99
183
  }
@@ -207,9 +291,9 @@ function SmartAccountInfo() {
207
291
  <div className="value">{formatEthBalance(account.balance)} ETH</div>
208
292
  </div>
209
293
  <div className="info-row">
210
- <label>SBC Balance:</label>
294
+ <label>{getTokenSymbol(chain)} Balance:</label>
211
295
  <div className="value">
212
- {isLoadingBalance ? 'Loading...' : `${formatSbcBalance(sbcBalance)} SBC`}
296
+ {isLoadingBalance ? 'Loading...' : `${formatSbcBalance(sbcBalance)} ${getTokenSymbol(chain)}`}
213
297
  </div>
214
298
  </div>
215
299
  </div>
@@ -229,7 +313,7 @@ function SendSBCForm() {
229
313
  try {
230
314
  const ownerChecksum = getAddress(ownerAddress);
231
315
  const spenderChecksum = getAddress(account.address);
232
- const value = parseUnits('1', SBC_DECIMALS(chain)); // Send 1 SBC
316
+ const value = parseUnits('1', SBC_DECIMALS(chain)); // Send 1 token
233
317
  const deadline = Math.floor(Date.now() / 1000) + 60 * 30; // 30 min
234
318
 
235
319
  const signature = await getPermitSignature({
@@ -275,7 +359,7 @@ function SendSBCForm() {
275
359
 
276
360
  return (
277
361
  <div className="card">
278
- <h3>💸 Send 1 SBC Token</h3>
362
+ <h3>💸 Send 1 {getTokenSymbol(chain)} Token</h3>
279
363
  <div className="form-group">
280
364
  <label>Recipient Address</label>
281
365
  <input
@@ -289,11 +373,11 @@ function SendSBCForm() {
289
373
  <span className="error-text">Invalid Ethereum address</span>
290
374
  )}
291
375
  </div>
292
-
376
+
293
377
  <div className="status-section">
294
378
  <div className="info-row">
295
379
  <label>Amount:</label>
296
- <div className="value">1.00 SBC</div>
380
+ <div className="value">1.00 {getTokenSymbol(chain)}</div>
297
381
  </div>
298
382
  <div className="info-row">
299
383
  <label>Gas fees:</label>
@@ -310,18 +394,18 @@ function SendSBCForm() {
310
394
  disabled={!isFormValid || isLoading || !account}
311
395
  className="primary"
312
396
  >
313
- {isLoading ? 'Waiting for signature...' : 'Send 1 SBC'}
397
+ {isLoading ? 'Waiting for signature...' : `Send 1 ${getTokenSymbol(chain)}`}
314
398
  </button>
315
399
 
316
400
  {isSuccess && data && (
317
401
  <div className="success-message">
318
402
  <p>✅ Transaction Successful!</p>
319
- <a
403
+ <a
320
404
  href={`${chainExplorer(chain)}/tx/${data.transactionHash}`}
321
405
  target="_blank"
322
406
  rel="noopener noreferrer"
323
407
  >
324
- View on BaseScan: {data.transactionHash}
408
+ View transaction: {data.transactionHash}
325
409
  </a>
326
410
  </div>
327
411
  )}
@@ -2,6 +2,6 @@
2
2
  VITE_SBC_API_KEY={{apiKey}}
3
3
  # Get your Dynamic Environment ID at https://app.dynamic.xyz/
4
4
  VITE_DYNAMIC_ENVIRONMENT_ID=your_dynamic_env_id
5
- # Optional:
6
- VITE_CHAIN=baseSepolia
5
+ # Supported chains: "baseSepolia" | "base"
6
+ VITE_CHAIN={{chain}}
7
7
  VITE_RPC_URL=
@@ -1,24 +1,142 @@
1
- # React + Dynamic + SBC App Kit
1
+ # {{projectName}}
2
2
 
3
- Gasless transactions on Base using Dynamic SDK with SBC App Kit.
3
+ React + Vite app with Dynamic wallet integration and SBC App Kit for gasless transactions.
4
+
5
+ ## Overview
6
+
7
+ This template combines:
8
+ - **Dynamic SDK** - Embedded wallet with social logins (Google, Twitter, Discord, email, SMS)
9
+ - **SBC App Kit** - Account abstraction and gasless transactions
10
+ - **React + Vite** - Fast, modern development experience
11
+
12
+ Perfect for applications that need a seamless onboarding experience with social authentication and gasless transactions.
4
13
 
5
14
  ## Quick Start
6
15
 
7
- ```bash
8
- pnpm install
9
- pnpm dev
10
- ```
16
+ 1. **Install dependencies:**
17
+
18
+ ```bash
19
+ pnpm install
20
+ # or
21
+ npm install
22
+ ```
23
+
24
+ 2. **Configure environment:**
25
+
26
+ Copy `.env.template` to `.env` and fill in your API keys:
27
+
28
+ ```env
29
+ # Your SBC API key from https://dashboard.stablecoin.xyz
30
+ VITE_SBC_API_KEY={{apiKey}}
31
+
32
+ # Your Dynamic Environment ID from https://app.dynamic.xyz/
33
+ VITE_DYNAMIC_ENVIRONMENT_ID=your_dynamic_env_id
34
+
35
+ # Supported chains: "baseSepolia" | "base"
36
+ VITE_CHAIN={{chain}}
37
+
38
+ # Optional: Custom RPC URL (e.g., from Alchemy)
39
+ VITE_RPC_URL=
40
+ ```
41
+
42
+ 3. **Start the development server:**
43
+
44
+ ```bash
45
+ pnpm dev
46
+ # or
47
+ npm run dev
48
+ ```
49
+
50
+ 4. **Open your browser:**
51
+ Visit [http://localhost:5173](http://localhost:5173) (or the port shown in your terminal).
52
+
53
+ ## Features
54
+
55
+ ### Dynamic Wallet Integration
56
+ - Social logins (Google, Twitter, Discord, GitHub, Apple, etc.)
57
+ - Email and SMS authentication
58
+ - Embedded wallet creation
59
+ - Multi-chain support
60
+ - Customizable authentication UI
11
61
 
12
- ## Environment
62
+ ### SBC App Kit Integration
63
+ - Automatic smart account creation
64
+ - Gasless transactions
65
+ - Gas estimation and previews
66
+ - Batch transactions
67
+ - Real-time balance tracking
68
+ - Transaction history
13
69
 
14
- Create `.env` from `.env.template` and fill in values:
70
+ ### Developer Experience
71
+ - TypeScript support with full type safety
72
+ - Hot module replacement for fast development
73
+ - Environment-based configuration
74
+ - Comprehensive error handling
75
+ - Debug logging
15
76
 
77
+ ## Getting Your API Keys
78
+
79
+ ### SBC API Key
80
+ 1. Visit [https://dashboard.stablecoin.xyz](https://dashboard.stablecoin.xyz)
81
+ 2. Sign up or log in
82
+ 3. Create a new API key
83
+ 4. Add it to your `.env` file as `VITE_SBC_API_KEY`
84
+
85
+ ### Dynamic Environment ID
86
+ 1. Visit [https://app.dynamic.xyz/](https://app.dynamic.xyz/)
87
+ 2. Create an account and a new project
88
+ 3. Copy your Environment ID
89
+ 4. Add it to your `.env` file as `VITE_DYNAMIC_ENVIRONMENT_ID`
90
+ 5. Configure your allowed authentication methods in the Dynamic dashboard
91
+
92
+ ## Customization
93
+
94
+ ### Modifying Authentication Methods
95
+ Edit the Dynamic configuration in `src/App.tsx`:
96
+ ```typescript
97
+ // Configure which authentication methods to enable
98
+ walletConnectors={[EthereumWalletConnectors]}
99
+ ```
100
+
101
+ ### Styling
102
+ - Edit `src/App.css` for component styles
103
+ - Edit `src/index.css` for global styles
104
+ - Customize Tailwind configuration in `tailwind.config.js`
105
+
106
+ ### Chain Configuration
107
+ Change the chain in your `.env` file:
16
108
  ```env
17
- VITE_SBC_API_KEY={{apiKey}}
18
- VITE_DYNAMIC_ENVIRONMENT_ID=your_dynamic_env_id
19
- # Optional
20
- VITE_CHAIN=baseSepolia # or "base"
21
- VITE_RPC_URL=
109
+ VITE_CHAIN=base # Base mainnet
110
+ VITE_CHAIN=baseSepolia # Base Sepolia testnet
22
111
  ```
23
112
 
113
+ **Note:** Radius Testnet is not currently supported by the Dynamic wallet provider. Use the base `react` template for Radius Testnet support.
114
+
115
+ ## Building for Production
116
+
117
+ ```bash
118
+ pnpm build
119
+ # or
120
+ npm run build
121
+ ```
122
+
123
+ The built files will be in the `dist/` directory, ready for deployment to any static host (Vercel, Netlify, Cloudflare Pages, etc.).
124
+
125
+ ## Learn More
126
+
127
+ - [SBC Documentation](https://docs.stablecoin.xyz)
128
+ - [Dynamic Documentation](https://docs.dynamic.xyz/)
129
+ - [SBC App Kit GitHub](https://github.com/stablecoinxyz/app-kit)
130
+ - [React Documentation](https://react.dev)
131
+ - [Vite Documentation](https://vitejs.dev)
132
+
133
+ ## Support
134
+
135
+ If you encounter any issues:
136
+ 1. Check the [SBC Documentation](https://docs.stablecoin.xyz)
137
+ 2. Review the [Dynamic Documentation](https://docs.dynamic.xyz/)
138
+ 3. Open an issue on the [SBC App Kit repository](https://github.com/stablecoinxyz/app-kit/issues)
139
+
140
+ ## License
24
141
 
142
+ MIT
@@ -2,25 +2,48 @@ import { DynamicContextProvider, useDynamicContext, DynamicUserProfile, DynamicW
2
2
  import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
3
3
  import { ZeroDevSmartWalletConnectors } from '@dynamic-labs/ethereum-aa';
4
4
  import { useSbcDynamic } from '@stablecoin.xyz/react';
5
+ import { radiusTestnet, TestSBC_CONTRACT_ADDRESS } from '@stablecoin.xyz/core';
5
6
  import { baseSepolia, base, type Chain } from 'viem/chains';
6
7
  import { createPublicClient, http, getAddress, parseUnits, encodeFunctionData, erc20Abi } from 'viem';
7
8
  import { useEffect, useState } from 'react';
8
9
  import './App.css';
9
10
 
10
- const chain = (import.meta.env.VITE_CHAIN === 'base') ? base : baseSepolia;
11
+ const getChain = () => {
12
+ const chainEnv = import.meta.env.VITE_CHAIN;
13
+ if (chainEnv === 'base') return base;
14
+ if (chainEnv === 'radiusTestnet') return radiusTestnet;
15
+ return baseSepolia;
16
+ };
17
+
18
+ const chain = getChain();
11
19
  const rpcUrl = import.meta.env.VITE_RPC_URL;
12
20
 
21
+ // Radius testnet uses TestSBC (a test token for development)
22
+ const TEST_SBC_DECIMALS = 6;
23
+
13
24
  const SBC_TOKEN_ADDRESS = (chain: Chain) => {
14
25
  if (chain.id === baseSepolia.id) return '0xf9FB20B8E097904f0aB7d12e9DbeE88f2dcd0F16';
15
26
  if (chain.id === base.id) return '0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798';
27
+ if (chain.id === radiusTestnet.id) return TestSBC_CONTRACT_ADDRESS;
28
+ throw new Error('Unsupported chain');
29
+ };
30
+
31
+ const SBC_DECIMALS = (chain: Chain) => {
32
+ if (chain.id === baseSepolia.id) return 6;
33
+ if (chain.id === base.id) return 18;
34
+ if (chain.id === radiusTestnet.id) return TEST_SBC_DECIMALS;
16
35
  throw new Error('Unsupported chain');
17
36
  };
18
37
 
19
- const SBC_DECIMALS = (chain: Chain) => chain.id === baseSepolia.id ? 6 : 18;
38
+ const getTokenSymbol = (chain: Chain) => {
39
+ if (chain.id === radiusTestnet.id) return 'TestSBC';
40
+ return 'SBC';
41
+ };
20
42
 
21
43
  const chainExplorer = (chain: Chain) => {
22
44
  if (chain.id === baseSepolia.id) return 'https://sepolia.basescan.org';
23
45
  if (chain.id === base.id) return 'https://basescan.org';
46
+ if (chain.id === radiusTestnet.id) return 'https://testnet.radiustech.xyz/testnet/explorer';
24
47
  return '';
25
48
  };
26
49
 
@@ -74,7 +97,8 @@ function WalletStatus() {
74
97
  })
75
98
  ]);
76
99
  setBalances({ eth: ethBalance.toString(), sbc: (sbcBalance as bigint).toString() });
77
- } catch {
100
+ } catch (error) {
101
+ console.error('Failed to fetch EOA balances:', error);
78
102
  setBalances({ eth: null, sbc: null });
79
103
  }
80
104
  })();
@@ -93,10 +117,10 @@ function WalletStatus() {
93
117
  <p className="text-xs text-green-600 mb-2">Connected via Dynamic SDK</p>
94
118
  <p className="text-xs text-green-600 mb-2"><strong>Chain:</strong> {chain.name} (ID: {chain.id})</p>
95
119
  <div className="mt-2 pt-2 border-t border-green-200">
96
- <p className="text-xs font-medium text-green-700 mb-1">Wallet Balances:</p>
120
+ <p className="text-xs font-medium text-green-700 mb-1">EOA Wallet Balances:</p>
97
121
  <div className="flex gap-4">
98
122
  <span className="text-xs text-green-600"><strong>ETH:</strong> {fmtEth(balances.eth)}</span>
99
- <span className="text-xs text-green-600"><strong>SBC:</strong> {fmtSbc(balances.sbc)}</span>
123
+ <span className="text-xs text-green-600"><strong>{getTokenSymbol(chain)}:</strong> {fmtSbc(balances.sbc)}</span>
100
124
  </div>
101
125
  </div>
102
126
  </div>
@@ -118,7 +142,10 @@ function SmartAccountInfo({ account, refreshAccount, isLoadingAccount, accountEr
118
142
  args: [account.address as `0x${string}`],
119
143
  });
120
144
  setSbcBalance((bal as bigint).toString());
121
- } catch { setSbcBalance('0'); }
145
+ } catch (error) {
146
+ console.error('Failed to fetch smart account SBC balance:', error);
147
+ setSbcBalance('0');
148
+ }
122
149
  })();
123
150
  }, [account?.address]);
124
151
 
@@ -136,13 +163,8 @@ function SmartAccountInfo({ account, refreshAccount, isLoadingAccount, accountEr
136
163
  <div className="flex justify-between"><span className="text-purple-700">Smart Account Address:</span><span className="font-mono text-xs text-purple-600 break-all">{account.address}</span></div>
137
164
  <div className="flex justify-between"><span className="text-purple-700">Deployed:</span><span className="text-purple-600">{account.isDeployed ? '✅ Yes' : '⏳ On first transaction'}</span></div>
138
165
  <div className="flex justify-between"><span className="text-purple-700">Nonce:</span><span className="text-purple-600">{account.nonce}</span></div>
139
- <div className="pt-2 border-t border-purple-200">
140
- <p className="text-xs font-medium text-purple-700 mb-2">Smart Account Balances:</p>
141
- <div className="space-y-1">
142
- <div className="flex justify-between"><span className="text-purple-700">ETH:</span><span className="text-purple-600 font-mono text-xs">{fmtEth(account.balance)} ETH</span></div>
143
- <div className="flex justify-between"><span className="text-purple-700">SBC:</span><span className="text-purple-600 font-mono text-xs">{fmtSbc(sbcBalance)} SBC</span></div>
144
- </div>
145
- </div>
166
+ <div className="flex justify-between"><span className="text-purple-700">ETH Balance:</span><span className="text-purple-600 font-mono text-xs">{fmtEth(account.balance)} ETH</span></div>
167
+ <div className="flex justify-between"><span className="text-purple-700">{getTokenSymbol(chain)} Balance:</span><span className="text-purple-600 font-mono text-xs">{fmtSbc(sbcBalance)} {getTokenSymbol(chain)}</span></div>
146
168
  </div>
147
169
  {accountError && <p className="mt-2 text-xs text-red-600">{String(accountError)}</p>}
148
170
  </div>
@@ -216,29 +238,29 @@ function TransactionForm({ account, sbcAppKit }: { account: any; sbcAppKit: any
216
238
 
217
239
  return (
218
240
  <div className="p-4 bg-white border border-gray-200 rounded-lg shadow-sm">
219
- <h3 className="font-semibold text-gray-800 mb-4">💸 Send SBC Tokens</h3>
241
+ <h3 className="font-semibold text-gray-800 mb-4">💸 Send {getTokenSymbol(chain)} Tokens</h3>
220
242
  <div className="space-y-4">
221
243
  <div>
222
244
  <label className="block text-sm font-medium text-gray-700 mb-2">Recipient Address</label>
223
245
  <input type="text" value={recipient} onChange={(e) => setRecipient(e.target.value)} placeholder="0x..." className="w-full px-3 py-2 text-xs font-mono border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 border-gray-300" />
224
246
  </div>
225
247
  <div>
226
- <label className="block text-sm font-medium text-gray-700 mb-2">Amount (SBC)</label>
248
+ <label className="block text-sm font-medium text-gray-700 mb-2">Amount ({getTokenSymbol(chain)})</label>
227
249
  <input type="number" value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="1.0" step="0.000001" min="0" className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 border-gray-300" />
228
250
  </div>
229
251
  <div className="p-3 bg-gray-50 rounded">
230
- <div className="flex justify-between text-sm"><span>Amount:</span><span className="font-medium">{amount} SBC</span></div>
252
+ <div className="flex justify-between text-sm"><span>Amount:</span><span className="font-medium">{amount} {getTokenSymbol(chain)}</span></div>
231
253
  <div className="flex justify-between text-xs text-gray-600"><span>Gas fees:</span><span>Covered by SBC Paymaster ✨</span></div>
232
254
  <div className="flex justify-between text-xs text-gray-600"><span>Signing:</span><span>Your Dynamic wallet will prompt to sign 🖊️</span></div>
233
255
  </div>
234
256
  <button onClick={sendTx} disabled={!isValid || status==='loading' || !account} className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed">
235
- {status==='loading' ? 'Waiting for signature...' : `Send ${amount} SBC`}
257
+ {status==='loading' ? 'Waiting for signature...' : `Send ${amount} ${getTokenSymbol(chain)}`}
236
258
  </button>
237
259
  {status==='success' && result && (
238
260
  <div className="p-3 bg-green-50 border border-green-200 rounded">
239
261
  <p className="text-sm text-green-800 font-medium">✅ Transaction Submitted</p>
240
262
  <p className="text-xs text-green-600 font-mono break-all mt-1">
241
- <a href={`${chainExplorer(chain)}/tx/${result.transactionHash}`} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">View on BaseScan: {result.transactionHash}</a>
263
+ <a href={`${chainExplorer(chain)}/tx/${result.transactionHash}`} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">View transaction: {result.transactionHash}</a>
242
264
  </p>
243
265
  </div>
244
266
  )}
@@ -2,6 +2,6 @@
2
2
  VITE_SBC_API_KEY={{apiKey}}
3
3
  # Get your Para API Key at https://developer.getpara.com/
4
4
  VITE_PARA_API_KEY=your_para_api_key
5
- # Optional:
6
- VITE_CHAIN=baseSepolia
5
+ # Supported chains: "baseSepolia" | "base"
6
+ VITE_CHAIN={{chain}}
7
7
  VITE_RPC_URL=
@@ -1,24 +1,165 @@
1
- # React + Para + SBC App Kit
1
+ # {{projectName}}
2
2
 
3
- Gasless user operation using EIP-2612 permit signed via Para and executed via SBC App Kit.
3
+ React + Vite app with Para wallet integration and SBC App Kit for advanced gasless transactions using EIP-2612 permits.
4
+
5
+ ## Overview
6
+
7
+ This template combines:
8
+ - **Para** - Advanced wallet integration with EIP-2612 permit support
9
+ - **SBC App Kit** - Account abstraction and gasless transactions
10
+ - **React + Vite** - Fast, modern development experience
11
+
12
+ Perfect for applications that need advanced gasless transaction patterns using EIP-2612 permit signatures, enabling token approvals without separate transactions.
13
+
14
+ ## What are EIP-2612 Permits?
15
+
16
+ EIP-2612 permits allow users to sign approval messages off-chain, eliminating the need for a separate approval transaction. This creates a superior user experience:
17
+ - No approval transaction fees
18
+ - Single-step token operations
19
+ - Enhanced security through time-limited permits
20
+ - Better UX for DeFi interactions
4
21
 
5
22
  ## Quick Start
6
23
 
7
- ```bash
8
- pnpm install
9
- pnpm dev
10
- ```
24
+ 1. **Install dependencies:**
25
+
26
+ ```bash
27
+ pnpm install
28
+ # or
29
+ npm install
30
+ ```
31
+
32
+ 2. **Configure environment:**
33
+
34
+ Copy `.env.template` to `.env` and fill in your API keys:
35
+
36
+ ```env
37
+ # Your SBC API key from https://dashboard.stablecoin.xyz
38
+ VITE_SBC_API_KEY={{apiKey}}
39
+
40
+ # Your Para API key
41
+ VITE_PARA_API_KEY=your_para_api_key
42
+
43
+ # Supported chains: "baseSepolia" | "base"
44
+ VITE_CHAIN={{chain}}
45
+
46
+ # Optional: Custom RPC URL (e.g., from Alchemy)
47
+ VITE_RPC_URL=
48
+ ```
49
+
50
+ 3. **Start the development server:**
51
+
52
+ ```bash
53
+ pnpm dev
54
+ # or
55
+ npm run dev
56
+ ```
57
+
58
+ 4. **Open your browser:**
59
+ Visit [http://localhost:5173](http://localhost:5173) (or the port shown in your terminal).
60
+
61
+ ## Features
62
+
63
+ ### Para Wallet Integration
64
+ - EIP-2612 permit signature support
65
+ - Gasless token approvals
66
+ - Advanced transaction patterns
67
+ - Seamless wallet experience
68
+ - Optimized for DeFi applications
11
69
 
12
- ## Environment
70
+ ### SBC App Kit Integration
71
+ - Automatic smart account creation
72
+ - Gasless transactions
73
+ - Gas estimation and previews
74
+ - Batch transactions
75
+ - Real-time balance tracking
76
+ - Transaction history
13
77
 
14
- Create `.env` from `.env.template`:
78
+ ### Advanced Use Cases
79
+ - Single-step token swaps (no separate approval)
80
+ - Gasless staking operations
81
+ - Permit-based token transfers
82
+ - Enhanced DeFi interactions
83
+ - Meta-transactions with permits
15
84
 
85
+ ### Developer Experience
86
+ - TypeScript support with full type safety
87
+ - Hot module replacement for fast development
88
+ - Environment-based configuration
89
+ - Comprehensive error handling
90
+ - Debug logging
91
+
92
+ ## Getting Your API Keys
93
+
94
+ ### SBC API Key
95
+ 1. Visit [https://dashboard.stablecoin.xyz](https://dashboard.stablecoin.xyz)
96
+ 2. Sign up or log in
97
+ 3. Create a new API key
98
+ 4. Add it to your `.env` file as `VITE_SBC_API_KEY`
99
+
100
+ ### Para API Key
101
+ 1. Visit [https://para.xyz](https://para.xyz)
102
+ 2. Create an account and register your application
103
+ 3. Copy your API key
104
+ 4. Add it to your `.env` file as `VITE_PARA_API_KEY`
105
+
106
+ ## Customization
107
+
108
+ ### Styling
109
+ - Edit `src/App.css` for component styles
110
+ - Edit `src/index.css` for global styles
111
+ - Customize Tailwind configuration in `tailwind.config.js`
112
+
113
+ ### Chain Configuration
114
+ Change the chain in your `.env` file:
16
115
  ```env
17
- VITE_SBC_API_KEY={{apiKey}}
18
- VITE_PARA_API_KEY=your_para_api_key
19
- # Optional
20
- VITE_CHAIN=baseSepolia # or "base"
21
- VITE_RPC_URL=
116
+ VITE_CHAIN=base # Base mainnet
117
+ VITE_CHAIN=baseSepolia # Base Sepolia testnet
22
118
  ```
23
119
 
120
+ **Note:** Radius Testnet is not currently supported by the Para wallet provider. Use the base `react` template for Radius Testnet support.
121
+
122
+ ### Permit Configuration
123
+ Customize permit parameters in your application logic:
124
+ - Deadline (expiration time)
125
+ - Spender address
126
+ - Token amounts
127
+
128
+ ## Building for Production
129
+
130
+ ```bash
131
+ pnpm build
132
+ # or
133
+ npm run build
134
+ ```
135
+
136
+ The built files will be in the `dist/` directory, ready for deployment to any static host (Vercel, Netlify, Cloudflare Pages, etc.).
137
+
138
+ ## Use Cases
139
+
140
+ This template is ideal for:
141
+ - DeFi applications requiring token approvals
142
+ - DEX interfaces with optimized UX
143
+ - Staking platforms
144
+ - Lending/borrowing protocols
145
+ - Any application benefiting from gasless approvals
146
+
147
+ ## Learn More
148
+
149
+ - [SBC Documentation](https://docs.stablecoin.xyz)
150
+ - [Para Documentation](https://docs.para.xyz)
151
+ - [EIP-2612 Specification](https://eips.ethereum.org/EIPS/eip-2612)
152
+ - [SBC App Kit GitHub](https://github.com/stablecoinxyz/app-kit)
153
+ - [React Documentation](https://react.dev)
154
+ - [Vite Documentation](https://vitejs.dev)
155
+
156
+ ## Support
157
+
158
+ If you encounter any issues:
159
+ 1. Check the [SBC Documentation](https://docs.stablecoin.xyz)
160
+ 2. Review the [Para Documentation](https://docs.para.xyz)
161
+ 3. Open an issue on the [SBC App Kit repository](https://github.com/stablecoinxyz/app-kit/issues)
162
+
163
+ ## License
24
164
 
165
+ MIT
@@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
2
  import { Environment, ParaProvider } from '@getpara/react-sdk';
3
3
  import '@getpara/react-sdk/styles.css';
4
4
  import { useSbcPara } from '@stablecoin.xyz/react';
5
+ import { radiusTestnet, TestSBC_CONTRACT_ADDRESS } from '@stablecoin.xyz/core';
5
6
  import { useAccount, useWallet, useSignMessage } from '@getpara/react-sdk';
6
7
  import { baseSepolia, base, type Chain } from 'viem/chains';
7
8
  import { createPublicClient, http, getAddress, parseUnits, encodeFunctionData, erc20Abi } from 'viem';
@@ -12,15 +13,39 @@ import { buildPermitTypedData, hashPermitTypedData, hex32ToBase64, normalizeSign
12
13
  import { usePara } from './hooks/usePara';
13
14
 
14
15
  const queryClient = new QueryClient();
15
- const chain = (import.meta.env.VITE_CHAIN === 'base') ? base : baseSepolia;
16
+
17
+ const getChain = () => {
18
+ const chainEnv = import.meta.env.VITE_CHAIN;
19
+ if (chainEnv === 'base') return base;
20
+ if (chainEnv === 'radiusTestnet') return radiusTestnet;
21
+ return baseSepolia;
22
+ };
23
+
24
+ const chain = getChain();
16
25
  const rpcUrl = import.meta.env.VITE_RPC_URL;
17
26
 
27
+ // Radius testnet uses TestSBC (a test token for development)
28
+ const TEST_SBC_DECIMALS = 6;
29
+
18
30
  const SBC_TOKEN_ADDRESS = (chain: Chain) => {
19
31
  if (chain.id === baseSepolia.id) return '0xf9FB20B8E097904f0aB7d12e9DbeE88f2dcd0F16';
20
32
  if (chain.id === base.id) return '0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798';
33
+ if (chain.id === radiusTestnet.id) return TestSBC_CONTRACT_ADDRESS;
34
+ throw new Error('Unsupported chain');
35
+ };
36
+
37
+ const SBC_DECIMALS = (chain: Chain) => {
38
+ if (chain.id === baseSepolia.id) return 6;
39
+ if (chain.id === base.id) return 18;
40
+ if (chain.id === radiusTestnet.id) return TEST_SBC_DECIMALS;
21
41
  throw new Error('Unsupported chain');
22
42
  };
23
- const SBC_DECIMALS = (chain: Chain) => chain.id === baseSepolia.id ? 6 : 18;
43
+
44
+ const getTokenSymbol = (chain: Chain) => {
45
+ if (chain.id === radiusTestnet.id) return 'TestSBC';
46
+ return 'SBC';
47
+ };
48
+
24
49
  const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
25
50
 
26
51
  // ERC20 + EIP-2612 nonces helper ABI
@@ -58,7 +83,12 @@ const permitAbi = [
58
83
  }
59
84
  ] as const;
60
85
 
61
- const chainExplorer = (c: Chain) => c.id === baseSepolia.id ? 'https://sepolia.basescan.org' : 'https://basescan.org';
86
+ const chainExplorer = (c: Chain) => {
87
+ if (c.id === baseSepolia.id) return 'https://sepolia.basescan.org';
88
+ if (c.id === base.id) return 'https://basescan.org';
89
+ if (c.id === radiusTestnet.id) return 'https://testnet.radiustech.xyz/testnet/explorer';
90
+ return '';
91
+ };
62
92
 
63
93
  function SmartAccountInfo({ account, refreshAccount, isLoadingAccount, accountError }: any) {
64
94
  const [sbcBalance, setSbcBalance] = useState<string | null>(null);
@@ -73,7 +103,10 @@ function SmartAccountInfo({ account, refreshAccount, isLoadingAccount, accountEr
73
103
  args: [account.address as `0x${string}`],
74
104
  });
75
105
  setSbcBalance((bal as bigint).toString());
76
- } catch { setSbcBalance('0'); }
106
+ } catch (error) {
107
+ console.error('Failed to fetch smart account SBC balance:', error);
108
+ setSbcBalance('0');
109
+ }
77
110
  })();
78
111
  }, [account?.address]);
79
112
 
@@ -91,13 +124,8 @@ function SmartAccountInfo({ account, refreshAccount, isLoadingAccount, accountEr
91
124
  <div className="flex justify-between"><span className="text-purple-700">Smart Account Address:</span><span className="font-mono text-xs text-purple-600 break-all">{account.address}</span></div>
92
125
  <div className="flex justify-between"><span className="text-purple-700">Deployed:</span><span className="text-purple-600">{account.isDeployed ? '✅ Yes' : '⏳ On first transaction'}</span></div>
93
126
  <div className="flex justify-between"><span className="text-purple-700">Nonce:</span><span className="text-purple-600">{account.nonce}</span></div>
94
- <div className="pt-2 border-t border-purple-200">
95
- <p className="text-xs font-medium text-purple-700 mb-2">Smart Account Balances:</p>
96
- <div className="space-y-1">
97
- <div className="flex justify-between"><span className="text-purple-700">ETH:</span><span className="text-purple-600 font-mono text-xs">{fmtEth(account.balance)} ETH</span></div>
98
- <div className="flex justify-between"><span className="text-purple-700">SBC:</span><span className="text-purple-600 font-mono text-xs">{fmtSbc(sbcBalance)} SBC</span></div>
99
- </div>
100
- </div>
127
+ <div className="flex justify-between"><span className="text-purple-700">ETH Balance:</span><span className="text-purple-600 font-mono text-xs">{fmtEth(account.balance)} ETH</span></div>
128
+ <div className="flex justify-between"><span className="text-purple-700">{getTokenSymbol(chain)} Balance:</span><span className="text-purple-600 font-mono text-xs">{fmtSbc(sbcBalance)} {getTokenSymbol(chain)}</span></div>
101
129
  </div>
102
130
  {accountError && <p className="mt-2 text-xs text-red-600">{String(accountError)}</p>}
103
131
  </div>
@@ -186,29 +214,29 @@ function TransactionForm({ account, sbcAppKit }: { account: any; sbcAppKit: any
186
214
 
187
215
  return (
188
216
  <div className="p-4 bg-white border border-gray-200 rounded-lg shadow-sm">
189
- <h3 className="font-semibold text-gray-800 mb-4">💸 Send SBC Tokens</h3>
217
+ <h3 className="font-semibold text-gray-800 mb-4">💸 Send {getTokenSymbol(chain)} Tokens</h3>
190
218
  <div className="space-y-4">
191
219
  <div>
192
220
  <label className="block text-sm font-medium text-gray-700 mb-2">Recipient Address</label>
193
221
  <input type="text" value={recipient} onChange={(e) => setRecipient(e.target.value)} placeholder="0x..." className="w-full px-3 py-2 text-xs font-mono border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 border-gray-300" />
194
222
  </div>
195
223
  <div>
196
- <label className="block text-sm font-medium text-gray-700 mb-2">Amount (SBC)</label>
224
+ <label className="block text-sm font-medium text-gray-700 mb-2">Amount ({getTokenSymbol(chain)})</label>
197
225
  <input type="number" value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="1.0" step="0.000001" min="0" className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 border-gray-300" />
198
226
  </div>
199
227
  <div className="p-3 bg-gray-50 rounded">
200
- <div className="flex justify-between text-sm"><span>Amount:</span><span className="font-medium">{amount} SBC</span></div>
228
+ <div className="flex justify-between text-sm"><span>Amount:</span><span className="font-medium">{amount} {getTokenSymbol(chain)}</span></div>
201
229
  <div className="flex justify-between text-xs text-gray-600"><span>Gas fees:</span><span>Covered by SBC Paymaster ✨</span></div>
202
230
  <div className="flex justify-between text-xs text-gray-600"><span>Signing:</span><span>Your wallet will prompt to sign 🖊️</span></div>
203
231
  </div>
204
232
  <button onClick={sendTx} disabled={!isValid || status==='loading' || !account} className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed">
205
- {status==='loading' ? 'Waiting for signature...' : `Send ${amount} SBC`}
233
+ {status==='loading' ? 'Waiting for signature...' : `Send ${amount} ${getTokenSymbol(chain)}`}
206
234
  </button>
207
235
  {status==='success' && result && (
208
236
  <div className="p-3 bg-green-50 border border-green-200 rounded">
209
237
  <p className="text-sm text-green-800 font-medium">✅ Transaction Submitted</p>
210
238
  <p className="text-xs text-green-600 font-mono break-all mt-1">
211
- <a href={`${chainExplorer(chain)}/tx/${result.transactionHash}`} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">View on BaseScan: {result.transactionHash}</a>
239
+ <a href={`${chainExplorer(chain)}/tx/${result.transactionHash}`} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">View transaction: {result.transactionHash}</a>
212
240
  </p>
213
241
  </div>
214
242
  )}
@@ -43,7 +43,6 @@ function esbuildStripVendorSourcemaps() {
43
43
 
44
44
  export default defineConfig({
45
45
  plugins: [react(), nodePolyfills(), stripVendorSourcemaps()],
46
- logLevel: 'error',
47
46
  define: { global: 'globalThis' },
48
47
  server: { port: 3000 },
49
48
  optimizeDeps: {