Skip to content

feat: admin clawback endpoint (#326) - #651

Open
baedboibidex-cmyk wants to merge 2 commits into
ritik4ever:mainfrom
baedboibidex-cmyk:feat/issue-326-admin-clawback
Open

feat: admin clawback endpoint (#326)#651
baedboibidex-cmyk wants to merge 2 commits into
ritik4ever:mainfrom
baedboibidex-cmyk:feat/issue-326-admin-clawback

Conversation

@baedboibidex-cmyk

@baedboibidex-cmyk baedboibidex-cmyk commented Jul 24, 2026

Copy link
Copy Markdown

Summary

Adds \POST /api/admin/streams/:id/clawback\ — admin-compliance endpoint that calls the on-chain \clawback\ function to reclaim unclaimed vested tokens.

Changes

  • *\streamStore.ts* — \clawbackStream()\ builds Soroban tx, signs with server keypair, sends, polls, returns {txHash, actualAmount}\
  • *\index.ts* — New route with \�dminAuth\ middleware, rate limiter (10 req/min), body validation (\�mount > 0), event recording
  • *\eventHistory.ts* — Added \clawed_back\ event type
  • *\swagger.ts* — OpenAPI spec + \AdminKey\ security scheme
  • *\�uth-protected-routes.integration.test.ts* — 6 tests covering auth (401), validation (400), not found (404), happy path (200), Soroban failure (502)

Acceptance Criteria

Criterion Status
Admin auth via \X-Admin-Key\ header adminAuth middleware
Calls on-chain \clawback(stream_id, amount, admin)\ Soroban tx via \clawbackStream()\
Returns on-chain tx hash Response includes \ xHash\
Records clawback event in DB \clawed_back\ event via \
ecordEventWithDb\
Validates amount > 0 Zod schema rejects non-positive amounts
Returns 502 if Soroban tx fails 502 propagated from \clawbackStream\

Closes #326

Summary by CodeRabbit

  • New Features

    • Added an admin-only endpoint to claw back funds from a stream.
    • Added validation for stream IDs and positive clawback amounts.
    • Added rate limiting for clawback requests.
    • Successful clawbacks return the transaction hash and actual amount processed.
    • Clawback activity is recorded in stream history.
  • Documentation

    • Added API documentation covering authentication, parameters, responses, and errors.
  • Tests

    • Added coverage for authorization, validation, missing streams, successful clawbacks, and service failures.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

@rhemaolamiju295-sudo is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 24, 2026

Copy link
Copy Markdown

@baedboibidex-cmyk Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an admin-authenticated stream clawback endpoint that validates requests, submits Soroban clawback transactions, records clawed_back events, returns transaction details, documents the API, and adds integration coverage.

Changes

Admin stream clawback

Layer / File(s) Summary
On-chain clawback execution
backend/src/services/streamStore.ts
Adds clawbackStream, Soroban transaction submission with retries and finalization polling, actual amount extraction, and typed results.
Admin route and event recording
backend/src/index.ts, backend/src/services/eventHistory.ts
Adds rate limiting, authentication, validation, clawback execution, clawed_back event recording, and standardized error responses.
API contract and integration validation
backend/src/swagger.ts, backend/src/auth-protected-routes.integration.test.ts
Documents AdminKey authentication and endpoint responses, and tests authorization, validation, missing streams, success, and failure handling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant AdminClient
  participant AdminRoute
  participant clawbackStream
  participant SorobanContract
  participant EventHistory
  AdminClient->>AdminRoute: POST clawback request
  AdminRoute->>clawbackStream: validated stream id and amount
  clawbackStream->>SorobanContract: signed clawback transaction
  SorobanContract-->>clawbackStream: finalized transaction result
  clawbackStream-->>AdminRoute: txHash and actualAmount
  AdminRoute->>EventHistory: record clawed_back event
  AdminRoute-->>AdminClient: clawback response
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The endpoint, authentication, signing, documentation, and tests are included, but amount validation and the required stream_clawback event are not evidenced. Validate amount against the remaining unclaimed vested amount and record the required stream_clawback event with the amount and admin address.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the new admin clawback endpoint, which is the main change in the pull request.
Out of Scope Changes check ✅ Passed The changes support the linked issue and the stated admin clawback objectives; no unrelated code changes are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.5)
backend/src/services/streamStore.ts

File contains syntax errors that prevent linting: Line 812: expected : but instead found built; Line 819: expected , but instead found ;


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
backend/src/auth-protected-routes.integration.test.ts (1)

558-570: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an assertion on the recorded clawed_back event.

The success test checks the HTTP response but never queries stream_events to verify the recorded actor/amount/metadata. Adding this would have caught the actor-attribution bug flagged in index.ts (the event's actor is set to stream.sender instead of the admin address).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/auth-protected-routes.integration.test.ts` around lines 558 -
570, Extend the success case in the test around “valid admin key + Soroban
success” to query the recorded clawed_back event from stream_events and assert
its actor is the admin address, amount matches the clawback amount, and metadata
contains the expected transaction details. Use the existing event-query helpers
and fixtures rather than changing the HTTP response assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/index.ts`:
- Around line 1952-1976: After a successful clawback in the handler surrounding
clawbackStream, persist the returned clawback amount and any required stream
progress fields to the streams table so subsequent calculations reflect the
mutation without reconciliation. Also access the in-memory cache via getCache()
and call resetStatsCache() after the database update, while preserving the
existing event recording and response behavior.
- Around line 1925-1927: Move clawbackBodySchema from index.ts into
backend/src/validation/schemas.ts, export it, and import it where the clawback
route handler uses it. Add integer validation alongside the existing positive
amount constraint so validated values satisfy the Swagger and downstream i128
contract requirements.

In `@backend/src/services/streamStore.ts`:
- Around line 1417-1454: Serialize all admin transaction flows using
serverKeypair, including createStream, estimateCreateStreamFee, cancelStream,
and clawbackStream, with a shared coroutine-level mutex or per-signer task queue
spanning account fetch, transaction preparation/build, and signing. Ensure
concurrent operations execute these sequence-sensitive steps one at a time while
preserving existing transaction behavior.
- Around line 1486-1489: Update the clawback result construction in the
surrounding clawback method to preserve the i128 amount as the bigint returned
by scValToNative, removing the Number conversion, and include the existing
adminAddress value in the returned ClawbackResult. Extend the result type as
needed so callers can access both actualAmount without precision loss and
adminAddress.
- Around line 1439-1442: Update the clawback request encoding near
nativeToScVal(amount, { type: "i128" }) so the amount is converted to an exact
BigInt before serialization, using the validated parsed amount or integer string
and truncating only as required. Preserve the i128 type and existing streamIdU64
and adminAddress arguments.

In `@backend/src/swagger.ts`:
- Around line 1872-1877: Update the amount schema in the Swagger definition and
the corresponding clawbackBodySchema to enforce positive integers, not
fractional numbers, while preserving the existing minimum constraint and
description. Align the validation with the i128 conversion used by streamStore
so all clawback amount boundaries are consistent.
- Around line 1921-1928: Update the 401 response documented near adminAuth to
match the actual `{ message: "Unauthorized" }` payload: replace the
`#/components/schemas/Error` reference with a schema requiring a string
`message`, or adjust adminAuth to return the existing Error-shaped payload. Keep
the documented response and runtime behavior consistent.

---

Nitpick comments:
In `@backend/src/auth-protected-routes.integration.test.ts`:
- Around line 558-570: Extend the success case in the test around “valid admin
key + Soroban success” to query the recorded clawed_back event from
stream_events and assert its actor is the admin address, amount matches the
clawback amount, and metadata contains the expected transaction details. Use the
existing event-query helpers and fixtures rather than changing the HTTP response
assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a9dfce6d-c19e-4977-b19f-3252dcf64759

📥 Commits

Reviewing files that changed from the base of the PR and between b3d32c1 and 6a7610b.

📒 Files selected for processing (5)
  • backend/src/auth-protected-routes.integration.test.ts
  • backend/src/index.ts
  • backend/src/services/eventHistory.ts
  • backend/src/services/streamStore.ts
  • backend/src/swagger.ts

Comment thread backend/src/index.ts
Comment on lines +1925 to +1927
const clawbackBodySchema = z.object({
amount: z.number().positive("amount must be positive"),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move clawbackBodySchema into backend/src/validation/schemas.ts.

This schema is defined inline in index.ts. As per coding guidelines, "In backend/src/index.ts, keep Express route handlers in the API layer and validate requests with Zod schemas from backend/src/validation/schemas.ts before passing data to services" and "apply validation by parsing, transforming, and refining Zod schemas in backend/src/validation/schemas.ts before passing data to services."

Additionally, the schema doesn't enforce integer amounts, but the Swagger doc (and the downstream i128 contract call) expect an integer — consider adding .int() here.

♻️ Suggested fix
-const clawbackBodySchema = z.object({
-  amount: z.number().positive("amount must be positive"),
-});
+// in backend/src/validation/schemas.ts
+export const clawbackBodySchema = z.object({
+  amount: z.number().int().positive("amount must be a positive integer"),
+});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 1925 - 1927, Move clawbackBodySchema from
index.ts into backend/src/validation/schemas.ts, export it, and import it where
the clawback route handler uses it. Add integer validation alongside the
existing positive amount constraint so validated values satisfy the Swagger and
downstream i128 contract requirements.

Source: Coding guidelines

Comment thread backend/src/index.ts
Comment on lines +1952 to +1976
try {
const result = await clawbackStream(parsedId.value, parsedBody.data.amount);

const db = (await import("./services/db")).getDb();
const { recordEventWithDb } = await import("./services/eventHistory");
const now = Math.floor(Date.now() / 1000);

recordEventWithDb(
db,
stream.id,
"clawed_back",
now,
stream.sender,
result.actualAmount,
{ txHash: result.txHash, requestedAmount: parsedBody.data.amount },
);

res.json({
result: {
txHash: result.txHash,
actualAmount: result.actualAmount,
requestedAmount: parsedBody.data.amount,
},
});
} catch (error: any) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Missing resetStatsCache() and no local stream-record update after a successful clawback.

As per coding guidelines, "access the in-memory LRU cache through getCache() and call resetStatsCache() after mutations that affect stats." A clawback reduces the stream's remaining/vested balance on-chain, which affects aggregate stats (totalVested, totalAmount, etc.) — yet resetStatsCache() is never called here.

Separately, the handler only records an event; it never updates the streams table itself (unlike, e.g., cancel/complete flows which appear to persist fields such as refundedAmount/completedAt). Until an admin manually calls /api/streams/{id}/reconcile, stream progress/vested-amount calculations served by other endpoints will not reflect the clawback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 1952 - 1976, After a successful clawback
in the handler surrounding clawbackStream, persist the returned clawback amount
and any required stream progress fields to the streams table so subsequent
calculations reflect the mutation without reconciliation. Also access the
in-memory cache via getCache() and call resetStatsCache() after the database
update, while preserving the existing event recording and response behavior.

Source: Coding guidelines

Comment on lines +1417 to +1454
export async function clawbackStream(id: string, amount: number): Promise<ClawbackResult> {
const contractId = process.env.CONTRACT_ID;
const netPass =
process.env.NETWORK_PASSPHRASE || "Test SDF Network ; September 2015";

if (!contractId || !rpcServer || !serverKeypair) {
throw new Error("Backend not configured for Soroban.");
}

const streamIdU64 = parseInt(id, 10);
if (isNaN(streamIdU64) || streamIdU64 < 0) {
const err: any = new Error("Invalid stream ID for on-chain clawback.");
err.statusCode = 400;
throw err;
}

const adminAddress = serverKeypair.publicKey();

const sourceAccount = await rpcServer.getAccount(adminAddress);
const contract = new Contract(contractId);
const tx = contract.call(
"clawback",
nativeToScVal(streamIdU64, { type: "u64" }),
nativeToScVal(amount, { type: "i128" }),
new Address(adminAddress).toScVal(),
);

const built = await rpcServer.prepareTransaction(
new TransactionBuilder(sourceAccount, {
fee: "1000",
networkPassphrase: netPass,
})
.addOperation(tx)
.setTimeout(30)
.build(),
);

built.sign(serverKeypair);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether other Soroban-signing functions in this file serialize access to serverKeypair
rg -n "serverKeypair" backend/src/services/streamStore.ts -B3 -A3

Repository: ritik4ever/stellar-stream

Length of output: 3376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant streamStore functions =="
sed -n '800,850p' backend/src/services/streamStore.ts
sed -n '900,935p' backend/src/services/streamStore.ts
sed -n '1125,1170p' backend/src/services/streamStore.ts
sed -n '1410,1475p' backend/src/services/streamStore.ts

echo
echo "== routes using clawbackStream/createStream/cancelStream =="
rg -n "clawbackStream|createStream\\(|cancelStream|cancel_stream" backend/src -B2 -A4

echo
echo "== middleware/rate limiter references =="
rg -n "rate limit|rateLimit|60|10|req|res|next" backend/src -g '*.ts' | sed -n '1,220p'

Repository: ritik4ever/stellar-stream

Length of output: 34200


Serialize admin transactions signed by serverKeypair.

getAccount(...) in createStream, estimateCreateStreamFee, cancelStream, and clawbackStream is followed by prepareTransaction, build, and sign without any shared mutex/queue around serverKeypair. Concurrent admin operations can fetch the same sequence number, causing one on-chain transaction to fail with a bad-sequence error. Add a coroutine-level lock or per-signer serialized task queue around these paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 1417 - 1454, Serialize all
admin transaction flows using serverKeypair, including createStream,
estimateCreateStreamFee, cancelStream, and clawbackStream, with a shared
coroutine-level mutex or per-signer task queue spanning account fetch,
transaction preparation/build, and signing. Ensure concurrent operations execute
these sequence-sensitive steps one at a time while preserving existing
transaction behavior.

Comment on lines +1439 to +1442
nativeToScVal(streamIdU64, { type: "u64" }),
nativeToScVal(amount, { type: "i128" }),
new Address(adminAddress).toScVal(),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo files around streamStore:"
git ls-files | rg 'backend/src/services/streamStore\.ts|backend/src/.*' || true

echo
echo "Relevant section line count:"
wc -l backend/src/services/streamStore.ts
sed -n '1350,1475p' backend/src/services/streamStore.ts

echo
echo "Search amount parameter declarations/callers:"
rg -n " clawback|amount" backend/src/services/streamStore.ts backend/src -g '*.ts' | head -200

Repository: ritik4ever/stellar-stream

Length of output: 29064


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Clawback route validation:"
sed -n '1850,1970p' backend/src/index.ts

echo
echo "Lockfile/package references for stellar-sdk:"
for f in package.json backend/package.json package-lock.json backend/package-lock.json pnpm-lock.yaml backend/pnpm-lock.yaml yarn.lock backend/yarn.lock; do
  if [ -f "$f" ]; then
    echo "=== $f ==="
    rg -n "stellar-sdk|`@stellar/stellar-sdk`|packages/stellar-sdk" "$f" | head -80
  fi
done

echo
echo "All nativeToScVal uses:"
rg -n "nativeToScVal\\(" backend/src -g '*.ts'

Repository: ritik4ever/stellar-stream

Length of output: 3513


🌐 Web query:

Stellar JavaScript SDK migration nativeToScVal numeric values silent rounding i128 amount

💡 Result:

In the Stellar JavaScript SDK, the handling of numeric values when using nativeToScVal has undergone significant improvements to prevent silent data corruption, though developers must remain vigilant regarding how they pass values [1][2]. Key points regarding the migration and numeric safety include: 1. Handling of Out-of-Range Values: Recent versions of the SDK (specifically starting with changes around #1399) have introduced strict bounds-checking for fixed-width integers like u32 and i32 [1][2]. For larger types such as i128, u128, i256, and u256, the SDK has been updated to reject out-of-range inputs rather than silently wrapping or truncating them [1][2]. Previously, some paths—particularly contract client calls using funcArgsToScVals—could lead to silent wrapping of oversized integers into incorrect on-chain values [3]. 2. Avoid JavaScript 'number' for Large Integers: You should strictly avoid passing JavaScript 'number' types for i128 or u128 amounts, as they are subject to floating-point precision limitations [2]. The SDK now warns that using high-precision JS numbers can lead to silent rounding [2]. Always use 'bigint' or string representations for exact integer values to ensure the SDK encodes them correctly without precision loss [2]. 3. Recommended Practices: - Always prefer BigInt for integer-based contract arguments [2]. - If you encounter complex conversion needs, utilize the newer, explicit conversion utilities (such as nativeToI128/i128ToNative) introduced to provide a clearer path between JS types and Soroban integers, reducing common "foot-guns" in earlier SDK versions [4]. - Ensure your SDK is updated to a recent version (e.g., v16.x or later) to benefit from these validation patches [5][1][2]. If you are using generated contract clients, ensure you are providing values that fit within the expected bounds, and prefer BigInt to guarantee that the value passed to the SDK is exactly what ends up on-chain [3][2].

Citations:


Encode the clawback request as an exact i128 value.

amount flows through as a Zod number, so large integer request values lose JS floating-point precision before nativeToScVal(amount, { type: "i128" }) encodes the contract argument. Use BigInt(Math.trunc(parsedBody.data.amount)) or a validated integer string before nativeToScVal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 1439 - 1442, Update the
clawback request encoding near nativeToScVal(amount, { type: "i128" }) so the
amount is converted to an exact BigInt before serialization, using the validated
parsed amount or integer string and truncating only as required. Preserve the
i128 type and existing streamIdU64 and adminAddress arguments.

Comment on lines +1486 to +1489
const actualAmount = Number(scValToNative(txResult.returnValue));

return { txHash: sendRes.hash, actualAmount };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Number(scValToNative(...)) can lose precision on the returned amount, and the admin address isn't surfaced in the result.

Two issues here:

  1. scValToNative converts an i128 return value to a bigint; wrapping it in Number(...) can silently lose precision for large values, same class of issue as the write path above.
  2. ClawbackResult doesn't include the admin address (adminAddress, computed at line 1433) even though the caller needs it to correctly attribute the on-chain action in the audit event (see consolidated comment with index.ts).
🛠️ Suggested fix
 export interface ClawbackResult {
   txHash: string;
   actualAmount: number;
+  adminAddress: string;
 }
...
-  const actualAmount = Number(scValToNative(txResult.returnValue));
-
-  return { txHash: sendRes.hash, actualAmount };
+  const actualAmount = Number(scValToNative(txResult.returnValue));
+
+  return { txHash: sendRes.hash, actualAmount, adminAddress };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 1486 - 1489, Update the
clawback result construction in the surrounding clawback method to preserve the
i128 amount as the bigint returned by scValToNative, removing the Number
conversion, and include the existing adminAddress value in the returned
ClawbackResult. Extend the result type as needed so callers can access both
actualAmount without precision loss and adminAddress.

Comment thread backend/src/swagger.ts
Comment on lines +1872 to +1877
amount: {
type: "number",
description: "Amount to clawback (positive integer). Capped at unclaimed vested amount by the contract.",
exclusiveMinimum: true,
minimum: 0,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Doc says "positive integer" but schema allows fractional numbers.

amount is typed number with exclusiveMinimum/minimum only — no integer type or multipleOf constraint, despite the description stating "positive integer." This mirrors the same gap in clawbackBodySchema (index.ts) and the i128 conversion in streamStore.ts — see consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/swagger.ts` around lines 1872 - 1877, Update the amount schema in
the Swagger definition and the corresponding clawbackBodySchema to enforce
positive integers, not fractional numbers, while preserving the existing minimum
constraint and description. Align the validation with the i128 conversion used
by streamStore so all clawback amount boundaries are consistent.

Comment thread backend/src/swagger.ts
Comment on lines +1921 to +1928
"401": {
description: "Unauthorized — missing or invalid admin key.",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Error" },
},
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate swagger and validation files"
fd -a 'swagger\.ts|schemas\.ts|index\.ts' backend/src 2>/dev/null | sed 's#^\./##' | head -50

echo
echo "Inspect relevant lines in backend/src/swagger.ts"
if [ -f backend/src/swagger.ts ]; then
  sed -n '1880,1945p' backend/src/swagger.ts | cat -n
fi

echo
echo "Search adminAuth and Error schema definitions/usages"
rg -n "adminAuth|schemas\.Error|Error"| backend/src -S

echo
echo "If validation schemas exist, show relevant sections"
for f in backend/src/validation/schemas.ts backend/src/swagger.ts; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n "Error|statusCode|error|code|message" "$f" -C 3 -S | head -220
  fi
done

Repository: ritik4ever/stellar-stream

Length of output: 3058


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find adminAuth definitions and json responses"
rg -n "adminAuth|Unauthorized|res\.status\(401\)|res\.status\(|JSONError|Error\)|statusCode:" backend/src -S

echo
echo "Show backend/src/swagger.ts Error schema references around the relevant schema object"
rg -n "Error:" backend/src/swagger.ts -A 20 -B 10 -S

echo
echo "Show backend/src/validation/schemas.ts relevant section if present"
if [ -f backend/src/validation/schemas.ts ]; then
  rg -n "Error|statusCode|error|code|message" backend/src/validation/schemas.ts -A 8 -B 4 -S | head -240
fi

echo
echo "Inspect route registration around adminAuth uses"
rg -n "adminAuth" backend/src -A 8 -B 8 -S

Repository: ritik4ever/stellar-stream

Length of output: 22451


Fix the 401 response schema to match adminAuth.

adminAuth returns { message: "Unauthorized" }, but this references #/components/schemas/Error, which requires error and statusCode. Define a separate 401 response schema or change adminAuth to send an Error-shaped body.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/swagger.ts` around lines 1921 - 1928, Update the 401 response
documented near adminAuth to match the actual `{ message: "Unauthorized" }`
payload: replace the `#/components/schemas/Error` reference with a schema
requiring a string `message`, or adjust adminAuth to return the existing
Error-shaped payload. Keep the documented response and runtime behavior
consistent.

@ritik4ever

Copy link
Copy Markdown
Owner

Hi @baedboibidex-cmyk,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
backend/src/services/streamStore.ts (3)

1070-1073: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use a named SQL parameter.

Replace the positional placeholder with @id and bind an object. This follows the required better-sqlite3 binding convention.

Proposed fix
-  const row = db.prepare("SELECT * FROM streams WHERE id = ?").get(id) as StreamRow | undefined;
+  const row = db
+    .prepare("SELECT * FROM streams WHERE id = `@id`")
+    .get({ id }) as StreamRow | undefined;

As per coding guidelines, use @name parameter binding syntax for better-sqlite3 prepared statements instead of ? placeholders.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 1070 - 1073, Update the SQL
query in getStreamById to use the named `@id` placeholder, and bind the input
through an object keyed by id when calling get. Preserve the existing StreamRow
lookup and return behavior.

Source: Coding guidelines


197-203: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use the admin key for clawbackStream.

clawbackStream calls clawback with serverKeypair.publicKey() as adminAddress and signs with serverKeypair, which comes from STELLAR_SECRET_KEY or SERVER_PRIVATE_KEY. Add ADMIN_SECRET_KEY as the admin secret, keep the server secret for Soroban operations that need it, and derive the clawback key from the admin secret so contract admin authorization matches the spec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 197 - 203, Update the key
initialization near serverKeypair so ADMIN_SECRET_KEY is used to derive a
separate admin/clawback keypair, while retaining STELLAR_SECRET_KEY or
SERVER_PRIVATE_KEY for serverKeypair and Soroban operations. Modify
clawbackStream to pass the admin keypair’s public key as adminAddress and sign
the clawback transaction with that same admin keypair, preserving the contract’s
admin authorization requirements.

811-819: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Complete the TransactionBuilder expression.

txToSimulate is not closed before built is declared, so this expression cannot compile. Use a single completed TransactionBuilder value for both simulation and preparation.

Proposed fix
     const txToSimulate = new TransactionBuilder(sourceAccount, {
-  const built = await rpcServer.prepareTransaction(
-    new TransactionBuilder(sourceAccount, {
       fee: "1000",
       networkPassphrase: netPass,
     })
       .addOperation(op)
       .setTimeout(30)
       .build();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 811 - 819, Complete the
TransactionBuilder expression assigned to txToSimulate before declaring built,
ensuring the builder is fully configured and built once. Pass that same
txToSimulate value to rpcServer.prepareTransaction instead of starting a second
TransactionBuilder expression.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/src/services/streamStore.ts`:
- Around line 1070-1073: Update the SQL query in getStreamById to use the named
`@id` placeholder, and bind the input through an object keyed by id when calling
get. Preserve the existing StreamRow lookup and return behavior.
- Around line 197-203: Update the key initialization near serverKeypair so
ADMIN_SECRET_KEY is used to derive a separate admin/clawback keypair, while
retaining STELLAR_SECRET_KEY or SERVER_PRIVATE_KEY for serverKeypair and Soroban
operations. Modify clawbackStream to pass the admin keypair’s public key as
adminAddress and sign the clawback transaction with that same admin keypair,
preserving the contract’s admin authorization requirements.
- Around line 811-819: Complete the TransactionBuilder expression assigned to
txToSimulate before declaring built, ensuring the builder is fully configured
and built once. Pass that same txToSimulate value to
rpcServer.prepareTransaction instead of starting a second TransactionBuilder
expression.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dc756489-9a18-4700-85b8-09a69ec8717c

📥 Commits

Reviewing files that changed from the base of the PR and between 6a7610b and 25a527f.

📒 Files selected for processing (2)
  • backend/src/index.ts
  • backend/src/services/streamStore.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/src/index.ts

@baedboibidex-cmyk

Copy link
Copy Markdown
Author

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)

backend/src/services/streamStore.ts (3)> 1070-1073: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use a named SQL parameter.
Replace the positional placeholder with @id and bind an object. This follows the required better-sqlite3 binding convention.

Proposed fix

-  const row = db.prepare("SELECT * FROM streams WHERE id = ?").get(id) as StreamRow | undefined;
+  const row = db
+    .prepare("SELECT * FROM streams WHERE id = `@id`")
+    .get({ id }) as StreamRow | undefined;

As per coding guidelines, use @name parameter binding syntax for better-sqlite3 prepared statements instead of ? placeholders.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 1070 - 1073, Update the SQL
query in getStreamById to use the named `@id` placeholder, and bind the input
through an object keyed by id when calling get. Preserve the existing StreamRow
lookup and return behavior.

Source: Coding guidelines

197-203: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Use the admin key for clawbackStream.
clawbackStream calls clawback with serverKeypair.publicKey() as adminAddress and signs with serverKeypair, which comes from STELLAR_SECRET_KEY or SERVER_PRIVATE_KEY. Add ADMIN_SECRET_KEY as the admin secret, keep the server secret for Soroban operations that need it, and derive the clawback key from the admin secret so contract admin authorization matches the spec.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 197 - 203, Update the key
initialization near serverKeypair so ADMIN_SECRET_KEY is used to derive a
separate admin/clawback keypair, while retaining STELLAR_SECRET_KEY or
SERVER_PRIVATE_KEY for serverKeypair and Soroban operations. Modify
clawbackStream to pass the admin keypair’s public key as adminAddress and sign
the clawback transaction with that same admin keypair, preserving the contract’s
admin authorization requirements.

811-819: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Complete the TransactionBuilder expression.
txToSimulate is not closed before built is declared, so this expression cannot compile. Use a single completed TransactionBuilder value for both simulation and preparation.

Proposed fix

     const txToSimulate = new TransactionBuilder(sourceAccount, {
-  const built = await rpcServer.prepareTransaction(
-    new TransactionBuilder(sourceAccount, {
       fee: "1000",
       networkPassphrase: netPass,
     })
       .addOperation(op)
       .setTimeout(30)
       .build();

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 811 - 819, Complete the
TransactionBuilder expression assigned to txToSimulate before declaring built,
ensuring the builder is fully configured and built once. Pass that same
txToSimulate value to rpcServer.prepareTransaction instead of starting a second
TransactionBuilder expression.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/src/services/streamStore.ts`:
- Around line 1070-1073: Update the SQL query in getStreamById to use the named
`@id` placeholder, and bind the input through an object keyed by id when calling
get. Preserve the existing StreamRow lookup and return behavior.
- Around line 197-203: Update the key initialization near serverKeypair so
ADMIN_SECRET_KEY is used to derive a separate admin/clawback keypair, while
retaining STELLAR_SECRET_KEY or SERVER_PRIVATE_KEY for serverKeypair and Soroban
operations. Modify clawbackStream to pass the admin keypair’s public key as
adminAddress and sign the clawback transaction with that same admin keypair,
preserving the contract’s admin authorization requirements.
- Around line 811-819: Complete the TransactionBuilder expression assigned to
txToSimulate before declaring built, ensuring the builder is fully configured
and built once. Pass that same txToSimulate value to
rpcServer.prepareTransaction instead of starting a second TransactionBuilder
expression.

ℹ️ Review info

@ritik4ever please merge it now its fixed

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.

Add clawback endpoint for admin compliance use

3 participants