create-sbc-app 0.1.5 → 0.3.0
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/LICENSE +21 -0
- package/README.md +21 -12
- package/bin/cli.js +63 -9
- package/package.json +1 -2
- package/templates/README.md +11 -4
- package/templates/react/.env.template +2 -1
- package/templates/react/package.json.template +3 -5
- package/templates/react/src/App.tsx.template +94 -10
- package/templates/react-dynamic/.env.template +7 -0
- package/templates/react-dynamic/README.md.template +24 -0
- package/templates/react-dynamic/eslint.config.js.template +34 -0
- package/templates/react-dynamic/index.html.template +14 -0
- package/templates/react-dynamic/package.json.template +33 -0
- package/templates/react-dynamic/postcss.config.js.template +8 -0
- package/templates/react-dynamic/public/sbc-logo.png +0 -0
- package/templates/react-dynamic/src/App.css.template +5 -0
- package/templates/react-dynamic/src/App.tsx.template +344 -0
- package/templates/react-dynamic/src/env.d.ts.template +14 -0
- package/templates/react-dynamic/src/index.css.template +15 -0
- package/templates/react-dynamic/src/main.tsx.template +12 -0
- package/templates/react-dynamic/tailwind.config.js.template +13 -0
- package/templates/react-dynamic/tsconfig.json.template +18 -0
- package/templates/react-dynamic/vite.config.ts.template +11 -0
- package/templates/react-para/.env.template +7 -0
- package/templates/react-para/README.md.template +24 -0
- package/templates/react-para/eslint.config.js.template +34 -0
- package/templates/react-para/index.html.template +14 -0
- package/templates/react-para/package.json.template +35 -0
- package/templates/react-para/postcss.config.js.template +8 -0
- package/templates/react-para/public/sbc-logo.png +0 -0
- package/templates/react-para/src/App.tsx.template +361 -0
- package/templates/react-para/src/components/ConnectButton.tsx.template +99 -0
- package/templates/react-para/src/env.d.ts.template +14 -0
- package/templates/react-para/src/hooks/usePara.ts.template +34 -0
- package/templates/react-para/src/hooks/useParaViem.ts.template +61 -0
- package/templates/react-para/src/index.css.template +5 -0
- package/templates/react-para/src/main.tsx.template +12 -0
- package/templates/react-para/src/providers.tsx.template +39 -0
- package/templates/react-para/src/utils/permit.ts.template +217 -0
- package/templates/react-para/tailwind.config.js.template +13 -0
- package/templates/react-para/tsconfig.json.template +18 -0
- package/templates/react-para/vite.config.ts.template +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Stable Coin Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
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
|
|
|
@@ -33,19 +33,27 @@ Arguments:
|
|
|
33
33
|
|
|
34
34
|
Options:
|
|
35
35
|
-V, --version output the version number
|
|
36
|
-
-t, --template <type> Template to use: react,
|
|
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 --
|
|
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
|
|
45
47
|
|
|
46
48
|
Available Templates:
|
|
47
|
-
- react
|
|
48
|
-
-
|
|
49
|
+
- react React + Vite template with SBC integration
|
|
50
|
+
- react-dynamic React + Vite with Dynamic wallet integration
|
|
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
|
|
49
57
|
```
|
|
50
58
|
|
|
51
59
|
## ✨ Features
|
|
@@ -105,7 +113,8 @@ The template includes comprehensive environment configuration:
|
|
|
105
113
|
```bash
|
|
106
114
|
# Your SBC API key (get from SBC dashboard)
|
|
107
115
|
VITE_SBC_API_KEY=your_api_key_here
|
|
108
|
-
|
|
116
|
+
|
|
117
|
+
# Supported chains: "baseSepolia" | "base" | "radiusTestnet"
|
|
109
118
|
VITE_CHAIN="baseSepolia"
|
|
110
119
|
```
|
|
111
120
|
|
|
@@ -136,8 +145,8 @@ cp .env.template .env
|
|
|
136
145
|
|
|
137
146
|
# then ensure your .env has the environment variables set up
|
|
138
147
|
|
|
139
|
-
# "base"
|
|
140
|
-
VITE_CHAIN="baseSepolia"
|
|
148
|
+
# Supported chains: "baseSepolia" | "base" | "radiusTestnet"
|
|
149
|
+
VITE_CHAIN="baseSepolia"
|
|
141
150
|
# Custom RPC URL (optional) - e.g. get one from Alchemey at https://dashboard.alchemy.com/apps
|
|
142
151
|
VITE_RPC_URL=
|
|
143
152
|
# Get your SBC API Key at https://dashboard.stablecoin.xyz
|
|
@@ -166,8 +175,8 @@ npm run dev
|
|
|
166
175
|
|
|
167
176
|
## 📚 Documentation
|
|
168
177
|
|
|
169
|
-
- **[SBC
|
|
170
|
-
- **[GitHub Repository](https://github.com/stablecoinxyz/app-kit)** - Source code and examples
|
|
178
|
+
- **[SBC Documentation](https://docs.stablecoin.xyz)** - Official docs
|
|
179
|
+
- **[GitHub Repository](https://github.com/stablecoinxyz/app-kit)** - AppKit API Reference. Source code and examples
|
|
171
180
|
|
|
172
181
|
## 📄 License
|
|
173
182
|
|
package/bin/cli.js
CHANGED
|
@@ -10,20 +10,27 @@ const program = new Command();
|
|
|
10
10
|
program
|
|
11
11
|
.name('create-sbc-app')
|
|
12
12
|
.description('Create a new SBC App Kit project with an opinionated template')
|
|
13
|
-
.version('0.
|
|
13
|
+
.version('0.2.0')
|
|
14
14
|
.argument('[project-directory]', 'Directory to create the new app in')
|
|
15
|
-
.option('-t, --template <template>', 'Template to use: react,
|
|
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
|
-
- react
|
|
26
|
-
-
|
|
26
|
+
- react React + Vite template with SBC integration
|
|
27
|
+
- react-dynamic React + Vite with Dynamic wallet integration
|
|
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
|
|
27
34
|
`)
|
|
28
35
|
.action(async (dir, options) => {
|
|
29
36
|
if (options.wallet) {
|
|
@@ -32,7 +39,13 @@ Available Templates:
|
|
|
32
39
|
}
|
|
33
40
|
const templateChoices = [
|
|
34
41
|
{ title: 'React', value: 'react' },
|
|
35
|
-
{ title: '
|
|
42
|
+
{ title: 'React (Dynamic wallet)', value: 'react-dynamic' },
|
|
43
|
+
{ title: 'React (Para wallet)', value: 'react-para' }
|
|
44
|
+
];
|
|
45
|
+
const chainChoices = [
|
|
46
|
+
{ title: 'Base Sepolia (testnet)', value: 'baseSepolia' },
|
|
47
|
+
{ title: 'Base (mainnet)', value: 'base' },
|
|
48
|
+
{ title: 'Radius Testnet', value: 'radiusTestnet' }
|
|
36
49
|
];
|
|
37
50
|
// Use provided argument or prompt for project directory
|
|
38
51
|
let projectDir = dir && dir.trim() ? dir.trim() : '';
|
|
@@ -49,7 +62,7 @@ Available Templates:
|
|
|
49
62
|
projectDir = res.dir.trim();
|
|
50
63
|
}
|
|
51
64
|
// Use provided option or prompt for template
|
|
52
|
-
let template = options.template && ['react', '
|
|
65
|
+
let template = options.template && ['react', 'react-dynamic', 'react-para'].includes(options.template) ? options.template : '';
|
|
53
66
|
if (!template) {
|
|
54
67
|
const res = await prompts({
|
|
55
68
|
type: 'select',
|
|
@@ -62,11 +75,31 @@ Available Templates:
|
|
|
62
75
|
process.exit(1);
|
|
63
76
|
}
|
|
64
77
|
template = res.template; // The value is already what we want from the choices
|
|
65
|
-
if (!template || !['react', '
|
|
78
|
+
if (!template || !['react', 'react-dynamic', 'react-para'].includes(template)) {
|
|
66
79
|
console.log('Template selection is required.');
|
|
67
80
|
process.exit(1);
|
|
68
81
|
}
|
|
69
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
|
+
}
|
|
70
103
|
// Use provided option or prompt for API key
|
|
71
104
|
let apiKey = options.apiKey && options.apiKey.trim() ? options.apiKey.trim() : '';
|
|
72
105
|
if (!apiKey) {
|
|
@@ -94,9 +127,30 @@ Available Templates:
|
|
|
94
127
|
}
|
|
95
128
|
await copyTemplate(templateDir, targetDir, {
|
|
96
129
|
projectName: projectDir,
|
|
97
|
-
chain:
|
|
130
|
+
chain: chain,
|
|
98
131
|
apiKey: apiKey
|
|
99
132
|
});
|
|
133
|
+
// Ensure SBC logo exists in public/ for all templates
|
|
134
|
+
try {
|
|
135
|
+
const publicDir = path.join(targetDir, 'public');
|
|
136
|
+
await fs.ensureDir(publicDir);
|
|
137
|
+
const sourceLogo = path.resolve(__dirname, '../templates/react/public/sbc-logo.png');
|
|
138
|
+
const destLogo = path.join(publicDir, 'sbc-logo.png');
|
|
139
|
+
if (!(await fs.pathExists(destLogo)) && (await fs.pathExists(sourceLogo))) {
|
|
140
|
+
await fs.copy(sourceLogo, destLogo);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch { }
|
|
144
|
+
// If .env.template exists, ensure apiKey placeholder is applied (copyTemplate already replaces)
|
|
145
|
+
// Also, create a default .env if none exists to make onboarding faster
|
|
146
|
+
try {
|
|
147
|
+
const envTemplatePath = path.join(targetDir, '.env.template');
|
|
148
|
+
const envPath = path.join(targetDir, '.env');
|
|
149
|
+
if (await fs.pathExists(envTemplatePath) && !(await fs.pathExists(envPath))) {
|
|
150
|
+
await fs.copy(envTemplatePath, envPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch { }
|
|
100
154
|
console.log(`\nSuccess! Created ${projectDir} using the ${template} template.`);
|
|
101
155
|
console.log(`\nNext steps:`);
|
|
102
156
|
console.log(` cd ${projectDir}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-sbc-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Scaffold a new SBC App Kit project with one command.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-sbc-app": "bin/cli.js"
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"create-app",
|
|
48
48
|
"template",
|
|
49
49
|
"react",
|
|
50
|
-
"nextjs",
|
|
51
50
|
"vite"
|
|
52
51
|
],
|
|
53
52
|
"author": "SBC Team",
|
package/templates/README.md
CHANGED
|
@@ -5,15 +5,22 @@ This directory contains ready-to-use templates for quickly starting new projects
|
|
|
5
5
|
## Available Templates
|
|
6
6
|
|
|
7
7
|
- **react/** – Minimal React app with SBC integration (Vite)
|
|
8
|
+
- **react-dynamic/** – React + Dynamic wallet + SBC App Kit (Vite)
|
|
9
|
+
- **react-para/** – React + Para wallet + SBC App Kit (Vite)
|
|
8
10
|
|
|
9
11
|
## How to Use a Template
|
|
10
12
|
|
|
11
13
|
1. **Copy the template directory** you want to use:
|
|
12
14
|
|
|
13
15
|
```bash
|
|
16
|
+
# Plain React template
|
|
14
17
|
cp -r create-sbc-app/react my-new-sbc-app
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
|
|
19
|
+
# Dynamic wallet template
|
|
20
|
+
cp -r create-sbc-app/react-dynamic my-dynamic-app
|
|
21
|
+
|
|
22
|
+
# Para wallet template
|
|
23
|
+
cp -r create-sbc-app/react-para my-para-app
|
|
17
24
|
```
|
|
18
25
|
|
|
19
26
|
2. **Install dependencies:**
|
|
@@ -34,8 +41,8 @@ This directory contains ready-to-use templates for quickly starting new projects
|
|
|
34
41
|
```
|
|
35
42
|
|
|
36
43
|
4. **Customize as needed:**
|
|
37
|
-
- Update the API key and config in `src/App.tsx
|
|
38
|
-
- Follow
|
|
44
|
+
- Update the API key and config in `src/App.tsx`.
|
|
45
|
+
- Follow each template’s README for specific details and environment variables.
|
|
39
46
|
|
|
40
47
|
## Keeping Templates Up to Date
|
|
41
48
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# SBC App Kit Configuration
|
|
2
|
-
|
|
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
|
|
@@ -11,13 +11,11 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@stablecoin.xyz/core": "latest",
|
|
13
13
|
"@stablecoin.xyz/react": "latest",
|
|
14
|
-
"react": "^
|
|
15
|
-
"react-dom": "^
|
|
16
|
-
"viem": "^2.
|
|
14
|
+
"react": "^19.1.0",
|
|
15
|
+
"react-dom": "^19.1.0",
|
|
16
|
+
"viem": "^2.33.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@types/react": "^18.2.0",
|
|
20
|
-
"@types/react-dom": "^18.2.0",
|
|
21
19
|
"@vitejs/plugin-react": "^4.0.0",
|
|
22
20
|
"typescript": "^5.0.0",
|
|
23
21
|
"vite": "^5.0.0"
|
|
@@ -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
|
|
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>
|
|
294
|
+
<label>{getTokenSymbol(chain)} Balance:</label>
|
|
211
295
|
<div className="value">
|
|
212
|
-
{isLoadingBalance ? 'Loading...' : `${formatSbcBalance(sbcBalance)}
|
|
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
|
|
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
|
|
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
|
|
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...' :
|
|
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
|
|
408
|
+
View transaction: {data.transactionHash}
|
|
325
409
|
</a>
|
|
326
410
|
</div>
|
|
327
411
|
)}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Get your SBC API Key at https://dashboard.stablecoin.xyz
|
|
2
|
+
VITE_SBC_API_KEY={{apiKey}}
|
|
3
|
+
# Get your Dynamic Environment ID at https://app.dynamic.xyz/
|
|
4
|
+
VITE_DYNAMIC_ENVIRONMENT_ID=your_dynamic_env_id
|
|
5
|
+
# Supported chains: "baseSepolia" | "base" | "radiusTestnet"
|
|
6
|
+
VITE_CHAIN={{chain}}
|
|
7
|
+
VITE_RPC_URL=
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# React + Dynamic + SBC App Kit
|
|
2
|
+
|
|
3
|
+
Gasless transactions on Base using Dynamic SDK with SBC App Kit.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Environment
|
|
13
|
+
|
|
14
|
+
Create `.env` from `.env.template` and fill in values:
|
|
15
|
+
|
|
16
|
+
```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=
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from '@typescript-eslint/eslint-plugin'
|
|
6
|
+
import tsparser from '@typescript-eslint/parser'
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
{ ignores: ['dist'] },
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
ecmaVersion: 2020,
|
|
14
|
+
globals: globals.browser,
|
|
15
|
+
parser: tsparser,
|
|
16
|
+
},
|
|
17
|
+
plugins: {
|
|
18
|
+
'@typescript-eslint': tseslint,
|
|
19
|
+
'react-hooks': reactHooks,
|
|
20
|
+
'react-refresh': reactRefresh,
|
|
21
|
+
},
|
|
22
|
+
rules: {
|
|
23
|
+
...js.configs.recommended.rules,
|
|
24
|
+
...tseslint.configs.recommended.rules,
|
|
25
|
+
...reactHooks.configs.recommended.rules,
|
|
26
|
+
'react-refresh/only-export-components': [
|
|
27
|
+
'warn',
|
|
28
|
+
{ allowConstantExport: true },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{projectName}} – SBC + Dynamic</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@dynamic-labs/ethereum": "^4.25.7",
|
|
13
|
+
"@dynamic-labs/ethereum-aa": "^4.25.7",
|
|
14
|
+
"@dynamic-labs/sdk-react-core": "^4.25.7",
|
|
15
|
+
"@stablecoin.xyz/core": "latest",
|
|
16
|
+
"@stablecoin.xyz/react": "latest",
|
|
17
|
+
"react": "^19.1.0",
|
|
18
|
+
"react-dom": "^19.1.0",
|
|
19
|
+
"viem": "^2.33.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^18.2.0",
|
|
23
|
+
"@types/react-dom": "^18.2.0",
|
|
24
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
25
|
+
"autoprefixer": "^10.4.0",
|
|
26
|
+
"postcss": "^8.4.0",
|
|
27
|
+
"tailwindcss": "^3.4.17",
|
|
28
|
+
"typescript": "^5.0.0",
|
|
29
|
+
"vite": "^5.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
Binary file
|