Skip to content
Closed
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
28 changes: 18 additions & 10 deletions src/app/api/gigs/[id]/invoice/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,16 +313,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
);
}

const selectedCurrency = preferredCoinToPaymentCurrency(payment_currency || null);
const selectedAddress = merchant_wallet_address?.trim() || "";

if (!selectedCurrency || !selectedAddress) {
return NextResponse.json(
{ error: "Select a CoinPay receiving wallet before sending the invoice" },
{ status: 400 }
);
}

const workerCoinpayToken = await getConnectedCoinpayAccessToken(workerId);
if (!workerCoinpayToken) {
return NextResponse.json(
Expand Down Expand Up @@ -353,6 +343,24 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
);
}

let selectedCurrency = preferredCoinToPaymentCurrency(payment_currency || null);
let selectedAddress = merchant_wallet_address?.trim() || "";

if (!selectedCurrency || !selectedAddress) {
const gigCoin = preferredCoinToPaymentCurrency(gig.payment_coin || null);
const preferred = workerWallets.find((w) => w.currency === gigCoin) || workerWallets[0];

if (preferred) {
selectedCurrency = preferred.currency;
selectedAddress = preferred.address;
} else {
return NextResponse.json(
{ error: "Select a CoinPay receiving wallet before sending the invoice" },
{ status: 400 }
);
}
}
Comment on lines +349 to +362

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Auto-selected wallet may mismatch the gig's payment coin

When payment_currency / merchant_wallet_address are absent (poster-initiated flow), the backend looks up the worker's wallet with w.currency === gigCoin using strict equality. The frontend walletMatchesCoin helper uses a broader rule: it also accepts currencies whose key starts with ${want}_ (e.g. "usdc_sol" matches gig coin "USDC"). If preferredCoinToPaymentCurrency maps "USDC" to a canonical key that differs from the wallet's .currency field, the .find() silently fails and the code falls back to workerWallets[0], which could be a completely different coin. The poster then creates a CoinPay invoice denominated in the wrong currency without any warning.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


const selectedWallet = findCoinpayGlobalWallet(
workerWallets,
selectedCurrency,
Expand Down
31 changes: 20 additions & 11 deletions src/components/gigs/InvoiceButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,19 @@

setIsCreating(true);
setError(null);
const selectedWallet =
wallets.find((wallet) => walletKey(wallet) === selectedWalletKey) || null;
if (!selectedWallet) {
setError("Select a CoinPay receiving wallet");
setIsCreating(false);
return;
let selectedWalletCurrency: string | undefined;
let selectedWalletAddress: string | undefined;

if (isWorker) {
const selectedWallet =
wallets.find((wallet) => walletKey(wallet) === selectedWalletKey) || null;
if (!selectedWallet) {
setError("Select a CoinPay receiving wallet");
setIsCreating(false);
return;
}
selectedWalletCurrency = selectedWallet.currency;
selectedWalletAddress = selectedWallet.address;
}

try {
Expand All @@ -251,8 +258,8 @@
items: lineItems,
amount: total,
currency: "USD",
payment_currency: selectedWallet.currency,
merchant_wallet_address: selectedWallet.address,
payment_currency: selectedWalletCurrency,
merchant_wallet_address: selectedWalletAddress,
notes: notes || undefined,
due_date: dueDate || undefined,
pr_links: prLinks.length > 0 ? prLinks : undefined,
Expand Down Expand Up @@ -844,7 +851,8 @@
/>
</div>

<div className="space-y-2 rounded-md border border-border bg-background p-3">
{isWorker && (

Check failure on line 854 in src/components/gigs/InvoiceButton.tsx

View workflow job for this annotation

GitHub Actions / build

Cannot find name 'isWorker'. Did you mean 'Worker'?
<div className="space-y-2 rounded-md border border-border bg-background p-3">
<div className="flex items-center justify-between gap-3">
<label className="text-xs font-medium text-muted-foreground">
CoinPay receiving wallet
Expand Down Expand Up @@ -906,14 +914,15 @@
)}
</div>
)}
</div>
</div>
)}

{error && <p className="text-sm text-destructive">{error}</p>}

<div className="flex gap-2">
<Button
onClick={onSubmit}
disabled={isCreating || total <= 0 || overCap || !hasWallets}
disabled={isCreating || total <= 0 || overCap || (isWorker && !hasWallets)}

Check failure on line 925 in src/components/gigs/InvoiceButton.tsx

View workflow job for this annotation

GitHub Actions / build

Cannot find name 'isWorker'. Did you mean 'Worker'?
Comment on lines 854 to +925

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 isWorker is not a prop of InvoiceForm — compile error

isWorker is used at two points inside InvoiceForm (the wallet section conditional and the submit-button disabled expression), but it is never declared in the component's props interface and is never passed at either of the two call sites. TypeScript will emit Cannot find name 'isWorker' and refuse to compile, blocking deployment. Even if the build somehow succeeded, isWorker would be undefined (falsy), causing the wallet-selector section to be invisible to workers and the "no wallets" guard on the submit button to never fire.

isWorker needs to be added to InvoiceForm's props type and destructuring, then passed as a prop from every <InvoiceForm ... /> call site in InvoiceButton.

className="flex-1"
size="sm"
>
Expand Down
Loading