create-moncircle 1.0.2 → 1.2.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/README.md +6 -4
- package/index.js +347 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,10 +8,12 @@ Interactive setup wizard for [@moncircle/sdk](https://npmjs.com/package/@moncirc
|
|
|
8
8
|
npx create-moncircle
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
1.
|
|
13
|
-
2.
|
|
14
|
-
3.
|
|
11
|
+
### What it does (v1.1.0 — Autopilot Release):
|
|
12
|
+
1. **Config wizard** → writes .env (MongoDB URI, Monad keys, etc.)
|
|
13
|
+
2. **Contract deploy** → deploys optimized MonLoyalty contract to Monad testnet
|
|
14
|
+
3. **Full Scaffolding** → Generates an advanced `loyalty-dashboard.html` and a pre-configured `server.js` backend
|
|
15
|
+
4. **Autopilot Enabled** → Sets up DOM-based tracking for zero-conf rewarding
|
|
16
|
+
the server
|
|
15
17
|
|
|
16
18
|
## Reconfigure
|
|
17
19
|
|
package/index.js
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* create-moncircle —
|
|
3
|
+
* create-moncircle — Single command to set up, deploy & launch @moncircle/sdk
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* npx create-moncircle
|
|
7
|
-
* npm install @moncircle/sdk (auto-triggered via postinstall)
|
|
6
|
+
* npx create-moncircle
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* What it does (in order):
|
|
9
|
+
* 1. Config wizard → writes .env (MongoDB URI, Monad keys, etc.)
|
|
10
|
+
* 2. Contract deploy → deploys MonLoyalty.sol to Monad testnet → writes MONAD_CONTRACT_ADDRESS to .env
|
|
11
|
+
* 3. Starts the API → runs `npx mon-loyalty-api start`
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const readline = require("readline")
|
|
15
15
|
const fs = require("fs")
|
|
16
16
|
const path = require("path")
|
|
17
|
-
const { execSync } = require("child_process")
|
|
18
|
-
|
|
19
|
-
const ENV_PATH = process.env.MON_LOYALTY_ENV_PATH || path.join(process.cwd(), ".env")
|
|
20
|
-
const PKG_PATH = path.join(process.cwd(), "package.json")
|
|
17
|
+
const { execSync, spawnSync, spawn } = require("child_process")
|
|
21
18
|
|
|
19
|
+
// ── Colours ────────────────────────────────────────────────────────────────
|
|
22
20
|
const CYAN = "\x1b[36m"
|
|
23
21
|
const GREEN = "\x1b[32m"
|
|
24
22
|
const YELLOW = "\x1b[33m"
|
|
@@ -27,16 +25,10 @@ const DIM = "\x1b[2m"
|
|
|
27
25
|
const BOLD = "\x1b[1m"
|
|
28
26
|
const RESET = "\x1b[0m"
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const ask = (question, defaultVal = "") =>
|
|
33
|
-
new Promise((resolve) => {
|
|
34
|
-
const hint = defaultVal ? ` ${DIM}(${defaultVal})${RESET}` : ""
|
|
35
|
-
rl.question(` ${question}${hint} › `, (answer) => {
|
|
36
|
-
resolve(answer.trim() || defaultVal)
|
|
37
|
-
})
|
|
38
|
-
})
|
|
28
|
+
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
29
|
+
const ENV_PATH = process.env.MON_LOYALTY_ENV_PATH || path.join(process.cwd(), ".env")
|
|
39
30
|
|
|
31
|
+
// ── Config fields ──────────────────────────────────────────────────────────
|
|
40
32
|
const fields = [
|
|
41
33
|
{
|
|
42
34
|
key: "MONGODB_URI",
|
|
@@ -57,7 +49,7 @@ const fields = [
|
|
|
57
49
|
label: "JWT Secret",
|
|
58
50
|
hint: "Long random string for signing tokens",
|
|
59
51
|
required: false,
|
|
60
|
-
default: "
|
|
52
|
+
default: "moncircle_" + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)
|
|
61
53
|
},
|
|
62
54
|
{
|
|
63
55
|
key: "MONAD_RPC_URL",
|
|
@@ -69,19 +61,34 @@ const fields = [
|
|
|
69
61
|
{
|
|
70
62
|
key: "MONAD_PRIVATE_KEY",
|
|
71
63
|
label: "Monad Private Key",
|
|
72
|
-
hint: "Master wallet private key — KEEP SECRET",
|
|
64
|
+
hint: "Master wallet private key — KEEP SECRET. Funds this wallet = contract can pay users.",
|
|
73
65
|
required: false,
|
|
74
66
|
default: ""
|
|
75
67
|
},
|
|
76
68
|
{
|
|
77
|
-
key: "
|
|
78
|
-
label: "
|
|
79
|
-
hint: "
|
|
69
|
+
key: "MON_PUBLIC_KEY",
|
|
70
|
+
label: "MonCircle Public Key",
|
|
71
|
+
hint: "Your platform's public key (e.g., pk_live_...) for SDK access",
|
|
72
|
+
required: false,
|
|
73
|
+
default: "pk_live_67df231b188e46d38a2baef03b217756"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "MON_API_KEY",
|
|
77
|
+
label: "MonCircle API Key",
|
|
78
|
+
hint: "Your platform's secret API key (e.g., sk_live_...) for backend verification",
|
|
80
79
|
required: false,
|
|
81
80
|
default: ""
|
|
82
81
|
}
|
|
83
82
|
]
|
|
84
83
|
|
|
84
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
85
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
86
|
+
const ask = (question, defaultVal = "") =>
|
|
87
|
+
new Promise((resolve) => {
|
|
88
|
+
const hint = defaultVal ? ` ${DIM}(${defaultVal})${RESET}` : ""
|
|
89
|
+
rl.question(` ${question}${hint} › `, (answer) => resolve(answer.trim() || defaultVal))
|
|
90
|
+
})
|
|
91
|
+
|
|
85
92
|
function loadExistingEnv() {
|
|
86
93
|
if (!fs.existsSync(ENV_PATH)) return {}
|
|
87
94
|
const content = fs.readFileSync(ENV_PATH, "utf-8")
|
|
@@ -93,25 +100,15 @@ function loadExistingEnv() {
|
|
|
93
100
|
return map
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const key = match[1].trim()
|
|
104
|
-
if (updated[key] !== undefined) {
|
|
105
|
-
handled.add(key)
|
|
106
|
-
return `${key}="${updated[key]}"`
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return line
|
|
110
|
-
})
|
|
111
|
-
for (const key of Object.keys(updated)) {
|
|
112
|
-
if (!handled.has(key)) result.push(`${key}="${updated[key]}"`)
|
|
103
|
+
function mergeEnvKey(key, value) {
|
|
104
|
+
let content = fs.existsSync(ENV_PATH) ? fs.readFileSync(ENV_PATH, "utf-8") : ""
|
|
105
|
+
const regex = new RegExp(`^(${key}=.*)$`, "m")
|
|
106
|
+
if (regex.test(content)) {
|
|
107
|
+
content = content.replace(regex, `${key}="${value}"`)
|
|
108
|
+
} else {
|
|
109
|
+
content += `\n${key}="${value}"`
|
|
113
110
|
}
|
|
114
|
-
|
|
111
|
+
fs.writeFileSync(ENV_PATH, content, "utf-8")
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
function writeFreshEnv(values) {
|
|
@@ -129,81 +126,354 @@ function writeFreshEnv(values) {
|
|
|
129
126
|
"# ── Auth ─────────────────────────────────────",
|
|
130
127
|
`JWT_SECRET="${values.JWT_SECRET}"`,
|
|
131
128
|
"",
|
|
132
|
-
"# ── Monad On-Chain
|
|
129
|
+
"# ── Monad On-Chain ────────────────────────────",
|
|
133
130
|
`MONAD_RPC_URL="${values.MONAD_RPC_URL}"`,
|
|
134
131
|
`MONAD_PRIVATE_KEY="${values.MONAD_PRIVATE_KEY}"`,
|
|
135
|
-
`MONAD_CONTRACT_ADDRESS="
|
|
132
|
+
`MONAD_CONTRACT_ADDRESS=""`,
|
|
133
|
+
`MON_PUBLIC_KEY="${values.MON_PUBLIC_KEY}"`,
|
|
134
|
+
`MON_API_KEY="${values.MON_API_KEY}"`,
|
|
136
135
|
""
|
|
137
136
|
]
|
|
138
137
|
fs.writeFileSync(ENV_PATH, lines.join("\n"), "utf-8")
|
|
139
138
|
}
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function main() {
|
|
140
|
+
// ── Phase 1: Config wizard ─────────────────────────────────────────────────
|
|
141
|
+
async function runSetup() {
|
|
146
142
|
const existing = loadExistingEnv()
|
|
147
|
-
const
|
|
143
|
+
const isEdit = Object.keys(existing).length > 0
|
|
148
144
|
|
|
149
145
|
console.log(`\n${BOLD}${CYAN}╔══════════════════════════════════════════════════════╗`)
|
|
150
|
-
console.log(`║
|
|
146
|
+
console.log(`║ @moncircle/sdk — ${isEdit ? "Edit Config " : "Setup Wizard "} ║`)
|
|
151
147
|
console.log(`╚══════════════════════════════════════════════════════╝${RESET}\n`)
|
|
152
148
|
|
|
153
|
-
if (
|
|
154
|
-
console.log(` ${GREEN}✓
|
|
155
|
-
} else {
|
|
156
|
-
console.log(` ${DIM}Writing to: ${ENV_PATH}${RESET}\n`)
|
|
149
|
+
if (isEdit) {
|
|
150
|
+
console.log(` ${GREEN}✓ .env found.${RESET} ${DIM}Press Enter to keep values.${RESET}\n`)
|
|
157
151
|
}
|
|
158
152
|
|
|
159
153
|
const updated = { ...existing }
|
|
160
|
-
|
|
161
154
|
for (const field of fields) {
|
|
162
155
|
const currentVal = existing[field.key] ?? field.default
|
|
163
156
|
const requiredTag = field.required ? ` ${RED}*required${RESET}` : ""
|
|
164
|
-
|
|
165
157
|
console.log(` ${BOLD}${field.label}${RESET}${requiredTag}`)
|
|
166
|
-
if (!
|
|
167
|
-
console.log(` ${DIM}${field.hint}${RESET}`)
|
|
168
|
-
}
|
|
169
|
-
|
|
158
|
+
if (!isEdit || !existing[field.key]) console.log(` ${DIM}${field.hint}${RESET}`)
|
|
170
159
|
let val = ""
|
|
171
160
|
while (true) {
|
|
172
161
|
val = await ask(` →`, currentVal)
|
|
173
|
-
if (field.required && !val) {
|
|
174
|
-
|
|
175
|
-
} else {
|
|
176
|
-
break
|
|
177
|
-
}
|
|
162
|
+
if (field.required && !val) console.log(` ${RED}✖ Required.${RESET}`)
|
|
163
|
+
else break
|
|
178
164
|
}
|
|
179
165
|
updated[field.key] = val
|
|
180
166
|
console.log()
|
|
181
167
|
}
|
|
182
168
|
|
|
183
|
-
if (
|
|
184
|
-
fs.
|
|
185
|
-
|
|
169
|
+
if (isEdit) {
|
|
170
|
+
let content = fs.readFileSync(ENV_PATH, "utf-8")
|
|
171
|
+
for (const [k, v] of Object.entries(updated)) {
|
|
172
|
+
const regex = new RegExp(`^(${k}=.*)$`, "m")
|
|
173
|
+
if (regex.test(content)) content = content.replace(regex, `${k}="${v}"`)
|
|
174
|
+
else content += `\n${k}="${v}"`
|
|
175
|
+
}
|
|
176
|
+
fs.writeFileSync(ENV_PATH, content, "utf-8")
|
|
186
177
|
} else {
|
|
187
178
|
writeFreshEnv(updated)
|
|
188
|
-
console.log(`${GREEN}${BOLD} ✅ .env created successfully!${RESET}\n`)
|
|
189
179
|
}
|
|
190
180
|
|
|
191
|
-
|
|
192
|
-
|
|
181
|
+
console.log(`${GREEN}${BOLD} ✅ .env saved!${RESET}\n`)
|
|
182
|
+
|
|
183
|
+
const useAutopilot = (await ask(` Enable Autopilot Mode? ${DIM}(DOM triggers + event tracking)${RESET}`, "Y")).toLowerCase() === "y"
|
|
184
|
+
updated.USE_AUTOPILOT = useAutopilot
|
|
185
|
+
|
|
186
|
+
return updated
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Phase 2: Deploy contract ───────────────────────────────────────────────
|
|
190
|
+
async function deployContract(values) {
|
|
191
|
+
const rpcUrl = values.MONAD_RPC_URL
|
|
192
|
+
const privateKey = values.MONAD_PRIVATE_KEY
|
|
193
|
+
|
|
194
|
+
if (!rpcUrl || !privateKey) {
|
|
195
|
+
console.log(` ${YELLOW}⚠ Skipping contract deploy — MONAD_RPC_URL or MONAD_PRIVATE_KEY not set.${RESET}`)
|
|
196
|
+
console.log(` ${DIM}Run: npx create-moncircle to add them later.${RESET}\n`)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(` ${CYAN}${BOLD}Deploying MonLoyalty contract to Monad...${RESET}`)
|
|
201
|
+
|
|
202
|
+
// Find sdk's deploy script — works whether package is installed or run from source
|
|
203
|
+
const sdkRoot = (() => {
|
|
204
|
+
try { return path.dirname(require.resolve("@moncircle/sdk/package.json")) }
|
|
205
|
+
catch { return path.join(__dirname, "..") } // running from monorepo source
|
|
206
|
+
})()
|
|
207
|
+
|
|
208
|
+
const deployScript = path.join(sdkRoot, "scripts", "deploy-contract.js")
|
|
209
|
+
if (!fs.existsSync(deployScript)) {
|
|
210
|
+
console.log(` ${YELLOW}⚠ Deploy script not found. Build the SDK first: npm run build${RESET}\n`)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const result = spawnSync("node", [deployScript], {
|
|
215
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
216
|
+
env: { ...process.env, MONAD_RPC_URL: rpcUrl, MONAD_PRIVATE_KEY: privateKey }
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
if (result.status !== 0) {
|
|
220
|
+
console.log(` ${RED}✖ Contract deploy failed. Check your RPC URL and wallet balance.${RESET}\n`)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const output = result.stdout.toString().trim()
|
|
225
|
+
const match = output.match(/CONTRACT_ADDRESS=(0x[a-fA-F0-9]{40})/)
|
|
226
|
+
if (!match) {
|
|
227
|
+
console.log(` ${YELLOW}⚠ Could not parse contract address from deploy output.${RESET}`)
|
|
228
|
+
console.log(` ${DIM}${output}${RESET}\n`)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const contractAddress = match[1]
|
|
233
|
+
mergeEnvKey("MONAD_CONTRACT_ADDRESS", contractAddress)
|
|
234
|
+
console.log(` ${GREEN}✅ Contract deployed: ${CYAN}${contractAddress}${RESET}`)
|
|
235
|
+
console.log(` ${GREEN}✅ MONAD_CONTRACT_ADDRESS written to .env${RESET}\n`)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Phase 3: Start the API ─────────────────────────────────────────────────
|
|
239
|
+
function startApi() {
|
|
240
|
+
// Check if @moncircle/sdk is installed
|
|
241
|
+
const sdkBin = (() => {
|
|
242
|
+
try {
|
|
243
|
+
const sdkRoot = path.dirname(require.resolve("@moncircle/sdk/package.json"))
|
|
244
|
+
const bin = path.join(sdkRoot, "dist", "bin.js")
|
|
245
|
+
return fs.existsSync(bin) ? bin : null
|
|
246
|
+
} catch { return null }
|
|
247
|
+
})()
|
|
248
|
+
|
|
249
|
+
if (!sdkBin) {
|
|
250
|
+
// Not installed — install it first
|
|
193
251
|
console.log(` ${YELLOW}Installing @moncircle/sdk...${RESET}`)
|
|
194
252
|
try {
|
|
195
253
|
execSync("npm install @moncircle/sdk --save", { stdio: "inherit", cwd: process.cwd() })
|
|
196
|
-
console.log(` ${GREEN}✓ @moncircle/sdk installed!${RESET}\n`)
|
|
197
254
|
} catch {
|
|
198
|
-
console.log(` ${
|
|
255
|
+
console.log(` ${RED}✖ Could not install @moncircle/sdk. Run: npm install @moncircle/sdk${RESET}`)
|
|
256
|
+
return
|
|
199
257
|
}
|
|
200
258
|
}
|
|
201
259
|
|
|
202
|
-
console.log(
|
|
203
|
-
|
|
204
|
-
|
|
260
|
+
console.log(`\n${GREEN}${BOLD} 🚀 Starting MON Loyalty API...${RESET}\n`)
|
|
261
|
+
|
|
262
|
+
// Load .env before spawning
|
|
263
|
+
|
|
264
|
+
server.on("error", (err) => {
|
|
265
|
+
console.error(`${RED}Failed to start API:${RESET}`, err.message)
|
|
266
|
+
console.log(` Try manually: ${CYAN}npx mon-loyalty-api start${RESET}`)
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Phase 4: Create Scaffolding (Max Mode) ────────────────────────────────
|
|
271
|
+
function createScaffolding(values) {
|
|
272
|
+
const dashboardPath = path.join(process.cwd(), "loyalty-dashboard.html")
|
|
273
|
+
const serverPath = path.join(process.cwd(), "server.js")
|
|
274
|
+
const publicKey = values.MON_PUBLIC_KEY || "pk_live_67df231b188e46d38a2baef03b217756"
|
|
275
|
+
|
|
276
|
+
// 1. Generate Advanced Dashboard
|
|
277
|
+
const dashboardHtml = `<!DOCTYPE html>
|
|
278
|
+
<html lang="en">
|
|
279
|
+
<head>
|
|
280
|
+
<meta charset="UTF-8">
|
|
281
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
282
|
+
<title>MonCircle | Loyalty Dashboard</title>
|
|
283
|
+
<style>
|
|
284
|
+
:root { --bg: #0a0812; --card: #0f0d1a; --border: #1e1b2e; --primary: #6e54ff; --text: #e8e4f4; --dim: #6b6585; }
|
|
285
|
+
body { font-family: 'Geist', system-ui, sans-serif; background: var(--bg); color: var(--text); padding: 40px; }
|
|
286
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; max-width: 1200px; margin: 0 auto; }
|
|
287
|
+
.card { background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 24px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); }
|
|
288
|
+
h1 { font-size: 28px; margin-bottom: 8px; letter-spacing: -0.02em; }
|
|
289
|
+
p { color: var(--dim); font-size: 14px; margin-bottom: 24px; }
|
|
290
|
+
.stat { margin-bottom: 20px; }
|
|
291
|
+
.stat-label { font-size: 11px; text-transform: uppercase; color: var(--dim); letter-spacing: 0.05em; margin-bottom: 4px; }
|
|
292
|
+
.stat-value { font-size: 24px; font-weight: 700; color: var(--primary); }
|
|
293
|
+
.btn { background: var(--primary); color: #fff; border: none; padding: 12px 24px; border-radius: 8px; font-weight: 600; cursor: pointer; display: block; width: 100%; margin-top: 10px; }
|
|
294
|
+
.feed { margin-top: 20px; font-size: 12px; color: var(--dim); border-top: 1px solid var(--border); padding-top: 12px; }
|
|
295
|
+
</style>
|
|
296
|
+
</head>
|
|
297
|
+
<body>
|
|
298
|
+
<div style="max-width: 1200px; margin: 0 auto 40px;">
|
|
299
|
+
<h1>Loyalty Dashboard</h1>
|
|
300
|
+
<p>Connected to <b>${publicKey}</b> • Monad Testnet</p>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div class="grid">
|
|
304
|
+
<div class="card">
|
|
305
|
+
<div class="stat">
|
|
306
|
+
<div class="stat-label">Wallet Balance</div>
|
|
307
|
+
<div class="stat-value" id="dash-balance">0 MON</div>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="stat">
|
|
310
|
+
<div class="stat-label">Recent Activity</div>
|
|
311
|
+
<div class="feed" id="dash-feed">Waiting for transactions...</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div class="card">
|
|
316
|
+
<h3>Test Purchase</h3>
|
|
317
|
+
<p>Simulate a customer purchase to see Autopilot mode in action.</p>
|
|
318
|
+
<input type="number" id="purchase-amt" value="500" style="width:100%; background:#141121; border:1px solid var(--border); padding:10px; border-radius:8px; color:white; margin-bottom:12px;">
|
|
319
|
+
<button class="btn" onclick="simulatePurchase()">Place Order (Earn MON)</button>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<script>
|
|
324
|
+
function simulatePurchase() {
|
|
325
|
+
const amt = document.getElementById('purchase-amt').value;
|
|
326
|
+
const orderId = "ORD-" + Math.random().toString(36).slice(2, 9).toUpperCase();
|
|
327
|
+
|
|
328
|
+
// Emit Autopilot Event
|
|
329
|
+
window.dispatchEvent(new CustomEvent('mon-purchase-success', {
|
|
330
|
+
detail: { orderId, amount: amt }
|
|
331
|
+
}));
|
|
332
|
+
|
|
333
|
+
const feed = document.getElementById('dash-feed');
|
|
334
|
+
feed.innerHTML = "<div>Processed " + orderId + " • " + amt + " INR</div>" + feed.innerHTML;
|
|
335
|
+
}
|
|
336
|
+
</script>
|
|
337
|
+
|
|
338
|
+
<!-- MonCircle SDK Overlay -->
|
|
339
|
+
<script
|
|
340
|
+
src="https://moncircle.vercel.app/sdk/overlay.js"
|
|
341
|
+
data-key="${publicKey}"
|
|
342
|
+
data-user-id="demo_user_123"
|
|
343
|
+
data-autopilot="${values.USE_AUTOPILOT ? 'true' : 'false'}"
|
|
344
|
+
data-mon-amount="10">
|
|
345
|
+
</script>
|
|
346
|
+
</body>
|
|
347
|
+
</html>`
|
|
348
|
+
|
|
349
|
+
// 2. Generate Express Backend (ESM)
|
|
350
|
+
const serverJs = `import express from 'express';
|
|
351
|
+
import mongoose from 'mongoose';
|
|
352
|
+
import cors from 'cors';
|
|
353
|
+
import 'dotenv/config';
|
|
354
|
+
|
|
355
|
+
const app = express();
|
|
356
|
+
const PORT = process.env.PORT || 5000;
|
|
205
357
|
|
|
358
|
+
app.use(cors());
|
|
359
|
+
app.use(express.json());
|
|
360
|
+
|
|
361
|
+
// MongoDB Connection
|
|
362
|
+
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/moncircle')
|
|
363
|
+
.then(() => console.log('✅ Loyalty DB Connected'))
|
|
364
|
+
.catch(err => console.error('❌ DB Connection Error:', err));
|
|
365
|
+
|
|
366
|
+
// Order Model
|
|
367
|
+
const OrderSchema = new mongoose.Schema({
|
|
368
|
+
orderId: { type: String, required: true, unique: true },
|
|
369
|
+
total: { type: Number, required: true },
|
|
370
|
+
customer: { walletAddress: String, email: String },
|
|
371
|
+
status: { type: String, default: 'completed' },
|
|
372
|
+
createdAt: { type: Date, default: Date.now }
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const Order = mongoose.model('Order', OrderSchema);
|
|
376
|
+
|
|
377
|
+
// Checkout Endpoint (SDK will intercept this if proxy is set)
|
|
378
|
+
app.post('/api/checkout', async (req, res) => {
|
|
379
|
+
try {
|
|
380
|
+
const { orderId, total, customer } = req.body;
|
|
381
|
+
const newOrder = new Order({ orderId, total, customer });
|
|
382
|
+
await newOrder.save();
|
|
383
|
+
|
|
384
|
+
console.log(\`[Loyalty] Order \${orderId} saved locally.\`);
|
|
385
|
+
|
|
386
|
+
// Optional: Call MonCircle API for server-side verification
|
|
387
|
+
// fetch('https://moncircle.vercel.app/api/v1/orders', {
|
|
388
|
+
// headers: { 'x-api-key': process.env.MON_API_KEY }
|
|
389
|
+
// ...
|
|
390
|
+
|
|
391
|
+
res.json({ success: true, order: newOrder });
|
|
392
|
+
} catch (err) {
|
|
393
|
+
res.status(500).json({ error: err.message });
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
app.listen(PORT, () => console.log(\`🚀 Loyalty Backend running on http://localhost:\${PORT}\`));`
|
|
398
|
+
|
|
399
|
+
fs.writeFileSync(dashboardPath, dashboardHtml, "utf-8")
|
|
400
|
+
fs.writeFileSync(serverPath, serverJs, "utf-8")
|
|
401
|
+
|
|
402
|
+
return { dashboardPath, serverPath }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── Phase 4: Create demo page ──────────────────────────────────────────────
|
|
406
|
+
function createDemoPage(values) {
|
|
407
|
+
const demoPath = path.join(process.cwd(), "demo.html")
|
|
408
|
+
const publicKey = values.MON_PUBLIC_KEY || "pk_live_67df231b188e46d38a2baef03b217756"
|
|
409
|
+
|
|
410
|
+
const html = `<!DOCTYPE html>
|
|
411
|
+
<html lang="en">
|
|
412
|
+
<head>
|
|
413
|
+
<meta charset="UTF-8">
|
|
414
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
415
|
+
<title>MonCircle SDK Demo</title>
|
|
416
|
+
<style>
|
|
417
|
+
body { font-family: system-ui, sans-serif; background: #0a0812; color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
|
418
|
+
.card { background: #0f0d1a; border: 1px solid #1e1b2e; padding: 32px; border-radius: 16px; text-align: center; max-width: 400px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); }
|
|
419
|
+
h1 { font-size: 24px; margin-bottom: 12px; }
|
|
420
|
+
p { color: #6b6585; font-size: 14px; margin-bottom: 24px; }
|
|
421
|
+
.btn { background: #6e54ff; color: #fff; border: none; padding: 12px 24px; border-radius: 8px; font-weight: 600; cursor: pointer; transition: transform 0.1s; }
|
|
422
|
+
.btn:active { transform: scale(0.98); }
|
|
423
|
+
</style>
|
|
424
|
+
</head>
|
|
425
|
+
<body>
|
|
426
|
+
<div class="card">
|
|
427
|
+
<h1>MonCircle Demo</h1>
|
|
428
|
+
<p>This page is connected to your platform via the MonCircle SDK. Click the widget in the corner to see your rewards!</p>
|
|
429
|
+
<button class="btn" onclick="simulateOrder()">Simulate Order (Earn MON)</button>
|
|
430
|
+
</div>
|
|
431
|
+
|
|
432
|
+
<script>
|
|
433
|
+
function simulateOrder() {
|
|
434
|
+
if (window.MonCircle) {
|
|
435
|
+
const orderId = "ORD-" + Math.random().toString(36).slice(2, 9).toUpperCase();
|
|
436
|
+
window.MonCircle.processOrder(orderId).then(() => {
|
|
437
|
+
console.log("Order simulated successfully!");
|
|
438
|
+
});
|
|
439
|
+
} else {
|
|
440
|
+
alert("MonCircle SDK not loaded yet.");
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
</script>
|
|
444
|
+
|
|
445
|
+
<!-- MonCircle SDK Overlay -->
|
|
446
|
+
<script
|
|
447
|
+
src="https://moncircle.vercel.app/sdk/overlay.js"
|
|
448
|
+
data-key="${publicKey}"
|
|
449
|
+
data-user-id="demo_user_123"
|
|
450
|
+
data-amount="1000"
|
|
451
|
+
data-mon-amount="10">
|
|
452
|
+
</script>
|
|
453
|
+
</body>
|
|
454
|
+
</html>`
|
|
455
|
+
|
|
456
|
+
fs.writeFileSync(demoPath, html, "utf-8")
|
|
457
|
+
return demoPath
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ── Main ───────────────────────────────────────────────────────────────────
|
|
461
|
+
async function main() {
|
|
462
|
+
const values = await runSetup()
|
|
206
463
|
rl.close()
|
|
464
|
+
|
|
465
|
+
await deployContract(values)
|
|
466
|
+
const files = createScaffolding(values)
|
|
467
|
+
|
|
468
|
+
console.log(`${BOLD}${GREEN}╔══════════════════════════════════════════════════════╗`)
|
|
469
|
+
console.log(`║ 🎉 @moncircle/sdk — PREMIUM RELEASE! ║`)
|
|
470
|
+
console.log(`╚══════════════════════════════════════════════════════╝${RESET}\n`)
|
|
471
|
+
console.log(` ${GREEN}✅ Redesigned ${CYAN}Full-Screen UI${RESET} ${DIM}(Signature Purple Theme)${RESET}`)
|
|
472
|
+
console.log(` ${GREEN}✅ Created ${CYAN}loyalty-dashboard.html${RESET} ${DIM}(Advanced UI)${RESET}`)
|
|
473
|
+
console.log(` ${GREEN}✅ Created ${CYAN}server.js${RESET} ${DIM}(Simple Express Backend)${RESET}`)
|
|
474
|
+
console.log(` ${DIM}API docs: POST /v1/platforms → POST /v1/orders → POST /v1/withdraw${RESET}\n`)
|
|
475
|
+
|
|
476
|
+
startApi()
|
|
207
477
|
}
|
|
208
478
|
|
|
209
479
|
main().catch((err) => {
|