Skip to content

Commit ecbe775

Browse files
committed
test(api): correct cross-chain offramp coverage
1 parent a534cd5 commit ecbe775

1 file changed

Lines changed: 75 additions & 22 deletions

File tree

apps/api/src/tests/corridors/alfredpay-currencies.scenario.test.ts

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ import { type FakeWorld, installFakeWorld } from "../../test-utils/fake-world";
3131
import { installFakeSupabaseAuth, testUserToken } from "../../test-utils/fake-world/fake-auth";
3232
import { startTestApp, type TestApp } from "../../test-utils/test-app";
3333

34-
const USDT_ON_ARBITRUM = evmTokenConfig[Networks.Arbitrum][EvmToken.USDT]?.erc20AddressSourceChain as `0x${string}`;
35-
if (!USDT_ON_ARBITRUM) {
34+
const USDT_ON_ARBITRUM_CONFIG = evmTokenConfig[Networks.Arbitrum][EvmToken.USDT];
35+
if (!USDT_ON_ARBITRUM_CONFIG?.erc20AddressSourceChain) {
3636
throw new Error("USDT token config missing for Arbitrum");
3737
}
38-
const USDT_ON_BSC = evmTokenConfig[Networks.BSC][EvmToken.USDT]?.erc20AddressSourceChain as `0x${string}`;
39-
if (!USDT_ON_BSC) {
38+
const USDT_ON_ARBITRUM = USDT_ON_ARBITRUM_CONFIG.erc20AddressSourceChain as `0x${string}`;
39+
const USDT_ON_BSC_CONFIG = evmTokenConfig[Networks.BSC][EvmToken.USDT];
40+
if (!USDT_ON_BSC_CONFIG?.erc20AddressSourceChain) {
4041
throw new Error("USDT token config missing for BSC");
4142
}
43+
const USDT_ON_BSC = USDT_ON_BSC_CONFIG.erc20AddressSourceChain as `0x${string}`;
4244

4345
const CHAIN_IDS: Partial<Record<Networks, number>> = {
4446
[Networks.Arbitrum]: 42161,
@@ -156,6 +158,35 @@ const CROSS_CHAIN_OFFRAMP_CASES: CurrencyCase[] = [
156158
}
157159
];
158160

161+
interface CrossChainOfframpSource {
162+
chainId: string;
163+
label: "Arbitrum" | "BSC";
164+
network: typeof Networks.Arbitrum | typeof Networks.BSC;
165+
token: `0x${string}`;
166+
tokenDecimals: number;
167+
}
168+
169+
const ARBITRUM_OFFRAMP_SOURCE = {
170+
chainId: "42161",
171+
label: "Arbitrum",
172+
network: Networks.Arbitrum,
173+
token: USDT_ON_ARBITRUM,
174+
tokenDecimals: USDT_ON_ARBITRUM_CONFIG.decimals
175+
} satisfies CrossChainOfframpSource;
176+
177+
const BSC_OFFRAMP_SOURCE = {
178+
chainId: "56",
179+
label: "BSC",
180+
network: Networks.BSC,
181+
token: USDT_ON_BSC,
182+
tokenDecimals: USDT_ON_BSC_CONFIG.decimals
183+
} satisfies CrossChainOfframpSource;
184+
185+
const CROSS_CHAIN_OFFRAMP_TEST_CASES = [
186+
...CROSS_CHAIN_OFFRAMP_CASES.map(currency => ({ currency, source: ARBITRUM_OFFRAMP_SOURCE })),
187+
{ currency: CURRENCY_CASES[0], source: BSC_OFFRAMP_SOURCE }
188+
];
189+
159190
/**
160191
* Parameterized scenarios for the Alfredpay corridors beyond MXN: USD (ach),
161192
* COP (ach) and ARS (cbu), each in both directions. The deeper security and
@@ -166,8 +197,9 @@ const CROSS_CHAIN_OFFRAMP_CASES: CurrencyCase[] = [
166197
* The cross-chain legs run here per currency too: BUY bridges the Polygon mint
167198
* to Arbitrum via squid (mirroring the MXN cross-chain corridor) and SELL
168199
* takes the no-permit cross-chain fallback (user-broadcast squid approve+swap
169-
* on BSC, verified by hash before the Polygon deposit transfer) — the
170-
* latter including MXN, whose own files only cover the direct Polygon path.
200+
* on Arbitrum, verified by hash before the Polygon deposit transfer) — the
201+
* latter including MXN, whose own files only cover the direct Polygon path —
202+
* plus a focused BSC case for its 18-to-6-decimal permit-probe regression.
171203
*/
172204
describe("Alfredpay currency corridors (USD/COP/ARS, on- and offramp)", () => {
173205
let world: FakeWorld;
@@ -199,6 +231,7 @@ describe("Alfredpay currency corridors (USD/COP/ARS, on- and offramp)", () => {
199231
world.alfredpay.offrampDepositAddress = privateKeyToAccount(generatePrivateKey()).address.toLowerCase();
200232
// Both direct and cross-chain Alfredpay SELL simulations price a
201233
// Squid-delivered Polygon USDT settlement leg.
234+
world.squidRouter.computeToAmount = params => params.fromAmount;
202235
world.squidRouter.toTokenDecimals = 6;
203236
world.squidRouter.bridgeStatus = "success";
204237
});
@@ -618,15 +651,20 @@ describe("Alfredpay currency corridors (USD/COP/ARS, on- and offramp)", () => {
618651
/**
619652
* Cross-chain SELL setup via the HTTP API on the no-permit fallback (the
620653
* Alfredpay analog of the BRL cross-chain offramp's squid-hash flow): the
621-
* user broadcasts the squid approve + swap from their own wallet on BSC,
654+
* user broadcasts the squid approve + swap from their own wallet on the source chain,
622655
* the hashes are reported through /v1/ramp/update together with
623656
* the presigned Polygon deposit transfer, and squidRouterPermitExecute
624657
* verifies them against the blueprints before any ephemeral funds move.
625658
*/
626-
async function setUpCrossChainOfframpRamp(currency: CurrencyCase): Promise<CrossChainOfframpSetup> {
659+
async function setUpCrossChainOfframpRamp(
660+
currency: CurrencyCase,
661+
source: CrossChainOfframpSource
662+
): Promise<CrossChainOfframpSetup> {
627663
world.alfredpay.offrampRate = currency.offrampRate;
628-
// The user's squid leg swaps 18-decimal BSC USDT into 6-decimal Polygon
629-
// USDT; the fake route must report destination decimals.
664+
// The fake route is a decimal 1:1 swap, so convert source raw units into
665+
// 6-decimal Polygon USDT raw units instead of re-labelling them.
666+
world.squidRouter.computeToAmount = params =>
667+
(BigInt(params.fromAmount) / 10n ** BigInt(source.tokenDecimals - ALFREDPAY_ERC20_DECIMALS)).toString();
630668
world.squidRouter.toTokenDecimals = 6;
631669
const ephemeral = privateKeyToAccount(generatePrivateKey());
632670
const userWallet = privateKeyToAccount(generatePrivateKey());
@@ -636,10 +674,10 @@ describe("Alfredpay currency corridors (USD/COP/ARS, on- and offramp)", () => {
636674
const user = await createTestUser();
637675
await createTestAlfredpayCustomer(user.id, { country: currency.country });
638676
const quote = await createQuoteViaApi({
639-
from: Networks.BSC,
677+
from: source.network,
640678
inputAmount: currency.offrampInputAmount,
641679
inputCurrency: EvmToken.USDT,
642-
network: Networks.BSC,
680+
network: source.network,
643681
outputCurrency: currency.fiat,
644682
rampType: RampDirection.SELL,
645683
to: currency.rail
@@ -651,9 +689,21 @@ describe("Alfredpay currency corridors (USD/COP/ARS, on- and offramp)", () => {
651689

652690
const persistedQuote = await QuoteTicket.findByPk(quote.id);
653691
const metadata = persistedQuote?.metadata as unknown as
654-
| { blocks: { alfredpayOfframp?: { inputAmountRaw?: string } } }
692+
| {
693+
blocks: {
694+
alfredpayOfframp?: {
695+
bridgeInputAmountRaw?: string;
696+
bridgeOutputAmountRaw?: string;
697+
inputAmountRaw?: string;
698+
};
699+
};
700+
}
655701
| undefined;
702+
const bridgeInputAmountRaw = BigInt(metadata?.blocks.alfredpayOfframp?.bridgeInputAmountRaw ?? "0");
703+
const bridgeOutputAmountRaw = BigInt(metadata?.blocks.alfredpayOfframp?.bridgeOutputAmountRaw ?? "0");
656704
const inputAmountRaw = BigInt(metadata?.blocks.alfredpayOfframp?.inputAmountRaw ?? "0");
705+
expect(bridgeInputAmountRaw).toBe(parseUnits(currency.offrampInputAmount, source.tokenDecimals));
706+
expect(bridgeOutputAmountRaw).toBe(parseUnits(currency.offrampInputAmount, ALFREDPAY_ERC20_DECIMALS));
657707
expect(inputAmountRaw).toBeGreaterThan(0n);
658708

659709
const registered = await RampState.findByPk(ramp.id);
@@ -664,8 +714,8 @@ describe("Alfredpay currency corridors (USD/COP/ARS, on- and offramp)", () => {
664714
const approveBlueprint = blueprintOf(allUnsignedTxs, "squidRouterNoPermitApprove");
665715
const swapBlueprint = blueprintOf(allUnsignedTxs, "squidRouterNoPermitSwap");
666716
const offrampTransferBlueprint = blueprintOf(allUnsignedTxs, "alfredpayOfframpTransfer");
667-
expect(approveBlueprint.network).toBe(Networks.BSC);
668-
expect(swapBlueprint.network).toBe(Networks.BSC);
717+
expect(approveBlueprint.network).toBe(source.network);
718+
expect(swapBlueprint.network).toBe(source.network);
669719
expect(approveBlueprint.signer.toLowerCase()).toBe(userWallet.address.toLowerCase());
670720
expect(swapBlueprint.signer.toLowerCase()).toBe(userWallet.address.toLowerCase());
671721

@@ -864,23 +914,26 @@ describe("Alfredpay currency corridors (USD/COP/ARS, on- and offramp)", () => {
864914
);
865915
}
866916

867-
for (const currency of CROSS_CHAIN_OFFRAMP_CASES) {
917+
for (const { currency, source } of CROSS_CHAIN_OFFRAMP_TEST_CASES) {
868918
it(
869-
`${currency.fiat} cross-chain offramp (USDT on BSC → squid → Polygon → ${currency.rail}) verifies the user's squid txs and completes`,
919+
`${currency.fiat} cross-chain offramp (USDT on ${source.label} → squid → Polygon → ${currency.rail}) verifies the user's squid txs and completes`,
870920
async () => {
871921
const offrampOrdersBefore = world.alfredpay.offrampOrders.length;
872-
const setup = await setUpCrossChainOfframpRamp(currency);
922+
const setup = await setUpCrossChainOfframpRamp(currency, source);
873923

874-
// Registration requested a BSC USDT → Polygon USDT route from
924+
// Registration requested a source-chain USDT → Polygon USDT route from
875925
// the user's wallet, delivering to the ephemeral.
876926
const registrationRoute = world.squidRouter.requestedRoutes.find(
877927
route =>
878-
route.fromToken.toLowerCase() === USDT_ON_BSC.toLowerCase() &&
928+
route.fromToken.toLowerCase() === source.token.toLowerCase() &&
879929
route.toToken.toLowerCase() === ALFREDPAY_ERC20_TOKEN.toLowerCase() &&
880930
route.toAddress?.toLowerCase() === setup.ephemeralAddress.toLowerCase()
881931
);
882-
expect(registrationRoute, "registration should request a BSC→Polygon USDT route to the ephemeral").toBeDefined();
883-
expect(registrationRoute?.fromChain).toBe("56");
932+
expect(
933+
registrationRoute,
934+
`registration should request a ${source.label}→Polygon USDT route to the ephemeral`
935+
).toBeDefined();
936+
expect(registrationRoute?.fromChain).toBe(source.chainId);
884937
expect(registrationRoute?.toChain).toBe("137");
885938
expect(registrationRoute?.fromAddress.toLowerCase()).toBe(setup.userWalletAddress.toLowerCase());
886939

0 commit comments

Comments
 (0)