Skip to content

Handle malformed wallet deposit bodies#187

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
morganschp:fix-wallet-deposit-invalid-json
May 23, 2026
Merged

Handle malformed wallet deposit bodies#187
ralyodio merged 1 commit into
profullstack:masterfrom
morganschp:fix-wallet-deposit-invalid-json

Conversation

@morganschp
Copy link
Copy Markdown
Contributor

Summary

  • return 400 Invalid request body for malformed or non-object wallet deposit JSON
  • require amount_sats to be a whole-number sat amount before wallet/invoice work
  • add route tests for malformed JSON, non-object JSON, invalid amounts, unauthenticated requests, and valid invoice creation

Fixes #186

uGig task

Submitted for the active uGig repo testing task: https://ugig.net/gigs/4741218f-a723-46bb-82cb-6516120331ae

No payout details included; those can be provided after acceptance.

Tests

  • ./node_modules/.bin/vitest run src/app/api/wallet/deposit/route.test.ts
  • ./node_modules/.bin/eslint src/app/api/wallet/deposit/route.ts src/app/api/wallet/deposit/route.test.ts
  • ./node_modules/.bin/tsc --noEmit
  • git diff --check -- src/app/api/wallet/deposit/route.ts src/app/api/wallet/deposit/route.test.ts

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 23, 2026

Greptile Summary

This PR hardens the wallet deposit endpoint against malformed input by extracting body parsing into a helper that catches JSON parse errors and rejects non-object payloads, and by tightening the amount_sats guard to explicitly require a positive integer via Number.isInteger. A new Vitest suite exercises the malformed JSON, non-object body, invalid amount, unauthenticated, and happy-path cases.

  • route.ts: adds parseDepositRequestBody to safely parse and type-check the request body before any downstream work, and expands the amount guard from a loose truthiness check to an explicit number/integer/range check.
  • route.test.ts: new test file covering the five key request paths for the POST handler.

Confidence Score: 5/5

Safe to merge — the changes are narrowly scoped to input validation and do not touch any payment, balance, or persistence logic.

The body-parsing helper correctly handles all problematic inputs (NaN, Infinity, floats, null, arrays, missing key, parse errors). The amount guard is stricter than the original and covers every edge case. Tests are accurate, mocks are well-isolated, and no existing behaviour is altered.

No files require special attention.

Important Files Changed

Filename Overview
src/app/api/wallet/deposit/route.ts Adds safe body parsing helper and tightened amount validation; logic is correct for all edge cases (NaN, Infinity, floats, strings, null, arrays).
src/app/api/wallet/deposit/route.test.ts New test suite covering malformed JSON, non-object body, float amount, unauthenticated request, and valid invoice creation; mocking is well-scoped and spy assertions are accurate.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[POST /api/wallet/deposit] --> B[getAuthContext]
    B -->|null| C[401 Unauthorized]
    B -->|user| D[parseDepositRequestBody]
    D -->|parse error / non-object| E[400 Invalid request body]
    D -->|valid object| F{amount_sats valid?\nnumber, integer,\n1-1,000,000}
    F -->|no| G[400 Invalid amount]
    F -->|yes| H[createServiceClient]
    H --> I[getUserLnWallet]
    I -->|null| J[400 No Lightning wallet found]
    I -->|wallet| K[createInvoice]
    K --> L[getLnBalance]
    L --> M{wallet row exists?}
    M -->|no| N[insert wallets row]
    M -->|yes| O[insert wallet_transactions pending]
    N --> O
    O --> P[200 ok + payment_request + payment_hash]
Loading

Reviews (3): Last reviewed commit: "Handle malformed wallet deposit bodies" | Re-trigger Greptile

Comment on lines +83 to +91
it("rejects non-integer amounts without wallet lookup", async () => {
const res = await POST(makeRequest({ amount_sats: 100.5 }));

expect(res.status).toBe(400);
const data = await res.json();
expect(data.error).toBe("Invalid amount (1-1,000,000 sats)");
expect(mockCreateServiceClient).not.toHaveBeenCalled();
expect(mockGetUserLnWallet).not.toHaveBeenCalled();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Missing boundary and negative-value test cases

The amount tests only cover a float (100.5), but amount_sats <= 0 means both 0 and any negative integer slip through the test suite without explicit coverage. For a financial endpoint, 0, -1, and 1 (the valid lower boundary) should each be tested directly — a future maintainer could accidentally invert the comparison and the existing tests would not catch it.

Comment on lines +93 to +141
it("creates an invoice for a valid deposit amount", async () => {
const walletSelectSingle = vi.fn().mockResolvedValue({
data: { id: "wallet-1", balance_sats: 250 },
});
const walletEq = vi.fn().mockReturnValue({ single: walletSelectSingle });
const walletSelect = vi.fn().mockReturnValue({ eq: walletEq });
const transactionInsert = vi.fn().mockResolvedValue({ data: null });
const admin = {
from: vi.fn((table: string) => {
if (table === "wallets") {
return {
select: walletSelect,
insert: vi.fn().mockResolvedValue({ data: null }),
};
}
if (table === "wallet_transactions") {
return { insert: transactionInsert };
}
return {};
}),
};
mockCreateServiceClient.mockReturnValue(admin);

const res = await POST(makeRequest({ amount_sats: 100 }));

expect(res.status).toBe(200);
const data = await res.json();
expect(data).toEqual({
ok: true,
payment_request: "lnbc123",
payment_hash: "hash-123",
amount_sats: 100,
});
expect(mockCreateInvoice).toHaveBeenCalledWith(
"invoice-key-123",
100,
"ugig.net deposit (user-123)",
);
expect(transactionInsert).toHaveBeenCalledWith({
user_id: "user-12345678",
type: "deposit",
amount_sats: 100,
balance_after: 250,
bolt11: "lnbc123",
payment_hash: "hash-123",
status: "pending",
});
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 No Lightning wallet not-found path is tested

The code path at line 49–53 of route.ts (if (!lnWallet)) returns a 400 with "No Lightning wallet found" but has no corresponding test. If getUserLnWallet is changed to return null/undefined for new users, a regression here would be silently missed.

@ralyodio ralyodio closed this May 23, 2026
@ralyodio ralyodio reopened this May 23, 2026
@ralyodio ralyodio closed this May 23, 2026
@ralyodio ralyodio reopened this May 23, 2026
@ralyodio ralyodio merged commit 8113de3 into profullstack:master May 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: wallet deposit API returns 500 on malformed JSON

2 participants