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
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,19 @@ playwright/.auth/

# Playwright screenshots (generated during e2e runs)
**/e2e/screenshots/

# Miscellaneous temporary / backup files
*.orig
*.bak
*.rej
.node_repl_history

# OS-specific
Thumbs.db:encryptable
ehthumbs.db
Desktop.ini

# Python bytecode (scripts/)
__pycache__/
*.py[cod]
*.pyo
5 changes: 5 additions & 0 deletions comebackhere-backend/src/routes/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { asyncHandler } from "../lib/errors.js"
import { requireAdmin } from "../middleware/adminAuth.js"
import { allowBodySchema, blockBodySchema, complianceAuditQuerySchema } from "../schemas/index.js"
import { connectMongo, getComplianceAuditCollection } from "../db/mongo.js"
import { cacheDelete } from "../lib/cache.js"

const router = Router()

Expand Down Expand Up @@ -206,6 +207,8 @@ router.post("/allow", requireAdmin, validateBody(allowBodySchema), asyncHandler(
env.signerSecret,
env.networkPassphrase
)
// #893 — evict any stale compliance status cache entry for this address
await cacheDelete(`compliance:status:${address}`)
res.status(200).json(result)
}))

Expand Down Expand Up @@ -243,6 +246,8 @@ router.post("/block", requireAdmin, validateBody(blockBodySchema), asyncHandler(
env.signerSecret,
env.networkPassphrase
)
// #893 — evict any stale compliance status cache entry for this address
await cacheDelete(`compliance:status:${address}`)
res.status(200).json(result)
}))

Expand Down
16 changes: 14 additions & 2 deletions comebackhere-backend/src/routes/invoices.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Router, type Request, type Response } from "express"
import type { FindCursor, WithId } from "mongodb"
import { Keypair, TransactionBuilder, BASE_FEE, Contract, nativeToScVal, SorobanRpc, xdr } from "stellar-sdk"
import { connectMongo, getInvoiceEventsCollection, getInvoicesCollection, type InvoiceRecord, type InvoiceStatus } from "../db/mongo.js"
import { connectMongo, getComplianceAuditCollection, getInvoiceEventsCollection, getInvoicesCollection, type InvoiceRecord, type InvoiceStatus } from "../db/mongo.js"
import { requireEnv } from "../lib/env.js"
import { asyncHandler, NotFoundError } from "../lib/errors.js"
import { asyncHandler, ForbiddenError, NotFoundError } from "../lib/errors.js"
import { cacheGet, cacheSet } from "../lib/cache.js"
import { validateBody, validateParams, validateQuery } from "../middleware/validate.js"
import { requireMerchantApiKey } from "../middleware/apiKey.js"
import { createInvoiceSchema, invoiceIdParamSchema, invoiceListQuerySchema } from "../schemas/index.js"

const router = Router()
Expand Down Expand Up @@ -715,6 +716,17 @@ router.post("/", requireMerchantApiKey, validateBody(createInvoiceSchema), async

const client = buildSorobanClient(env.rpcUrl)
const body = { ...req.body, merchant_address: res.locals.merchantAddress } as CreateInvoiceBody

// #892 — reject invoice creation for compliance-blocked addresses
const complianceDb = await connectMongo()
const latestAudit = await getComplianceAuditCollection(complianceDb).findOne(
{ address: body.merchant_address },
{ sort: { ledger: -1, _id: -1 } },
)
if (latestAudit && latestAudit.event_type === "address_blocked") {
throw new ForbiddenError("Merchant address has been blocked by compliance")
}

const result = await createInvoice(
body,
client,
Expand Down
33 changes: 18 additions & 15 deletions comebackhere-backend/src/routes/treasury.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
escalateHoldSchema,
} from "../schemas/index.js"
import { getBalanceCache, setBalanceCache, invalidateBalanceCache } from "../lib/cache.js"
import { logger } from "../lib/logger.js"

const router = Router()

Expand Down Expand Up @@ -211,7 +212,7 @@ export async function executeSettlementWithBalanceCheck(
env.networkPassphrase,
)

res.locals.logger.info(
logger.info(
{
settlementId: body.settlement_id,
required: settlement.amount.toString(),
Expand Down Expand Up @@ -327,20 +328,22 @@ router.post("/execute-settlement", requireAdmin, validateBody(executeSettlementS

const { settlement_id: settlementId, token_contract } = req.body as { settlement_id: number; token_contract?: string }

try {
const result = await executeSettlementWithBalanceCheck(
{ settlement_id: settlementId, token_contract },
env,
)
// #212 — balance changed; evict the cache so the next GET /balances is fresh
invalidateBalanceCache(`execute-settlement id=${settlementId}`)
res.json(result)
} catch (err: unknown) {
const status = (err as { status?: number })?.status ?? 500
const message = err instanceof Error ? err.message : String(err)
res.status(status).json({ error: message })
}
})
const result = await executeSettlementWithBalanceCheck(
{ settlement_id: settlementId, token_contract },
env,
)

// #891 — mark the settlement as Executed now that the ledger has confirmed it
const database = await connectMongo()
await getSettlementsCollection(database).updateOne(
{ id: settlementId },
{ $set: { status: "Executed", executed_tx_hash: result.tx_hash, updated_at: new Date() } },
)

// #212 — balance changed; evict the cache so the next GET /balances is fresh
invalidateBalanceCache(`execute-settlement id=${settlementId}`)
res.json(result)
}))

export interface SimulateSettlementBody {
settlement_id: number
Expand Down
Loading