Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "x402_transaction_metadata" (
"id" UUID NOT NULL,
"resourcePath" TEXT NOT NULL,
"resourceArgs" JSONB NOT NULL,
"resourceResponse" JSONB NOT NULL,
"resourceError" JSONB NOT NULL,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ NOT NULL,

CONSTRAINT "x402_transaction_metadata_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "transactions" ADD CONSTRAINT "transactions_id_fkey" FOREIGN KEY ("id") REFERENCES "x402_transaction_metadata"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- DropForeignKey
ALTER TABLE "public"."transactions" DROP CONSTRAINT "transactions_id_fkey";

-- AlterTable
ALTER TABLE "transactions" ADD COLUMN "x402TransactionMetadataId" UUID;

-- AlterTable
ALTER TABLE "x402_transaction_metadata" ALTER COLUMN "resourceArgs" SET DEFAULT '{}',
ALTER COLUMN "resourceResponse" SET DEFAULT '{}',
ALTER COLUMN "resourceError" SET DEFAULT '{}';

-- AddForeignKey
ALTER TABLE "transactions" ADD CONSTRAINT "transactions_x402TransactionMetadataId_fkey" FOREIGN KEY ("x402TransactionMetadataId") REFERENCES "x402_transaction_metadata"("id") ON DELETE CASCADE ON UPDATE CASCADE;
152 changes: 84 additions & 68 deletions packages/app/control/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ model User {
latestFreeCreditsVersion Decimal?
OutboundEmailSent OutboundEmailSent[]
creditGrantCodeUsages CreditGrantCodeUsage[]
VideoGenerationX402 VideoGenerationX402[]
VideoGenerationX402 VideoGenerationX402[]

@@map("users")
}
Expand Down Expand Up @@ -103,9 +103,9 @@ model EchoApp {
markUp MarkUp? // The markup rate for this app (one-to-one)
githubLink GithubLink? // The GitHub link for this app (one-to-one)
spendPools SpendPool[] // Spend pools associated with this app
currentReferralRewardId String? @db.Uuid // Reference to current active referral reward
currentReferralReward ReferralReward? @relation("CurrentReferralReward", fields: [currentReferralRewardId], references: [id])
ReferralRewards ReferralReward[] @relation("AppReferralRewards") // All referral rewards for this app
currentReferralRewardId String? @db.Uuid // Reference to current active referral reward
currentReferralReward ReferralReward? @relation("CurrentReferralReward", fields: [currentReferralRewardId], references: [id])
ReferralRewards ReferralReward[] @relation("AppReferralRewards") // All referral rewards for this app
appSessions AppSession[]
payouts Payout[]
OutboundEmailSent OutboundEmailSent[]
Expand Down Expand Up @@ -234,23 +234,23 @@ enum EnumPayoutType {
}

model Payout {
id String @id @default(uuid()) @db.Uuid
amount Decimal @db.Decimal(65, 14)
id String @id @default(uuid()) @db.Uuid
amount Decimal @db.Decimal(65, 14)
status EnumPayoutStatus
payoutBatchId String? @db.Uuid
payoutBatchId String? @db.Uuid
type EnumPayoutType
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
description String?
transactionId String?
senderAddress String?
recipientGithubLinkId String? @db.Uuid
recipientGithubLinkId String? @db.Uuid
recipientAddress String?
userId String? @db.Uuid // User ID if type is "referral"
echoAppId String? @db.Uuid // App ID if type is "markup"
recipientGithubLink GithubLink? @relation(fields: [recipientGithubLinkId], references: [id])
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
echoApp EchoApp? @relation(fields: [echoAppId], references: [id], onDelete: Cascade)
userId String? @db.Uuid // User ID if type is "referral"
echoAppId String? @db.Uuid // App ID if type is "markup"
recipientGithubLink GithubLink? @relation(fields: [recipientGithubLinkId], references: [id])
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
echoApp EchoApp? @relation(fields: [echoAppId], references: [id], onDelete: Cascade)

@@map("payouts")
}
Expand Down Expand Up @@ -302,34 +302,36 @@ enum EnumPaymentSource {
}

model Transaction {
id String @id @default(uuid()) @db.Uuid
transactionMetadataId String? @db.Uuid
totalCost Decimal @default(0.0) @db.Decimal(65, 14)
appProfit 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)
status String?
isArchived Boolean @default(false)
archivedAt DateTime? @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
userId String @db.Uuid
echoAppId String @db.Uuid
apiKeyId String? @db.Uuid
markUpId String? @db.Uuid
spendPoolId String? @db.Uuid
userSpendPoolUsageId String? @db.Uuid
referralCodeId String? @db.Uuid
referrerRewardId String? @db.Uuid
apiKey ApiKey? @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
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)
userSpendPoolUsage UserSpendPoolUsage? @relation(fields: [userSpendPoolUsageId], references: [id])
referralCode ReferralCode? @relation(fields: [referralCodeId], references: [id])
referrerReward ReferralReward? @relation(fields: [referrerRewardId], references: [id])
id String @id @default(uuid()) @db.Uuid
transactionMetadataId String? @db.Uuid
x402TransactionMetadataId String? @db.Uuid
totalCost Decimal @default(0.0) @db.Decimal(65, 14)
appProfit 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)
status String?
rawTransactionCost Decimal @default(0.0) @db.Decimal(65, 14)
isArchived Boolean @default(false)
archivedAt DateTime? @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
userId String @db.Uuid
echoAppId String @db.Uuid
apiKeyId String? @db.Uuid
markUpId String? @db.Uuid
spendPoolId String? @db.Uuid
userSpendPoolUsageId String? @db.Uuid
referralCodeId String? @db.Uuid
referrerRewardId String? @db.Uuid
apiKey ApiKey? @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
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])
x402TransactionMetadata x402TransactionMetadata? @relation(fields: [x402TransactionMetadataId], 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])

