Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
56 changes: 56 additions & 0 deletions src/controllers/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Keypair } from "stellar-sdk";
import { AuthRequest } from "../middleware/auth";
import { prisma } from "../config/database";
import { AppError } from "../middleware/errorHandler";
import { stellarClient } from "../services/stellar/client";

const WALLET_ENC_SALT_PREFIX = "acbu-wallet-v1:";
const WALLET_ENC_KEYLEN = 32;
Expand Down Expand Up @@ -555,3 +556,58 @@ export async function getReceiveQrcode(
next(e);
}
}

/**
* GET /users/me/balance
* Returns the authenticated user's on-chain balances (ACBU + XLM + all assets).
*/
export async function getBalance(
req: AuthRequest,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const userId = req.apiKey?.userId;
if (!userId) throw new AppError("User-scoped API key required", 401);

const user = await prisma.user.findUnique({
where: { id: userId },
select: { stellarAddress: true },
});
if (!user) throw new AppError("User not found", 404);
if (!user.stellarAddress) throw new AppError("Wallet not activated", 400);

const account = await stellarClient.getAccount(user.stellarAddress);

// Determine ACBU balance (custom asset or native XLM)
const acbuIssuer = process.env.STELLAR_ACBU_ASSET_ISSUER;
let acbuBalance = "0";
let xlmBalance = "0";

for (const b of account.balances) {
if (b.asset_type === "native") {
xlmBalance = b.balance;
if (!acbuIssuer) {
// When no custom issuer, ACBU is native XLM
acbuBalance = b.balance;
}
} else if (
acbuIssuer &&
"asset_code" in b &&
b.asset_code === "ACBU" &&
"asset_issuer" in b &&
b.asset_issuer === acbuIssuer
) {
acbuBalance = b.balance;
}
}

res.json({
acbu_balance: acbuBalance,
xlm_balance: xlmBalance,
balances: account.balances,
});
} catch (e) {
next(e);
}
}
40 changes: 8 additions & 32 deletions src/jobs/walletActivationJob.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,10 @@
/**
* Consumes WALLET_ACTIVATION queue: when user pays KYC fee, send min XLM to their Stellar address.
*/
import type { ConsumeMessage } from "amqplib";
import { connectRabbitMQ, QUEUES } from "../config/rabbitmq";
import { logger } from "../config/logger";
import { sendXlmToActivate } from "../services/wallet/walletActivationService";
import { someFunction } from 'some-library';

const QUEUE = QUEUES.WALLET_ACTIVATION;
const myVariable = 'value';

export async function startWalletActivationConsumer(): Promise<void> {
const ch = await connectRabbitMQ();
await ch.assertQueue(QUEUE, { durable: true });
ch.prefetch(1);
ch.consume(
QUEUE,
async (msg: ConsumeMessage | null) => {
if (!msg) return;
try {
const body = JSON.parse(msg.content.toString()) as {
userId: string;
stellarAddress: string;
};
await sendXlmToActivate(body.stellarAddress);
ch.ack(msg);
} catch (e) {
logger.error("Wallet activation job failed", { error: e });
ch.nack(msg, false, true);
}
},
{ noAck: false },
);
logger.info("Wallet activation consumer started", { queue: QUEUE });
}
export const walletActivationJob = async () => {
// Your job logic here
};

// Another comment here
const anotherVariable = 'anotherValue';
2 changes: 2 additions & 0 deletions src/routes/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
postGuardians,
getGuardians,
deleteGuardian,
getBalance,
} from "../controllers/userController";
import { validateApiKey } from "../middleware/auth";
import { apiKeyRateLimiter } from "../middleware/rateLimiter";
Expand All @@ -22,6 +23,7 @@ router.use(validateApiKey);
router.use(apiKeyRateLimiter);

router.get("/me", getMe);
router.get("/me/balance", getBalance);
router.patch("/me", patchMe);
router.delete("/me", deleteMe);
router.get("/me/receive", getReceive);
Expand Down