From db98640b13be4f6566b107920f6e7445caf12bc1 Mon Sep 17 00:00:00 2001 From: Cyber-Mitch Date: Wed, 29 Oct 2025 14:35:39 +0100 Subject: [PATCH 01/13] feat(echo-start): auto register template referral system --- package.json | 14 ++- .../app/control/docs/components/meta.json | 20 ++-- packages/app/server/src/handlers.ts | 4 +- packages/sdk/echo-start/src/index.ts | 110 ++++++++++++++++-- 4 files changed, 125 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 48b012f52..ecb447a8b 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,18 @@ "@ai-sdk/google": "2.0.14", "@openrouter/ai-sdk-provider": "1.2.0" }, - "engine-strict": true + "engine-strict": true, + "onlyBuiltDependencies": [ + "@coinbase/x402", + "@tailwindcss/oxide", + "@vercel/speed-insights", + "bigint-buffer", + "bufferutil", + "core-js", + "keccak", + "oxc-resolver", + "protobufjs", + "utf-8-validate" + ] } } diff --git a/packages/app/control/docs/components/meta.json b/packages/app/control/docs/components/meta.json index af4bafd74..1c48c8d03 100644 --- a/packages/app/control/docs/components/meta.json +++ b/packages/app/control/docs/components/meta.json @@ -1,11 +1,11 @@ { - "title": "Components", - "pages": [ - "index", - "installation", - "echo-account", - "ui-components", - "customization" - ], - "icon": "Component" - } \ No newline at end of file + "title": "Components", + "pages": [ + "index", + "installation", + "echo-account", + "ui-components", + "customization" + ], + "icon": "Component" +} diff --git a/packages/app/server/src/handlers.ts b/packages/app/server/src/handlers.ts index 4f6e2f42f..c21a6172e 100644 --- a/packages/app/server/src/handlers.ts +++ b/packages/app/server/src/handlers.ts @@ -150,7 +150,9 @@ export async function finalize( transaction.rawTransactionCost ); if (markUpAmount.greaterThan(0)) { - logger.info(`PROFIT RECEIVED: ${markUpAmount.toNumber()} USD, checking for a repo send operation`); + logger.info( + `PROFIT RECEIVED: ${markUpAmount.toNumber()} USD, checking for a repo send operation` + ); try { await safeFundRepoIfWorthwhile(); } catch (error) { diff --git a/packages/sdk/echo-start/src/index.ts b/packages/sdk/echo-start/src/index.ts index 74b222491..eab85bb3f 100644 --- a/packages/sdk/echo-start/src/index.ts +++ b/packages/sdk/echo-start/src/index.ts @@ -73,6 +73,71 @@ const DEFAULT_TEMPLATES = { type TemplateName = keyof typeof DEFAULT_TEMPLATES; type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'; +function escapeRegExp(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function upsertEnvVar(filePath: string, varName: string, value: string) { + const line = `${varName}=${value}`; + if (existsSync(filePath)) { + const content = readFileSync(filePath, 'utf-8'); + const re = new RegExp(`^(${escapeRegExp(varName)}\\s*=\\s*).+$`, 'm'); + const updated = re.test(content) + ? content.replace(re, `$1${value}`) + : content.endsWith('\n') + ? content + line + '\n' + : content + '\n' + line + '\n'; + writeFileSync(filePath, updated); + } else { + writeFileSync(filePath, line + '\n'); + } +} + +function deriveReferralVarNameFromAppVar(appVar: string): string { + if (appVar.includes('ECHO_APP_ID')) { + return appVar.replace('ECHO_APP_ID', 'ECHO_REFERRAL_CODE'); + } + return 'NEXT_PUBLIC_ECHO_REFERRAL_CODE'; +} + +function readJsonIfExists(p: string): T | null { + if (!existsSync(p)) return null; + try { + return JSON.parse(readFileSync(p, 'utf-8')) as T; + } catch { + return null; + } +} + +function extractTemplateReferralCode(projectPath: string): string | null { + const dotEchoPath = path.join(projectPath, '.echo', 'template.json'); + const dotEcho = readJsonIfExists<{ referralCode?: string }>(dotEchoPath); + if (dotEcho?.referralCode && typeof dotEcho.referralCode === 'string') { + return dotEcho.referralCode; + } + + const echoTemplatePath = path.join(projectPath, 'echo-template.json'); + const echoTemplate = readJsonIfExists<{ referralCode?: string }>( + echoTemplatePath + ); + if ( + echoTemplate?.referralCode && + typeof echoTemplate.referralCode === 'string' + ) { + return echoTemplate.referralCode; + } + + const pkgPath = path.join(projectPath, 'package.json'); + type PkgEcho = { echo?: { referralCode?: string } }; + const pkg = readJsonIfExists(pkgPath); + const pkgReferral = pkg?.echo?.referralCode; + if (pkgReferral && typeof pkgReferral === 'string') { + return pkgReferral; + } + + return null; +} + function printHeader(): void { console.log(); console.log(`${chalk.cyan('Echo Start')} ${chalk.gray(`(${VERSION})`)}`); @@ -179,7 +244,10 @@ function isExternalTemplate(template: string): boolean { function resolveTemplateRepo(template: string): string { let repo = template; - if (repo.startsWith('https://github.com/') || repo.startsWith('http://github.com/')) { + if ( + repo.startsWith('https://github.com/') || + repo.startsWith('http://github.com/') + ) { repo = repo.replace(/^https?:\/\/github\.com\//, ''); } @@ -192,29 +260,34 @@ function resolveTemplateRepo(template: string): string { function detectEnvVarName(projectPath: string): string | null { const envFiles = ['.env.local', '.env.example', '.env']; - + for (const fileName of envFiles) { const filePath = path.join(projectPath, fileName); if (existsSync(filePath)) { const content = readFileSync(filePath, 'utf-8'); - const match = content.match(/(NEXT_PUBLIC_|VITE_|REACT_APP_)?ECHO_APP_ID/); + const match = content.match( + /(NEXT_PUBLIC_|VITE_|REACT_APP_)?ECHO_APP_ID/ + ); if (match) { return match[0]; } } } - + return null; } function detectFrameworkEnvVarName(projectPath: string): string { const packageJsonPath = path.join(projectPath, 'package.json'); - + if (existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); - const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; - + const deps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + }; + if (deps['next']) { return 'NEXT_PUBLIC_ECHO_APP_ID'; } else if (deps['vite']) { @@ -227,7 +300,7 @@ function detectFrameworkEnvVarName(projectPath: string): string { console.error(e); } } - + return 'NEXT_PUBLIC_ECHO_APP_ID'; } @@ -262,7 +335,7 @@ async function createApp(projectDir: string, options: CreateAppOptions) { } const isExternal = isExternalTemplate(template); - + if (isExternal) { log.step(`Using external template: ${template}`); } else { @@ -306,7 +379,7 @@ async function createApp(projectDir: string, options: CreateAppOptions) { s.start('Downloading template files'); let repoPath: string; - + if (isExternal) { repoPath = resolveTemplateRepo(template); } else { @@ -390,12 +463,27 @@ async function createApp(projectDir: string, options: CreateAppOptions) { } } else if (isExternal) { const detectedVarName = detectEnvVarName(absoluteProjectPath); - const envVarName = detectedVarName || detectFrameworkEnvVarName(absoluteProjectPath); + const envVarName = + detectedVarName || detectFrameworkEnvVarName(absoluteProjectPath); const envContent = `${envVarName}=${appId}\n`; writeFileSync(envPath, envContent); log.message(`Created .env.local with ${envVarName}`); } + if (isExternal) { + const referralCode = extractTemplateReferralCode(absoluteProjectPath); + if (referralCode) { + const existingAppVar = + detectEnvVarName(absoluteProjectPath) || + detectFrameworkEnvVarName(absoluteProjectPath); + const referralVar = deriveReferralVarNameFromAppVar(existingAppVar); + upsertEnvVar(envPath, referralVar, referralCode); + log.message( + `Registered template referral code in .env.local as ${referralVar}` + ); + } + } + log.step('Project setup completed successfully'); // Auto-install dependencies unless skipped From 95e88273a25edbc8f1186b0f68db8ef7d427cb25 Mon Sep 17 00:00:00 2001 From: Reentrancy Date: Wed, 29 Oct 2025 18:32:11 +0100 Subject: [PATCH 02/13] Update packages/sdk/echo-start/src/index.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/sdk/echo-start/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/echo-start/src/index.ts b/packages/sdk/echo-start/src/index.ts index eab85bb3f..893a83216 100644 --- a/packages/sdk/echo-start/src/index.ts +++ b/packages/sdk/echo-start/src/index.ts @@ -83,7 +83,7 @@ function upsertEnvVar(filePath: string, varName: string, value: string) { const content = readFileSync(filePath, 'utf-8'); const re = new RegExp(`^(${escapeRegExp(varName)}\\s*=\\s*).+$`, 'm'); const updated = re.test(content) - ? content.replace(re, `$1${value}`) + ? content.replace(re, (match, group1) => group1 + value) : content.endsWith('\n') ? content + line + '\n' : content + '\n' + line + '\n'; From 8109f5656fb507820c01bb59782d1045f52885c4 Mon Sep 17 00:00:00 2001 From: Cyber-Mitch Date: Wed, 29 Oct 2025 19:28:19 +0100 Subject: [PATCH 03/13] removed only built dependencies --- package.json | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/package.json b/package.json index ecb447a8b..48b012f52 100644 --- a/package.json +++ b/package.json @@ -53,18 +53,6 @@ "@ai-sdk/google": "2.0.14", "@openrouter/ai-sdk-provider": "1.2.0" }, - "engine-strict": true, - "onlyBuiltDependencies": [ - "@coinbase/x402", - "@tailwindcss/oxide", - "@vercel/speed-insights", - "bigint-buffer", - "bufferutil", - "core-js", - "keccak", - "oxc-resolver", - "protobufjs", - "utf-8-validate" - ] + "engine-strict": true } } From 2a74516bf77221953301a998b4cabab5f64f6001 Mon Sep 17 00:00:00 2001 From: Cyber-Mitch Date: Thu, 30 Oct 2025 17:24:07 +0100 Subject: [PATCH 04/13] resolved conflict --- packages/sdk/echo-start/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/echo-start/src/index.ts b/packages/sdk/echo-start/src/index.ts index 893a83216..6f154301b 100644 --- a/packages/sdk/echo-start/src/index.ts +++ b/packages/sdk/echo-start/src/index.ts @@ -83,7 +83,7 @@ function upsertEnvVar(filePath: string, varName: string, value: string) { const content = readFileSync(filePath, 'utf-8'); const re = new RegExp(`^(${escapeRegExp(varName)}\\s*=\\s*).+$`, 'm'); const updated = re.test(content) - ? content.replace(re, (match, group1) => group1 + value) + ? content.replace(re, `$1${value}`) : content.endsWith('\n') ? content + line + '\n' : content + '\n' + line + '\n'; From e40c9d5c4f1420c5f20463dc7c30c5aaf77e1c98 Mon Sep 17 00:00:00 2001 From: Cyber-Mitch Date: Thu, 30 Oct 2025 17:29:29 +0100 Subject: [PATCH 05/13] resolved --- packages/sdk/echo-start/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/echo-start/src/index.ts b/packages/sdk/echo-start/src/index.ts index 6f154301b..893a83216 100644 --- a/packages/sdk/echo-start/src/index.ts +++ b/packages/sdk/echo-start/src/index.ts @@ -83,7 +83,7 @@ function upsertEnvVar(filePath: string, varName: string, value: string) { const content = readFileSync(filePath, 'utf-8'); const re = new RegExp(`^(${escapeRegExp(varName)}\\s*=\\s*).+$`, 'm'); const updated = re.test(content) - ? content.replace(re, `$1${value}`) + ? content.replace(re, (match, group1) => group1 + value) : content.endsWith('\n') ? content + line + '\n' : content + '\n' + line + '\n'; From c9cf075d13013db0f32f14270e1041c1075246ed Mon Sep 17 00:00:00 2001 From: Cyber-Mitch Date: Thu, 30 Oct 2025 17:32:11 +0100 Subject: [PATCH 06/13] resolved --- pnpm-lock.yaml | 159 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c92b4dd4e..f3598ba2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,9 @@ importers: .: dependencies: + open: + specifier: 9.1.0 + version: 9.1.0 vitest: specifier: ^3.2.3 version: 3.2.3(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/ui@3.2.3)(jiti@2.5.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(lightningcss@1.30.1)(msw@2.11.2(@types/node@24.3.1)(typescript@5.9.2))(terser@5.42.0)(tsx@4.20.5)(yaml@2.8.0) @@ -5793,6 +5796,7 @@ packages: '@walletconnect/ethereum-provider@2.21.1': resolution: {integrity: sha512-SSlIG6QEVxClgl1s0LMk4xr2wg4eT3Zn/Hb81IocyqNSGfXpjtawWxKxiC5/9Z95f1INyBD6MctJbL/R1oBwIw==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/events@1.0.1': resolution: {integrity: sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==} @@ -5837,9 +5841,11 @@ packages: '@walletconnect/sign-client@2.21.0': resolution: {integrity: sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/sign-client@2.21.1': resolution: {integrity: sha512-QaXzmPsMnKGV6tc4UcdnQVNOz4zyXgarvdIQibJ4L3EmLat73r5ZVl4c0cCOcoaV7rgM9Wbphgu5E/7jNcd3Zg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/time@1.0.2': resolution: {integrity: sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==} @@ -5852,9 +5858,11 @@ packages: '@walletconnect/universal-provider@2.21.0': resolution: {integrity: sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/universal-provider@2.21.1': resolution: {integrity: sha512-Wjx9G8gUHVMnYfxtasC9poGm8QMiPCpXpbbLFT+iPoQskDDly8BwueWnqKs4Mx2SdIAWAwuXeZ5ojk5qQOxJJg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' '@walletconnect/utils@2.21.0': resolution: {integrity: sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig==} @@ -6191,6 +6199,10 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + big.js@6.2.2: resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} @@ -6228,6 +6240,10 @@ packages: bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + bplist-parser@0.2.0: + resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} + engines: {node: '>= 5.10.0'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -6267,6 +6283,10 @@ packages: resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} engines: {node: '>=6.14.2'} + bundle-name@3.0.0: + resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} + engines: {node: '>=12'} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6933,6 +6953,14 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-browser-id@3.0.0: + resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} + engines: {node: '>=12'} + + default-browser@4.0.0: + resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} + engines: {node: '>=14.16'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -6941,6 +6969,10 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -7489,6 +7521,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + execa@7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + execa@9.6.0: resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} engines: {node: ^18.19.0 || >=20.5.0} @@ -8159,6 +8195,10 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + human-signals@4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} + engines: {node: '>=14.18.0'} + human-signals@8.0.1: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} @@ -8330,6 +8370,11 @@ packages: engines: {node: '>=8'} hasBin: true + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -8356,6 +8401,11 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} @@ -8417,6 +8467,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-stream@4.0.1: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} @@ -9157,6 +9211,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -9455,6 +9513,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@6.0.0: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} @@ -9555,6 +9617,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -9569,6 +9635,10 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + open@9.1.0: + resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} + engines: {node: '>=14.16'} + openai@4.104.0: resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} hasBin: true @@ -10496,6 +10566,10 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-applescript@5.0.0: + resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} + engines: {node: '>=12'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -10852,6 +10926,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + strip-final-newline@4.0.0: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} @@ -11050,6 +11128,10 @@ packages: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} + titleize@3.0.0: + resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} + engines: {node: '>=12'} + tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} @@ -11429,6 +11511,10 @@ packages: uploadthing: optional: true + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -20870,6 +20956,8 @@ snapshots: dependencies: open: 8.4.2 + big-integer@1.6.52: {} + big.js@6.2.2: {} bigint-buffer@1.1.5: @@ -20927,6 +21015,10 @@ snapshots: bowser@2.11.0: {} + bplist-parser@0.2.0: + dependencies: + big-integer: 1.6.52 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -20976,6 +21068,10 @@ snapshots: dependencies: node-gyp-build: 4.8.4 + bundle-name@3.0.0: + dependencies: + run-applescript: 5.0.0 + bundle-require@5.1.0(esbuild@0.25.9): dependencies: esbuild: 0.25.9 @@ -21606,6 +21702,18 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@3.0.0: + dependencies: + bplist-parser: 0.2.0 + untildify: 4.0.0 + + default-browser@4.0.0: + dependencies: + bundle-name: 3.0.0 + default-browser-id: 3.0.0 + execa: 7.2.0 + titleize: 3.0.0 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -21614,6 +21722,8 @@ snapshots: define-lazy-prop@2.0.0: {} + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -22369,6 +22479,18 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + execa@7.2.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 4.3.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + execa@9.6.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -23295,6 +23417,8 @@ snapshots: human-signals@2.1.0: {} + human-signals@4.3.1: {} + human-signals@8.0.1: {} humanize-ms@1.2.1: @@ -23447,6 +23571,8 @@ snapshots: is-docker@2.2.1: {} + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -23470,6 +23596,10 @@ snapshots: is-hexadecimal@2.0.1: {} + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-interactive@2.0.0: {} is-map@2.0.3: {} @@ -23512,6 +23642,8 @@ snapshots: is-stream@2.0.1: {} + is-stream@3.0.0: {} + is-stream@4.0.1: {} is-string@1.1.1: @@ -24544,6 +24676,8 @@ snapshots: mimic-fn@2.1.0: {} + mimic-fn@4.0.0: {} + mimic-function@5.0.1: {} min-indent@1.0.1: {} @@ -24887,6 +25021,10 @@ snapshots: dependencies: path-key: 3.1.1 + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + npm-run-path@6.0.0: dependencies: path-key: 4.0.0 @@ -25006,6 +25144,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -25024,6 +25166,13 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + open@9.1.0: + dependencies: + default-browser: 4.0.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 2.2.0 + openai@4.104.0(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11): dependencies: '@types/node': 18.19.112 @@ -26280,6 +26429,10 @@ snapshots: rrweb-cssom@0.8.0: {} + run-applescript@5.0.0: + dependencies: + execa: 5.1.1 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -26818,6 +26971,8 @@ snapshots: strip-final-newline@2.0.0: {} + strip-final-newline@3.0.0: {} + strip-final-newline@4.0.0: {} strip-indent@3.0.0: @@ -27055,6 +27210,8 @@ snapshots: tinyspy@4.0.3: {} + titleize@3.0.0: {} + tldts-core@6.1.86: {} tldts-core@7.0.14: {} @@ -27514,6 +27671,8 @@ snapshots: '@vercel/blob': 0.25.1 idb-keyval: 6.2.2 + untildify@4.0.0: {} + update-browserslist-db@1.1.3(browserslist@4.25.0): dependencies: browserslist: 4.25.0 From fd6dc206e439b0c2f1ecfa13a7a482156d4e7e27 Mon Sep 17 00:00:00 2001 From: Cyber-Mitch Date: Thu, 30 Oct 2025 17:52:36 +0100 Subject: [PATCH 07/13] patch applied --- packages/app/server/src/handlers.ts | 146 ---------------------------- 1 file changed, 146 deletions(-) diff --git a/packages/app/server/src/handlers.ts b/packages/app/server/src/handlers.ts index 96596b626..5d4034782 100644 --- a/packages/app/server/src/handlers.ts +++ b/packages/app/server/src/handlers.ts @@ -9,152 +9,6 @@ import { prisma } from 'server'; import { makeProxyPassthroughRequest } from 'services/ProxyPassthroughService'; import logger from 'logger'; import { ProviderType } from 'providers/ProviderType'; -import { safeFundRepoIfWorthwhile } from 'services/fund-repo/fundRepoService'; -import { applyMaxCostMarkup } from 'services/PricingService'; - -export async function refund( - paymentAmountDecimal: Decimal, - payload: ExactEvmPayload -) { - try { - const refundAmountUsdcBigInt = decimalToUsdcBigInt(paymentAmountDecimal); - const authPayload = payload.authorization; - await transfer(authPayload.from as `0x${string}`, refundAmountUsdcBigInt); - } catch (error) { - logger.error('Failed to refund', error); - } -} - -export async function settle( - req: Request, - res: Response, - headers: Record, - maxCost: Decimal -): Promise< - { payload: ExactEvmPayload; paymentAmountDecimal: Decimal } | undefined -> { - const network = process.env.NETWORK as Network; - - let recipient: string; - try { - recipient = (await getSmartAccount()).smartAccount.address; - } catch (error) { - buildX402Response(req, res, maxCost); - return undefined; - } - - let xPaymentData: PaymentPayload; - try { - xPaymentData = validateXPaymentHeader(headers, req); - } catch (error) { - buildX402Response(req, res, maxCost); - return undefined; - } - - const parseResult = ExactEvmPayloadSchema.safeParse(xPaymentData.payload); - - if (!parseResult.success) { - logger.error('Invalid EVM payload', { - error: parseResult.error.format() - }); - buildX402Response(req, res, maxCost); - return undefined; - } - - const payload = parseResult.data; - logger.info(`Payment payload: ${JSON.stringify(payload)}`); - - const paymentAmount = payload.authorization.value; - const paymentAmountDecimal = usdcBigIntToDecimal(paymentAmount); - - // Note(shafu, alvaro): Edge case where client sends the x402-challenge - // but the payment amount is less than what we returned in the first response - if (BigInt(paymentAmount) < decimalToUsdcBigInt(maxCost)) { - buildX402Response(req, res, maxCost); - return undefined; - } - - const facilitatorClient = new FacilitatorClient(); - const paymentRequirements = PaymentRequirementsSchema.parse({ - scheme: 'exact', - network, - maxAmountRequired: paymentAmount, - resource: `${req.protocol}://${req.get('host')}${req.url}`, - description: 'Echo x402', - mimeType: 'application/json', - payTo: recipient, - maxTimeoutSeconds: 60, - asset: USDC_ADDRESS, - extra: { - name: 'USD Coin', - version: '2', - }, - }); - - const settleRequest = SettleRequestSchema.parse({ - paymentPayload: xPaymentData, - paymentRequirements, - }); - - const settleResult = await facilitatorClient.settle(settleRequest); - - if (!settleResult.success || !settleResult.transaction) { - buildX402Response(req, res, maxCost); - return undefined; - } - - return { payload, paymentAmountDecimal }; -} - -export async function finalize( - paymentAmountDecimal: Decimal, - transaction: Transaction, - payload: ExactEvmPayload -) { - const transactionCostWithMarkup = applyMaxCostMarkup( - transaction.rawTransactionCost - ); - - // rawTransactionCost is what we pay to OpenAI - // transactionCostWithMarkup is what we charge the user - // markup is the difference between the two, and is sent with fundRepo (not every time, just when it is worthwhile to send a payment) - - // The user should be refunded paymentAmountDecimal - transactionCostWithMarkup\ - - const refundAmount = calculateRefundAmount( - paymentAmountDecimal, - transactionCostWithMarkup - ); - logger.info(`Payment amount decimal: ${paymentAmountDecimal.toNumber()} USD`); - logger.info(`Refunding ${refundAmount.toNumber()} USD`); - logger.info( - `Transaction cost with markup: ${transactionCostWithMarkup.toNumber()} USD` - ); - logger.info( - `Transaction cost: ${transaction.rawTransactionCost.toNumber()} USD` - ); - - if (!refundAmount.equals(0) && refundAmount.greaterThan(0)) { - const refundAmountUsdcBigInt = decimalToUsdcBigInt(refundAmount); - const authPayload = payload.authorization; - await transfer(authPayload.from as `0x${string}`, refundAmountUsdcBigInt); - } - - const markUpAmount = transactionCostWithMarkup.minus( - transaction.rawTransactionCost - ); - if (markUpAmount.greaterThan(0)) { - logger.info( - `PROFIT RECEIVED: ${markUpAmount.toNumber()} USD, checking for a repo send operation` - ); - try { - await safeFundRepoIfWorthwhile(); - } catch (error) { - logger.error('Failed to fund repo', error); - // Don't re-throw - repo funding is not critical to the transaction - } - } -} import { settle } from 'handlers/settle'; import { finalize } from 'handlers/finalize'; import { refund } from 'handlers/refund'; From 7d8560f514f202b1ef0af1bcdc6cb7a6e7dfde7e Mon Sep 17 00:00:00 2001 From: Cyber-Mitch Date: Tue, 4 Nov 2025 20:23:34 +0100 Subject: [PATCH 08/13] feat(echo-start): auto register template referral system --- packages/sdk/echo-start/src/index.ts | 94 +++++----------------------- 1 file changed, 16 insertions(+), 78 deletions(-) diff --git a/packages/sdk/echo-start/src/index.ts b/packages/sdk/echo-start/src/index.ts index 893a83216..faf9fdb96 100644 --- a/packages/sdk/echo-start/src/index.ts +++ b/packages/sdk/echo-start/src/index.ts @@ -77,67 +77,6 @@ function escapeRegExp(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } -function upsertEnvVar(filePath: string, varName: string, value: string) { - const line = `${varName}=${value}`; - if (existsSync(filePath)) { - const content = readFileSync(filePath, 'utf-8'); - const re = new RegExp(`^(${escapeRegExp(varName)}\\s*=\\s*).+$`, 'm'); - const updated = re.test(content) - ? content.replace(re, (match, group1) => group1 + value) - : content.endsWith('\n') - ? content + line + '\n' - : content + '\n' + line + '\n'; - writeFileSync(filePath, updated); - } else { - writeFileSync(filePath, line + '\n'); - } -} - -function deriveReferralVarNameFromAppVar(appVar: string): string { - if (appVar.includes('ECHO_APP_ID')) { - return appVar.replace('ECHO_APP_ID', 'ECHO_REFERRAL_CODE'); - } - return 'NEXT_PUBLIC_ECHO_REFERRAL_CODE'; -} - -function readJsonIfExists(p: string): T | null { - if (!existsSync(p)) return null; - try { - return JSON.parse(readFileSync(p, 'utf-8')) as T; - } catch { - return null; - } -} - -function extractTemplateReferralCode(projectPath: string): string | null { - const dotEchoPath = path.join(projectPath, '.echo', 'template.json'); - const dotEcho = readJsonIfExists<{ referralCode?: string }>(dotEchoPath); - if (dotEcho?.referralCode && typeof dotEcho.referralCode === 'string') { - return dotEcho.referralCode; - } - - const echoTemplatePath = path.join(projectPath, 'echo-template.json'); - const echoTemplate = readJsonIfExists<{ referralCode?: string }>( - echoTemplatePath - ); - if ( - echoTemplate?.referralCode && - typeof echoTemplate.referralCode === 'string' - ) { - return echoTemplate.referralCode; - } - - const pkgPath = path.join(projectPath, 'package.json'); - type PkgEcho = { echo?: { referralCode?: string } }; - const pkg = readJsonIfExists(pkgPath); - const pkgReferral = pkg?.echo?.referralCode; - if (pkgReferral && typeof pkgReferral === 'string') { - return pkgReferral; - } - - return null; -} - function printHeader(): void { console.log(); console.log(`${chalk.cyan('Echo Start')} ${chalk.gray(`(${VERSION})`)}`); @@ -336,8 +275,19 @@ async function createApp(projectDir: string, options: CreateAppOptions) { const isExternal = isExternalTemplate(template); + let owner = ''; + let createLink = 'https://echo.merit.systems/new'; + if (isExternal) { + const repoPath = resolveTemplateRepo(template); + owner = repoPath.split('/')[0] || ''; log.step(`Using external template: ${template}`); + if (owner) { + createLink += `?ref=${owner}`; + log.step(`Auto-applying referral for template creator: ${owner}. Use the link below when creating a new app to register it.`); + } else { + log.warning('Could not extract template owner for referral; using default creation link.'); + } } else { const templateName = template as TemplateName; log.step(`Selected template: ${DEFAULT_TEMPLATES[templateName].title}`); @@ -350,7 +300,7 @@ async function createApp(projectDir: string, options: CreateAppOptions) { placeholder: 'Enter your app ID...', validate: (value: string) => { if (!value.trim()) { - return 'Please enter an App ID or create one at https://echo.merit.systems/new'; + return `Please enter an App ID or create one at ${createLink} to auto-register the referral (if applicable).`; } return; }, @@ -362,6 +312,8 @@ async function createApp(projectDir: string, options: CreateAppOptions) { } appId = enteredAppId; + } else if (isExternal && owner) { + log.warning(`App ID provided via CLI; referral for ${owner} cannot be auto-registered for existing apps. Create new apps via ${createLink} to apply referrals.`); } log.step(`Using App ID: ${appId}`); @@ -470,20 +422,6 @@ async function createApp(projectDir: string, options: CreateAppOptions) { log.message(`Created .env.local with ${envVarName}`); } - if (isExternal) { - const referralCode = extractTemplateReferralCode(absoluteProjectPath); - if (referralCode) { - const existingAppVar = - detectEnvVarName(absoluteProjectPath) || - detectFrameworkEnvVarName(absoluteProjectPath); - const referralVar = deriveReferralVarNameFromAppVar(existingAppVar); - upsertEnvVar(envPath, referralVar, referralCode); - log.message( - `Registered template referral code in .env.local as ${referralVar}` - ); - } - } - log.step('Project setup completed successfully'); // Auto-install dependencies unless skipped @@ -564,7 +502,7 @@ async function main() { .argument('[directory]', 'Directory to create the app in') .option( '-t, --template