Skip to content
Merged
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
10 changes: 10 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ RAMP_WIDGET_URL=https://www.vortexfinance.co/widget
# Vortex fee config
VORTEX_FEE_PEN_PERCENTAGE=0.0

# Fee-collecting EVM BUY destination execution pricing. Exact provider-token
# direct payouts remain covered by their existing source reserve. Other quotes
# are unavailable when the expected funding plus payout fee exceeds the USD
# ceiling, and registration is rejected when fees move beyond the quote margin
# (12000 = 20%). Keep dynamic funding disabled for the first deployment, then
# enable it only after every API and worker replica runs the new executor.
EVM_DYNAMIC_DESTINATION_FUNDING_ENABLED=false
EVM_DESTINATION_MAX_EXECUTION_FEE_USD=5
EVM_DESTINATION_NETWORK_FEE_MARGIN_BPS=12000

# Rate Limiting
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_WINDOW_MINUTES=1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ const CORE_PHASES: RampPhase[] = [
function buildCtx(): PhaseCtx {
return {
addNote: () => undefined,
evmDestinationGas: {
executionFeeUsd: "0.01",
fundingGasLimit: "21000",
isNativeTransfer: false,
maximumFeePerGas: "1",
network: Networks.Arbitrum,
programVersion: 2,
transferGasLimit: "100000"
},
notes: [],
now: new Date(),
partner: { id: null },
Expand Down Expand Up @@ -150,12 +159,12 @@ describe("Alfredpay cross-chain onramp flow", () => {

expect(output.token).toBe(EvmToken.USDC);
expect(output.chain).toBe(Networks.Arbitrum);
expect(output.amount.toFixed()).toBe("95");
expect(output.amount.toFixed()).toBe("94.99");
expect(metadata.globals.fees.usd).toEqual({
anchor: "2",
network: "0",
network: "0.01",
partnerMarkup: "1",
total: "4.000000",
total: "4.010000",
vortex: "1"
});
expect(Object.keys(metadata.blocks)).toEqual([
Expand All @@ -168,7 +177,7 @@ describe("Alfredpay cross-chain onramp flow", () => {
"distributeFees"
]);
expect(getBlockMetadata(metadata, AlfredpayMintContext).outputAmountRaw).toBe("98000000");
expect(getBlockMetadata(metadata, SubsidizePreContext).targetInputAmountRaw).toBe("96000000");
expect(getBlockMetadata(metadata, SquidRouterSwapContext).inputAmountRaw).toBe("96000000");
expect(getBlockMetadata(metadata, SubsidizePreContext).targetInputAmountRaw).toBe("95990000");
expect(getBlockMetadata(metadata, SquidRouterSwapContext).inputAmountRaw).toBe("95990000");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ const ALFREDPAY_ONRAMP_DIRECT: RampPhase[] = ["initial", ...CORE_PHASES, "comple
function buildCtx(outputCurrency: EvmToken): PhaseCtx {
return {
addNote: () => undefined,
evmDestinationGas: {
executionFeeUsd: "0.01",
fundingGasLimit: "21000",
isNativeTransfer: false,
maximumFeePerGas: "1",
network: Networks.Polygon,
programVersion: 2,
transferGasLimit: "100000"
},
notes: [],
now: new Date(),
partner: { id: null },
Expand Down Expand Up @@ -135,12 +144,12 @@ describe("Alfredpay direct onramp flow", () => {

expect(squidCalculations).toBe(0);
expect(capturedProviderRequests[0]?.metadata.customerId).toBe("anonymous");
expect(output).toMatchObject({ amountRaw: "96000000", chain: Networks.Polygon, token: ALFREDPAY_EVM_TOKEN });
expect(output).toMatchObject({ amountRaw: "95990000", chain: Networks.Polygon, token: ALFREDPAY_EVM_TOKEN });
expect(metadata.blocks.squidRouterSwap).toMatchObject({
effectiveExchangeRate: "1",
inputAmountRaw: "96000000",
inputAmountRaw: "95990000",
networkFeeUSD: "0",
outputAmountRaw: "96000000"
outputAmountRaw: "95990000"
});
});

Expand All @@ -149,10 +158,10 @@ describe("Alfredpay direct onramp flow", () => {
const { metadata, output } = await makeAlfredpayOnrampDirectFlow(EvmToken.USDC).simulate(buildCtx(EvmToken.USDC));

expect(squidCalculations).toBe(1);
expect(output).toMatchObject({ amountRaw: "95000000", chain: Networks.Polygon, token: EvmToken.USDC });
expect(output).toMatchObject({ amountRaw: "94990000", chain: Networks.Polygon, token: EvmToken.USDC });
expect(metadata.blocks.squidRouterSwap).toMatchObject({
inputAmountRaw: "96000000",
outputAmountRaw: "95000000"
inputAmountRaw: "95990000",
outputAmountRaw: "94990000"
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterAll, describe, expect, it, mock } from "bun:test";
import Big from "big.js";
import { BrlaApiService, EPaymentMethod, EvmToken, FiatToken, Networks, RampDirection, RampPhase } from "@vortexfi/shared";
import { config } from "../../../../../config/vars";
import * as partnerPricingNamespace from "../../../partners/partner-pricing.service";

const partnerPricingReal = { ...partnerPricingNamespace };
Expand Down Expand Up @@ -166,7 +167,7 @@ describe("BRL cross-chain onramp flow compile-time adjacency", () => {
});
});

function buildCtx(): PhaseCtx {
function buildCtx(includeDynamicFunding = true): PhaseCtx {
const notes: string[] = [];
return {
addNote: (note: string) => {
Expand All @@ -176,6 +177,19 @@ function buildCtx(): PhaseCtx {
displayFiat: { anchor: "0.1", currency: FiatToken.BRL, network: "0", partnerMarkup: "0", total: "0.2", vortex: "0.1" },
usd: { anchor: "0.1", network: "0", partnerMarkup: "0", total: "0.2", vortex: "0.1" }
},
...(includeDynamicFunding
? {
evmDestinationGas: {
executionFeeUsd: "0.01",
fundingGasLimit: "21000",
isNativeTransfer: false,
maximumFeePerGas: "1",
network: Networks.Arbitrum,
programVersion: 2 as const,
transferGasLimit: "100000"
}
}
: {}),
notes,
now: new Date(),
partner: null,
Expand All @@ -191,7 +205,7 @@ function buildCtx(): PhaseCtx {
};
}

async function runFlow(flow: typeof brlOnrampBaseCrossChainFlow) {
async function runFlow(flow: typeof brlOnrampBaseCrossChainFlow, includeDynamicFunding = true) {
BrlaApiService.getInstance = mock(() => ({
createPayInQuote: mock(async (request: { inputCurrency: string }) => ({
appliedFees: [{ amount: "0.2", type: "Gas Fee" }],
Expand All @@ -200,7 +214,7 @@ async function runFlow(flow: typeof brlOnrampBaseCrossChainFlow) {
}))
})) as unknown as typeof BrlaApiService.getInstance;

return flow.simulate(buildCtx());
return flow.simulate(buildCtx(includeDynamicFunding));
}

describe("BRL cross-chain onramp flow simulation", () => {
Expand All @@ -210,14 +224,25 @@ describe("BRL cross-chain onramp flow simulation", () => {
expect(output.token).toBe(EvmToken.USDC);
expect(output.chain).toBe(Networks.Arbitrum);
});

it("keeps producing legacy-compatible metadata until the rollout flag is enabled", async () => {
const originalEnabled = config.evmDestinationGas.dynamicFundingEnabled;
config.evmDestinationGas.dynamicFundingEnabled = false;
try {
const { metadata } = await runFlow(brlOnrampBaseCrossChainFlow, false);
expect(metadata.globals.evmDestinationGas).toBeUndefined();
} finally {
config.evmDestinationGas.dynamicFundingEnabled = originalEnabled;
}
});
});

describe("BRL cross-chain onramp flow metadata ownership", () => {
it("accumulates one context per block beneath explicit globals", async () => {
const { metadata } = await runFlow(brlOnrampBaseCrossChainFlow);
const { blocks, globals } = metadata;

expect(globals.fees.usd).toMatchObject({ anchor: "1.5", network: "0.1", total: "1.700000", vortex: "0.1" });
expect(globals.fees.usd).toMatchObject({ anchor: "1.5", network: "0.11", total: "1.710000", vortex: "0.1" });
expect(Object.keys(blocks)).toEqual([
"aveniaMint",
"fundEphemeral",
Expand All @@ -233,7 +258,7 @@ describe("BRL cross-chain onramp flow metadata ownership", () => {
const aveniaMint = getBlockMetadata(metadata, AveniaMintContext).mint;
expect(aveniaMint).toBeDefined();
expect(aveniaMint.currency).toBe(FiatToken.BRL);
// 100 BRL in, 99 BRLA quoted -> 1 BRL mint fee, 0.2 gas fee deducted from delivery
// 100 BRL in, 99 BRLA quoted -> 1 BRL mint fee, 0.2 provider gas fee deducted from delivery
expect(Big(aveniaMint.fee).toFixed()).toBe("1");
expect(Big(aveniaMint.inputAmountDecimal).toFixed()).toBe("100");
expect(Big(aveniaMint.outputAmountDecimal).toFixed()).toBe("98.8");
Expand All @@ -260,8 +285,8 @@ describe("BRL cross-chain onramp flow metadata ownership", () => {
expect(evmToEvm.networkFeeUSD).toBe("0.1");

const distributeFees = getBlockMetadata(metadata, DistributeFeesContext);
expect(distributeFees.networkFeeUsd).toBe("0.1");
expect(distributeFees.totalFeesUsd).toBe("0.2");
expect(distributeFees.networkFeeUsd).toBe("0.11");
expect(distributeFees.totalFeesUsd).toBe("0.21");

const subsidy = getBlockMetadata(metadata, FinalSettlementSubsidyContext);
expect(subsidy).toBeDefined();
Expand All @@ -271,7 +296,7 @@ describe("BRL cross-chain onramp flow metadata ownership", () => {
expect(getBlockMetadata(metadata, SubsidizePreContext).inputCurrency).toBe(EvmToken.BRLA);
const subsidizePost = getBlockMetadata(metadata, SubsidizePostContext);
expect(subsidizePost.outputCurrency).toBe(EvmToken.USDC);
expect(Big(subsidizePost.actualOutputAmountDecimal).toFixed()).toBe("17.8");
expect(Big(subsidizePost.actualOutputAmountDecimal).toFixed()).toBe("17.79");
expect(getBlockMetadata(metadata, DestinationTransferContext).amountRaw).toBe("17500000");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as evmFundingNamespace from "../core/evm-funding";
import * as partnerPricingNamespace from "../../../partners/partner-pricing.service";
import type { QuoteTicketAttributes } from "../../../../../models/quoteTicket.model";
import Big from "big.js";
import { decodeFunctionData, erc20Abi } from "viem";
import { decodeFunctionData, erc20Abi, parseTransaction } from "viem";
import type { FlowMetadata } from "../core/metadata";
import type { SubsidyMetadata } from "../phases/subsidize-pre/simulation";

Expand Down Expand Up @@ -203,6 +203,15 @@ function buildMetadata(): FlowMetadata<BrlBlockMetadata> {
}
},
globals: {
evmDestinationGas: {
executionFeeUsd: "0.363",
fundingGasLimit: "21000",
isNativeTransfer: false,
maximumFeePerGas: "1000000000",
network: Networks.Arbitrum,
programVersion: 2,
transferGasLimit: "100000"
},
fees: { usd: { anchor: "0.1", network: "0.1", partnerMarkup: "0", total: "0.3", vortex: "0.1" } },
partner: null,
request: REQUEST
Expand Down Expand Up @@ -263,6 +272,10 @@ describe("BRL onramp Base cross-chain transactions", () => {
expect(blocks.unsignedTxs.find(tx => tx.phase === "squidRouterSwap")?.txData).toMatchObject({ data: "0xa2" });
expect(blocks.unsignedTxs.find(tx => tx.phase === "backupSquidRouterApprove")?.txData).toMatchObject({ data: "0xb1" });
expect(blocks.unsignedTxs.find(tx => tx.phase === "backupSquidRouterSwap")?.txData).toMatchObject({ data: "0xb2" });
expect(blocks.unsignedTxs.find(tx => tx.phase === "destinationTransfer")?.txData).toMatchObject({
maxFeePerGas: "1000000000",
maxPriorityFeePerGas: "1000000"
});
});

it("allocates the production nonce lanes per network", async () => {
Expand Down Expand Up @@ -300,6 +313,9 @@ describe("BRL onramp Base cross-chain transactions", () => {
const presignedTxs = await signUnsignedTransactions(blocks.unsignedTxs, { evmEphemeral });
expect(presignedTxs.length).toBeGreaterThanOrEqual(blocks.unsignedTxs.length);
expect(presignedTxs.every(tx => typeof tx.txData === "string" && tx.txData.startsWith("0x"))).toBe(true);
const destinationTransfer = presignedTxs.find(tx => tx.phase === "destinationTransfer");
expect(destinationTransfer).toBeDefined();
expect(parseTransaction(destinationTransfer?.txData as `0x${string}`).maxFeePerGas).toBe(3_000_000_000n);
}, 60_000);

it("preserves 18-decimal BSC USDT precision in the destination transfer", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ function buildCtx(outputCurrency: EvmToken): PhaseCtx {
displayFiat: { anchor: "0.1", currency: FiatToken.BRL, network: "0", partnerMarkup: "0", total: "0.2", vortex: "0.1" },
usd: { anchor: "0.1", network: "0", partnerMarkup: "0", total: "0.2", vortex: "0.1" }
},
evmDestinationGas: {
executionFeeUsd: "0.01",
fundingGasLimit: "21000",
isNativeTransfer: false,
maximumFeePerGas: "1",
maximumFundingL1FeeRaw: "1",
maximumPayoutL1FeeRaw: "1",
network: Networks.Base,
programVersion: 2,
transferGasLimit: "100000"
},
notes: [],
now: new Date(),
partner: null,
Expand Down Expand Up @@ -166,15 +177,17 @@ describe("BRL Base same-chain block flows", () => {
expect(destinationTransfer.network).toBe(Networks.Base);
expect(destinationTransfer.token).toBe(outputCurrency);
expect(metadata.globals.fees.usd.anchor).toBe("1.5");
expect(metadata.globals.fees.usd.network).toBe(outputCurrency === EvmToken.USDC ? "0" : "0.1");
expect(metadata.globals.fees.usd.total).toBe(outputCurrency === EvmToken.USDC ? "1.600000" : "1.700000");
expect(metadata.globals.fees.displayFiat?.network).toBe(outputCurrency === EvmToken.USDC ? "0" : "0.1");
expect(metadata.globals.fees.displayFiat?.total).toBe(outputCurrency === EvmToken.USDC ? "1.60" : "1.70");
expect(metadata.globals.fees.usd.network).toBe(outputCurrency === EvmToken.USDC ? "0.01" : "0.11");
expect(metadata.globals.fees.usd.total).toBe(outputCurrency === EvmToken.USDC ? "1.610000" : "1.710000");
expect(metadata.globals.fees.displayFiat?.network).toBe(outputCurrency === EvmToken.USDC ? "0.01" : "0.11");
expect(metadata.globals.fees.displayFiat?.total).toBe(outputCurrency === EvmToken.USDC ? "1.61" : "1.71");
expect(getBlockMetadata(metadata, DistributeFeesContext).networkFeeUsd).toBe(
outputCurrency === EvmToken.USDC ? "0" : "0.1"
outputCurrency === EvmToken.USDC ? "0.01" : "0.11"
);
const subsidizePost = getBlockMetadata(metadata, SubsidizePostContext);
expect(Big(subsidizePost.actualOutputAmountDecimal).toFixed()).toBe(outputCurrency === EvmToken.USDC ? "17.9" : "17.8");
expect(Big(subsidizePost.actualOutputAmountDecimal).toFixed()).toBe(
outputCurrency === EvmToken.USDC ? "17.89" : "17.79"
);
expect(subsidizePost.applied).toBe(false);
});
}
Expand Down
Loading
Loading