@@map("transactions")
}
Expand Down Expand Up @@ -372,26 +374,39 @@ model UserSpendPoolUsage {
}

model TransactionMetadata {
id String @id @default(uuid()) @db.Uuid
providerId String
provider String
model String
inputTokens Int?
outputTokens Int?
totalTokens Int?
id String @id @default(uuid()) @db.Uuid
providerId String
provider String
model String
inputTokens Int?
outputTokens Int?
totalTokens Int?
durationSeconds Int?
generateAudio Boolean?
toolCost Decimal @default(0.0) @db.Decimal(65, 14)
prompt String?
isArchived Boolean @default(false)
archivedAt DateTime? @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
transactions Transaction[]
generateAudio Boolean?
toolCost Decimal @default(0.0) @db.Decimal(65, 14)
prompt String?
isArchived Boolean @default(false)
archivedAt DateTime? @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
transactions Transaction[]

@@map("transaction_metadata")
}

model x402TransactionMetadata {
id String @id @default(uuid()) @db.Uuid
resourcePath String
resourceArgs Json @default("{}")
resourceResponse Json @default("{}")
resourceError Json @default("{}")
createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz
transactions Transaction[]

@@map("x402_transaction_metadata")
}

model ReferralCode {
id String @id @default(uuid()) @db.Uuid
code String @unique
Expand Down Expand Up @@ -494,16 +509,17 @@ model OutboundEmailSent {
}

model VideoGenerationX402 {
videoId String @id
wallet String?
userId String? @db.Uuid
echoAppId String? @db.Uuid
cost Decimal
createdAt DateTime @default(now()) @db.Timestamptz(6)
expiresAt DateTime @db.Timestamptz(6)
isFinal Boolean @default(false)

user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
videoId String @id
wallet String?
userId String? @db.Uuid
echoAppId String? @db.Uuid
cost Decimal
createdAt DateTime @default(now()) @db.Timestamptz(6)
expiresAt DateTime @db.Timestamptz(6)
isFinal Boolean @default(false)

user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
echoApp EchoApp? @relation(fields: [echoAppId], references: [id], onDelete: Cascade)

@@map("video_generation_x402")
}
2 changes: 2 additions & 0 deletions packages/app/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"sora-2-client": "npx tsx src/clients/sora-2-client.ts",
"openrouter-client": "npx tsx src/clients/openrouter-client.ts",
"openai-image-formdata-client": "npx tsx src/clients/openai-image-formdata-client.ts",
"echo-credit-client": "npx tsx src/clients/echo-credit-client.ts",
"test": "vitest run",
"test:integration": "vitest run",
"test:watch": "vitest",
Expand Down Expand Up @@ -79,6 +80,7 @@
"uuid": "^11.1.0",
"viem": "^2.33.3",
"winston": "^3.17.0",
"x402": "^0.6.5",
"x402-express": "^0.6.5",
"zod": "^4.1.11"
},
Expand Down
79 changes: 79 additions & 0 deletions packages/app/server/src/clients/echo-credit-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import dotenv from 'dotenv';
dotenv.config();

const TRUECAST_URL = 'https://true-cast-agent.vercel.app/api/trueCast';
const GLORIA_URL = 'https://api.itsgloria.ai/news';
const ECHO_ROUTER_BASE_URL =
process.env.ECHO_ROUTER_BASE_URL || 'http://localhost:3070';
const ECHO_API_KEY = process.env.ECHO_API_KEY;

interface TrueCastRequest {
prompt: string;
}
interface GloriaNewsParams {
feed_categories?: string;
}
export async function postTrueCastRequest(
prompt: string,
apiKey?: string
): Promise<string> {
const key = apiKey || ECHO_API_KEY;

if (!key) {
throw new Error('ECHO_API_KEY is required');
}

const trueCastBody: TrueCastRequest = { prompt };

const response = await fetch(
`${ECHO_ROUTER_BASE_URL}/x402?proxy=${encodeURIComponent(TRUECAST_URL)}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${key}`,
},
body: JSON.stringify(trueCastBody),
}
);
console.log('response', response.status);
const data = await response.json();
console.log('data', data);
return data;
Comment on lines +16 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The postTrueCastRequest function is declared to return Promise<string> but actually returns the parsed JSON response object, not a string. The same issue exists in getGloriaNews on line 45.

