From 69d8c5a1ae002bb5344e996c0d9a2359d6cc0ee5 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Mon, 27 Oct 2025 21:25:14 -0400 Subject: [PATCH 01/13] progress --- .../migration.sql | 13 +++ packages/app/control/prisma/schema.prisma | 16 ++-- .../src/services/db/apps/transactions.ts | 6 +- .../app/control/src/services/db/apps/users.ts | 3 + packages/app/server/src/handlers.ts | 12 +-- packages/app/server/src/resources/handler.ts | 2 +- packages/app/server/src/server.ts | 7 +- packages/app/server/src/services/DbService.ts | 51 +++++++++--- .../server/src/services/EchoControlService.ts | 80 +++++++++++++++---- .../app/server/src/services/PricingService.ts | 13 ++- .../src/services/x402AuthenticationService.ts | 45 +++++++++++ packages/app/server/src/types.ts | 13 ++- 12 files changed, 215 insertions(+), 46 deletions(-) create mode 100644 packages/app/control/prisma/migrations/20251027210418_x402_transactions/migration.sql create mode 100644 packages/app/server/src/services/x402AuthenticationService.ts diff --git a/packages/app/control/prisma/migrations/20251027210418_x402_transactions/migration.sql b/packages/app/control/prisma/migrations/20251027210418_x402_transactions/migration.sql new file mode 100644 index 000000000..158045262 --- /dev/null +++ b/packages/app/control/prisma/migrations/20251027210418_x402_transactions/migration.sql @@ -0,0 +1,13 @@ +-- CreateEnum +CREATE TYPE "EnumTransactionType" AS ENUM ('X402', 'BALANCE'); + +-- DropForeignKey +ALTER TABLE "public"."transactions" DROP CONSTRAINT "transactions_echoAppId_fkey"; + +-- AlterTable +ALTER TABLE "transactions" ADD COLUMN "transactionType" "EnumTransactionType" NOT NULL DEFAULT 'BALANCE', +ALTER COLUMN "userId" DROP NOT NULL, +ALTER COLUMN "echoAppId" DROP NOT NULL; + +-- AddForeignKey +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_echoAppId_fkey" FOREIGN KEY ("echoAppId") REFERENCES "echo_apps"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/app/control/prisma/schema.prisma b/packages/app/control/prisma/schema.prisma index 1978962bc..64a132f0f 100644 --- a/packages/app/control/prisma/schema.prisma +++ b/packages/app/control/prisma/schema.prisma @@ -301,6 +301,11 @@ enum EnumPaymentSource { balance } +enum EnumTransactionType { + X402 + BALANCE +} + model Transaction { id String @id @default(uuid()) @db.Uuid transactionMetadataId String? @db.Uuid @@ -313,8 +318,9 @@ model Transaction { isArchived Boolean @default(false) archivedAt DateTime? @db.Timestamptz(6) createdAt DateTime @default(now()) @db.Timestamptz(6) - userId String @db.Uuid - echoAppId String @db.Uuid + transactionType EnumTransactionType @default(BALANCE) + userId String? @db.Uuid + echoAppId String? @db.Uuid apiKeyId String? @db.Uuid markUpId String? @db.Uuid spendPoolId String? @db.Uuid @@ -322,11 +328,11 @@ model Transaction { referralCodeId String? @db.Uuid referrerRewardId String? @db.Uuid apiKey ApiKey? @relation(fields: [apiKeyId], references: [id], onDelete: Cascade) - echoApp EchoApp @relation(fields: [echoAppId], references: [id]) + echoApp EchoApp? @relation(fields: [echoAppId], references: [id]) markUp MarkUp? @relation(fields: [markUpId], references: [id]) spendPool SpendPool? @relation(fields: [spendPoolId], references: [id]) transactionMetadata TransactionMetadata? @relation(fields: [transactionMetadataId], references: [id]) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userSpendPoolUsage UserSpendPoolUsage? @relation(fields: [userSpendPoolUsageId], references: [id]) referralCode ReferralCode? @relation(fields: [referralCodeId], references: [id]) referrerReward ReferralReward? @relation(fields: [referrerRewardId], references: [id]) @@ -506,4 +512,4 @@ model VideoGenerationX402 { user User? @relation(fields: [userId], references: [id], onDelete: Cascade) echoApp EchoApp? @relation(fields: [echoAppId], references: [id], onDelete: Cascade) @@map("video_generation_x402") -} +} \ No newline at end of file diff --git a/packages/app/control/src/services/db/apps/transactions.ts b/packages/app/control/src/services/db/apps/transactions.ts index 4b1c17ca6..de50ac8ff 100644 --- a/packages/app/control/src/services/db/apps/transactions.ts +++ b/packages/app/control/src/services/db/apps/transactions.ts @@ -90,9 +90,9 @@ export const listAppTransactions = async ( groupedTransactions.set(userKey, { id: transaction.id, user: { - id: transaction.userId, - name: transaction.user.name, - image: transaction.user.image, + id: transaction.userId!, + name: transaction.user?.name ?? null, + image: transaction.user?.image ?? null, }, callCount: 1, markUpProfit: Number(transaction.markUpProfit), diff --git a/packages/app/control/src/services/db/apps/users.ts b/packages/app/control/src/services/db/apps/users.ts index 2024e30e7..162acc114 100644 --- a/packages/app/control/src/services/db/apps/users.ts +++ b/packages/app/control/src/services/db/apps/users.ts @@ -19,6 +19,9 @@ export const listAppUsers = async ( { page, page_size }: PaginationParams ) => { const where: Prisma.TransactionWhereInput = { + userId: { + not: null, + }, echoAppId: appId, isArchived: false, ...((startDate !== undefined || endDate !== undefined) && { diff --git a/packages/app/server/src/handlers.ts b/packages/app/server/src/handlers.ts index 4f6e2f42f..ab5f12ab0 100644 --- a/packages/app/server/src/handlers.ts +++ b/packages/app/server/src/handlers.ts @@ -1,6 +1,6 @@ import { TransactionEscrowMiddleware } from 'middleware/transaction-escrow-middleware'; import { modelRequestService } from 'services/ModelRequestService'; -import { HandlerInput, Network, Transaction, X402HandlerInput } from 'types'; +import { ApiKeyHandlerInput, Network, Transaction, X402HandlerInput } from 'types'; import { usdcBigIntToDecimal, decimalToUsdcBigInt, @@ -168,11 +168,11 @@ export async function handleX402Request({ isPassthroughProxyRoute, provider, isStream, + x402AuthenticationService, }: X402HandlerInput) { if (isPassthroughProxyRoute) { return await makeProxyPassthroughRequest(req, res, provider, headers); } - const settleResult = await settle(req, res, headers, maxCost); if (!settleResult) { return; @@ -189,7 +189,6 @@ export async function handleX402Request({ isStream ); const transaction = transactionResult.transaction; - if (provider.getType() === ProviderType.OPENAI_VIDEOS) { await prisma.videoGenerationX402.create({ data: { @@ -207,6 +206,9 @@ export async function handleX402Request({ transactionResult.data ); + logger.info(`Creating X402 transaction for app. Metadata: ${JSON.stringify(transaction.metadata)}`); + await x402AuthenticationService.createX402Transaction(transaction); + await finalize( paymentAmountDecimal, transactionResult.transaction, @@ -226,7 +228,7 @@ export async function handleApiKeyRequest({ isPassthroughProxyRoute, provider, isStream, -}: HandlerInput) { +}: ApiKeyHandlerInput) { const transactionEscrowMiddleware = new TransactionEscrowMiddleware(prisma); if (isPassthroughProxyRoute) { @@ -263,7 +265,7 @@ export async function handleApiKeyRequest({ modelRequestService.handleResolveResponse(res, isStream, data); - await echoControlService.createTransaction(transaction, maxCost); + await echoControlService.createTransaction(transaction); if (provider.getType() === ProviderType.OPENAI_VIDEOS) { const transactionCost = await echoControlService.computeTransactionCosts( diff --git a/packages/app/server/src/resources/handler.ts b/packages/app/server/src/resources/handler.ts index 4cfd7107f..f589be39d 100644 --- a/packages/app/server/src/resources/handler.ts +++ b/packages/app/server/src/resources/handler.ts @@ -32,7 +32,7 @@ async function handleApiRequest( const actualCost = calculateActualCost(parsedBody, output); const transaction = createTransaction(parsedBody, output, actualCost); - await echoControlService.createTransaction(transaction, actualCost); + await echoControlService.createTransaction(transaction); return output; } diff --git a/packages/app/server/src/server.ts b/packages/app/server/src/server.ts index 958e913ce..20905c394 100644 --- a/packages/app/server/src/server.ts +++ b/packages/app/server/src/server.ts @@ -34,6 +34,7 @@ import { } from './services/PricingService'; import { Decimal } from '@prisma/client/runtime/library'; import resourceRouter from './routers/resource'; +import { X402AuthenticationService } from 'services/x402AuthenticationService'; dotenv.config(); @@ -107,11 +108,14 @@ app.all('*', async (req: EscrowRequest, res: Response, next: NextFunction) => { const headers = req.headers as Record; const { provider, isStream, isPassthroughProxyRoute, is402Sniffer } = await initializeProvider(req, res); + + const x402AuthenticationService = new X402AuthenticationService(prisma); + const x402AuthenticationResult = await x402AuthenticationService.authenticateX402Request(headers); if (!provider || is402Sniffer) { return buildX402Response(req, res, new Decimal(0)); } const maxCost = getRequestMaxCost(req, provider, isPassthroughProxyRoute); - const maxCostWithMarkup = applyMaxCostMarkup(maxCost); + const maxCostWithMarkup = applyMaxCostMarkup(maxCost, x402AuthenticationResult?.markUp || null); if ( !isApiRequest(headers) && @@ -148,6 +152,7 @@ app.all('*', async (req: EscrowRequest, res: Response, next: NextFunction) => { isPassthroughProxyRoute, provider, isStream, + x402AuthenticationService }); return; } diff --git a/packages/app/server/src/services/DbService.ts b/packages/app/server/src/services/DbService.ts index a019cf089..253e4b35a 100644 --- a/packages/app/server/src/services/DbService.ts +++ b/packages/app/server/src/services/DbService.ts @@ -5,6 +5,7 @@ import { TransactionRequest, isLlmTransactionMetadata, isVeoTransactionMetadata, + EchoApp, } from '../types'; import { createHmac } from 'crypto'; import { jwtVerify } from 'jose'; @@ -412,13 +413,14 @@ export class EchoDbService { transaction ); - // Update user's total spent amount - await this.updateUserTotalSpent( - tx, - transaction.userId, - transaction.totalCost - ); - + if (transaction.userId) { + // Update user's total spent amount + await this.updateUserTotalSpent( + tx, + transaction.userId, + transaction.totalCost + ); + } // Update API key's last used timestamp if provided if (transaction.apiKeyId) { await this.updateApiKeyLastUsed(tx, transaction.apiKeyId); @@ -449,7 +451,7 @@ export class EchoDbService { spendPoolId: string ): Promise<{ transaction: Transaction; - userSpendPoolUsage: UserSpendPoolUsage; + userSpendPoolUsage: UserSpendPoolUsage | null; }> { try { return await this.db.$transaction(async tx => { @@ -462,15 +464,14 @@ export class EchoDbService { if (!spendPool) { throw new Error('Spend pool not found'); } - // 2. Upsert UserSpendPoolUsage record using helper - const userSpendPoolUsage = await this.upsertUserSpendPoolUsage( + const userSpendPoolUsage = transactionData.userId ? + await this.upsertUserSpendPoolUsage( tx, transactionData.userId, spendPoolId, transactionData.totalCost - ); - + ) : null; // 3. Create the transaction record const transaction = await this.createTransactionRecord( tx, @@ -522,4 +523,30 @@ export class EchoDbService { return !!transaction; } + + async getEchoAppById(echoAppId: string): Promise { + const echoApp = await this.db.echoApp.findUnique({ + where: { id: echoAppId }, + }); + if (!echoApp) { + return null; + } + return { + id: echoApp.id, + name: echoApp.name, + createdAt: echoApp.createdAt.toISOString(), + updatedAt: echoApp.updatedAt.toISOString(), + }; + } + async getCurrentMarkupByEchoAppId(echoAppId: string) { + const echoApp = await this.getEchoAppById(echoAppId); + if (!echoApp) { + return null; + } + const markup = await this.db.echoApp.findUnique({ + where: { id: echoAppId }, + select: { markUp: true }, + }); + return markup?.markUp || null; + } } diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index dca4e6558..6d17845da 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -6,12 +6,13 @@ import type { Transaction, TransactionRequest, User, + X402AuthenticationResult, } from '../types'; import { EchoDbService } from './DbService'; import { Decimal } from '@prisma/client/runtime/library'; import { PaymentRequiredError, UnauthorizedError } from '../errors/http'; -import { PrismaClient, SpendPool } from '../generated/prisma'; +import { MarkUp, PrismaClient, SpendPool } from '../generated/prisma'; import logger from '../logger'; import { EarningsService } from './EarningsService'; import FreeTierService from './FreeTierService'; @@ -21,8 +22,9 @@ export class EchoControlService { private readonly dbService: EchoDbService; private readonly freeTierService: FreeTierService; private earningsService: EarningsService; - private readonly apiKey: string; + private readonly apiKey: string | undefined; private authResult: ApiKeyValidationResult | null = null; + private x402AuthenticationResult: X402AuthenticationResult | null = null; private markUpAmount: Decimal | null = null; private markUpId: string | null = null; private referralAmount: Decimal | null = null; @@ -30,7 +32,7 @@ export class EchoControlService { private referralCodeId: string | null = null; private freeTierSpendPool: SpendPool | null = null; - constructor(db: PrismaClient, apiKey: string) { + constructor(db: PrismaClient, apiKey?: string) { // Check if the generated Prisma client exists const generatedPrismaPath = join(__dirname, 'generated', 'prisma'); if (!existsSync(generatedPrismaPath)) { @@ -53,7 +55,9 @@ export class EchoControlService { */ async verifyApiKey(): Promise { try { - this.authResult = await this.dbService.validateApiKey(this.apiKey); + if (this.apiKey) { + this.authResult = await this.dbService.validateApiKey(this.apiKey); + } } catch (error) { logger.error(`Error verifying API key: ${error}`); return null; @@ -81,6 +85,26 @@ export class EchoControlService { return this.authResult; } + identifyX402Request(echoApp: EchoApp | null, markUp: MarkUp | null): void { + if (echoApp) { + this.x402AuthenticationResult = { + echoApp: echoApp, + echoAppId: echoApp.id, + }; + } + + if (markUp) { + this.markUpAmount = markUp.amount; + this.markUpId = markUp.id; + } + + this.referralAmount = new Decimal(1.0); + this.referrerRewardId = null; + this.referralCodeId = null; + this.freeTierSpendPool = null; + this.referralCodeId = null; + } + /** * Get the cached authentication result */ @@ -143,7 +167,6 @@ export class EchoControlService { */ async createTransaction( transaction: Transaction, - maxCost: Decimal ): Promise { try { if (!this.authResult) { @@ -160,7 +183,7 @@ export class EchoControlService { await this.createFreeTierTransaction(transaction); return; } else { - await this.createPaidTransaction(transaction, maxCost); + await this.createPaidTransaction(transaction); return; } } catch (error) { @@ -294,7 +317,6 @@ export class EchoControlService { async createPaidTransaction( transaction: Transaction, - maxCost: Decimal ): Promise { if (!this.authResult) { logger.error('No authentication result available'); @@ -309,13 +331,6 @@ export class EchoControlService { markUpProfit, } = await this.computeTransactionCosts(transaction, this.referralCodeId); - logger.info( - `Transaction cost: ${rawTransactionCost}, Max cost: ${maxCost}` - ); - if (rawTransactionCost.greaterThan(maxCost)) { - logger.info(` Difference: ${rawTransactionCost.minus(maxCost)}`); - } - const { userId, echoAppId, apiKeyId } = this.authResult; const transactionData: TransactionRequest = { @@ -336,4 +351,41 @@ export class EchoControlService { await this.dbService.createPaidTransaction(transactionData); } + + async identifyX402Transaction(echoApp: EchoApp, markUp: MarkUp): Promise { + this.markUpId = markUp.id; + this.markUpAmount = markUp.amount; + this.x402AuthenticationResult = { + echoApp, + echoAppId: echoApp.id, + }; + } + + + async createX402Transaction( + transaction: Transaction, + ): Promise { + + const { + rawTransactionCost, + totalTransactionCost, + totalAppProfit, + referralProfit, + markUpProfit, + } = await this.computeTransactionCosts(transaction, null); + + const transactionData: TransactionRequest = { + totalCost: totalTransactionCost, + appProfit: totalAppProfit, + markUpProfit: markUpProfit, + referralProfit: referralProfit, + rawTransactionCost: rawTransactionCost, + metadata: transaction.metadata, + status: transaction.status, + ...(this.x402AuthenticationResult?.echoAppId && { echoAppId: this.x402AuthenticationResult?.echoAppId }), + ...(this.markUpId && { markUpId: this.markUpId }), + }; + + await this.dbService.createPaidTransaction(transactionData); + } } diff --git a/packages/app/server/src/services/PricingService.ts b/packages/app/server/src/services/PricingService.ts index 101f39c9b..347277518 100644 --- a/packages/app/server/src/services/PricingService.ts +++ b/packages/app/server/src/services/PricingService.ts @@ -14,10 +14,17 @@ import { EscrowRequest } from '../middleware/transaction-escrow-middleware'; import { ProviderType } from 'providers/ProviderType'; import { Tool } from 'openai/resources/responses/responses'; import { SupportedVideoModel } from '@merit-systems/echo-typescript-sdk'; +import { MarkUp } from 'generated/prisma/client'; -export function applyMaxCostMarkup(maxCost: Decimal): Decimal { - const markup = process.env.MAX_COST_MARKUP || '1.25'; - return maxCost.mul(new Decimal(markup)); +export function applyMaxCostMarkup(maxCost: Decimal, markUp: MarkUp | null): Decimal { + const echoMarkup = process.env.MAX_COST_MARKUP || '1.25'; + const appMarkup = markUp?.amount || 1.0; + + const echoMarkupApplied = maxCost.mul(new Decimal(echoMarkup)).minus(maxCost); + + const appMarkupApplied = maxCost.mul(new Decimal(appMarkup)).minus(maxCost); + + return maxCost.add(echoMarkupApplied).add(appMarkupApplied); } export function getRequestMaxCost( diff --git a/packages/app/server/src/services/x402AuthenticationService.ts b/packages/app/server/src/services/x402AuthenticationService.ts new file mode 100644 index 000000000..f4f4515e7 --- /dev/null +++ b/packages/app/server/src/services/x402AuthenticationService.ts @@ -0,0 +1,45 @@ +import { MarkUp, PrismaClient } from "../generated/prisma"; +import { EchoDbService } from "./DbService"; +import { EchoApp, Transaction } from "../types"; +import { EchoControlService } from "./EchoControlService"; +import logger from "logger"; + +export class X402AuthenticationService { + + private readonly dbService: EchoDbService; + private readonly echoControlService: EchoControlService; + + constructor(prisma: PrismaClient) { + this.dbService = new EchoDbService(prisma); + this.echoControlService = new EchoControlService(prisma); + } + + async authenticateX402Request(headers: Record): Promise<{ + echoApp: EchoApp | null; + markUp: MarkUp | null; + } | null> { + const requestedAppId = headers["x-echo-app-id"]; + + logger.info(`Authenticating X402 request for echo app ${requestedAppId}`); + + if (!requestedAppId) { + return null; + } + + const echoApp = await this.dbService.getEchoAppById(requestedAppId); + + const markUp = await this.dbService.getCurrentMarkupByEchoAppId(requestedAppId); + + this.echoControlService.identifyX402Request(echoApp, markUp); + + return { echoApp, markUp }; + } + + + + async createX402Transaction(transaction: Transaction): Promise { + await this.echoControlService.createX402Transaction(transaction); + + logger.info(`Created X402 transaction for echo app ${transaction.metadata.provider}`); + } +} \ No newline at end of file diff --git a/packages/app/server/src/types.ts b/packages/app/server/src/types.ts index b92451816..c174790dc 100644 --- a/packages/app/server/src/types.ts +++ b/packages/app/server/src/types.ts @@ -4,6 +4,7 @@ import { EchoControlService } from 'services/EchoControlService'; import { Response } from 'express'; import { BaseProvider } from 'providers/BaseProvider'; import { Hex } from 'viem'; +import { X402AuthenticationService } from 'services/x402AuthenticationService'; export interface EchoApp { id: string; @@ -81,8 +82,8 @@ export interface TransactionRequest extends Transaction { appProfit: Decimal; markUpProfit: Decimal; referralProfit: Decimal; - userId: string; - echoAppId: string; + userId?: string; + echoAppId?: string; apiKeyId?: string; markUpId?: string; spendPoolId?: string; @@ -99,6 +100,11 @@ export interface ApiKeyValidationResult { apiKey?: ApiKey; } +export interface X402AuthenticationResult { + echoApp: EchoApp; + echoAppId: string; +} + /** * JWT payload for Echo Access Tokens */ @@ -234,8 +240,11 @@ export type HandlerInput = { isPassthroughProxyRoute: boolean; provider: BaseProvider; isStream: boolean; + x402AuthenticationService: X402AuthenticationService; }; +export type ApiKeyHandlerInput = Omit; + export type X402HandlerInput = Omit; /** From e0f1295db447473395fb9fdd3bf9876caa139f76 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 28 Oct 2025 13:35:44 -0400 Subject: [PATCH 02/13] fix build in control plane --- .../_components/overview/activity/charts.tsx | 13 ++++++------- .../_components/overview/transactions/rows.tsx | 2 +- .../src/app/(app)/app/[id]/_hooks/use-app-setup.ts | 12 ++++++++++-- .../[id]/transactions/_components/transactions.tsx | 2 +- packages/app/control/src/services/db/apps/users.ts | 2 +- .../control/src/services/db/user/payouts/markup.ts | 1 + .../src/services/db/user/payouts/referrals.ts | 1 + .../app/server/src/services/EchoControlService.ts | 7 ++++++- packages/app/server/src/types.ts | 2 ++ 9 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/activity/charts.tsx b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/activity/charts.tsx index 066b150e7..805a47e57 100644 --- a/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/activity/charts.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/activity/charts.tsx @@ -5,7 +5,7 @@ import { Charts, LoadingCharts } from '@/app/(app)/_components/charts'; import type { ChartData } from '@/app/(app)/_components/charts/base-chart'; import { api } from '@/trpc/client'; -import { useActivityContext } from '../../../../../../_components/time-range-selector/context'; +import { useActivityContext } from '@/app/(app)/_components/time-range-selector/context'; import { formatCurrency } from '@/lib/utils'; import { useMemo } from 'react'; @@ -30,14 +30,13 @@ export const ActivityCharts: React.FC = ({ appId }) => { ); const [isOwner] = api.apps.app.isOwner.useSuspenseQuery(appId); - const [numTokens] = api.apps.app.getNumTokens.useSuspenseQuery({ appId }); const [numTransactions] = api.apps.app.transactions.count.useSuspenseQuery({ appId, }); const isInitialized = useMemo(() => { - return !isOwner || (numTokens > 0 && numTransactions > 0); - }, [isOwner, numTokens, numTransactions]); + return !isOwner || numTransactions > 0; + }, [isOwner, numTransactions]); // Transform data for the chart const chartData: ChartData>[] = @@ -80,7 +79,7 @@ export const ActivityCharts: React.FC = ({ appId }) => { trigger: { value: 'profit', label: 'Profit', - amount: numTokens === 0 ? '--' : formatCurrency(totalProfit), + amount: numTransactions === 0 ? '--' : formatCurrency(totalProfit), }, bars: [ { @@ -112,7 +111,7 @@ export const ActivityCharts: React.FC = ({ appId }) => { value: 'tokens', label: 'Tokens', amount: - numTokens === 0 + numTransactions === 0 ? '--' : totalTokens.toLocaleString(undefined, { notation: 'compact', @@ -161,7 +160,7 @@ export const ActivityCharts: React.FC = ({ appId }) => { value: 'transactions', label: 'Transactions', amount: - numTokens === 0 + numTransactions === 0 ? '--' : totalTransactions.toLocaleString(undefined, { notation: 'compact', diff --git a/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx index 25ee2840c..5f9fba805 100644 --- a/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx @@ -30,7 +30,7 @@ export const TransactionRows = async ({ appId }: { appId: string }) => {

