create-sbc-app 0.3.0 → 0.4.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.
@@ -0,0 +1,206 @@
1
+ # {{projectName}}
2
+
3
+ SBC + Turnkey fullstack example with embedded wallets and gasless transactions.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Backend Architecture**: Express server handling Turnkey sub-org creation
8
+ - ✅ **Passkey Authentication**: Users create wallets with biometric auth (Face ID/Touch ID)
9
+ - ✅ **Wallet Authentication**: Connect with MetaMask/Coinbase Wallet as alternative to passkeys
10
+ - ✅ **Embedded Wallets**: Non-custodial wallets managed by Turnkey
11
+ - ✅ **Smart Accounts**: ERC-4337 account abstraction with SBC paymaster
12
+ - ✅ **Gasless Transactions**: All gas fees sponsored by SBC
13
+ - ✅ **Account History**: Never lose access to accounts - all accounts saved and switchable
14
+
15
+ ## Prerequisites
16
+
17
+ Before you begin, you need:
18
+
19
+ 1. **SBC API Key**: Get from https://dashboard.stablecoin.xyz
20
+ 2. **Turnkey Organization + API Keys**: Get from https://app.turnkey.com
21
+
22
+ ### Getting Turnkey API Keys
23
+
24
+ 1. Go to https://app.turnkey.com and sign up
25
+ 2. Create a new organization (or use existing)
26
+ 3. Navigate to **Settings** → **API Keys**
27
+ 4. Click **"Create API Key"**
28
+ 5. **Save both keys securely**:
29
+ - Copy the **API Public Key**
30
+ - Copy the **API Private Key** (shown only once!)
31
+ 6. Copy your **Organization ID** from Settings
32
+
33
+ ## Quick Start
34
+
35
+ ### 1. Install Dependencies
36
+
37
+ ```bash
38
+ npm install
39
+ # or
40
+ pnpm install
41
+ ```
42
+
43
+ ### 2. Configure Environment Variables
44
+
45
+ Edit the `.env` file that was created for you:
46
+
47
+ ```bash
48
+ # SBC Configuration
49
+ VITE_SBC_API_KEY=your_sbc_api_key_here
50
+
51
+ # Turnkey Frontend (Public - safe for browser)
52
+ VITE_TURNKEY_API_BASE_URL=https://api.turnkey.com
53
+ VITE_TURNKEY_RPID=localhost
54
+
55
+ # Turnkey Backend (Secret - never expose to frontend!)
56
+ TURNKEY_API_BASE_URL=https://api.turnkey.com
57
+ TURNKEY_ORGANIZATION_ID=your_turnkey_org_id_here
58
+ TURNKEY_API_PUBLIC_KEY=your_turnkey_public_key_here
59
+ TURNKEY_API_PRIVATE_KEY=your_turnkey_private_key_here
60
+
61
+ # Backend Server
62
+ PORT=3001
63
+ VITE_BACKEND_URL=http://localhost:3001
64
+ ```
65
+
66
+ ### 3. Run the Application
67
+
68
+ **Option A: Run both frontend and backend together (recommended)**
69
+
70
+ ```bash
71
+ npm run dev:fullstack
72
+ ```
73
+
74
+ This starts:
75
+ - Backend server on `http://localhost:3001`
76
+ - Frontend on `http://localhost:5173`
77
+
78
+ **Option B: Run separately**
79
+
80
+ ```bash
81
+ # Terminal 1 - Backend
82
+ npm run dev:backend
83
+
84
+ # Terminal 2 - Frontend
85
+ npm run dev
86
+ ```
87
+
88
+ ## How It Works
89
+
90
+ ### Authentication Flows
91
+
92
+ #### Passkey Flow (Biometric)
93
+ 1. User clicks "Continue with Passkey"
94
+ 2. Browser creates passkey via WebAuthn (Face ID/Touch ID)
95
+ 3. Frontend → Backend: `POST /api/create-sub-org` with attestation
96
+ 4. Backend → Turnkey: Creates sub-org + Turnkey-managed wallet
97
+ 5. User signs transactions with biometric auth
98
+
99
+ #### Wallet Flow (MetaMask/Coinbase)
100
+ 1. User clicks "Connect Wallet"
101
+ 2. MetaMask prompts for connection + signature
102
+ 3. Frontend derives public key from signature
103
+ 4. Frontend → Backend: `POST /api/create-sub-org-with-wallet` with public key
104
+ 5. Backend → Turnkey: Creates sub-org (uses user's wallet as owner)
105
+ 6. User signs transactions with their connected wallet
106
+
107
+ ### Transaction Flow
108
+
109
+ 1. User initiates transaction (e.g., "Send 1 SBC")
110
+ 2. Frontend builds transaction via SBC App Kit
111
+ 3. User signs transaction:
112
+ - **Passkey**: Biometric prompt (Face ID/Touch ID)
113
+ - **Wallet**: MetaMask/Coinbase popup
114
+ 4. SBC paymaster sponsors all gas fees
115
+ 5. Transaction executes on-chain via ERC-4337
116
+
117
+ ## Project Structure
118
+
119
+ ```
120
+ {{projectName}}/
121
+ ├── server/ # Backend Express server
122
+ │ └── index.ts # Turnkey API endpoints
123
+ ├── src/ # Frontend React app
124
+ │ ├── App.tsx # Main app component
125
+ │ ├── main.tsx # Entry point
126
+ │ └── index.css # Styles
127
+ ├── public/ # Static assets
128
+ ├── .env # Environment variables (do not commit!)
129
+ └── package.json # Dependencies and scripts
130
+ ```
131
+
132
+ ## Available Scripts
133
+
134
+ - `npm run dev` - Start frontend development server
135
+ - `npm run dev:backend` - Start backend server with hot reload
136
+ - `npm run dev:fullstack` - Run both frontend and backend concurrently
137
+ - `npm run build` - Build for production
138
+ - `npm run preview` - Preview production build
139
+
140
+ ## Security Notes
141
+
142
+ ⚠️ **IMPORTANT**: Never expose Turnkey API keys to the frontend!
143
+
144
+ - API keys stay on the backend only
145
+ - Frontend uses passkeys for user authentication
146
+ - Each user gets their own isolated sub-organization
147
+
148
+ ## Production Deployment
149
+
150
+ ### Backend Deployment
151
+ Deploy the Express server to:
152
+ - Railway, Render, Fly.io (Node.js)
153
+ - Vercel, Netlify (Serverless functions)
154
+ - AWS Lambda, Google Cloud Functions
155
+
156
+ ### Frontend Deployment
157
+ Deploy the Vite app to:
158
+ - Vercel, Netlify, CloudFlare Pages
159
+ - Any static hosting service
160
+
161
+ ### Environment Variables
162
+
163
+ **Frontend (.env):**
164
+ ```bash
165
+ VITE_SBC_API_KEY=prod_key_here
166
+ VITE_TURNKEY_API_BASE_URL=https://api.turnkey.com
167
+ VITE_TURNKEY_RPID=yourdomain.com # Your production domain
168
+ VITE_BACKEND_URL=https://your-backend.com
169
+ ```
170
+
171
+ **Backend (.env):**
172
+ ```bash
173
+ TURNKEY_API_BASE_URL=https://api.turnkey.com
174
+ TURNKEY_ORGANIZATION_ID=prod_org_id
175
+ TURNKEY_API_PUBLIC_KEY=prod_public_key
176
+ TURNKEY_API_PRIVATE_KEY=prod_private_key
177
+ PORT=3001
178
+ ```
179
+
180
+ ## Troubleshooting
181
+
182
+ ### "Failed to create sub-org"
183
+ - Check backend logs for detailed error
184
+ - Verify `TURNKEY_API_PUBLIC_KEY` and `TURNKEY_API_PRIVATE_KEY` are correct
185
+ - Ensure `TURNKEY_ORGANIZATION_ID` matches your Turnkey org
186
+
187
+ ### "Network Error" when signing up
188
+ - Make sure backend is running (`npm run dev:backend`)
189
+ - Check `VITE_BACKEND_URL` points to correct backend URL
190
+ - Verify CORS is configured (backend allows frontend origin)
191
+
192
+ ### Passkey creation fails
193
+ - Use HTTPS in production (passkeys require secure context)
194
+ - For localhost: use `http://localhost` (not `127.0.0.1`)
195
+ - Check `VITE_TURNKEY_RPID` matches your domain
196
+
197
+ ## Resources
198
+
199
+ - [Turnkey Documentation](https://docs.turnkey.com)
200
+ - [Turnkey Dashboard](https://app.turnkey.com)
201
+ - [SBC App Kit Docs](https://docs.stablecoin.xyz)
202
+ - [Turnkey SDK GitHub](https://github.com/tkhq/sdk)
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,32 @@
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
+ ]
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/sbc-logo.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{projectName}}</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="/src/main.tsx"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "dev:backend": "tsx watch server/index.ts",
9
+ "dev:fullstack": "concurrently \"npm run dev:backend\" \"npm run dev\"",
10
+ "build": "tsc && vite build",
11
+ "preview": "vite preview",
12
+ "lint": "eslint .",
13
+ "start": "vite"
14
+ },
15
+ "dependencies": {
16
+ "@stablecoin.xyz/core": "latest",
17
+ "@stablecoin.xyz/react": "latest",
18
+ "@turnkey/sdk-react": "^5.4.10",
19
+ "@turnkey/sdk-server": "^4.12.0",
20
+ "@turnkey/viem": "^0.14.12",
21
+ "cors": "^2.8.5",
22
+ "dotenv": "^17.2.3",
23
+ "express": "^5.1.0",
24
+ "react": "^18.2.0",
25
+ "react-dom": "^18.2.0",
26
+ "viem": "^2.33.0"
27
+ },
28
+ "devDependencies": {
29
+ "@eslint/js": "^9.9.0",
30
+ "@types/cors": "^2.8.19",
31
+ "@types/express": "^5.0.5",
32
+ "@types/react": "^18.2.66",
33
+ "@types/react-dom": "^18.2.22",
34
+ "@vitejs/plugin-react": "^4.0.0",
35
+ "concurrently": "^9.2.1",
36
+ "eslint": "^9.9.0",
37
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
38
+ "eslint-plugin-react-refresh": "^0.4.9",
39
+ "globals": "^15.9.0",
40
+ "tsx": "^4.20.6",
41
+ "typescript": "^5.0.0",
42
+ "typescript-eslint": "^8.0.1",
43
+ "vite": "^5.0.0"
44
+ }
45
+ }
@@ -0,0 +1,271 @@
1
+ import 'dotenv/config';
2
+ import express from 'express';
3
+ import cors from 'cors';
4
+ import { Turnkey } from '@turnkey/sdk-server';
5
+
6
+ const app = express();
7
+ const port = process.env.PORT || 3001;
8
+
9
+ app.use(cors());
10
+ app.use(express.json());
11
+
12
+ // Log environment variables BEFORE initialization
13
+ console.log('\n🔍 Environment Variables:');
14
+ console.log(' TURNKEY_ORGANIZATION_ID:', process.env.TURNKEY_ORGANIZATION_ID);
15
+ console.log(' TURNKEY_API_BASE_URL:', process.env.TURNKEY_API_BASE_URL);
16
+ console.log(' TURNKEY_API_PUBLIC_KEY:', process.env.TURNKEY_API_PUBLIC_KEY?.substring(0, 20) + '...');
17
+ console.log(' TURNKEY_API_PRIVATE_KEY:', process.env.TURNKEY_API_PRIVATE_KEY ? '[SET]' : '[NOT SET]');
18
+
19
+ // Initialize Turnkey SDK
20
+ const turnkey = new Turnkey({
21
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!,
22
+ apiBaseUrl: process.env.TURNKEY_API_BASE_URL || 'https://api.turnkey.com',
23
+ apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!,
24
+ apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!,
25
+ });
26
+
27
+ // Create API client for signing requests
28
+ const turnkeyClient = turnkey.apiClient();
29
+
30
+ console.log('\n✓ Turnkey SDK initialized');
31
+ console.log(' Using Organization ID:', process.env.TURNKEY_ORGANIZATION_ID);
32
+ console.log(' API Base URL:', process.env.TURNKEY_API_BASE_URL);
33
+
34
+ /**
35
+ * Create sub-organization with passkey authentication
36
+ *
37
+ * Creates a new Turnkey sub-organization for a user using WebAuthn passkey authentication.
38
+ * This is the primary flow for biometric authentication (Face ID/Touch ID).
39
+ * Also creates a Turnkey-managed wallet for the user.
40
+ *
41
+ * @route POST /api/create-sub-org
42
+ * @param {string} req.body.userName - User's display name
43
+ * @param {string} req.body.userEmail - User's email address
44
+ * @param {string} req.body.attestation - WebAuthn attestation object from passkey creation
45
+ * @param {string} req.body.challenge - WebAuthn challenge used for passkey creation
46
+ * @returns {object} Response object
47
+ * @returns {string} response.subOrganizationId - ID of the created sub-organization
48
+ * @returns {string[]} response.addresses - Array of wallet addresses (Turnkey-managed wallet)
49
+ * @throws {500} If sub-organization creation fails
50
+ */
51
+ app.post('/api/create-sub-org', async (req, res) => {
52
+ console.log('\n🚀 [BACKEND] POST /api/create-sub-org - Request received');
53
+
54
+ try {
55
+ const { userName, userEmail, attestation, challenge } = req.body;
56
+ console.log('📝 [BACKEND] Request data:', {
57
+ userName,
58
+ userEmail,
59
+ hasAttestation: !!attestation,
60
+ hasChallenge: !!challenge,
61
+ attestationLength: attestation?.length,
62
+ });
63
+
64
+ // Step 1: Create sub-organization
65
+ console.log('🏢 [BACKEND] Step 1: Creating sub-organization...');
66
+ console.log('🔑 [BACKEND] Forcing organization ID:', process.env.TURNKEY_ORGANIZATION_ID);
67
+
68
+ const subOrgResponse = await turnkeyClient.createSubOrganization({
69
+ organizationId: process.env.TURNKEY_ORGANIZATION_ID!,
70
+ subOrganizationName: `${userName}'s Organization`,
71
+ rootUsers: [{
72
+ userName,
73
+ userEmail,
74
+ apiKeys: [],
75
+ authenticators: [{
76
+ authenticatorName: `${userName}'s Passkey`,
77
+ challenge,
78
+ attestation,
79
+ }],
80
+ oauthProviders: [],
81
+ }],
82
+ rootQuorumThreshold: 1,
83
+ wallet: {
84
+ walletName: `${userName}'s Wallet`,
85
+ accounts: [{
86
+ curve: 'CURVE_SECP256K1',
87
+ pathFormat: 'PATH_FORMAT_BIP32',
88
+ path: "m/44'/60'/0'/0/0",
89
+ addressFormat: 'ADDRESS_FORMAT_ETHEREUM',
90
+ }],
91
+ },
92
+ });
93
+ console.log('✅ [BACKEND] Sub-org created successfully!', {
94
+ subOrgId: subOrgResponse.subOrganizationId,
95
+ wallet: subOrgResponse.wallet,
96
+ });
97
+
98
+ const subOrgId = subOrgResponse.subOrganizationId;
99
+ const walletAddresses = subOrgResponse.wallet?.addresses || [];
100
+
101
+ const responseData = {
102
+ subOrganizationId: subOrgId,
103
+ addresses: walletAddresses,
104
+ };
105
+ console.log('📤 [BACKEND] Sending success response:', responseData);
106
+ res.json(responseData);
107
+ } catch (error: any) {
108
+ console.error('❌ [BACKEND] Error creating sub-org:', error);
109
+ console.error('❌ [BACKEND] Error message:', error.message);
110
+ console.error('❌ [BACKEND] Error stack:', error.stack);
111
+ res.status(500).json({ error: error.message });
112
+ }
113
+ });
114
+
115
+ /**
116
+ * Create sub-organization with wallet authentication
117
+ *
118
+ * Creates a new Turnkey sub-organization for a user using external wallet authentication
119
+ * (MetaMask, Coinbase Wallet, etc.). The user's wallet becomes the smart account owner.
120
+ * Does NOT create a Turnkey-managed wallet - uses user's existing wallet instead.
121
+ *
122
+ * @route POST /api/create-sub-org-with-wallet
123
+ * @param {string} req.body.userName - User's display name
124
+ * @param {string} req.body.userEmail - User's email address
125
+ * @param {string} req.body.publicKey - Compressed secp256k1 public key derived from wallet signature
126
+ * @param {string} req.body.walletAddress - User's wallet address (0x...)
127
+ * @returns {object} Response object
128
+ * @returns {string} response.subOrganizationId - ID of the created sub-organization
129
+ * @returns {string[]} response.addresses - Array containing the user's wallet address
130
+ * @throws {400} If required parameters are missing
131
+ * @throws {500} If sub-organization creation fails
132
+ */
133
+ app.post('/api/create-sub-org-with-wallet', async (req, res) => {
134
+ console.log('\n🚀 [BACKEND] POST /api/create-sub-org-with-wallet - Request received');
135
+
136
+ try {
137
+ const { userName, userEmail, publicKey, walletAddress } = req.body;
138
+ console.log('📝 [BACKEND] Request data:', {
139
+ userName,
140
+ userEmail,
141
+ publicKey,
142
+ walletAddress,
143
+ });
144
+
145
+ if (!userName || !userEmail || !publicKey || !walletAddress) {
146
+ return res.status(400).json({ error: 'userName, userEmail, publicKey, and walletAddress are required' });
147
+ }
148
+
149
+ // Compressed public keys from secp256k1 start with 0x02 or 0x03 (33 bytes / 66 hex chars)
150
+ console.log('📝 [BACKEND] Using public key:', publicKey);
151
+ const formattedPublicKey = publicKey;
152
+
153
+ // Create sub-organization with wallet-based API key (NO Turnkey wallet creation)
154
+ console.log('🏢 [BACKEND] Creating sub-organization with wallet authentication...');
155
+ console.log('🔑 [BACKEND] User will use their MetaMask wallet as owner:', walletAddress);
156
+
157
+ const subOrgResponse = await turnkeyClient.createSubOrganization({
158
+ organizationId: process.env.TURNKEY_ORGANIZATION_ID!,
159
+ subOrganizationName: `${userName}'s Organization`,
160
+ rootUsers: [{
161
+ userName,
162
+ userEmail,
163
+ apiKeys: [{
164
+ apiKeyName: `${userName}'s Wallet Key`,
165
+ publicKey: formattedPublicKey,
166
+ curveType: 'API_KEY_CURVE_SECP256K1',
167
+ }],
168
+ authenticators: [], // No passkey authenticators for wallet-based auth
169
+ oauthProviders: [],
170
+ }],
171
+ rootQuorumThreshold: 1,
172
+ // NO wallet creation - user's MetaMask wallet will be the owner
173
+ });
174
+
175
+ console.log('✅ [BACKEND] Sub-org created with wallet auth!', {
176
+ subOrgId: subOrgResponse.subOrganizationId,
177
+ userWalletAddress: walletAddress,
178
+ });
179
+
180
+ const subOrgId = subOrgResponse.subOrganizationId;
181
+
182
+ // Return the user's MetaMask wallet address, not a Turnkey wallet
183
+ const responseData = {
184
+ subOrganizationId: subOrgId,
185
+ addresses: [walletAddress], // User's MetaMask wallet is the owner
186
+ };
187
+ console.log('📤 [BACKEND] Sending success response:', responseData);
188
+ res.json(responseData);
189
+ } catch (error: any) {
190
+ console.error('❌ [BACKEND] Error creating sub-org with wallet:', error);
191
+ console.error('❌ [BACKEND] Error message:', error.message);
192
+ console.error('❌ [BACKEND] Error stack:', error.stack);
193
+ res.status(500).json({ error: error.message });
194
+ }
195
+ });
196
+
197
+ /**
198
+ * Get sub-organization by wallet public key
199
+ *
200
+ * Looks up a sub-organization ID using the wallet's public key.
201
+ * Used to check if a wallet user already has an existing sub-organization.
202
+ * Also fetches wallet addresses associated with the sub-organization.
203
+ *
204
+ * @route POST /api/get-sub-org-by-wallet
205
+ * @param {string} req.body.publicKey - Compressed secp256k1 public key to search for
206
+ * @returns {object} Response object
207
+ * @returns {string|null} response.subOrganizationId - ID of the found sub-organization, or null if not found
208
+ * @returns {string[]} response.addresses - Array of wallet addresses (empty if not found)
209
+ * @throws {400} If publicKey is missing or invalid
210
+ * @throws {500} If sub-organization lookup fails
211
+ */
212
+ app.post('/api/get-sub-org-by-wallet', async (req, res) => {
213
+ console.log('\n🔍 [BACKEND] POST /api/get-sub-org-by-wallet - Request received');
214
+
215
+ try {
216
+ const { publicKey } = req.body;
217
+ console.log('📝 [BACKEND] Public key:', publicKey);
218
+
219
+ if (!publicKey || typeof publicKey !== 'string') {
220
+ return res.status(400).json({ error: 'publicKey is required' });
221
+ }
222
+
223
+ console.log('🔎 [BACKEND] Searching for sub-org with public key...');
224
+ const result = await turnkeyClient.getSubOrgIds({
225
+ organizationId: process.env.TURNKEY_ORGANIZATION_ID!,
226
+ filterType: 'PUBLIC_KEY',
227
+ filterValue: publicKey,
228
+ });
229
+
230
+ console.log('✅ [BACKEND] Sub-org IDs found:', result.organizationIds);
231
+
232
+ const subOrgId = result.organizationIds?.[0] ?? null;
233
+
234
+ if (subOrgId) {
235
+ // Fetch wallet info for this sub-org
236
+ console.log('💼 [BACKEND] Fetching wallet for sub-org:', subOrgId);
237
+ const walletsResponse = await turnkeyClient.getWallets({
238
+ organizationId: subOrgId,
239
+ });
240
+
241
+ const addresses: string[] = [];
242
+ if (walletsResponse.wallets && walletsResponse.wallets.length > 0) {
243
+ const wallet = walletsResponse.wallets[0];
244
+ const accountsResponse = await turnkeyClient.getWalletAccounts({
245
+ organizationId: subOrgId,
246
+ walletId: wallet.walletId,
247
+ });
248
+ if (accountsResponse.accounts) {
249
+ addresses.push(...accountsResponse.accounts.map(acc => acc.address));
250
+ }
251
+ }
252
+
253
+ res.json({
254
+ subOrganizationId: subOrgId,
255
+ addresses,
256
+ });
257
+ } else {
258
+ res.json({
259
+ subOrganizationId: null,
260
+ addresses: [],
261
+ });
262
+ }
263
+ } catch (error: any) {
264
+ console.error('❌ [BACKEND] Error fetching sub-org:', error);
265
+ res.status(500).json({ error: error.message });
266
+ }
267
+ });
268
+
269
+ app.listen(port, () => {
270
+ console.log(`Turnkey backend server running on http://localhost:${port}`);
271
+ });