Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
104e49e
create transactions for BRL onramp on base
gianfra-t Apr 6, 2026
95bdfd9
add quote engine for brl onramp on base
gianfra-t Apr 7, 2026
fb397cc
adjust phases for base brl flow
gianfra-t Apr 7, 2026
cb54d5e
handle subsidy, fee distribution on BRL Base
gianfra-t Apr 7, 2026
0bebbfc
create offramp transactions for brla base flow, first iteration.
gianfra-t Apr 8, 2026
fac170f
implement quote logic for brla on base.
gianfra-t Apr 8, 2026
d66365a
route new brla base offramp flow
gianfra-t Apr 8, 2026
b95e42e
fix avenia kyc machine guard bug
gianfra-t Apr 9, 2026
25f8627
use new pix evm quote strategy
gianfra-t Apr 9, 2026
e71a10b
extra validations, fixing gas issues
gianfra-t Apr 10, 2026
0eaa559
add quote engine phase for subsidy merge on evm
gianfra-t Apr 10, 2026
bfd2f8e
patch on offramp brla flow on base
gianfra-t Apr 10, 2026
142c849
patch more incorrect flows
gianfra-t Apr 13, 2026
127902a
more patches after onramp testing
gianfra-t Apr 13, 2026
688c2e4
use proper squidrouter funding value
gianfra-t Apr 13, 2026
0d7b006
adjust brla offramp on base, new phases
gianfra-t Apr 15, 2026
2857835
add final destination transaction to avenia deposit evm address on base
gianfra-t Apr 15, 2026
7b2e339
update ramp journey
gianfra-t Apr 15, 2026
04bcfd7
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
b72857c
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
6513473
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
a7cb50f
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
a0232a9
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
0bdedcb
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
dddd1fc
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
6f4cfca
Potential fix for pull request finding 'Unused variable, import, func…
gianfra-t Apr 15, 2026
9e97b6d
code quality fix
gianfra-t Apr 15, 2026
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
5 changes: 3 additions & 2 deletions apps/api/src/api/controllers/brla.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,11 +568,12 @@ export const newKyc = async (
* @throws 500 - For any server-side errors during processing
*/
export const initiateKybLevel1 = async (
req: Request<unknown, unknown, unknown, { subAccountId?: string }>,
req: Request<unknown, { redirectUrl: string }, unknown, { subAccountId?: string }>,
res: Response<KybLevel1Response | BrlaErrorResponse>
): Promise<void> => {
try {
const { subAccountId } = req.query;
const { redirectUrl } = req.body as { redirectUrl: string };

if (!subAccountId) {
res.status(httpStatus.BAD_REQUEST).json({ error: "Missing subAccountId" });
Expand All @@ -593,7 +594,7 @@ export const initiateKybLevel1 = async (
}

const brlaApiService = BrlaApiService.getInstance();
const response = await brlaApiService.initiateKybLevel1(subAccountId);
const response = await brlaApiService.initiateKybLevel1(subAccountId, redirectUrl);

res.status(httpStatus.OK).json(response);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
BrlaApiService,
BrlaCurrency,
checkEvmBalancePeriodically,
FiatToken,
getAnyFiatTokenDetailsMoonbeam,
EvmToken,
evmTokenConfig,
Networks,
RampPhase,
waitUntilTrueWithTimeout
Expand All @@ -28,7 +28,7 @@ import { StateMetadata } from "../meta-state-types";
const PAYMENT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
const EVM_BALANCE_CHECK_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes

// Phase description: wait for the tokens to arrive at the Moonbeam ephemeral address.
// Phase description: wait for the tokens to arrive at the Base ephemeral address.
// If the timeout is reached, we assume the user has NOT made the payment and we cancel the ramp.
export class BrlaOnrampMintHandler extends BasePhaseHandler {
public getPhaseName(): RampPhase {
Expand Down Expand Up @@ -106,7 +106,7 @@ export class BrlaOnrampMintHandler extends BasePhaseHandler {
inputPaymentMethod: AveniaPaymentMethod.INTERNAL,
inputThirdParty: false,
outputCurrency: BrlaCurrency.BRLA,
outputPaymentMethod: AveniaPaymentMethod.MOONBEAM,
outputPaymentMethod: AveniaPaymentMethod.BASE,
outputThirdParty: false,
subAccountId: taxIdRecord.subAccountId
});
Expand All @@ -118,7 +118,7 @@ export class BrlaOnrampMintHandler extends BasePhaseHandler {
quoteToken: aveniaQuote.quoteToken,
ticketBlockchainOutput: {
walletAddress: state.state.evmEphemeralAddress,
walletChain: AveniaPaymentMethod.MOONBEAM
walletChain: AveniaPaymentMethod.BASE
}
},
taxIdRecord.subAccountId
Expand All @@ -127,20 +127,24 @@ export class BrlaOnrampMintHandler extends BasePhaseHandler {
const expectedAmountReceived = quote.metadata.aveniaTransfer?.outputAmountRaw;

logger.info(
`BrlaOnrampMintHandler: Created Avenia transfer ticket with id ${aveniaTicket.id} to transfer ${quote.metadata.aveniaTransfer.outputAmountDecimal} BRLA to Moonbeam address ${state.state.evmEphemeralAddress}`
`BrlaOnrampMintHandler: Created Avenia transfer ticket with id ${aveniaTicket.id} to transfer ${quote.metadata.aveniaTransfer.outputAmountDecimal} BRLA to Base address ${state.state.evmEphemeralAddress}`
);

try {
const pollingTimeMs = 1000;
const tokenDetails = getAnyFiatTokenDetailsMoonbeam(FiatToken.BRL);
const tokenDetails = evmTokenConfig[Networks.Base][EvmToken.BRLA];

if (!tokenDetails) {
throw new Error("BRLA token details not found for Base network");
}

await checkEvmBalancePeriodically(
tokenDetails.moonbeamErc20Address,
tokenDetails.erc20AddressSourceChain,
evmEphemeralAddress,
expectedAmountReceived,
pollingTimeMs,
EVM_BALANCE_CHECK_TIMEOUT_MS,
Networks.Moonbeam
Networks.Base
);
} catch (error) {
if (!(error instanceof BalanceCheckError)) throw error;
Expand All @@ -153,7 +157,7 @@ export class BrlaOnrampMintHandler extends BasePhaseHandler {

throw isCheckTimeout
? this.createRecoverableError(`BrlaOnrampMintHandler: phase timeout reached with error: ${error}`)
: new Error(`Error checking Moonbeam balance: ${error}`);
: new Error(`Error checking Base balance: ${error}`);
}

return this.transitionToNextPhase(state, "fundEphemeral");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { AveniaTicketStatus, BrlaApiService, isFiatTokenEnum, PixOutputTicketPayload, RampPhase } from "@vortexfi/shared";
import {
AveniaTicketStatus,
BrlaApiService,
EvmClientManager,
isFiatTokenEnum,
Networks,
PixOutputTicketPayload,
RampPhase
} from "@vortexfi/shared";
import Big from "big.js";
import logger from "../../../../config/logger";
import QuoteTicket from "../../../../models/quoteTicket.model";
Expand All @@ -8,13 +16,13 @@ import { PhaseError } from "../../../errors/phase-error";
import { BasePhaseHandler } from "../base-phase-handler";
import { StateMetadata } from "../meta-state-types";

export class BrlaPayoutOnMoonbeamPhaseHandler extends BasePhaseHandler {
export class BrlaPayoutOnBasePhaseHandler extends BasePhaseHandler {
public getPhaseName(): RampPhase {
return "brlaPayoutOnMoonbeam";
return "brlaPayoutOnBase";
}

protected async executePhase(state: RampState): Promise<RampState> {
const { taxId, pixDestination, payOutTicketId } = state.state as StateMetadata;
const { taxId, pixDestination, payOutTicketId, brlaPayoutTxHash } = state.state as StateMetadata;

if (!taxId || !pixDestination) {
throw new Error("BrlaPayoutOnMoonbeamPhaseHandler: State metadata corrupted. This is a bug.");
Expand All @@ -37,11 +45,11 @@ export class BrlaPayoutOnMoonbeamPhaseHandler extends BasePhaseHandler {
throw new Error("BrlaPayoutOnMoonbeamPhaseHandler: Invalid token type.");
}

if (!quote.metadata.pendulumToMoonbeamXcm?.outputAmountDecimal) {
throw new Error("BrlaPayoutOnMoonbeamPhaseHandler: Missing pendulumToMoonbeamXcm metadata.");
if (!quote.metadata.nablaSwapEvm?.outputAmountDecimal) {
throw new Error("BrlaPayoutOnMoonbeamPhaseHandler: Missing nablaSwapEvm metadata.");
}

const amountForPayout = quote.metadata.pendulumToMoonbeamXcm.outputAmountDecimal;
const amountForPayout = quote.metadata.nablaSwapEvm.outputAmountDecimal;

const brlaApiService = BrlaApiService.getInstance();

Expand All @@ -51,6 +59,9 @@ export class BrlaPayoutOnMoonbeamPhaseHandler extends BasePhaseHandler {
return this.transitionToNextPhase(state, "complete");
}

// send the "final destination"
await this.sendBrlaPayoutTransaction(state, brlaPayoutTxHash);

const pollForSufficientBalance = async () => {
const pollInterval = 5000; // 5 seconds
const timeout = 5 * 60 * 1000; // 5 minutes
Expand Down Expand Up @@ -104,7 +115,6 @@ export class BrlaPayoutOnMoonbeamPhaseHandler extends BasePhaseHandler {
subAccountId: taxIdRecord.subAccountId
});

logger.debug("Debug: payOutQuote", payOutQuote);
const payOutTicketParams: PixOutputTicketPayload = {
quoteToken: payOutQuote.quoteToken,
ticketBlockchainInput: {
Expand All @@ -114,8 +124,10 @@ export class BrlaPayoutOnMoonbeamPhaseHandler extends BasePhaseHandler {
pixKey: pixDestination
}
};
console.log("payOutTicketParams: ", payOutTicketParams);
const payOutTicketId = "mocked-ticket-id-for-now";

const { id: payOutTicketId } = await brlaApiService.createPixOutputTicket(payOutTicketParams, taxIdRecord.subAccountId);
//const { id: payOutTicketId } = await brlaApiService.createPixOutputTicket(payOutTicketParams, taxIdRecord.subAccountId);
logger.debug("Debug: payOutTicketId", payOutTicketId);
// Update the state with the transaction hashes
await state.update({
Expand All @@ -133,6 +145,78 @@ export class BrlaPayoutOnMoonbeamPhaseHandler extends BasePhaseHandler {
}
}

private async sendBrlaPayoutTransaction(state: RampState, brlaPayoutTxHash?: `0x${string}`): Promise<void> {
try {
const evmClientManager = EvmClientManager.getInstance();
const baseClient = evmClientManager.getClient(Networks.Base);
const { txData: brlaPayoutTx } = this.getPresignedTransaction(state, "brlaPayoutOnBase");

if (!brlaPayoutTx) {
throw new Error("Missing presigned transaction for brlaPayoutOnBase");
}

let txHash: `0x${string}`;

if (brlaPayoutTxHash) {
// Check existing transaction status
logger.info(
`BrlaPayoutOnBasePhaseHandler: Found existing transaction hash ${brlaPayoutTxHash}. Waiting for receipt...`
);
const receipt = await baseClient.waitForTransactionReceipt({ hash: brlaPayoutTxHash });

if (receipt.status !== "success") {
logger.warn(
`BrlaPayoutOnBasePhaseHandler: Existing transaction ${brlaPayoutTxHash} failed. Sending new transaction...`
);

txHash = (await evmClientManager.sendRawTransactionWithRetry(
Networks.Base,
brlaPayoutTx as `0x${string}`
)) as `0x${string}`;

const newReceipt = await baseClient.waitForTransactionReceipt({ hash: txHash });

if (newReceipt.status !== "success") {
throw new Error(`Transaction ${txHash} failed on chain`);
}
logger.info(`BrlaPayoutOnBasePhaseHandler: New transaction ${txHash} succeeded.`);

await state.update({
state: {
...state.state,
brlaPayoutTxHash: txHash
}
});
} else {
logger.info(`BrlaPayoutOnBasePhaseHandler: Existing transaction ${brlaPayoutTxHash} succeeded.`);
}
} else {
txHash = (await evmClientManager.sendRawTransactionWithRetry(
Networks.Base,
brlaPayoutTx as `0x${string}`
)) as `0x${string}`;
logger.info(`BrlaPayoutOnBasePhaseHandler: Transaction sent with hash ${txHash}. Waiting for receipt...`);
const receipt = await baseClient.waitForTransactionReceipt({ hash: txHash });

if (receipt.status !== "success") {
throw new Error(`Transaction ${txHash} failed on chain`);
}
logger.info(`BrlaPayoutOnBasePhaseHandler: Transaction ${txHash} succeeded.`);

// Store hash in state
await state.update({
state: {
...state.state,
brlaPayoutTxHash: txHash
}
});
}
} catch (error) {
logger.error("BrlaPayoutOnBasePhaseHandler: Failed to send BRLA payout transaction.", error);
throw this.createRecoverableError("Failed to send BRLA payout transaction");
}
}

protected async checkTicketStatusPaid({
ticketId,
subAccountId
Expand Down Expand Up @@ -179,4 +263,4 @@ export class BrlaPayoutOnMoonbeamPhaseHandler extends BasePhaseHandler {
}
}

export default new BrlaPayoutOnMoonbeamPhaseHandler();
export default new BrlaPayoutOnBasePhaseHandler();
Loading
Loading