- {transaction.user.name} made{' '} + {transaction.user.name ?? 'Unknown User'} made{' '} {transaction.callCount} requests

diff --git a/packages/app/control/src/app/(app)/app/[id]/_hooks/use-app-setup.ts b/packages/app/control/src/app/(app)/app/[id]/_hooks/use-app-setup.ts index b5ffd9e7a..2665159bf 100644 --- a/packages/app/control/src/app/(app)/app/[id]/_hooks/use-app-setup.ts +++ b/packages/app/control/src/app/(app)/app/[id]/_hooks/use-app-setup.ts @@ -25,10 +25,18 @@ export const useAppConnectionSetup = (appId: string) => { refetchInterval: shouldRefetchConnection ? 2500 : undefined, } ); + const [transactionsCount] = api.apps.app.transactions.count.useSuspenseQuery( + { + appId, + }, + { + refetchInterval: shouldRefetchTransactions ? 2500 : undefined, + } + ); const isConnected = useMemo(() => { - return numTokens > 0 || numApiKeys > 0; - }, [numTokens, numApiKeys]); + return numTokens > 0 || numApiKeys > 0 || transactionsCount > 0; + }, [numTokens, numApiKeys, transactionsCount]); useEffect(() => { setShouldRefetchConnection(!isConnected); diff --git a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx index bc3a7239e..794f14bb2 100644 --- a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx @@ -107,7 +107,7 @@ const TransactionRow = ({ transaction }: { transaction: Transaction }) => {

- {transaction.user.name} made{' '} + {transaction.user.name ?? 'Unknown User'} made{' '} {transaction.callCount} requests

diff --git a/packages/app/control/src/services/db/apps/users.ts b/packages/app/control/src/services/db/apps/users.ts index 162acc114..711b29c6a 100644 --- a/packages/app/control/src/services/db/apps/users.ts +++ b/packages/app/control/src/services/db/apps/users.ts @@ -59,7 +59,7 @@ export const listAppUsers = async ( }); // Get user details and membership info for the top users - const userIds = topUsersWithStats.map(stat => stat.userId); + const userIds = topUsersWithStats.map(stat => stat.userId).filter((id): id is string => id !== null); const usersWithDetails = await db.user.findMany({ where: { diff --git a/packages/app/control/src/services/db/user/payouts/markup.ts b/packages/app/control/src/services/db/user/payouts/markup.ts index 97ca2fece..64a798361 100644 --- a/packages/app/control/src/services/db/user/payouts/markup.ts +++ b/packages/app/control/src/services/db/user/payouts/markup.ts @@ -124,6 +124,7 @@ export async function calculateUserMarkupEarnings( const byApp: Record = {}; for (const row of groupedByApp) { + if (!row.echoAppId) continue; const gross = row._sum.markUpProfit ? Number(row._sum.markUpProfit) : 0; const claimed = claimedMap[row.echoAppId] || 0; const net = Math.max(0, gross - claimed); diff --git a/packages/app/control/src/services/db/user/payouts/referrals.ts b/packages/app/control/src/services/db/user/payouts/referrals.ts index f9892e0c6..685acb751 100644 --- a/packages/app/control/src/services/db/user/payouts/referrals.ts +++ b/packages/app/control/src/services/db/user/payouts/referrals.ts @@ -81,6 +81,7 @@ export async function calculateUserReferralEarnings( const byApp: Record = {}; for (const row of groupedByApp) { + if (!row.echoAppId) continue; const gross = row._sum.referralProfit ? Number(row._sum.referralProfit) : 0; const claimed = claimedMap[row.echoAppId] || 0; const net = Math.max(0, gross - claimed); diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index 6d17845da..a5eef8766 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -12,7 +12,7 @@ import { EchoDbService } from './DbService'; import { Decimal } from '@prisma/client/runtime/library'; import { PaymentRequiredError, UnauthorizedError } from '../errors/http'; -import { MarkUp, PrismaClient, SpendPool } from '../generated/prisma'; +import { EnumTransactionType, MarkUp, PrismaClient, SpendPool } from '../generated/prisma'; import logger from '../logger'; import { EarningsService } from './EarningsService'; import FreeTierService from './FreeTierService'; @@ -96,6 +96,10 @@ export class EchoControlService { if (markUp) { this.markUpAmount = markUp.amount; this.markUpId = markUp.id; + } else { + // Default markup amount when no markup is configured + this.markUpAmount = new Decimal(1.0); + this.markUpId = null; } this.referralAmount = new Decimal(1.0); @@ -384,6 +388,7 @@ export class EchoControlService { status: transaction.status, ...(this.x402AuthenticationResult?.echoAppId && { echoAppId: this.x402AuthenticationResult?.echoAppId }), ...(this.markUpId && { markUpId: this.markUpId }), + transactionType: EnumTransactionType.X402, }; await this.dbService.createPaidTransaction(transactionData); diff --git a/packages/app/server/src/types.ts b/packages/app/server/src/types.ts index c174790dc..82a9b888b 100644 --- a/packages/app/server/src/types.ts +++ b/packages/app/server/src/types.ts @@ -5,6 +5,7 @@ import { Response } from 'express'; import { BaseProvider } from 'providers/BaseProvider'; import { Hex } from 'viem'; import { X402AuthenticationService } from 'services/x402AuthenticationService'; +import { EnumTransactionType } from 'generated/prisma'; export interface EchoApp { id: string; @@ -89,6 +90,7 @@ export interface TransactionRequest extends Transaction { spendPoolId?: string; referralCodeId?: string; referrerRewardId?: string; + transactionType?: EnumTransactionType; } export interface ApiKeyValidationResult { From a729ad49a3756d2b71b26a14e6bf7128b4ec7701 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 28 Oct 2025 13:44:20 -0400 Subject: [PATCH 03/13] fix format --- .../app/control/docs/components/meta.json | 20 +++++++++---------- .../overview/transactions/rows.tsx | 6 ++++-- .../transactions/_components/transactions.tsx | 6 ++++-- .../app/control/src/services/db/apps/users.ts | 4 +++- 4 files changed, 21 insertions(+), 15 deletions(-) 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/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx index 5f9fba805..6c06de6f3 100644 --- a/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx @@ -30,8 +30,10 @@ export const TransactionRows = async ({ appId }: { appId: string }) => {

- {transaction.user.name ?? 'Unknown User'} made{' '} - {transaction.callCount} requests + + {transaction.user.name ?? 'Unknown User'} + {' '} + made {transaction.callCount} requests

{formatDistanceToNow(transaction.date, { diff --git a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx index 794f14bb2..1bff9fd73 100644 --- a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx @@ -107,8 +107,10 @@ const TransactionRow = ({ transaction }: { transaction: Transaction }) => {

- {transaction.user.name ?? 'Unknown User'} made{' '} - {transaction.callCount} requests + + {transaction.user.name ?? 'Unknown User'} + {' '} + made {transaction.callCount} requests

{formatDistanceToNow(transaction.date, { diff --git a/packages/app/control/src/services/db/apps/users.ts b/packages/app/control/src/services/db/apps/users.ts index 711b29c6a..2cd00f03b 100644 --- a/packages/app/control/src/services/db/apps/users.ts +++ b/packages/app/control/src/services/db/apps/users.ts @@ -59,7 +59,9 @@ export const listAppUsers = async ( }); // Get user details and membership info for the top users - const userIds = topUsersWithStats.map(stat => stat.userId).filter((id): id is string => id !== null); + const userIds = topUsersWithStats + .map(stat => stat.userId) + .filter((id): id is string => id !== null); const usersWithDetails = await db.user.findMany({ where: { From 09079c23095c5232e95a98ac9282efa4766b843e Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 28 Oct 2025 16:40:19 -0400 Subject: [PATCH 04/13] handlers refactor for finalize --- .../src/services/db/apps/transactions.ts | 10 +- packages/app/server/src/handlers.ts | 162 +----------------- packages/app/server/src/handlers/finalize.ts | 60 +++++++ packages/app/server/src/handlers/refund.ts | 22 +++ packages/app/server/src/handlers/settle.ts | 91 ++++++++++ packages/app/server/src/resources/handler.ts | 6 +- .../server/src/services/EchoControlService.ts | 31 ++-- .../app/server/src/services/PricingService.ts | 11 +- .../src/services/x402AuthenticationService.ts | 8 +- packages/app/server/src/types.ts | 9 + 10 files changed, 223 insertions(+), 187 deletions(-) create mode 100644 packages/app/server/src/handlers/finalize.ts create mode 100644 packages/app/server/src/handlers/refund.ts create mode 100644 packages/app/server/src/handlers/settle.ts diff --git a/packages/app/control/src/services/db/apps/transactions.ts b/packages/app/control/src/services/db/apps/transactions.ts index de50ac8ff..5a92dd67f 100644 --- a/packages/app/control/src/services/db/apps/transactions.ts +++ b/packages/app/control/src/services/db/apps/transactions.ts @@ -67,7 +67,7 @@ export const listAppTransactions = async ( { id: string; user: { - id: string; + id: string | null; name: string | null; image: string | null; }; @@ -78,7 +78,9 @@ export const listAppTransactions = async ( >(); for (const transaction of transactions) { - const userKey = `${transaction.userId}-${format(transaction.createdAt, 'yyyy-MM-dd')}`; + // Use 'unknown' as userId for null userIds, ensuring they all group together + const userId = transaction.userId ?? 'unknown'; + const userKey = `${userId}-${format(transaction.createdAt, 'yyyy-MM-dd')}`; if (groupedTransactions.has(userKey)) { // Aggregate existing group @@ -90,8 +92,8 @@ export const listAppTransactions = async ( groupedTransactions.set(userKey, { id: transaction.id, user: { - id: transaction.userId!, - name: transaction.user?.name ?? null, + id: transaction.userId, + name: transaction.user?.name ?? (transaction.userId === null ? 'Unknown User' : null), image: transaction.user?.image ?? null, }, callCount: 1, diff --git a/packages/app/server/src/handlers.ts b/packages/app/server/src/handlers.ts index ab5f12ab0..026d99a0e 100644 --- a/packages/app/server/src/handlers.ts +++ b/packages/app/server/src/handlers.ts @@ -1,164 +1,18 @@ import { TransactionEscrowMiddleware } from 'middleware/transaction-escrow-middleware'; import { modelRequestService } from 'services/ModelRequestService'; -import { ApiKeyHandlerInput, Network, Transaction, X402HandlerInput } from 'types'; +import { ApiKeyHandlerInput, X402HandlerInput } from 'types'; import { - usdcBigIntToDecimal, - decimalToUsdcBigInt, - buildX402Response, - getSmartAccount, calculateRefundAmount, - validateXPaymentHeader, } from 'utils'; -import { transfer } from 'transferWithAuth'; import { checkBalance } from 'services/BalanceCheckService'; import { prisma } from 'server'; import { makeProxyPassthroughRequest } from 'services/ProxyPassthroughService'; -import { USDC_ADDRESS } from 'services/fund-repo/constants'; -import { FacilitatorClient } from 'services/facilitator/facilitatorService'; -import { - ExactEvmPayload, - PaymentPayload, - PaymentRequirementsSchema, - SettleRequestSchema, -} from 'services/facilitator/x402-types'; -import { Decimal } from '@prisma/client/runtime/library'; import logger from 'logger'; -import { Request, Response } from 'express'; 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 payload = xPaymentData.payload as ExactEvmPayload; - 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'; +import { applyEchoMarkup } from 'services/PricingService'; export async function handleX402Request({ req, @@ -207,11 +61,13 @@ export async function handleX402Request({ ); logger.info(`Creating X402 transaction for app. Metadata: ${JSON.stringify(transaction.metadata)}`); - await x402AuthenticationService.createX402Transaction(transaction); + const transactionCosts = await x402AuthenticationService.createX402Transaction(transaction); await finalize( paymentAmountDecimal, - transactionResult.transaction, + transactionCosts.rawTransactionCost, + transactionCosts.markUpProfit, + applyEchoMarkup(transactionCosts.rawTransactionCost), payload ); } catch (error) { diff --git a/packages/app/server/src/handlers/finalize.ts b/packages/app/server/src/handlers/finalize.ts new file mode 100644 index 000000000..a51b750b3 --- /dev/null +++ b/packages/app/server/src/handlers/finalize.ts @@ -0,0 +1,60 @@ +import { + decimalToUsdcBigInt, + calculateRefundAmount, +} from 'utils'; +import { transfer } from 'transferWithAuth'; +import { + ExactEvmPayload, +} from 'services/facilitator/x402-types'; +import { Decimal } from '@prisma/client/runtime/library'; +import { Transaction } from 'types'; + + +export async function finalize( + originalPaymentAmountDecimal: Decimal, + rawTransactionCost: Decimal, + appMarkupProfit: Decimal, + echoMarkupProfit: Decimal, + payload: ExactEvmPayload +) { + + const appMarkupAmount = rawTransactionCost.plus(appMarkupProfit); + + const totalCostToUser = appMarkupAmount.add(echoMarkupProfit); + + const refundAmount = calculateRefundAmount( + originalPaymentAmountDecimal, + totalCostToUser + ); + + if (!refundAmount.equals(0) && refundAmount.greaterThan(0)) { + const refundAmountUsdcBigInt = decimalToUsdcBigInt(refundAmount); + const authPayload = payload.authorization; + await transfer(authPayload.from as `0x${string}`, refundAmountUsdcBigInt); + } + + + + // We need to send the rawTransactionCost to a separate wallet (and allow claim through control) + + // We need to send the echo markup profit to the echo repo (and allow claim through control) + + // We need to send the app Markup Profit to the app Repo (and allow them to claim through control) +} + +export async function finalizeResource( + originalPaymentAmountDecimal: Decimal, + transaction: Transaction, + payload: ExactEvmPayload +) { + const refundAmount = calculateRefundAmount( + originalPaymentAmountDecimal, + transaction.rawTransactionCost + ); + + if (!refundAmount.equals(0) && refundAmount.greaterThan(0)) { + const refundAmountUsdcBigInt = decimalToUsdcBigInt(refundAmount); + const authPayload = payload.authorization; + await transfer(authPayload.from as `0x${string}`, refundAmountUsdcBigInt); + } +} \ No newline at end of file diff --git a/packages/app/server/src/handlers/refund.ts b/packages/app/server/src/handlers/refund.ts new file mode 100644 index 000000000..d8d576d82 --- /dev/null +++ b/packages/app/server/src/handlers/refund.ts @@ -0,0 +1,22 @@ +import { + decimalToUsdcBigInt, +} from 'utils'; +import { transfer } from 'transferWithAuth'; +import { + ExactEvmPayload, +} from 'services/facilitator/x402-types'; +import { Decimal } from '@prisma/client/runtime/library'; +import logger from 'logger'; + +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); + } +} diff --git a/packages/app/server/src/handlers/settle.ts b/packages/app/server/src/handlers/settle.ts new file mode 100644 index 000000000..d78f9a9dc --- /dev/null +++ b/packages/app/server/src/handlers/settle.ts @@ -0,0 +1,91 @@ +import { Network } from 'types'; +import { + usdcBigIntToDecimal, + decimalToUsdcBigInt, + buildX402Response, + getSmartAccount, + validateXPaymentHeader, +} from 'utils'; +import { USDC_ADDRESS } from 'services/fund-repo/constants'; +import { FacilitatorClient } from 'services/facilitator/facilitatorService'; +import { + ExactEvmPayload, + PaymentPayload, + PaymentRequirementsSchema, + SettleRequestSchema, +} from 'services/facilitator/x402-types'; +import { Decimal } from '@prisma/client/runtime/library'; +import logger from 'logger'; +import { Request, Response } from 'express'; + + +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 payload = xPaymentData.payload as ExactEvmPayload; + 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 }; + } \ No newline at end of file diff --git a/packages/app/server/src/resources/handler.ts b/packages/app/server/src/resources/handler.ts index f589be39d..397c9a0b5 100644 --- a/packages/app/server/src/resources/handler.ts +++ b/packages/app/server/src/resources/handler.ts @@ -4,7 +4,9 @@ import { Decimal } from '@prisma/client/runtime/library'; import { buildX402Response, isApiRequest, isX402Request } from 'utils'; import { authenticateRequest } from 'auth'; import { prisma } from 'server'; -import { settle, finalize, refund } from 'handlers'; +import { settle } from 'handlers/settle'; +import { finalizeResource } from 'handlers/finalize'; +import { refund } from 'handlers/refund'; import logger from 'logger'; import { ExactEvmPayload } from 'services/facilitator/x402-types'; import { HttpError, PaymentRequiredError } from 'errors/http'; @@ -64,7 +66,7 @@ async function handle402Request( const actualCost = calculateActualCost(parsedBody, output); const transaction = createTransaction(parsedBody, output, actualCost); - finalize(paymentAmountDecimal, transaction, payload).catch(error => { + finalizeResource(paymentAmountDecimal, transaction, payload).catch(error => { logger.error('Failed to finalize transaction', error); }); diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index a5eef8766..e596e1266 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -4,6 +4,7 @@ import type { ApiKeyValidationResult, EchoApp, Transaction, + TransactionCosts, TransactionRequest, User, X402AuthenticationResult, @@ -214,13 +215,7 @@ export class EchoControlService { async computeTransactionCosts( transaction: Transaction, referralCodeId: string | null - ): Promise<{ - rawTransactionCost: Decimal; - totalTransactionCost: Decimal; - totalAppProfit: Decimal; - referralProfit: Decimal; - markUpProfit: Decimal; - }> { + ): Promise { if (!this.markUpAmount) { logger.error('User has not authenticated'); throw new UnauthorizedError('User has not authenticated'); @@ -368,22 +363,16 @@ export class EchoControlService { async createX402Transaction( transaction: Transaction, - ): Promise { + ): Promise { - const { - rawTransactionCost, - totalTransactionCost, - totalAppProfit, - referralProfit, - markUpProfit, - } = await this.computeTransactionCosts(transaction, null); + const transactionCosts = await this.computeTransactionCosts(transaction, null); const transactionData: TransactionRequest = { - totalCost: totalTransactionCost, - appProfit: totalAppProfit, - markUpProfit: markUpProfit, - referralProfit: referralProfit, - rawTransactionCost: rawTransactionCost, + totalCost: transactionCosts.totalTransactionCost, + appProfit: transactionCosts.totalAppProfit, + markUpProfit: transactionCosts.markUpProfit, + referralProfit: transactionCosts.referralProfit, + rawTransactionCost: transactionCosts.rawTransactionCost, metadata: transaction.metadata, status: transaction.status, ...(this.x402AuthenticationResult?.echoAppId && { echoAppId: this.x402AuthenticationResult?.echoAppId }), @@ -392,5 +381,7 @@ export class EchoControlService { }; await this.dbService.createPaidTransaction(transactionData); + + return transactionCosts; } } diff --git a/packages/app/server/src/services/PricingService.ts b/packages/app/server/src/services/PricingService.ts index 347277518..88e41a383 100644 --- a/packages/app/server/src/services/PricingService.ts +++ b/packages/app/server/src/services/PricingService.ts @@ -16,14 +16,15 @@ import { Tool } from 'openai/resources/responses/responses'; import { SupportedVideoModel } from '@merit-systems/echo-typescript-sdk'; import { MarkUp } from 'generated/prisma/client'; -export function applyMaxCostMarkup(maxCost: Decimal, markUp: MarkUp | null): Decimal { +export function applyEchoMarkup(cost: Decimal): Decimal { const echoMarkup = process.env.MAX_COST_MARKUP || '1.25'; - const appMarkup = markUp?.amount || 1.0; - - const echoMarkupApplied = maxCost.mul(new Decimal(echoMarkup)).minus(maxCost); + return cost.mul(new Decimal(echoMarkup)).minus(cost); +} +export function applyMaxCostMarkup(maxCost: Decimal, markUp: MarkUp | null): Decimal { + const appMarkup = markUp?.amount || 1.0; const appMarkupApplied = maxCost.mul(new Decimal(appMarkup)).minus(maxCost); - + const echoMarkupApplied = applyEchoMarkup(maxCost); return maxCost.add(echoMarkupApplied).add(appMarkupApplied); } diff --git a/packages/app/server/src/services/x402AuthenticationService.ts b/packages/app/server/src/services/x402AuthenticationService.ts index f4f4515e7..522e6f7cc 100644 --- a/packages/app/server/src/services/x402AuthenticationService.ts +++ b/packages/app/server/src/services/x402AuthenticationService.ts @@ -1,6 +1,6 @@ import { MarkUp, PrismaClient } from "../generated/prisma"; import { EchoDbService } from "./DbService"; -import { EchoApp, Transaction } from "../types"; +import { EchoApp, Transaction, TransactionCosts } from "../types"; import { EchoControlService } from "./EchoControlService"; import logger from "logger"; @@ -37,9 +37,11 @@ export class X402AuthenticationService { - async createX402Transaction(transaction: Transaction): Promise { - await this.echoControlService.createX402Transaction(transaction); + async createX402Transaction(transaction: Transaction): Promise { + const transactionCosts = await this.echoControlService.createX402Transaction(transaction); logger.info(`Created X402 transaction for echo app ${transaction.metadata.provider}`); + + return transactionCosts; } } \ No newline at end of file diff --git a/packages/app/server/src/types.ts b/packages/app/server/src/types.ts index 82a9b888b..158ccd4b6 100644 --- a/packages/app/server/src/types.ts +++ b/packages/app/server/src/types.ts @@ -258,3 +258,12 @@ export type SendUserOperationReturnType = { /** The hash of the user operation. This is not the transaction hash which is only available after the operation is completed.*/ userOpHash: Hex; }; + + +export interface TransactionCosts { + rawTransactionCost: Decimal; + totalTransactionCost: Decimal; + totalAppProfit: Decimal; + referralProfit: Decimal; + markUpProfit: Decimal; +} \ No newline at end of file From bb8cbf30d4b0a77e3e0f20feb40936dea18961ed Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 28 Oct 2025 16:58:22 -0400 Subject: [PATCH 05/13] add echo profit line item in transaction --- .../migration.sql | 2 + packages/app/control/prisma/schema.prisma | 1 + packages/app/server/src/handlers.ts | 2 +- packages/app/server/src/handlers/finalize.ts | 15 +- packages/app/server/src/handlers/refund.ts | 8 +- packages/app/server/src/handlers/settle.ts | 139 +++++++++--------- packages/app/server/src/services/DbService.ts | 22 +-- .../server/src/services/EchoControlService.ts | 43 ++++-- .../app/server/src/services/PricingService.ts | 13 +- .../src/services/x402AuthenticationService.ts | 75 +++++----- packages/app/server/src/types.ts | 2 + 11 files changed, 173 insertions(+), 149 deletions(-) create mode 100644 packages/app/control/prisma/migrations/20251028204423_add_echo_profit/migration.sql diff --git a/packages/app/control/prisma/migrations/20251028204423_add_echo_profit/migration.sql b/packages/app/control/prisma/migrations/20251028204423_add_echo_profit/migration.sql new file mode 100644 index 000000000..3910cd516 --- /dev/null +++ b/packages/app/control/prisma/migrations/20251028204423_add_echo_profit/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "transactions" ADD COLUMN "echoProfit" DECIMAL(65,14) NOT NULL DEFAULT 0.0; diff --git a/packages/app/control/prisma/schema.prisma b/packages/app/control/prisma/schema.prisma index 64a132f0f..3122689f3 100644 --- a/packages/app/control/prisma/schema.prisma +++ b/packages/app/control/prisma/schema.prisma @@ -311,6 +311,7 @@ model Transaction { transactionMetadataId String? @db.Uuid totalCost Decimal @default(0.0) @db.Decimal(65, 14) appProfit Decimal @default(0.0) @db.Decimal(65, 14) + echoProfit Decimal @default(0.0) @db.Decimal(65, 14) markUpProfit Decimal @default(0.0) @db.Decimal(65, 14) referralProfit Decimal @default(0.0) @db.Decimal(65, 14) rawTransactionCost Decimal @default(0.0) @db.Decimal(65, 14) diff --git a/packages/app/server/src/handlers.ts b/packages/app/server/src/handlers.ts index 026d99a0e..7282f90bb 100644 --- a/packages/app/server/src/handlers.ts +++ b/packages/app/server/src/handlers.ts @@ -67,7 +67,7 @@ export async function handleX402Request({ paymentAmountDecimal, transactionCosts.rawTransactionCost, transactionCosts.markUpProfit, - applyEchoMarkup(transactionCosts.rawTransactionCost), + transactionCosts.echoProfit, payload ); } catch (error) { diff --git a/packages/app/server/src/handlers/finalize.ts b/packages/app/server/src/handlers/finalize.ts index a51b750b3..236a21f27 100644 --- a/packages/app/server/src/handlers/finalize.ts +++ b/packages/app/server/src/handlers/finalize.ts @@ -1,15 +1,9 @@ -import { - decimalToUsdcBigInt, - calculateRefundAmount, -} from 'utils'; +import { decimalToUsdcBigInt, calculateRefundAmount } from 'utils'; import { transfer } from 'transferWithAuth'; -import { - ExactEvmPayload, -} from 'services/facilitator/x402-types'; +import { ExactEvmPayload } from 'services/facilitator/x402-types'; import { Decimal } from '@prisma/client/runtime/library'; import { Transaction } from 'types'; - export async function finalize( originalPaymentAmountDecimal: Decimal, rawTransactionCost: Decimal, @@ -17,7 +11,6 @@ export async function finalize( echoMarkupProfit: Decimal, payload: ExactEvmPayload ) { - const appMarkupAmount = rawTransactionCost.plus(appMarkupProfit); const totalCostToUser = appMarkupAmount.add(echoMarkupProfit); @@ -33,8 +26,6 @@ export async function finalize( await transfer(authPayload.from as `0x${string}`, refundAmountUsdcBigInt); } - - // We need to send the rawTransactionCost to a separate wallet (and allow claim through control) // We need to send the echo markup profit to the echo repo (and allow claim through control) @@ -57,4 +48,4 @@ export async function finalizeResource( const authPayload = payload.authorization; await transfer(authPayload.from as `0x${string}`, refundAmountUsdcBigInt); } -} \ No newline at end of file +} diff --git a/packages/app/server/src/handlers/refund.ts b/packages/app/server/src/handlers/refund.ts index d8d576d82..9279303e4 100644 --- a/packages/app/server/src/handlers/refund.ts +++ b/packages/app/server/src/handlers/refund.ts @@ -1,10 +1,6 @@ -import { - decimalToUsdcBigInt, -} from 'utils'; +import { decimalToUsdcBigInt } from 'utils'; import { transfer } from 'transferWithAuth'; -import { - ExactEvmPayload, -} from 'services/facilitator/x402-types'; +import { ExactEvmPayload } from 'services/facilitator/x402-types'; import { Decimal } from '@prisma/client/runtime/library'; import logger from 'logger'; diff --git a/packages/app/server/src/handlers/settle.ts b/packages/app/server/src/handlers/settle.ts index d78f9a9dc..91ce6da36 100644 --- a/packages/app/server/src/handlers/settle.ts +++ b/packages/app/server/src/handlers/settle.ts @@ -18,74 +18,73 @@ import { Decimal } from '@prisma/client/runtime/library'; import logger from 'logger'; import { Request, Response } from 'express'; - 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 payload = xPaymentData.payload as ExactEvmPayload; - 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 }; - } \ No newline at end of file + 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 payload = xPaymentData.payload as ExactEvmPayload; + 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 }; +} diff --git a/packages/app/server/src/services/DbService.ts b/packages/app/server/src/services/DbService.ts index 253e4b35a..437399629 100644 --- a/packages/app/server/src/services/DbService.ts +++ b/packages/app/server/src/services/DbService.ts @@ -330,9 +330,10 @@ export class EchoDbService { markUpProfit: transaction.markUpProfit, referralProfit: transaction.referralProfit, rawTransactionCost: transaction.rawTransactionCost, - status: transaction.status, - userId: transaction.userId, - echoAppId: transaction.echoAppId, + echoProfit: transaction.echoProfit, + status: transaction.status ?? null, + userId: transaction.userId ?? null, + echoAppId: transaction.echoAppId ?? null, apiKeyId: transaction.apiKeyId || null, markUpId: transaction.markUpId || null, spendPoolId: transaction.spendPoolId || null, @@ -465,13 +466,14 @@ export class EchoDbService { throw new Error('Spend pool not found'); } // 2. Upsert UserSpendPoolUsage record using helper - const userSpendPoolUsage = transactionData.userId ? - await this.upsertUserSpendPoolUsage( - tx, - transactionData.userId, - spendPoolId, - transactionData.totalCost - ) : null; + const userSpendPoolUsage = transactionData.userId + ? await this.upsertUserSpendPoolUsage( + tx, + transactionData.userId, + spendPoolId, + transactionData.totalCost + ) + : null; // 3. Create the transaction record const transaction = await this.createTransactionRecord( tx, diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index e596e1266..43461d4ae 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -13,10 +13,16 @@ import { EchoDbService } from './DbService'; import { Decimal } from '@prisma/client/runtime/library'; import { PaymentRequiredError, UnauthorizedError } from '../errors/http'; -import { EnumTransactionType, MarkUp, PrismaClient, SpendPool } from '../generated/prisma'; +import { + EnumTransactionType, + MarkUp, + PrismaClient, + SpendPool, +} from '../generated/prisma'; import logger from '../logger'; import { EarningsService } from './EarningsService'; import FreeTierService from './FreeTierService'; +import { applyEchoMarkup } from './PricingService'; export class EchoControlService { private readonly db: PrismaClient; @@ -170,9 +176,7 @@ export class EchoControlService { * Create an LLM transaction record directly in the database * Uses centralized logic from EchoDbService */ - async createTransaction( - transaction: Transaction, - ): Promise { + async createTransaction(transaction: Transaction): Promise { try { if (!this.authResult) { logger.error('No authentication result available'); @@ -214,7 +218,8 @@ export class EchoControlService { async computeTransactionCosts( transaction: Transaction, - referralCodeId: string | null + referralCodeId: string | null, + addEchoProfit: boolean = false ): Promise { if (!this.markUpAmount) { logger.error('User has not authenticated'); @@ -255,6 +260,10 @@ export class EchoControlService { totalAppProfitDecimal ); + const echoProfitDecimal = addEchoProfit + ? applyEchoMarkup(transaction.rawTransactionCost) + : new Decimal(0); + // Return Decimal values directly return { rawTransactionCost: transaction.rawTransactionCost, @@ -262,6 +271,7 @@ export class EchoControlService { totalAppProfit: totalAppProfitDecimal, referralProfit: referralProfitDecimal, markUpProfit: markUpProfitDecimal, + echoProfit: echoProfitDecimal, }; } async createFreeTierTransaction(transaction: Transaction): Promise { @@ -314,9 +324,7 @@ export class EchoControlService { ); } - async createPaidTransaction( - transaction: Transaction, - ): Promise { + async createPaidTransaction(transaction: Transaction): Promise { if (!this.authResult) { logger.error('No authentication result available'); throw new UnauthorizedError('No authentication result available'); @@ -351,7 +359,10 @@ export class EchoControlService { await this.dbService.createPaidTransaction(transactionData); } - async identifyX402Transaction(echoApp: EchoApp, markUp: MarkUp): Promise { + async identifyX402Transaction( + echoApp: EchoApp, + markUp: MarkUp + ): Promise { this.markUpId = markUp.id; this.markUpAmount = markUp.amount; this.x402AuthenticationResult = { @@ -360,12 +371,15 @@ export class EchoControlService { }; } - async createX402Transaction( transaction: Transaction, + addEchoProfit: boolean = true ): Promise { - - const transactionCosts = await this.computeTransactionCosts(transaction, null); + const transactionCosts = await this.computeTransactionCosts( + transaction, + null, + addEchoProfit + ); const transactionData: TransactionRequest = { totalCost: transactionCosts.totalTransactionCost, @@ -373,9 +387,12 @@ export class EchoControlService { markUpProfit: transactionCosts.markUpProfit, referralProfit: transactionCosts.referralProfit, rawTransactionCost: transactionCosts.rawTransactionCost, + echoProfit: transactionCosts.echoProfit, metadata: transaction.metadata, status: transaction.status, - ...(this.x402AuthenticationResult?.echoAppId && { echoAppId: this.x402AuthenticationResult?.echoAppId }), + ...(this.x402AuthenticationResult?.echoAppId && { + echoAppId: this.x402AuthenticationResult?.echoAppId, + }), ...(this.markUpId && { markUpId: this.markUpId }), transactionType: EnumTransactionType.X402, }; diff --git a/packages/app/server/src/services/PricingService.ts b/packages/app/server/src/services/PricingService.ts index 88e41a383..983e1e682 100644 --- a/packages/app/server/src/services/PricingService.ts +++ b/packages/app/server/src/services/PricingService.ts @@ -17,11 +17,18 @@ import { SupportedVideoModel } from '@merit-systems/echo-typescript-sdk'; import { MarkUp } from 'generated/prisma/client'; export function applyEchoMarkup(cost: Decimal): Decimal { - const echoMarkup = process.env.MAX_COST_MARKUP || '1.25'; - return cost.mul(new Decimal(echoMarkup)).minus(cost); + const echoMarkup = process.env.ECHO_MARKUP || '1.25'; + const applyEchoMarkup = process.env.APPLY_ECHO_MARKUP === 'true'; + if (applyEchoMarkup) { + return cost.mul(new Decimal(echoMarkup)).minus(cost); + } + return new Decimal(0); } -export function applyMaxCostMarkup(maxCost: Decimal, markUp: MarkUp | null): Decimal { +export function applyMaxCostMarkup( + maxCost: Decimal, + markUp: MarkUp | null +): Decimal { const appMarkup = markUp?.amount || 1.0; const appMarkupApplied = maxCost.mul(new Decimal(appMarkup)).minus(maxCost); const echoMarkupApplied = applyEchoMarkup(maxCost); diff --git a/packages/app/server/src/services/x402AuthenticationService.ts b/packages/app/server/src/services/x402AuthenticationService.ts index 522e6f7cc..a6196f45f 100644 --- a/packages/app/server/src/services/x402AuthenticationService.ts +++ b/packages/app/server/src/services/x402AuthenticationService.ts @@ -1,47 +1,54 @@ -import { MarkUp, PrismaClient } from "../generated/prisma"; -import { EchoDbService } from "./DbService"; -import { EchoApp, Transaction, TransactionCosts } from "../types"; -import { EchoControlService } from "./EchoControlService"; -import logger from "logger"; +import { MarkUp, PrismaClient } from '../generated/prisma'; +import { EchoDbService } from './DbService'; +import { EchoApp, Transaction, TransactionCosts } from '../types'; +import { EchoControlService } from './EchoControlService'; +import logger from 'logger'; export class X402AuthenticationService { + private readonly dbService: EchoDbService; + private readonly echoControlService: EchoControlService; - private readonly dbService: EchoDbService; - private readonly echoControlService: EchoControlService; + constructor(prisma: PrismaClient) { + this.dbService = new EchoDbService(prisma); + this.echoControlService = new EchoControlService(prisma); + } - constructor(prisma: PrismaClient) { - this.dbService = new EchoDbService(prisma); - this.echoControlService = new EchoControlService(prisma); - } - - async authenticateX402Request(headers: Record): Promise<{ - echoApp: EchoApp | null; - markUp: MarkUp | null; - } | null> { - const requestedAppId = headers["x-echo-app-id"]; - - logger.info(`Authenticating X402 request for echo app ${requestedAppId}`); - - if (!requestedAppId) { - return null; - } + async authenticateX402Request(headers: Record): Promise<{ + echoApp: EchoApp | null; + markUp: MarkUp | null; + } | null> { + const requestedAppId = headers['x-echo-app-id']; - const echoApp = await this.dbService.getEchoAppById(requestedAppId); + logger.info(`Authenticating X402 request for echo app ${requestedAppId}`); - const markUp = await this.dbService.getCurrentMarkupByEchoAppId(requestedAppId); + if (!requestedAppId) { + return null; + } - this.echoControlService.identifyX402Request(echoApp, markUp); + const echoApp = await this.dbService.getEchoAppById(requestedAppId); - return { echoApp, markUp }; - } + const markUp = + await this.dbService.getCurrentMarkupByEchoAppId(requestedAppId); + this.echoControlService.identifyX402Request(echoApp, markUp); + return { echoApp, markUp }; + } - async createX402Transaction(transaction: Transaction): Promise { - const transactionCosts = await this.echoControlService.createX402Transaction(transaction); + async createX402Transaction( + transaction: Transaction + ): Promise { + const applyEchoMarkup = process.env.APPLY_ECHO_MARKUP === 'true'; + const transactionCosts = + await this.echoControlService.createX402Transaction( + transaction, + applyEchoMarkup + ); - logger.info(`Created X402 transaction for echo app ${transaction.metadata.provider}`); + logger.info( + `Created X402 transaction for echo app ${transaction.metadata.provider}` + ); - return transactionCosts; - } -} \ No newline at end of file + return transactionCosts; + } +} diff --git a/packages/app/server/src/types.ts b/packages/app/server/src/types.ts index 158ccd4b6..a0cf7daa0 100644 --- a/packages/app/server/src/types.ts +++ b/packages/app/server/src/types.ts @@ -83,6 +83,7 @@ export interface TransactionRequest extends Transaction { appProfit: Decimal; markUpProfit: Decimal; referralProfit: Decimal; + echoProfit: Decimal; userId?: string; echoAppId?: string; apiKeyId?: string; @@ -266,4 +267,5 @@ export interface TransactionCosts { totalAppProfit: Decimal; referralProfit: Decimal; markUpProfit: Decimal; + echoProfit: Decimal; } \ No newline at end of file From 62f48d312f7565a189ffb3e414633ca11d24ef04 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Tue, 28 Oct 2025 17:00:53 -0400 Subject: [PATCH 06/13] v0 nit - should be totalAppProfit despite referrals not currently used --- packages/app/server/src/handlers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/server/src/handlers.ts b/packages/app/server/src/handlers.ts index 7282f90bb..bd9ff8469 100644 --- a/packages/app/server/src/handlers.ts +++ b/packages/app/server/src/handlers.ts @@ -66,7 +66,7 @@ export async function handleX402Request({ await finalize( paymentAmountDecimal, transactionCosts.rawTransactionCost, - transactionCosts.markUpProfit, + transactionCosts.totalAppProfit, transactionCosts.echoProfit, payload ); From aaa495b08ed0c63a19a662678d813a953f0901ed Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Wed, 29 Oct 2025 16:44:53 -0400 Subject: [PATCH 07/13] control admin dashboard --- packages/app/control/package.json | 5 +- .../migration.sql | 2 + .../20251029193331_app_profit/migration.sql | 2 + packages/app/control/prisma/schema.prisma | 2 + .../_components/wallet/AppX402ProfitTotal.tsx | 225 +++++++ .../wallet/EchoX402ProfitTotal.tsx | 254 ++++++++ .../wallet/X402TransactionCostTotal.tsx | 45 ++ .../control/src/app/(app)/admin/layout.tsx | 4 + .../src/app/(app)/admin/x402-payouts/page.tsx | 20 + .../transactions/_components/transactions.tsx | 13 +- packages/app/control/src/env.ts | 38 ++ .../app/control/src/services/crypto/abi.ts | 66 +++ .../control/src/services/crypto/fund-repo.ts | 105 ++++ .../src/services/crypto/get-balance.ts | 61 ++ .../src/services/crypto/smart-account.ts | 35 ++ .../control/src/services/crypto/transfer.ts | 42 ++ .../control/src/services/db/admin/wallet.ts | 299 ++++++++++ .../src/services/db/apps/transactions.ts | 4 +- .../control/src/trpc/routers/admin/admin.ts | 2 + .../control/src/trpc/routers/admin/wallet.ts | 56 ++ packages/app/server/src/handlers/finalize.ts | 6 - packages/app/server/src/services/DbService.ts | 2 + .../server/src/services/EchoControlService.ts | 5 + pnpm-lock.yaml | 560 ++++++++++++------ 24 files changed, 1653 insertions(+), 200 deletions(-) create mode 100644 packages/app/control/prisma/migrations/20251029185610_echo_profit_payout/migration.sql create mode 100644 packages/app/control/prisma/migrations/20251029193331_app_profit/migration.sql create mode 100644 packages/app/control/src/app/(app)/admin/_components/wallet/AppX402ProfitTotal.tsx create mode 100644 packages/app/control/src/app/(app)/admin/_components/wallet/EchoX402ProfitTotal.tsx create mode 100644 packages/app/control/src/app/(app)/admin/_components/wallet/X402TransactionCostTotal.tsx create mode 100644 packages/app/control/src/app/(app)/admin/x402-payouts/page.tsx create mode 100644 packages/app/control/src/services/crypto/abi.ts create mode 100644 packages/app/control/src/services/crypto/fund-repo.ts create mode 100644 packages/app/control/src/services/crypto/get-balance.ts create mode 100644 packages/app/control/src/services/crypto/smart-account.ts create mode 100644 packages/app/control/src/services/crypto/transfer.ts create mode 100644 packages/app/control/src/services/db/admin/wallet.ts create mode 100644 packages/app/control/src/trpc/routers/admin/wallet.ts diff --git a/packages/app/control/package.json b/packages/app/control/package.json index eaee75d2a..2c6f21f32 100644 --- a/packages/app/control/package.json +++ b/packages/app/control/package.json @@ -37,6 +37,8 @@ "dependencies": { "@auth/core": "^0.40.0", "@auth/prisma-adapter": "^2.10.0", + "@coinbase/cdp-sdk": "^1.34.0", + "@coinbase/x402": "^0.6.4", "@hookform/resolvers": "^5.2.1", "@icons-pack/react-simple-icons": "^13.7.0", "@merit-systems/sdk": "0.0.8", @@ -88,7 +90,6 @@ "autonumeric": "^4.10.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "@coinbase/x402": "^0.6.4", "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.4.5", @@ -137,9 +138,9 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@faker-js/faker": "^9.9.0", + "@next/eslint-plugin-next": "^15.5.3", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", - "@next/eslint-plugin-next": "^15.5.3", "@types/node": "^20", "@types/react": "19.1.10", "@types/react-dom": "19.1.7", diff --git a/packages/app/control/prisma/migrations/20251029185610_echo_profit_payout/migration.sql b/packages/app/control/prisma/migrations/20251029185610_echo_profit_payout/migration.sql new file mode 100644 index 000000000..0b97c9e8c --- /dev/null +++ b/packages/app/control/prisma/migrations/20251029185610_echo_profit_payout/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "EnumPayoutType" ADD VALUE 'ECHO_PROFIT'; diff --git a/packages/app/control/prisma/migrations/20251029193331_app_profit/migration.sql b/packages/app/control/prisma/migrations/20251029193331_app_profit/migration.sql new file mode 100644 index 000000000..31772870f --- /dev/null +++ b/packages/app/control/prisma/migrations/20251029193331_app_profit/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "EnumPayoutType" ADD VALUE 'APP_PROFIT'; diff --git a/packages/app/control/prisma/schema.prisma b/packages/app/control/prisma/schema.prisma index 3122689f3..b18061082 100644 --- a/packages/app/control/prisma/schema.prisma +++ b/packages/app/control/prisma/schema.prisma @@ -231,6 +231,8 @@ enum EnumPayoutStatus { enum EnumPayoutType { MARKUP REFERRAL + ECHO_PROFIT + APP_PROFIT } model Payout { diff --git a/packages/app/control/src/app/(app)/admin/_components/wallet/AppX402ProfitTotal.tsx b/packages/app/control/src/app/(app)/admin/_components/wallet/AppX402ProfitTotal.tsx new file mode 100644 index 000000000..0ac7eefc2 --- /dev/null +++ b/packages/app/control/src/app/(app)/admin/_components/wallet/AppX402ProfitTotal.tsx @@ -0,0 +1,225 @@ +'use client'; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Button } from '@/components/ui/button'; +import { api } from '@/trpc/client'; +import { toast } from 'sonner'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { useState } from 'react'; + +export function AppX402ProfitTotal() { + const [processingAppIds, setProcessingAppIds] = useState>( + new Set() + ); + const [isSendingAll, setIsSendingAll] = useState(false); + const utils = api.useUtils(); + + const { data: totalProfit, isLoading: isTotalLoading } = + api.admin.wallet.getX402AppProfit.useQuery(); + + const { data: appBreakdown, isLoading: isBreakdownLoading } = + api.admin.wallet.getX402AppProfitByApp.useQuery(); + + const payoutMutation = api.admin.wallet.payoutX402AppProfit.useMutation({ + onSuccess: data => { + toast.success('Payout successful!', { + description: `Sent to ECHO_PAYOUTS. Tx: ${data.userOpHash.slice(0, 10)}...`, + }); + void utils.admin.wallet.getX402AppProfit.invalidate(); + void utils.admin.wallet.getX402AppProfitByApp.invalidate(); + }, + onError: error => { + toast.error('Payout failed', { + description: error.message, + }); + }, + onSettled: (data, error, variables) => { + setProcessingAppIds(prev => { + const next = new Set(prev); + next.delete(variables.appId); + return next; + }); + }, + }); + + const handlePayout = (appId: string, amount: number) => { + if (amount <= 0) { + toast.error('Invalid amount', { + description: 'Amount must be greater than 0', + }); + return; + } + + setProcessingAppIds(prev => new Set(prev).add(appId)); + payoutMutation.mutate({ appId, amount }); + }; + + const handleSendAll = async () => { + if (!appBreakdown || appBreakdown.length === 0) { + toast.error('No apps to payout'); + return; + } + + const appsWithProfit = appBreakdown.filter(app => app.remainingProfit > 0); + + if (appsWithProfit.length === 0) { + toast.error('No apps with remaining profit'); + return; + } + + setIsSendingAll(true); + + for (const app of appsWithProfit) { + setProcessingAppIds(prev => new Set(prev).add(app.appId)); + payoutMutation.mutate({ + appId: app.appId, + amount: app.remainingProfit, + }); + } + + setIsSendingAll(false); + }; + + return ( +

+ + + App X402 Profit Total + + Total unclaimed profit generated by apps from X402 transactions + + + + +
+

+ Total App Profit +

+ {isTotalLoading ? ( + + ) : ( +
+ ${(totalProfit ?? 0).toFixed(6)} +
+ )} +

+ Available for payout to applications +

+
+ +

+ The App Profit represents the sum of all appProfit from X402 + transactions minus any payouts already made to applications. +

+
+
+ + + +
+
+ Profit Breakdown by Application + + X402 profit generated by each application + +
+ {appBreakdown && + appBreakdown.some(app => app.remainingProfit > 0) && ( + + )} +
+
+ + {isBreakdownLoading ? ( +
+ + + +
+ ) : !appBreakdown || appBreakdown.length === 0 ? ( +

+ No app profit data available +

+ ) : ( +
+ + + + Application + Total Profit + Total Payouts + Remaining + Actions + + + + {appBreakdown.map(app => ( + + + {app.appName} + + + ${app.totalProfit.toFixed(6)} + + + ${app.totalPayouts.toFixed(6)} + + + 0 + ? 'text-green-600 font-semibold' + : '' + } + > + ${app.remainingProfit.toFixed(6)} + + + + + + + ))} + +
+
+ )} +
+
+
+ ); +} diff --git a/packages/app/control/src/app/(app)/admin/_components/wallet/EchoX402ProfitTotal.tsx b/packages/app/control/src/app/(app)/admin/_components/wallet/EchoX402ProfitTotal.tsx new file mode 100644 index 000000000..e9078a5cd --- /dev/null +++ b/packages/app/control/src/app/(app)/admin/_components/wallet/EchoX402ProfitTotal.tsx @@ -0,0 +1,254 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; +import { api } from '@/trpc/client'; +import { toast } from 'sonner'; +import { formatDistance } from 'date-fns'; + +export function EchoX402ProfitTotal() { + const { + data: profitData, + isLoading: isProfitLoading, + refetch: refetchProfit, + isFetching: isProfitFetching, + } = api.admin.wallet.getEchoX402ProfitTotal.useQuery(); + + const { data: walletAddress, isLoading: isAddressLoading } = + api.admin.wallet.getSmartAccountAddress.useQuery(); + + const { + data: usdcBalance, + isLoading: isUSDCLoading, + refetch: refetchUSDC, + isFetching: isUSDCFetching, + } = api.admin.wallet.getSmartAccountUSDCBalance.useQuery(); + + const { + data: ethBalance, + isLoading: isETHLoading, + refetch: refetchETH, + isFetching: isETHFetching, + } = api.admin.wallet.getSmartAccountETHBalance.useQuery(); + + const { + data: payoutHistory, + isLoading: isHistoryLoading, + refetch: refetchHistory, + } = api.admin.wallet.getEchoPayoutHistory.useQuery(); + + const fundRepoMutation = api.admin.wallet.fundEchoRepo.useMutation({ + onSuccess: data => { + toast.success('Successfully funded repo!', { + description: `Transaction hash: ${data.userOpHash}`, + }); + void refetchProfit(); + void refetchUSDC(); + void refetchETH(); + void refetchHistory(); + }, + onError: error => { + toast.error('Failed to fund repo', { + description: error.message, + }); + }, + }); + + const isAnyFetching = isProfitFetching || isUSDCFetching || isETHFetching; + + const handleRefresh = () => { + void refetchProfit(); + void refetchUSDC(); + void refetchETH(); + void refetchHistory(); + }; + + const handleFundRepo = () => { + if (!profitData || profitData <= 0) { + toast.error('Invalid amount', { + description: 'Profit total must be greater than 0', + }); + return; + } + + fundRepoMutation.mutate({ amount: profitData }); + }; + + return ( +
+ + +
+ Echo X402 Wallet +
+ {isAnyFetching && ( + + Refreshing… + + )} + +
+
+ Smart Account Wallet Status +
+ + +
+

+ Wallet Address +

+ {isAddressLoading ? ( + + ) : ( + + {walletAddress} + + )} +
+ +
+
+

+ Total Echo Profit +

+ {isProfitLoading ? ( + + ) : ( +
+ ${(profitData ?? 0).toFixed(6)} +
+ )} +

+ From X402 transactions +

+
+ +
+

+ USDC Balance +

+ {isUSDCLoading ? ( + + ) : ( +
+ ${(usdcBalance ?? 0).toFixed(6)} +
+ )} +

USDC on Base

+
+ +
+

+ ETH Balance +

+ {isETHLoading ? ( + + ) : ( +
+ {(ethBalance ?? 0).toFixed(6)} ETH +
+ )} +

+ Native ETH on Base +

+
+
+ +

+ The Echo Profit represents the sum of all echoProfit from X402 + transactions that should be deposited into the Echo repository. +

+
+ + + + +
+ + + + Payout History + + Recent Echo payouts to the Merit repository + + + + {isHistoryLoading ? ( +
+ + + +
+ ) : !payoutHistory || payoutHistory.length === 0 ? ( +

+ No payout history yet +

+ ) : ( +
+ + + + + + + + + + {payoutHistory.map(payout => ( + + + + + + ))} + +
+ Date + + Amount + + Status +
+ {formatDistance( + new Date(payout.createdAt), + new Date(), + { addSuffix: true } + )} + + ${Number(payout.amount).toFixed(6)} + + + {payout.status} + +
+
+ )} +
+
+
+ ); +} diff --git a/packages/app/control/src/app/(app)/admin/_components/wallet/X402TransactionCostTotal.tsx b/packages/app/control/src/app/(app)/admin/_components/wallet/X402TransactionCostTotal.tsx new file mode 100644 index 000000000..5b145f4c3 --- /dev/null +++ b/packages/app/control/src/app/(app)/admin/_components/wallet/X402TransactionCostTotal.tsx @@ -0,0 +1,45 @@ +'use client'; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; +import { api } from '@/trpc/client'; + +export function X402TransactionCostTotal() { + const { data: totalCost, isLoading } = + api.admin.wallet.getX402RawTransactionCostTotal.useQuery(); + + return ( + + + X402 Transaction Costs + + Total raw transaction costs for X402 transactions + + + + +
+

+ Total Raw Transaction Cost +

+ {isLoading ? ( + + ) : ( +
+ ${(totalCost ?? 0).toFixed(6)} +
+ )} +

+ Sum of inference costs from X402 transactions +

+
+
+
+ ); +} diff --git a/packages/app/control/src/app/(app)/admin/layout.tsx b/packages/app/control/src/app/(app)/admin/layout.tsx index cdd7ecab7..ede020614 100644 --- a/packages/app/control/src/app/(app)/admin/layout.tsx +++ b/packages/app/control/src/app/(app)/admin/layout.tsx @@ -30,6 +30,10 @@ export default async function AdminLayout({ children }: LayoutProps<'/admin'>) { label: 'Payouts', href: '/admin/payouts', }, + { + label: 'X402 Payouts', + href: '/admin/x402-payouts', + }, { label: 'Credit Grants', href: '/admin/credit-grants', diff --git a/packages/app/control/src/app/(app)/admin/x402-payouts/page.tsx b/packages/app/control/src/app/(app)/admin/x402-payouts/page.tsx new file mode 100644 index 000000000..a5e7518bd --- /dev/null +++ b/packages/app/control/src/app/(app)/admin/x402-payouts/page.tsx @@ -0,0 +1,20 @@ +import TableLayout from '../_components/TableLayout'; +import { EchoX402ProfitTotal } from '../_components/wallet/EchoX402ProfitTotal'; +import { AppX402ProfitTotal } from '../_components/wallet/AppX402ProfitTotal'; +import { X402TransactionCostTotal } from '../_components/wallet/X402TransactionCostTotal'; + +export default function X402PayoutsPage() { + return ( + + <> +

+ On this page, you can claim the Echo Payouts from X402 transactions + and manage the distribution of funds. +

+ + + + +
+ ); +} diff --git a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx index 1bff9fd73..6faa134dc 100644 --- a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx @@ -23,8 +23,8 @@ import { api } from '@/trpc/client'; interface Transaction { id: string; - user: { - id: string; + user?: { + id: string | null; name: string | null; image: string | null; }; @@ -71,7 +71,7 @@ export const TransactionsTable: React.FC = ({ appId }) => { ({ id: row.id, - user: row.user, + user: row.user ?? undefined, date: row.date, callCount: row.callCount, markUpProfit: row.markUpProfit, @@ -104,11 +104,14 @@ const TransactionRow = ({ transaction }: { transaction: Transaction }) => {
- +

- {transaction.user.name ?? 'Unknown User'} + {transaction.user?.name ?? 'Unknown User'} {' '} made {transaction.callCount} requests

diff --git a/packages/app/control/src/env.ts b/packages/app/control/src/env.ts index de7d41d11..d6e371205 100644 --- a/packages/app/control/src/env.ts +++ b/packages/app/control/src/env.ts @@ -152,6 +152,44 @@ export const env = createEnv({ MERIT_SENDER_GITHUB_ID: IS_STRICT ? z.coerce.number() : z.coerce.number().default(1), + MERIT_CONTRACT_ADDRESS: IS_STRICT + ? z.string().regex(/^0x[a-fA-F0-9]{40}$/, { + message: 'MERIT_CONTRACT_ADDRESS must be a valid Ethereum address', + }) + : z.string().default('0x1234567890123456789012345678901234567890'), + MERIT_REPO_ID: IS_STRICT ? z.string() : z.string().default('1'), + + // coinbase cdp + + CDP_API_KEY_ID: IS_STRICT + ? z.string() + : z.string().default('cdp-api-key-id'), + CDP_API_KEY_SECRET: IS_STRICT + ? z.string() + : z.string().default('cdp-api-key-secret'), + CDP_WALLET_SECRET: IS_STRICT + ? z.string() + : z.string().default('cdp-wallet-secret'), + WALLET_OWNER: IS_STRICT ? z.string() : z.string().default('wallet-owner'), + BASE_RPC_URL: IS_STRICT ? z.string().url() : z.string().url().optional(), + + // crypto addresses + + USDC_ADDRESS: IS_STRICT + ? z.string().regex(/^0x[a-fA-F0-9]{40}$/, { + message: 'USDC_ADDRESS must be a valid Ethereum address', + }) + : z.string().default('0x1234567890123456789012345678901234567890'), + ETH_ADDRESS: IS_STRICT + ? z.string().regex(/^0x[a-fA-F0-9]{40}$/, { + message: 'ETH_ADDRESS must be a valid Ethereum address', + }) + : z.string().default('0x1234567890123456789012345678901234567890'), + ECHO_PAYOUTS_ADDRESS: IS_STRICT + ? z.string().regex(/^0x[a-fA-F0-9]{40}$/, { + message: 'ECHO_PAYOUTS_ADDRESS must be a valid Ethereum address', + }) + : z.string().default('0x1234567890123456789012345678901234567890'), // qstash diff --git a/packages/app/control/src/services/crypto/abi.ts b/packages/app/control/src/services/crypto/abi.ts new file mode 100644 index 000000000..1e7c48ca8 --- /dev/null +++ b/packages/app/control/src/services/crypto/abi.ts @@ -0,0 +1,66 @@ +// Constants +export const MERIT_ABI = [ + { + inputs: [ + { + internalType: 'uint256', + name: 'repoId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'instanceId', + type: 'uint256', + }, + { + internalType: 'contract ERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'fundRepo', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +]; + +export const ERC20_CONTRACT_ABI = [ + { + name: 'approve', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { name: 'spender', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [], + }, + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ name: 'balance', type: 'uint256' }], + }, + { + name: 'transfer', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [{ name: 'success', type: 'bool' }], + }, +] as const; diff --git a/packages/app/control/src/services/crypto/fund-repo.ts b/packages/app/control/src/services/crypto/fund-repo.ts new file mode 100644 index 000000000..c3fe5ab8f --- /dev/null +++ b/packages/app/control/src/services/crypto/fund-repo.ts @@ -0,0 +1,105 @@ +import { encodeFunctionData, type Abi } from 'viem'; +import { getSmartAccount } from './smart-account'; +import { logger } from '@/logger'; +import { ERC20_CONTRACT_ABI, MERIT_ABI } from './abi'; +import { env } from '@/env'; + +export const MERIT_CONTRACT_ADDRESS = + env.MERIT_CONTRACT_ADDRESS as `0x${string}`; +const USDC_ADDRESS = env.USDC_ADDRESS as `0x${string}`; + +// Types +export interface FundRepoResult { + success: boolean; + userOpHash: string; + smartAccountAddress: string; + amount: number; + repoId: string; + tokenAddress: string; +} +// Main functions +export async function fundRepo( + amount: number, + repoId: number +): Promise { + try { + if (!amount || typeof amount !== 'number') { + throw new Error('Invalid amount provided'); + } + + if (!USDC_ADDRESS || !MERIT_CONTRACT_ADDRESS) { + throw new Error('Missing required environment variables'); + } + + const tokenAddress = USDC_ADDRESS; + const repoInstanceId = 0; + // Convert to BigInt safely by avoiding floating point precision issues + // USDC has 6 decimals, so multiply by 10^6 + // Use Math.ceil for defensive rounding to avoid undercharging + const amountBigInt = BigInt(Math.ceil(amount * 10 ** 6)); + + const { smartAccount } = await getSmartAccount(); + + // Send user operation to fund the repo + const result = await smartAccount.sendUserOperation({ + network: 'base', + calls: [ + { + to: tokenAddress, + value: BigInt(0), + data: encodeFunctionData({ + abi: ERC20_CONTRACT_ABI as Abi, + functionName: 'approve', + args: [MERIT_CONTRACT_ADDRESS, amountBigInt], + }), + }, + { + to: MERIT_CONTRACT_ADDRESS, + value: BigInt(0), + data: encodeFunctionData({ + abi: MERIT_ABI as Abi, + functionName: 'fundRepo', + args: [ + BigInt(repoId), + BigInt(repoInstanceId), + tokenAddress, + amountBigInt, + '0x', + ], + }), + }, + ], + }); + + // Wait for the user operation to be processed + await smartAccount.waitForUserOperation({ + userOpHash: result.userOpHash, + }); + + logger.emit({ + severityText: 'INFO', + body: 'User operation processed successfully', + }); + + return { + success: true, + userOpHash: result.userOpHash, + smartAccountAddress: smartAccount.address, + amount: amount, + repoId: repoId.toString(), + tokenAddress: tokenAddress, + }; + } catch (error) { + logger.emit({ + severityText: 'ERROR', + body: `Error in funding repo: ${error instanceof Error ? error.message : 'Unknown error'}`, + attributes: { + amount, + stack: error instanceof Error ? error.stack : 'No stack', + timestamp: new Date().toISOString(), + }, + }); + + throw error; + } +} diff --git a/packages/app/control/src/services/crypto/get-balance.ts b/packages/app/control/src/services/crypto/get-balance.ts new file mode 100644 index 000000000..f9aa6b772 --- /dev/null +++ b/packages/app/control/src/services/crypto/get-balance.ts @@ -0,0 +1,61 @@ +import { createPublicClient, http, type Address } from 'viem'; +import { base, baseSepolia } from 'viem/chains'; +import { ERC20_CONTRACT_ABI } from './abi'; +import { env } from '@/env'; + +export type Network = 'base' | 'base-sepolia'; + +const NETWORK_TO_CHAIN = { + base: base, + 'base-sepolia': baseSepolia, +} as const; + +export async function getERC20Balance( + network: Network, + erc20Address: Address, + userAddress: Address +): Promise { + const chain = NETWORK_TO_CHAIN[network]; + if (!chain) { + throw new Error(`Unsupported network for balance check: ${network}`); + } + + const baseRpcUrl = env.BASE_RPC_URL ?? undefined; + + const client = createPublicClient({ + chain, + transport: http(baseRpcUrl), + }); + + const balance = await client.readContract({ + address: erc20Address, + abi: ERC20_CONTRACT_ABI, + functionName: 'balanceOf', + args: [userAddress], + }); + + return balance; +} + +export async function getEthereumBalance( + network: Network, + userAddress: Address +): Promise { + const chain = NETWORK_TO_CHAIN[network]; + if (!chain) { + throw new Error(`Unsupported network for balance check: ${network}`); + } + + const baseRpcUrl = env.BASE_RPC_URL ?? undefined; + + const client = createPublicClient({ + chain, + transport: http(baseRpcUrl), + }); + + const balance = await client.getBalance({ + address: userAddress, + }); + + return balance; +} diff --git a/packages/app/control/src/services/crypto/smart-account.ts b/packages/app/control/src/services/crypto/smart-account.ts new file mode 100644 index 000000000..92786e5d6 --- /dev/null +++ b/packages/app/control/src/services/crypto/smart-account.ts @@ -0,0 +1,35 @@ +import { CdpClient, type EvmSmartAccount } from '@coinbase/cdp-sdk'; +import { env } from '@/env'; + +const API_KEY_ID = env.CDP_API_KEY_ID ?? 'your-api-key-id'; +const API_KEY_SECRET = env.CDP_API_KEY_SECRET ?? 'your-api-key-secret'; +const WALLET_SECRET = env.CDP_WALLET_SECRET ?? 'your-wallet-secret'; +const WALLET_OWNER = env.WALLET_OWNER ?? 'your-wallet-owner'; +const WALLET_SMART_ACCOUNT = env.WALLET_OWNER + '-smart-account'; + +export async function getSmartAccount(): Promise<{ + smartAccount: EvmSmartAccount; +}> { + try { + const cdp = new CdpClient({ + apiKeyId: API_KEY_ID, + apiKeySecret: API_KEY_SECRET, + walletSecret: WALLET_SECRET, + }); + + const owner = await cdp.evm.getOrCreateAccount({ + name: WALLET_OWNER, + }); + + const smartAccount = await cdp.evm.getOrCreateSmartAccount({ + name: WALLET_SMART_ACCOUNT, + owner, + }); + + return { smartAccount }; + } catch (error) { + throw new Error( + `CDP authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}` + ); + } +} diff --git a/packages/app/control/src/services/crypto/transfer.ts b/packages/app/control/src/services/crypto/transfer.ts new file mode 100644 index 000000000..eb29a082b --- /dev/null +++ b/packages/app/control/src/services/crypto/transfer.ts @@ -0,0 +1,42 @@ +import type { Address, Hex } from 'viem'; +import { getSmartAccount } from './smart-account'; +import type { Network } from './get-balance'; + +interface TransferParams { + recipientAddress: string; + amount: bigint; + token?: 'eth' | 'usdc' | Hex; + network?: Network; +} + +export interface TransferResult { + userOpHash: Hex; + status: string; + smartAccountAddress: Address; + recipientAddress: string; + amount: bigint; +} + +export async function transfer({ + recipientAddress, + amount, + token = 'usdc', + network = 'base', +}: TransferParams): Promise { + const { smartAccount } = await getSmartAccount(); + + const result = await smartAccount.transfer({ + to: recipientAddress as Address, + amount, + token, + network, + }); + + return { + userOpHash: result.userOpHash, + status: result.status, + smartAccountAddress: result.smartAccountAddress, + recipientAddress, + amount, + }; +} diff --git a/packages/app/control/src/services/db/admin/wallet.ts b/packages/app/control/src/services/db/admin/wallet.ts new file mode 100644 index 000000000..9a5a93097 --- /dev/null +++ b/packages/app/control/src/services/db/admin/wallet.ts @@ -0,0 +1,299 @@ +import { db } from '@/services/db/client'; +import { + EnumTransactionType, + EnumPayoutStatus, + EnumPayoutType, + type Payout, +} from '@/generated/prisma'; +import { getSmartAccount } from '@/services/crypto/smart-account'; +import { + getERC20Balance, + getEthereumBalance, +} from '@/services/crypto/get-balance'; +import { + fundRepo, + type FundRepoResult, + MERIT_CONTRACT_ADDRESS, +} from '@/services/crypto/fund-repo'; +import { transfer, type TransferResult } from '@/services/crypto/transfer'; +import { logger } from '@/logger'; +import type { Address } from 'viem'; +import { Decimal } from '@prisma/client/runtime/library'; +import { env } from '@/env'; + +const USDC_ADDRESS = env.USDC_ADDRESS as Address; +const MERIT_REPO_ID = env.MERIT_REPO_ID; +const ECHO_PAYOUTS_ADDRESS = env.ECHO_PAYOUTS_ADDRESS as Address; +const NETWORK = 'base' as const; +const USDC_DECIMALS = 6; +const ETH_DECIMALS = 18; + +export async function getEchoX402ProfitTotal(): Promise { + const profitResult = await db.transaction.aggregate({ + where: { + transactionType: EnumTransactionType.X402, + isArchived: false, + }, + _sum: { + echoProfit: true, + }, + }); + + const payoutResult = await db.payout.aggregate({ + where: { + type: EnumPayoutType.ECHO_PROFIT, + }, + _sum: { + amount: true, + }, + }); + + const totalProfit = new Decimal(profitResult._sum.echoProfit ?? 0); + const totalPayouts = new Decimal(payoutResult._sum.amount ?? 0); + + return totalProfit.minus(totalPayouts).toNumber(); +} + +export async function getX402RawTransactionCostTotal(): Promise { + const result = await db.transaction.aggregate({ + where: { + transactionType: EnumTransactionType.X402, + isArchived: false, + }, + _sum: { + rawTransactionCost: true, + }, + }); + + return new Decimal(result._sum.rawTransactionCost ?? 0).toNumber(); +} + +export async function getSmartAccountAddress(): Promise { + const { smartAccount } = await getSmartAccount(); + return smartAccount.address; +} + +export async function getSmartAccountUSDCBalance(): Promise { + if (!USDC_ADDRESS) { + throw new Error('USDC_ADDRESS environment variable not set'); + } + + const { smartAccount } = await getSmartAccount(); + const balanceBigInt = await getERC20Balance( + NETWORK, + USDC_ADDRESS, + smartAccount.address + ); + + return Number(balanceBigInt) / 10 ** USDC_DECIMALS; +} + +export async function getSmartAccountETHBalance(): Promise { + const { smartAccount } = await getSmartAccount(); + const balanceBigInt = await getEthereumBalance(NETWORK, smartAccount.address); + + return Number(balanceBigInt) / 10 ** ETH_DECIMALS; +} + +export async function fundEchoRepo(amount: number): Promise { + if (!MERIT_REPO_ID) { + throw new Error('MERIT_REPO_ID environment variable not set'); + } + + if (!amount || amount <= 0) { + throw new Error('Amount must be greater than 0'); + } + + const result = await fundRepo(amount, Number(MERIT_REPO_ID)); + + await db.payout.create({ + data: { + type: EnumPayoutType.ECHO_PROFIT, + status: EnumPayoutStatus.COMPLETED, + amount: amount, + description: `Echo profit payout to Merit repository`, + transactionId: result.userOpHash, + senderAddress: result.smartAccountAddress, + recipientAddress: MERIT_CONTRACT_ADDRESS, + }, + }); + + logger.emit({ + severityText: 'INFO', + body: 'Created ECHO_PROFIT payout record', + attributes: { + amount, + userOpHash: result.userOpHash, + smartAccountAddress: result.smartAccountAddress, + }, + }); + + return result; +} + +export async function getEchoPayoutHistory(): Promise { + return await db.payout.findMany({ + where: { + type: EnumPayoutType.ECHO_PROFIT, + }, + orderBy: { + createdAt: 'desc', + }, + take: 50, + }); +} + +export async function getX402AppProfit(): Promise { + const profitResult = await db.transaction.aggregate({ + where: { + transactionType: EnumTransactionType.X402, + isArchived: false, + }, + _sum: { + appProfit: true, + }, + }); + + const payoutResult = await db.payout.aggregate({ + where: { + type: EnumPayoutType.APP_PROFIT, + }, + _sum: { + amount: true, + }, + }); + + const totalProfit = new Decimal(profitResult._sum.appProfit ?? 0); + const totalPayouts = new Decimal(payoutResult._sum.amount ?? 0); + + return totalProfit.minus(totalPayouts).toNumber(); +} + +interface AppProfitBreakdown { + appId: string; + appName: string; + totalProfit: number; + totalPayouts: number; + remainingProfit: number; +} + +export async function getX402AppProfitByApp(): Promise { + const profitByApp = await db.transaction.groupBy({ + by: ['echoAppId'], + where: { + transactionType: EnumTransactionType.X402, + isArchived: false, + echoAppId: { not: null }, + }, + _sum: { + appProfit: true, + }, + }); + + const payoutsByApp = await db.payout.groupBy({ + by: ['echoAppId'], + where: { + type: EnumPayoutType.APP_PROFIT, + echoAppId: { not: null }, + }, + _sum: { + amount: true, + }, + }); + + const payoutsMap = new Map( + payoutsByApp.map(p => [p.echoAppId, new Decimal(p._sum.amount ?? 0)]) + ); + + const appIds = profitByApp + .map(p => p.echoAppId) + .filter((id): id is string => id !== null); + + const apps = await db.echoApp.findMany({ + where: { + id: { in: appIds }, + }, + select: { + id: true, + name: true, + }, + }); + + const appsMap = new Map(apps.map(a => [a.id, a.name])); + + return profitByApp + .filter((p): p is typeof p & { echoAppId: string } => p.echoAppId !== null) + .map(p => { + const totalProfit = new Decimal(p._sum.appProfit ?? 0); + const totalPayouts = payoutsMap.get(p.echoAppId) ?? new Decimal(0); + const remainingProfit = totalProfit.minus(totalPayouts); + + return { + appId: p.echoAppId, + appName: appsMap.get(p.echoAppId) ?? 'Unknown App', + totalProfit: totalProfit.toNumber(), + totalPayouts: totalPayouts.toNumber(), + remainingProfit: remainingProfit.toNumber(), + }; + }) + .sort((a, b) => b.remainingProfit - a.remainingProfit); +} + +export async function PayoutX402AppProfit( + appId: string, + amount: number +): Promise { + if (!ECHO_PAYOUTS_ADDRESS) { + throw new Error('ECHO_PAYOUTS_ADDRESS environment variable not set'); + } + + if (!amount || amount <= 0) { + throw new Error('Amount must be greater than 0'); + } + + const app = await db.echoApp.findUnique({ + where: { id: appId }, + select: { id: true, name: true }, + }); + + if (!app) { + throw new Error(`App with ID ${appId} not found`); + } + + const amountBigInt = BigInt(Math.round(amount * 10 ** USDC_DECIMALS)); + + const result = await transfer({ + recipientAddress: ECHO_PAYOUTS_ADDRESS, + amount: amountBigInt, + token: 'usdc', + network: NETWORK, + }); + + await db.payout.create({ + data: { + type: EnumPayoutType.APP_PROFIT, + status: EnumPayoutStatus.COMPLETED, + amount: amount, + echoAppId: appId, + description: `App profit payout for ${app.name}`, + transactionId: result.userOpHash, + senderAddress: result.smartAccountAddress, + recipientAddress: ECHO_PAYOUTS_ADDRESS, + }, + }); + + logger.emit({ + severityText: 'INFO', + body: 'Created APP_PROFIT payout record', + attributes: { + appId, + appName: app.name, + amount, + userOpHash: result.userOpHash, + smartAccountAddress: result.smartAccountAddress, + recipientAddress: ECHO_PAYOUTS_ADDRESS, + }, + }); + + return result; +} diff --git a/packages/app/control/src/services/db/apps/transactions.ts b/packages/app/control/src/services/db/apps/transactions.ts index 5a92dd67f..5d24a4f9e 100644 --- a/packages/app/control/src/services/db/apps/transactions.ts +++ b/packages/app/control/src/services/db/apps/transactions.ts @@ -93,7 +93,9 @@ export const listAppTransactions = async ( id: transaction.id, user: { id: transaction.userId, - name: transaction.user?.name ?? (transaction.userId === null ? 'Unknown User' : null), + name: + transaction.user?.name ?? + (transaction.userId === null ? 'Unknown User' : null), image: transaction.user?.image ?? null, }, callCount: 1, diff --git a/packages/app/control/src/trpc/routers/admin/admin.ts b/packages/app/control/src/trpc/routers/admin/admin.ts index 0c403dcce..d6aa09230 100644 --- a/packages/app/control/src/trpc/routers/admin/admin.ts +++ b/packages/app/control/src/trpc/routers/admin/admin.ts @@ -10,6 +10,7 @@ import { adminPaymentsRouter } from './payments'; import { adminEmailCampaignsRouter } from './email-campaigns'; import { adminCreditGrantsRouter } from './credit-grants'; import { adminTokensRouter } from './tokens'; +import { adminWalletRouter } from './wallet'; export const adminRouter = createTRPCRouter({ ...adminBaseProcedures, @@ -22,4 +23,5 @@ export const adminRouter = createTRPCRouter({ emailCampaigns: adminEmailCampaignsRouter, creditGrants: adminCreditGrantsRouter, tokens: adminTokensRouter, + wallet: adminWalletRouter, }); diff --git a/packages/app/control/src/trpc/routers/admin/wallet.ts b/packages/app/control/src/trpc/routers/admin/wallet.ts new file mode 100644 index 000000000..eed3f40d2 --- /dev/null +++ b/packages/app/control/src/trpc/routers/admin/wallet.ts @@ -0,0 +1,56 @@ +import { adminProcedure, createTRPCRouter } from '../../trpc'; +import { + getEchoX402ProfitTotal, + getSmartAccountAddress, + getSmartAccountUSDCBalance, + getSmartAccountETHBalance, + fundEchoRepo, + getEchoPayoutHistory, + getX402AppProfit, + getX402AppProfitByApp, + PayoutX402AppProfit, + getX402RawTransactionCostTotal, +} from '@/services/db/admin/wallet'; +import { z } from 'zod'; + +export const adminWalletRouter = createTRPCRouter({ + getEchoX402ProfitTotal: adminProcedure.query(async () => { + return await getEchoX402ProfitTotal(); + }), + getSmartAccountAddress: adminProcedure.query(async () => { + return await getSmartAccountAddress(); + }), + getSmartAccountUSDCBalance: adminProcedure.query(async () => { + return await getSmartAccountUSDCBalance(); + }), + getSmartAccountETHBalance: adminProcedure.query(async () => { + return await getSmartAccountETHBalance(); + }), + getEchoPayoutHistory: adminProcedure.query(async () => { + return await getEchoPayoutHistory(); + }), + fundEchoRepo: adminProcedure + .input(z.object({ amount: z.number().positive() })) + .mutation(async ({ input }) => { + return await fundEchoRepo(input.amount); + }), + getX402AppProfit: adminProcedure.query(async () => { + return await getX402AppProfit(); + }), + getX402AppProfitByApp: adminProcedure.query(async () => { + return await getX402AppProfitByApp(); + }), + payoutX402AppProfit: adminProcedure + .input( + z.object({ + appId: z.string().uuid(), + amount: z.number().positive(), + }) + ) + .mutation(async ({ input }) => { + return await PayoutX402AppProfit(input.appId, input.amount); + }), + getX402RawTransactionCostTotal: adminProcedure.query(async () => { + return await getX402RawTransactionCostTotal(); + }), +}); diff --git a/packages/app/server/src/handlers/finalize.ts b/packages/app/server/src/handlers/finalize.ts index 236a21f27..997108cdc 100644 --- a/packages/app/server/src/handlers/finalize.ts +++ b/packages/app/server/src/handlers/finalize.ts @@ -25,12 +25,6 @@ export async function finalize( const authPayload = payload.authorization; await transfer(authPayload.from as `0x${string}`, refundAmountUsdcBigInt); } - - // We need to send the rawTransactionCost to a separate wallet (and allow claim through control) - - // We need to send the echo markup profit to the echo repo (and allow claim through control) - - // We need to send the app Markup Profit to the app Repo (and allow them to claim through control) } export async function finalizeResource( diff --git a/packages/app/server/src/services/DbService.ts b/packages/app/server/src/services/DbService.ts index 437399629..87280c450 100644 --- a/packages/app/server/src/services/DbService.ts +++ b/packages/app/server/src/services/DbService.ts @@ -14,6 +14,7 @@ import { Prisma, Transaction, UserSpendPoolUsage, + EnumTransactionType, } from '../generated/prisma'; import { Decimal } from '@prisma/client/runtime/library'; import logger from '../logger'; @@ -331,6 +332,7 @@ export class EchoDbService { referralProfit: transaction.referralProfit, rawTransactionCost: transaction.rawTransactionCost, echoProfit: transaction.echoProfit, + transactionType: transaction.transactionType ?? EnumTransactionType.BALANCE, status: transaction.status ?? null, userId: transaction.userId ?? null, echoAppId: transaction.echoAppId ?? null, diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index 43461d4ae..cf8be19d9 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -295,6 +295,7 @@ export class EchoControlService { rawTransactionCost, totalTransactionCost, totalAppProfit, + echoProfit, referralProfit, markUpProfit, } = await this.computeTransactionCosts(transaction, this.referralCodeId); @@ -305,6 +306,7 @@ export class EchoControlService { markUpProfit: markUpProfit, referralProfit: referralProfit, rawTransactionCost: rawTransactionCost, + echoProfit: echoProfit, metadata: transaction.metadata, status: transaction.status, userId: userId, @@ -336,6 +338,7 @@ export class EchoControlService { totalAppProfit, referralProfit, markUpProfit, + echoProfit, } = await this.computeTransactionCosts(transaction, this.referralCodeId); const { userId, echoAppId, apiKeyId } = this.authResult; @@ -346,6 +349,7 @@ export class EchoControlService { markUpProfit: markUpProfit, referralProfit: referralProfit, rawTransactionCost: rawTransactionCost, + echoProfit: echoProfit, metadata: transaction.metadata, status: transaction.status, userId: userId, @@ -395,6 +399,7 @@ export class EchoControlService { }), ...(this.markUpId && { markUpId: this.markUpId }), transactionType: EnumTransactionType.X402, + }; await this.dbService.createPaidTransaction(transactionData); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c92b4dd4e..ffae9677b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,9 +65,12 @@ importers: '@auth/prisma-adapter': specifier: ^2.10.0 version: 2.10.0(@prisma/client@6.16.0(prisma@6.16.0(magicast@0.3.5)(typescript@5.9.2))(typescript@5.9.2)) + '@coinbase/cdp-sdk': + specifier: ^1.34.0 + version: 1.34.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10) '@coinbase/x402': specifier: ^0.6.4 - version: 0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@hookform/resolvers': specifier: ^5.2.1 version: 5.2.1(react-hook-form@7.62.0(react@19.1.1)) @@ -349,7 +352,7 @@ importers: version: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) x402-next: specifier: ^0.5.0 - version: 0.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10) + version: 0.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10) zod: specifier: ^4.0.15 version: 4.1.11 @@ -416,7 +419,7 @@ importers: version: 1.34.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10) '@coinbase/x402': specifier: ^0.6.5 - version: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@e2b/code-interpreter': specifier: ^2.0.1 version: 2.0.1 @@ -506,7 +509,7 @@ importers: version: 2.7.0 openai: specifier: ^6.2.0 - version: 6.2.0(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11) + version: 6.2.0(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11) prisma: specifier: 6.16.0 version: 6.16.0(magicast@0.3.5)(typescript@5.9.2) @@ -527,7 +530,7 @@ importers: version: 3.17.0 x402-express: specifier: ^0.6.5 - version: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) zod: specifier: ^4.1.11 version: 4.1.11 @@ -12670,11 +12673,11 @@ snapshots: - utf-8-validate - zod - '@coinbase/x402@0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@coinbase/x402@0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@coinbase/cdp-sdk': 1.34.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - x402: 0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + x402: 0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) zod: 3.25.76 transitivePeerDependencies: - '@azure/app-configuration' @@ -12710,11 +12713,11 @@ snapshots: - utf-8-validate - ws - '@coinbase/x402@0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@coinbase/x402@0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@coinbase/cdp-sdk': 1.34.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - x402: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + x402: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) zod: 3.25.76 transitivePeerDependencies: - '@azure/app-configuration' @@ -16053,7 +16056,7 @@ snapshots: dependencies: '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2(@types/react@19.1.10)(react@18.3.1) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: @@ -16087,7 +16090,7 @@ snapshots: dependencies: '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2(@types/react@19.1.10)(react@19.1.0) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: @@ -16155,7 +16158,7 @@ snapshots: dependencies: '@reown/appkit-common': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2 viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: @@ -16758,7 +16761,7 @@ snapshots: '@reown/appkit-polyfills': 1.7.8 '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) '@walletconnect/logger': 2.1.2 - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2(@types/react@19.1.10)(react@18.3.1) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: @@ -16795,7 +16798,7 @@ snapshots: '@reown/appkit-polyfills': 1.7.8 '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) '@walletconnect/logger': 2.1.2 - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2(@types/react@19.1.10)(react@19.1.0) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: @@ -16869,7 +16872,7 @@ snapshots: '@reown/appkit-polyfills': 1.7.8 '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) '@walletconnect/logger': 2.1.2 - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) valtio: 1.13.2 viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: @@ -16963,7 +16966,7 @@ snapshots: '@reown/appkit-utils': 1.7.8(@types/react@19.1.10)(bufferutil@4.0.9)(react@18.3.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.1.10)(react@18.3.1))(zod@3.25.76) '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) '@walletconnect/types': 2.21.0(@vercel/blob@0.25.1) - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) bs58: 6.0.0 valtio: 1.13.2(@types/react@19.1.10)(react@18.3.1) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) @@ -17005,7 +17008,7 @@ snapshots: '@reown/appkit-utils': 1.7.8(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(typescript@5.9.2)(utf-8-validate@5.0.10)(valtio@1.13.2(@types/react@19.1.10)(react@19.1.0))(zod@3.25.76) '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) '@walletconnect/types': 2.21.0(@vercel/blob@0.25.1) - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) bs58: 6.0.0 valtio: 1.13.2(@types/react@19.1.10)(react@19.1.0) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) @@ -17089,7 +17092,7 @@ snapshots: '@reown/appkit-utils': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(valtio@1.13.2)(zod@3.25.76) '@reown/appkit-wallet': 1.7.8(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10) '@walletconnect/types': 2.21.0(@vercel/blob@0.25.1) - '@walletconnect/universal-provider': 2.21.0(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) bs58: 6.0.0 valtio: 1.13.2 viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) @@ -17429,10 +17432,6 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@solana-program/compute-budget@0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': - dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana-program/compute-budget@0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) @@ -17441,14 +17440,6 @@ snapshots: dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana-program/compute-budget@0.8.0(@solana/kit@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': - dependencies: - '@solana/kit': 2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - - '@solana-program/token-2022@0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': - dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana-program/token-2022@0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2))': dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) @@ -17459,14 +17450,6 @@ snapshots: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@solana/sysvars': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana-program/token-2022@0.4.2(@solana/kit@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': - dependencies: - '@solana/kit': 2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - - '@solana-program/token@0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': - dependencies: - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana-program/token@0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) @@ -17475,10 +17458,6 @@ snapshots: dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana-program/token@0.5.1(@solana/kit@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': - dependencies: - '@solana/kit': 2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/accounts@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)': dependencies: '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) @@ -17634,31 +17613,6 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/errors': 2.3.0(typescript@5.9.2) - '@solana/functional': 2.3.0(typescript@5.9.2) - '@solana/instructions': 2.3.0(typescript@5.9.2) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/programs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc-parsed-types': 2.3.0(typescript@5.9.2) - '@solana/rpc-spec-types': 2.3.0(typescript@5.9.2) - '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/signers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/sysvars': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transaction-confirmation': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - ws - '@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) @@ -17709,31 +17663,6 @@ snapshots: - fastestsmallesttextencoderdecoder - ws - '@solana/kit@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/accounts': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/codecs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/errors': 2.3.0(typescript@5.9.2) - '@solana/functional': 2.3.0(typescript@5.9.2) - '@solana/instructions': 2.3.0(typescript@5.9.2) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/programs': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc-parsed-types': 2.3.0(typescript@5.9.2) - '@solana/rpc-spec-types': 2.3.0(typescript@5.9.2) - '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/signers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/sysvars': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transaction-confirmation': 2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - ws - '@solana/nominal-types@2.3.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 @@ -17816,15 +17745,6 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/rpc-subscriptions-channel-websocket@2.3.0(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/errors': 2.3.0(typescript@5.9.2) - '@solana/functional': 2.3.0(typescript@5.9.2) - '@solana/rpc-subscriptions-spec': 2.3.0(typescript@5.9.2) - '@solana/subscribable': 2.3.0(typescript@5.9.2) - typescript: 5.9.2 - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@solana/rpc-subscriptions-channel-websocket@2.3.0(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@solana/errors': 2.3.0(typescript@5.9.2) @@ -17851,24 +17771,6 @@ snapshots: '@solana/subscribable': 2.3.0(typescript@5.9.2) typescript: 5.9.2 - '@solana/rpc-subscriptions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/errors': 2.3.0(typescript@5.9.2) - '@solana/fast-stable-stringify': 2.3.0(typescript@5.9.2) - '@solana/functional': 2.3.0(typescript@5.9.2) - '@solana/promises': 2.3.0(typescript@5.9.2) - '@solana/rpc-spec-types': 2.3.0(typescript@5.9.2) - '@solana/rpc-subscriptions-api': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc-subscriptions-channel-websocket': 2.3.0(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-subscriptions-spec': 2.3.0(typescript@5.9.2) - '@solana/rpc-transformers': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/subscribable': 2.3.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - ws - '@solana/rpc-subscriptions@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@solana/errors': 2.3.0(typescript@5.9.2) @@ -18011,23 +17913,6 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/transaction-confirmation@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/errors': 2.3.0(typescript@5.9.2) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/promises': 2.3.0(typescript@5.9.2) - '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - ws - '@solana/transaction-confirmation@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) @@ -18062,23 +17947,6 @@ snapshots: - fastestsmallesttextencoderdecoder - ws - '@solana/transaction-confirmation@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))': - dependencies: - '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/codecs-strings': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/errors': 2.3.0(typescript@5.9.2) - '@solana/keys': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/promises': 2.3.0(typescript@5.9.2) - '@solana/rpc': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/rpc-subscriptions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/rpc-types': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transaction-messages': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - '@solana/transactions': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - ws - '@solana/transaction-messages@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)': dependencies: '@solana/addresses': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2) @@ -19576,6 +19444,49 @@ snapshots: - utf-8-validate - zod + '@walletconnect/core@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@walletconnect/heartbeat': 1.2.2 + '@walletconnect/jsonrpc-provider': 1.0.14 + '@walletconnect/jsonrpc-types': 1.0.4 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) + '@walletconnect/logger': 2.1.2 + '@walletconnect/relay-api': 1.0.11 + '@walletconnect/relay-auth': 1.1.0 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.21.0(@vercel/blob@0.25.1) + '@walletconnect/utils': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/window-getters': 1.0.1 + es-toolkit: 1.33.0 + events: 3.3.0 + uint8arrays: 3.1.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/core@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@walletconnect/heartbeat': 1.2.2 @@ -19662,6 +19573,49 @@ snapshots: - utf-8-validate - zod + '@walletconnect/core@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@walletconnect/heartbeat': 1.2.2 + '@walletconnect/jsonrpc-provider': 1.0.14 + '@walletconnect/jsonrpc-types': 1.0.4 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) + '@walletconnect/logger': 2.1.2 + '@walletconnect/relay-api': 1.0.11 + '@walletconnect/relay-auth': 1.1.0 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.21.1(@vercel/blob@0.25.1) + '@walletconnect/utils': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/window-getters': 1.0.1 + es-toolkit: 1.33.0 + events: 3.3.0 + uint8arrays: 3.1.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/core@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@walletconnect/heartbeat': 1.2.2 @@ -19757,10 +19711,10 @@ snapshots: '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) - '@walletconnect/sign-client': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/types': 2.21.1(@vercel/blob@0.25.1) - '@walletconnect/universal-provider': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/utils': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/utils': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -19797,10 +19751,10 @@ snapshots: '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) - '@walletconnect/sign-client': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/types': 2.21.1(@vercel/blob@0.25.1) - '@walletconnect/universal-provider': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/utils': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/utils': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -19877,10 +19831,10 @@ snapshots: '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) - '@walletconnect/sign-client': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/sign-client': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/types': 2.21.1(@vercel/blob@0.25.1) - '@walletconnect/universal-provider': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - '@walletconnect/utils': 2.21.1(@vercel/blob@0.25.1)(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/universal-provider': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/utils': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -20036,6 +19990,41 @@ snapshots: - utf-8-validate - zod + '@walletconnect/sign-client@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@walletconnect/core': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.2 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.1.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.21.0(@vercel/blob@0.25.1) + '@walletconnect/utils': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/sign-client@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@walletconnect/core': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) @@ -20106,6 +20095,41 @@ snapshots: - utf-8-validate - zod + '@walletconnect/sign-client@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@walletconnect/core': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.2 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.1.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.21.1(@vercel/blob@0.25.1) + '@walletconnect/utils': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/sign-client@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@walletconnect/core': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) @@ -20240,6 +20264,45 @@ snapshots: - utf-8-validate - zod + '@walletconnect/universal-provider@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/jsonrpc-http-connection': 1.0.8 + '@walletconnect/jsonrpc-provider': 1.0.14 + '@walletconnect/jsonrpc-types': 1.0.4 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) + '@walletconnect/logger': 2.1.2 + '@walletconnect/sign-client': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.0(@vercel/blob@0.25.1) + '@walletconnect/utils': 2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + es-toolkit: 1.33.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - encoding + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/universal-provider@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@walletconnect/events': 1.0.1 @@ -20318,6 +20381,45 @@ snapshots: - utf-8-validate - zod + '@walletconnect/universal-provider@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/jsonrpc-http-connection': 1.0.8 + '@walletconnect/jsonrpc-provider': 1.0.14 + '@walletconnect/jsonrpc-types': 1.0.4 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) + '@walletconnect/logger': 2.1.2 + '@walletconnect/sign-client': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.21.1(@vercel/blob@0.25.1) + '@walletconnect/utils': 2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + es-toolkit: 1.33.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - encoding + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/universal-provider@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@walletconnect/events': 1.0.1 @@ -20400,6 +20502,49 @@ snapshots: - utf-8-validate - zod + '@walletconnect/utils@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@noble/ciphers': 1.2.1 + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) + '@walletconnect/relay-api': 1.0.11 + '@walletconnect/relay-auth': 1.1.0 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.21.0(@vercel/blob@0.25.1) + '@walletconnect/window-getters': 1.0.1 + '@walletconnect/window-metadata': 1.0.1 + bs58: 6.0.0 + detect-browser: 5.3.0 + query-string: 7.1.3 + uint8arrays: 3.1.0 + viem: 2.23.2(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/utils@2.21.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@noble/ciphers': 1.2.1 @@ -20486,6 +20631,49 @@ snapshots: - utf-8-validate - zod + '@walletconnect/utils@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76)': + dependencies: + '@noble/ciphers': 1.2.1 + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/keyvaluestorage': 1.1.1(@vercel/blob@0.25.1) + '@walletconnect/relay-api': 1.0.11 + '@walletconnect/relay-auth': 1.1.0 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.21.1(@vercel/blob@0.25.1) + '@walletconnect/window-getters': 1.0.1 + '@walletconnect/window-metadata': 1.0.1 + bs58: 6.0.0 + detect-browser: 5.3.0 + query-string: 7.1.3 + uint8arrays: 3.1.0 + viem: 2.23.2(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - bufferutil + - db0 + - ioredis + - typescript + - uploadthing + - utf-8-validate + - zod + '@walletconnect/utils@2.21.1(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)': dependencies: '@noble/ciphers': 1.2.1 @@ -22028,7 +22216,7 @@ snapshots: '@typescript-eslint/parser': 8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) @@ -22048,7 +22236,7 @@ snapshots: '@typescript-eslint/parser': 8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) @@ -22072,7 +22260,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -22087,14 +22275,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -22109,7 +22297,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -25050,9 +25238,9 @@ snapshots: ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) zod: 4.1.11 - openai@6.2.0(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11): + openai@6.2.0(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11): optionalDependencies: - ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) zod: 4.1.11 openapi-fetch@0.13.8: @@ -28332,13 +28520,13 @@ snapshots: bufferutil: 4.0.9 utf-8-validate: 5.0.10 - x402-express@0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + x402-express@0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: '@coinbase/cdp-sdk': 1.34.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10) - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) express: 4.21.2 viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) - x402: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + x402: 0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) zod: 3.25.76 transitivePeerDependencies: - '@azure/app-configuration' @@ -28412,7 +28600,7 @@ snapshots: - utf-8-validate - ws - x402-next@0.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10): + x402-next@0.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10): dependencies: '@coinbase/cdp-sdk': 1.34.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10) next: 15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -28581,14 +28769,14 @@ snapshots: - utf-8-validate - ws - x402@0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + x402@0.6.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: '@scure/base': 1.2.6 - '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token': 0.5.1(@solana/kit@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana/kit': 2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/transaction-confirmation': 2.3.0(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token': 0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/transaction-confirmation': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) wagmi: 2.17.5(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.10)(@vercel/blob@0.25.1)(bufferutil@4.0.9)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(viem@2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))(zod@3.25.76) zod: 3.25.76 @@ -28625,14 +28813,14 @@ snapshots: - utf-8-validate - ws - x402@0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + x402@0.6.5(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: '@scure/base': 1.2.6 - '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token': 0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@solana/transaction-confirmation': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana-program/compute-budget': 0.8.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token': 0.5.1(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@solana/sysvars@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)) + '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/transaction-confirmation': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) viem: 2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@3.25.76) wagmi: 2.17.5(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(viem@2.33.3(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))(zod@3.25.76) zod: 3.25.76 From 9fc22267d86d60e18ebeb2885c1ffbcd865e3522 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Wed, 29 Oct 2025 16:50:39 -0400 Subject: [PATCH 08/13] format --- packages/app/server/src/services/DbService.ts | 3 ++- packages/app/server/src/services/EchoControlService.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/server/src/services/DbService.ts b/packages/app/server/src/services/DbService.ts index 87280c450..3b08d9c59 100644 --- a/packages/app/server/src/services/DbService.ts +++ b/packages/app/server/src/services/DbService.ts @@ -332,7 +332,8 @@ export class EchoDbService { referralProfit: transaction.referralProfit, rawTransactionCost: transaction.rawTransactionCost, echoProfit: transaction.echoProfit, - transactionType: transaction.transactionType ?? EnumTransactionType.BALANCE, + transactionType: + transaction.transactionType ?? EnumTransactionType.BALANCE, status: transaction.status ?? null, userId: transaction.userId ?? null, echoAppId: transaction.echoAppId ?? null, diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index cf8be19d9..ef376c8c7 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -399,7 +399,6 @@ export class EchoControlService { }), ...(this.markUpId && { markUpId: this.markUpId }), transactionType: EnumTransactionType.X402, - }; await this.dbService.createPaidTransaction(transactionData); From dbe181f220b67815b71ed3ce994472ac423a369e Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Wed, 29 Oct 2025 17:06:07 -0400 Subject: [PATCH 09/13] fix accounting bug, thanks v0 agent --- packages/app/server/src/services/EchoControlService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index ef376c8c7..d78e66a3c 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -256,14 +256,15 @@ export class EchoControlService { const markUpProfitDecimal = totalAppProfitDecimal.minus( referralProfitDecimal ); - const totalTransactionCostDecimal = transaction.rawTransactionCost.plus( - totalAppProfitDecimal - ); const echoProfitDecimal = addEchoProfit ? applyEchoMarkup(transaction.rawTransactionCost) : new Decimal(0); + const totalTransactionCostDecimal = transaction.rawTransactionCost.plus( + totalAppProfitDecimal + ).plus(echoProfitDecimal); + // Return Decimal values directly return { rawTransactionCost: transaction.rawTransactionCost, From de308a8087daf728a8e0b6ac7670763dfbce566d Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Wed, 29 Oct 2025 18:48:49 -0400 Subject: [PATCH 10/13] adjust wording --- .../[id]/(overview)/_components/overview/transactions/rows.tsx | 2 +- .../(app)/app/[id]/transactions/_components/transactions.tsx | 2 +- packages/app/control/src/services/db/apps/transactions.ts | 2 +- packages/app/server/package.json | 2 +- packages/app/server/src/clients/openrouter-client.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx index 6c06de6f3..034d4f078 100644 --- a/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/(overview)/_components/overview/transactions/rows.tsx @@ -31,7 +31,7 @@ export const TransactionRows = async ({ appId }: { appId: string }) => {

- {transaction.user.name ?? 'Unknown User'} + {transaction.user.name ?? 'x402 Users'} {' '} made {transaction.callCount} requests

diff --git a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx index 6faa134dc..3d6c0a5bf 100644 --- a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx +++ b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx @@ -111,7 +111,7 @@ const TransactionRow = ({ transaction }: { transaction: Transaction }) => {

- {transaction.user?.name ?? 'Unknown User'} + {transaction.user?.name ?? 'x402 Users'} {' '} made {transaction.callCount} requests

diff --git a/packages/app/control/src/services/db/apps/transactions.ts b/packages/app/control/src/services/db/apps/transactions.ts index 5d24a4f9e..a9c2b885b 100644 --- a/packages/app/control/src/services/db/apps/transactions.ts +++ b/packages/app/control/src/services/db/apps/transactions.ts @@ -95,7 +95,7 @@ export const listAppTransactions = async ( id: transaction.userId, name: transaction.user?.name ?? - (transaction.userId === null ? 'Unknown User' : null), + (transaction.userId === null ? 'x402 Users' : null), image: transaction.user?.image ?? null, }, callCount: 1, diff --git a/packages/app/server/package.json b/packages/app/server/package.json index f0a2fcc47..04048d256 100644 --- a/packages/app/server/package.json +++ b/packages/app/server/package.json @@ -12,7 +12,7 @@ "prebuild": "pnpm run copy-schema && pnpm run prisma:generate", "predev": "pnpm run copy-prisma", "prestart": "pnpm run copy-prisma", - "dev": "npx tsx src/server.ts", + "dev": "pnpm run build && node dist/server.js", "start": "node dist/server.js", "prisma:migrate-deploy": "pnpx prisma migrate deploy", "anthropic-client": "npx tsx src/clients/anthropic-client.ts", diff --git a/packages/app/server/src/clients/openrouter-client.ts b/packages/app/server/src/clients/openrouter-client.ts index bb3f687c3..edbda19df 100644 --- a/packages/app/server/src/clients/openrouter-client.ts +++ b/packages/app/server/src/clients/openrouter-client.ts @@ -25,7 +25,7 @@ async function makeRequest(useStreaming: boolean = false) { // Initialize OpenAI client with custom baseURL const openai = new OpenAI({ - baseURL: 'https://echo-staging.up.railway.app', + baseURL: 'http://localhost:3070', apiKey: process.env.ECHO_API_KEY, // Required by the client but not used with local server }); From 0239fb51a5c934e63da767274560f3683ae2dd77 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Wed, 29 Oct 2025 18:54:05 -0400 Subject: [PATCH 11/13] crit patch - identify empty user object --- packages/app/server/src/services/x402AuthenticationService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/server/src/services/x402AuthenticationService.ts b/packages/app/server/src/services/x402AuthenticationService.ts index a6196f45f..b46748cd9 100644 --- a/packages/app/server/src/services/x402AuthenticationService.ts +++ b/packages/app/server/src/services/x402AuthenticationService.ts @@ -22,6 +22,7 @@ export class X402AuthenticationService { logger.info(`Authenticating X402 request for echo app ${requestedAppId}`); if (!requestedAppId) { + this.echoControlService.identifyX402Request(null, null); return null; } From 7bc263d7a6f8249ab0493119101ac04e5e24c6f0 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Wed, 29 Oct 2025 18:57:27 -0400 Subject: [PATCH 12/13] remove fundRepoService --- .../src/services/fund-repo/fundRepoService.ts | 194 ------------------ 1 file changed, 194 deletions(-) delete mode 100644 packages/app/server/src/services/fund-repo/fundRepoService.ts diff --git a/packages/app/server/src/services/fund-repo/fundRepoService.ts b/packages/app/server/src/services/fund-repo/fundRepoService.ts deleted file mode 100644 index 0310eec8f..000000000 --- a/packages/app/server/src/services/fund-repo/fundRepoService.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { encodeFunctionData, Abi, formatUnits, parseUnits } from 'viem'; -import { - MERIT_ABI, - MERIT_CONTRACT_ADDRESS, - USDC_ADDRESS, - ERC20_CONTRACT_ABI, - ETH_ADDRESS, -} from './constants'; -import logger, { logMetric } from '../../logger'; -import { getSmartAccount } from 'utils'; - -export interface FundRepoResult { - success: boolean; - userOpHash: string; - smartAccountAddress: string; - amount: number; - repoId: string; - tokenAddress: string; -} -export async function fundRepo( - amount: number, - repoId: number -): Promise { - try { - if (!amount || typeof amount !== 'number') { - throw new Error('Invalid amount provided'); - } - - if (!USDC_ADDRESS || !MERIT_CONTRACT_ADDRESS) { - throw new Error('Missing required environment variables'); - } - - const tokenAddress = USDC_ADDRESS; - const repoInstanceId = 0; - // Convert to BigInt safely by avoiding floating point precision issues - // USDC has 6 decimals, so multiply by 10^6 - // Use Math.ceil for defensive rounding to avoid undercharging - const amountBigInt = BigInt(Math.ceil(amount * 10 ** 6)); - - const { smartAccount } = await getSmartAccount(); - - // Send user operation to fund the repo - const result = await smartAccount.sendUserOperation({ - network: 'base', - calls: [ - { - to: tokenAddress as `0x${string}`, - value: 0n, - data: encodeFunctionData({ - abi: ERC20_CONTRACT_ABI as Abi, - functionName: 'approve', - args: [MERIT_CONTRACT_ADDRESS, amountBigInt], - }), - }, - { - to: MERIT_CONTRACT_ADDRESS as `0x${string}`, - value: 0n, - data: encodeFunctionData({ - abi: MERIT_ABI as Abi, - functionName: 'fundRepo', - args: [ - BigInt(repoId), - BigInt(repoInstanceId), - tokenAddress, - amountBigInt, - '0x', - ], - }), - }, - ], - }); - - // Wait for the user operation to be processed - await smartAccount.waitForUserOperation({ - userOpHash: result.userOpHash, - }); - - logger.info('User operation processed successfully'); - - return { - success: true, - userOpHash: result.userOpHash, - smartAccountAddress: smartAccount.address, - amount: amount, - repoId: repoId.toString(), - tokenAddress: tokenAddress, - }; - } catch (error) { - logger.error( - `Error in funding repo: ${error instanceof Error ? error.message : 'Unknown error'} | Amount: ${amount} | Stack: ${error instanceof Error ? error.stack : 'No stack'} | Timestamp: ${new Date().toISOString()}` - ); - - throw error; - } -} - -export async function safeFundRepo(amount: number): Promise { - try { - const repoId = process.env.MERIT_REPO_ID; - if (!repoId) { - throw new Error('Missing required environment variables'); - } - await fundRepo(amount, Number(repoId)); - } catch (error) { - logger.error( - `Error in safe funding repo: ${error instanceof Error ? error.message : 'Unknown error'} | Amount: ${amount}` - ); - } -} - -export async function safeFundRepoIfWorthwhile(): Promise { - const repoId = process.env.MERIT_REPO_ID; - if (!repoId) { - throw new Error('Missing required environment variables'); - } - - // check balance of wallet. If it is > 100 USD, send all of the USD to the repo. - - const { smartAccount } = await getSmartAccount(); - const balances = await smartAccount.listTokenBalances({ - network: 'base', - }); - const baseUsdcBalance = balances.balances.find( - balance => balance.token.contractAddress === USDC_ADDRESS - ); - - const ethereumBalance = balances.balances.find( - balance => balance.token.contractAddress === ETH_ADDRESS - ); - - if (!ethereumBalance) { - logger.info('No Ethereum balance found, skipping fundRepo event'); - return; - } - - if (!baseUsdcBalance) { - logger.info('No base USDC balance found, skipping fundRepo event'); - return; - } - - const ethereumBalanceAmount = ethereumBalance.amount.amount; - const ethBalanceFormatted = formatUnits( - ethereumBalanceAmount, - ethereumBalance.amount.decimals - ); - logger.info(`Ethereum balance is ${ethBalanceFormatted} ETH`, { - amount: ethBalanceFormatted, - address: smartAccount.address, - }); - - const baseUsdcBalanceAmount = baseUsdcBalance.amount.amount; - const usdcBalanceFormatted = formatUnits( - baseUsdcBalanceAmount, - baseUsdcBalance.amount.decimals - ); - logger.info(`Base USDC balance is ${usdcBalanceFormatted} USD`, { - amount: usdcBalanceFormatted, - address: smartAccount.address, - }); - - const ETH_WARNING_THRESHOLD = parseUnits( - String(process.env.ETH_WARNING_THRESHOLD || '0.0001'), - ethereumBalance.amount.decimals - ); - const BASE_USDC_WARNING_THRESHOLD = parseUnits( - String(process.env.BASE_USDC_TRANSFER_THRESHOLD || '5'), - baseUsdcBalance.amount.decimals - ); - - if (ethereumBalanceAmount < ETH_WARNING_THRESHOLD) { - const readableEthWarningThreshold = formatUnits( - ETH_WARNING_THRESHOLD, - ethereumBalance.amount.decimals - ); - logger.error( - `[Critical] Ethereum balance is less than ${readableEthWarningThreshold} ETH, skipping fundRepo event` - ); - logMetric('server_wallet.ethereum_balance_running_low', 1, { - amount: ethBalanceFormatted, - address: smartAccount.address, - }); - return; - } - - if (baseUsdcBalanceAmount < BASE_USDC_WARNING_THRESHOLD) { - logger.info( - 'Base USDC balance is less than threshold, skipping fundRepo event' - ); - return; - } - logger.info(`Base USDC balance is ${usdcBalanceFormatted} USD, funding repo`); - - await safeFundRepo(Number(usdcBalanceFormatted)); -} From efd8cafd5af53cf8847ce7b8d43fdbb742d01b39 Mon Sep 17 00:00:00 2001 From: Ben Reilly Date: Wed, 29 Oct 2025 19:25:53 -0400 Subject: [PATCH 13/13] return zod parsing --- packages/app/server/src/handlers/settle.ts | 15 ++++++++++++--- .../app/server/src/services/EchoControlService.ts | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/app/server/src/handlers/settle.ts b/packages/app/server/src/handlers/settle.ts index 91ce6da36..99567eb9a 100644 --- a/packages/app/server/src/handlers/settle.ts +++ b/packages/app/server/src/handlers/settle.ts @@ -1,4 +1,3 @@ -import { Network } from 'types'; import { usdcBigIntToDecimal, decimalToUsdcBigInt, @@ -10,9 +9,11 @@ import { USDC_ADDRESS } from 'services/fund-repo/constants'; import { FacilitatorClient } from 'services/facilitator/facilitatorService'; import { ExactEvmPayload, + ExactEvmPayloadSchema, PaymentPayload, PaymentRequirementsSchema, SettleRequestSchema, + Network, } from 'services/facilitator/x402-types'; import { Decimal } from '@prisma/client/runtime/library'; import logger from 'logger'; @@ -44,8 +45,16 @@ export async function settle( return undefined; } - const payload = xPaymentData.payload as ExactEvmPayload; - logger.info(`Payment payload: ${JSON.stringify(payload)}`); + const payloadResult = ExactEvmPayloadSchema.safeParse(xPaymentData.payload); + if (!payloadResult.success) { + logger.error('Invalid ExactEvmPayload in settle', { + error: payloadResult.error, + payload: xPaymentData.payload, + }); + buildX402Response(req, res, maxCost); + return undefined; + } + const payload = payloadResult.data; const paymentAmount = payload.authorization.value; const paymentAmountDecimal = usdcBigIntToDecimal(paymentAmount); diff --git a/packages/app/server/src/services/EchoControlService.ts b/packages/app/server/src/services/EchoControlService.ts index d78e66a3c..861db6a62 100644 --- a/packages/app/server/src/services/EchoControlService.ts +++ b/packages/app/server/src/services/EchoControlService.ts @@ -261,9 +261,9 @@ export class EchoControlService { ? applyEchoMarkup(transaction.rawTransactionCost) : new Decimal(0); - const totalTransactionCostDecimal = transaction.rawTransactionCost.plus( - totalAppProfitDecimal - ).plus(echoProfitDecimal); + const totalTransactionCostDecimal = transaction.rawTransactionCost + .plus(totalAppProfitDecimal) + .plus(echoProfitDecimal); // Return Decimal values directly return {