View Details
📝 Patch Details
diff --git a/packages/app/server/src/clients/echo-credit-client.ts b/packages/app/server/src/clients/echo-credit-client.ts
index aeb674ae..c55a261e 100644
--- a/packages/app/server/src/clients/echo-credit-client.ts
+++ b/packages/app/server/src/clients/echo-credit-client.ts
@@ -16,7 +16,7 @@ interface GloriaNewsParams {
 export async function postTrueCastRequest(
   prompt: string,
   apiKey?: string
-): Promise<string> {
+): Promise<any> {
   const key = apiKey || ECHO_API_KEY;
 
   if (!key) {
@@ -45,7 +45,7 @@ export async function postTrueCastRequest(
 export async function getGloriaNews(
   params: GloriaNewsParams = { feed_categories: 'base' },
   apiKey?: string
-): Promise<string> {
+): Promise<any> {
   const key = apiKey || ECHO_API_KEY;
 
   if (!key) {

Analysis

Type mismatch in postTrueCastRequest() and getGloriaNews() return types

What fails: Functions postTrueCastRequest() and getGloriaNews() in packages/app/server/src/clients/echo-credit-client.ts declare return type Promise<string> but actually return Promise<any> (parsed JSON objects)

How to reproduce:

// Functions call response.json() which returns Promise<any>, not Promise<string>
const data = await response.json(); // Returns object, not string
return data; // Type mismatch: returning object where Promise<string> expected

Result: TypeScript type checker allows incorrect type assumptions. Calling code expecting strings would receive objects at runtime.

Expected: Return type should be Promise<any> to match actual behavior per TypeScript fetch documentation - response.json() returns Promise<any>

}

export async function getGloriaNews(
params: GloriaNewsParams = { feed_categories: 'base' },
apiKey?: string
): Promise<string> {
const key = apiKey || ECHO_API_KEY;

if (!key) {
throw new Error('ECHO_API_KEY is required');
}

const queryParams = new URLSearchParams(
params as Record<string, string>
).toString();
const gloriaUrlWithParams = `${GLORIA_URL}?${queryParams}`;

const response = await fetch(
`${ECHO_ROUTER_BASE_URL}/x402?proxy=${encodeURIComponent(gloriaUrlWithParams)}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${key}`,
},
}
);

console.log('response', response.status);
const data = await response.json();
console.log('data', data);
return data;
}
getGloriaNews({ feed_categories: 'base' }, ECHO_API_KEY);
postTrueCastRequest(
'Was donald trump the first president of the united states?',
ECHO_API_KEY
);
Comment on lines +75 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
getGloriaNews({ feed_categories: 'base' }, ECHO_API_KEY);
postTrueCastRequest(
'Was donald trump the first president of the united states?',
ECHO_API_KEY
);
getGloriaNews({ feed_categories: 'base' }, ECHO_API_KEY).then(() => {
console.log('done');
}).catch((error) => {
console.error('Error making getGloriaNews request:', error);
});
postTrueCastRequest(
'Was donald trump the first president of the united states?',
ECHO_API_KEY
).then(() => {
console.log('done');
}).catch((error) => {
console.error('Error making postTrueCastRequest:', error);
});

The module-level code at the end of the file executes async functions without awaiting them, which will cause unhandled promise rejections if these functions fail and cannot be debugged properly.

View Details

Analysis

Unhandled promise rejections in echo-credit-client.ts module-level code

What fails: Module-level async function calls getGloriaNews() and postTrueCastRequest() at lines 75-79 in packages/app/server/src/clients/echo-credit-client.ts cause unhandled promise rejections when ECHO_API_KEY environment variable is missing

How to reproduce:

# Import module without ECHO_API_KEY set:
cd packages/app/server && pnpm exec tsx -e "import('./src/clients/echo-credit-client.ts')"

Result: Process crashes with "UNHANDLED PROMISE REJECTION DETECTED: Error: ECHO_API_KEY is required" - both functions throw but have no .catch() handlers

Expected: Errors should be caught and handled gracefully like all other client files in the same directory, which use .then().catch() patterns for module-level async calls

16 changes: 16 additions & 0 deletions packages/app/server/src/errors/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,19 @@ export class UnknownModelError extends HttpError {
super(400, message);
}
}

export class MissingProxyError extends HttpError {
constructor(
message: string = 'Missing proxy parameter: Query must be passed with ?proxy="<proxy_url>"'
) {
super(400, message);
}
}

export class InvalidProxyError extends HttpError {
constructor(
message: string = 'Invalid proxy URL: Proxy must be a valid URL. Example: ?proxy="https://proxy.example.com"'
) {
super(400, message);
}
}
Loading
Loading