Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
db98640
feat(echo-start): auto register template referral system
Cyber-Mitch Oct 29, 2025
f22c992
Merge branch 'Merit-Systems:master' into feat/template-referral-system
Cyber-Mitch Oct 29, 2025
95e8827
Update packages/sdk/echo-start/src/index.ts
Cyber-Mitch Oct 29, 2025
8109f56
removed only built dependencies
Cyber-Mitch Oct 29, 2025
214900d
merge
Cyber-Mitch Oct 29, 2025
2a74516
resolved conflict
Cyber-Mitch Oct 30, 2025
d38d94c
Merge branch 'master' into feat/template-referral-system
Cyber-Mitch Oct 30, 2025
e40c9d5
resolved
Cyber-Mitch Oct 30, 2025
c9cf075
resolved
Cyber-Mitch Oct 30, 2025
1aee0ec
Merge branch 'feat/template-referral-system' of https://github.com/Cy…
Cyber-Mitch Oct 30, 2025
fd6dc20
patch applied
Cyber-Mitch Oct 30, 2025
17e28c1
Merge branch 'Merit-Systems:master' into feat/template-referral-system
Cyber-Mitch Nov 4, 2025
7d8560f
feat(echo-start): auto register template referral system
Cyber-Mitch Nov 4, 2025
335dafd
feat(echo-start): auto register template referral system
Cyber-Mitch Nov 4, 2025
357639d
feat(echo-start): auto register template referral system
Cyber-Mitch Nov 4, 2025
f32c94a
Merge branch 'feat/template-referral-system' of https://github.com/Cy…
Cyber-Mitch Nov 4, 2025
1858866
feat(echo-start): auto register template referral system
Cyber-Mitch Nov 4, 2025
ae5510f
Merge branch 'Merit-Systems:master' into feat/template-referral-system
Cyber-Mitch Nov 4, 2025
02095de
feat:(auto refferal template system)
Cyber-Mitch Nov 5, 2025
ec5099f
feat:(auto refferal template system)
Cyber-Mitch Nov 5, 2025
05cfefb
Merge branch 'Merit-Systems:master' into feat/template-referral-system
Cyber-Mitch Nov 5, 2025
adf8de2
patch applied
Cyber-Mitch Nov 6, 2025
1d88083
Merge branch 'Merit-Systems:master' into feat/template-referral-system
Cyber-Mitch Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions packages/app/server/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,152 @@ 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<string, string>,
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';
Expand Down
110 changes: 99 additions & 11 deletions packages/sdk/echo-start/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = unknown>(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<PkgEcho>(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})`)}`);
Expand Down Expand Up @@ -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\//, '');
}

Expand All @@ -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']) {
Expand All @@ -227,7 +300,7 @@ function detectFrameworkEnvVarName(projectPath: string): string {
console.error(e);
}
}

return 'NEXT_PUBLIC_ECHO_APP_ID';
}

Expand Down Expand Up @@ -262,7 +335,7 @@ async function createApp(projectDir: string, options: CreateAppOptions) {
}

const isExternal = isExternalTemplate(template);

if (isExternal) {
log.step(`Using external template: ${template}`);
} else {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down