Skip to content

Overpay squidrouter transaction value. #652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 20, 2025
10 changes: 5 additions & 5 deletions api/src/api/controllers/brla.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ export const triggerBrlaOfframp = async (
}
};

export const getOfframpStatus = async (
req: Request<unknown, unknown, unknown, BrlaEndpoints.GetOfframpStatusRequest>,
res: Response<BrlaEndpoints.GetOfframpStatusResponse | BrlaEndpoints.BrlaErrorResponse>,
export const getRampStatus = async (
req: Request<unknown, unknown, unknown, BrlaEndpoints.GetRampStatusRequest>,
res: Response<BrlaEndpoints.GetRampStatusResponse | BrlaEndpoints.BrlaErrorResponse>,
): Promise<void> => {
try {
const { taxId } = req.query;
Expand Down Expand Up @@ -230,7 +230,7 @@ export const getOfframpStatus = async (
status: lastEventCached.data.status,
});
} catch (error) {
handleApiError(error, res, 'getOfframpStatus');
handleApiError(error, res, 'getRampStatus');
}
};

Expand Down Expand Up @@ -406,7 +406,7 @@ export const startKYC2 = async (
const subaccount = await brlaApiService.getSubaccount(taxId);

if (!subaccount) {
res.status(httpStatus.BAD_REQUEST).json({ error: 'Subaccount not found'});
res.status(httpStatus.BAD_REQUEST).json({ error: 'Subaccount not found' });
return;
}

Expand Down
4 changes: 3 additions & 1 deletion api/src/api/controllers/price.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ export const getPriceForProvider: RequestHandler<unknown, any, unknown, PriceQue
res.status(httpStatus.BAD_GATEWAY).json({ error: err.message });
} else {
console.error('Unexpected server error:', err);
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: 'An internal server error occurred while fetching the price.' });
res
.status(httpStatus.INTERNAL_SERVER_ERROR)
.json({ error: 'An internal server error occurred while fetching the price.' });
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion api/src/api/controllers/siwe.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ export const validateSiweSignature = async (
const message = error instanceof Error ? error.message : 'Unknown error';
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: `Could not validate signature: ${message}` });
}
};
};
12 changes: 9 additions & 3 deletions api/src/api/controllers/stellar.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export const createStellarTransactionHandler = async (
return;
} catch (error) {
console.error('Error in createStellarTransaction:', error);
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: 'Failed to create transaction', details: (error as Error).message });
res
.status(httpStatus.INTERNAL_SERVER_ERROR)
.json({ error: 'Failed to create transaction', details: (error as Error).message });
}
};

Expand All @@ -51,7 +53,9 @@ export const signSep10ChallengeHandler = async (
return;
} catch (error) {
console.error('Error in signSep10Challenge:', error);
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: 'Failed to sign challenge', details: (error as Error).message });
res
.status(httpStatus.INTERNAL_SERVER_ERROR)
.json({ error: 'Failed to sign challenge', details: (error as Error).message });
}
};

Expand All @@ -69,7 +73,9 @@ export const getSep10MasterPKHandler = async (
return;
} catch (error) {
console.error('Error in getSep10MasterPK:', error);
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: 'Failed to get master public key', details: (error as Error).message });
res
.status(httpStatus.INTERNAL_SERVER_ERROR)
.json({ error: 'Failed to get master public key', details: (error as Error).message });
}
};

Expand Down
2 changes: 0 additions & 2 deletions api/src/api/helpers/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export interface ContractBalance {
approximateNumber: number;
}


export function multiplyByPowerOfTen(bigDecimal: BigNumber, power: number) {
const newBigDecimal = new BigNumber(bigDecimal);
if (newBigDecimal.c[0] === 0) return newBigDecimal;
Expand Down Expand Up @@ -100,4 +99,3 @@ export function parseContractBalanceResponse(
approximateNumber: preciseBigDecimal.toNumber(),
};
}

2 changes: 1 addition & 1 deletion api/src/api/middlewares/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,4 +418,4 @@ export const validateStartKyc2: RequestHandler = (req, res, next) => {
}

next();
};
};
2 changes: 1 addition & 1 deletion api/src/api/routes/v1/brla.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ router.route('/getUser').get(brlaController.getBrlaUser);

router.route('/getUserRemainingLimit').get(brlaController.getBrlaUserRemainingLimit);

router.route('/getOfframpStatus').get(brlaController.getOfframpStatus);
router.route('/getRampStatus').get(brlaController.getRampStatus);

router.route('/getKycStatus').get(brlaController.fetchSubaccountKycStatus);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import logger from '../../../../config/logger';
import { ApiManager } from '../../pendulum/apiManager';
import { submitExtrinsic } from '@pendulum-chain/api-solang';

const CLEANUP_WAITING_TIME_MINUTES = 180; // 3 hours
/**
* Post process handler for Moonbeam cleanup operations
*/
Expand Down Expand Up @@ -51,10 +52,12 @@ export class MoonbeamPostProcessHandler extends BasePostProcessHandler {
const timeDifferenceMs = Date.now() - completeTime.getTime();
const timeDifferenceMinutes = timeDifferenceMs / (1000 * 60);

if (timeDifferenceMinutes < 15) {
if (timeDifferenceMinutes < CLEANUP_WAITING_TIME_MINUTES) {
return [
false,
this.createErrorObject(`At least 15 minutes must pass after the complete phase for moonbeam cleanup`),
this.createErrorObject(
`At least ${CLEANUP_WAITING_TIME_MINUTES} minutes must pass after the complete phase for moonbeam cleanup`,
),
];
}
} catch (e) {
Expand Down
8 changes: 2 additions & 6 deletions api/src/api/services/ramp/quote.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,7 @@ export class QuoteService extends BaseRampService {
const { toAmountMin } = route.estimate;

// Check against our moonbeam funding amounts.
const squidrouterSwapValue = multiplyByPowerOfTen(Big(route.transactionRequest.value), -18);

const squidrouterSwapValue = multiplyByPowerOfTen(new Big(route.transactionRequest.value), -18);
const fundingAmountUnits =
getNetworkFromDestination(to) === Networks.Ethereum
? Big(MOONBEAM_EPHEMERAL_STARTING_BALANCE_UNITS_ETHEREUM)
Expand All @@ -268,10 +267,7 @@ export class QuoteService extends BaseRampService {
});
}

amountOut.preciseQuotedAmountOut = parseContractBalanceResponse(
outTokenDetails.decimals,
BigInt(toAmountMin),
);
amountOut.preciseQuotedAmountOut = parseContractBalanceResponse(outTokenDetails.decimals, BigInt(toAmountMin));

amountOut.roundedDownQuotedAmountOut = amountOut.preciseQuotedAmountOut.preciseBigDecimal.round(2, 0);
amountOut.effectiveExchangeRate = stringifyBigWithSignificantDecimals(
Expand Down
1 change: 0 additions & 1 deletion api/src/api/services/slack.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export class SlackNotifier {
}

public async sendMessage(message: SlackMessage): Promise<void> {

const slackUserId = process.env.SLACK_USER_ID;

const messageWithUserTag = {
Expand Down
2 changes: 2 additions & 0 deletions api/src/api/services/transactions/squidrouter/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AXL_USDC_MOONBEAM, getNetworkId, Networks } from 'shared';

export const SQUIDROUTER_FEE_OVERPAY = 0.25; // 25% overpayment

interface ConfigBase {
toChainId: string;
axlUSDC_MOONBEAM: string;
Expand Down
26 changes: 24 additions & 2 deletions api/src/api/services/transactions/squidrouter/onramp.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { createPublicClient, encodeFunctionData, http } from 'viem';
import { moonbeam } from 'viem/chains';
import { AXL_USDC_MOONBEAM, EvmTokenDetails, getNetworkId, Networks } from 'shared';
import { AXL_USDC_MOONBEAM, EvmTokenDetails, getNetworkFromDestination, getNetworkId, Networks } from 'shared';
import { createOnrampRouteParams, getRoute } from './route';

import erc20ABI from '../../../../contracts/ERC20';
import Big from 'big.js';
import { SQUIDROUTER_FEE_OVERPAY } from './config';
import {
MOONBEAM_EPHEMERAL_STARTING_BALANCE_UNITS,
MOONBEAM_EPHEMERAL_STARTING_BALANCE_UNITS_ETHEREUM,
} from '../../../../constants/constants';
import { multiplyByPowerOfTen } from '../../pendulum/helpers';

export interface OnrampSquidrouterParams {
fromAddress: string;
Expand Down Expand Up @@ -35,6 +42,10 @@ export interface OnrampTransactionData {
};
}

function bigNumberMin(a: Big, b: Big): Big {
return a.lt(b) ? a : b;
}

export async function createOnrampSquidrouterTransactions(
params: OnrampSquidrouterParams,
): Promise<OnrampTransactionData> {
Expand Down Expand Up @@ -80,10 +91,21 @@ export async function createOnrampSquidrouterTransactions(
maxPriorityFeePerGas: maxPriorityFeePerGas.toString(),
};

const fundingAmountUnits =
getNetworkFromDestination(params.toNetwork) === Networks.Ethereum
? Big(MOONBEAM_EPHEMERAL_STARTING_BALANCE_UNITS_ETHEREUM)
: Big(MOONBEAM_EPHEMERAL_STARTING_BALANCE_UNITS);
const squidrouterSwapValueBuffer = getNetworkFromDestination(params.toNetwork) === Networks.Ethereum ? 10 : 2;
const freeFundingAmountRaw = multiplyByPowerOfTen(fundingAmountUnits.minus(squidrouterSwapValueBuffer), 18); // 18 decimals for GLMR. Moonbeam is always starting chain.
const overpaidFee = bigNumberMin(
new Big(route.transactionRequest.value).mul(1 + SQUIDROUTER_FEE_OVERPAY),
freeFundingAmountRaw,
);

const swapData = {
to: transactionRequest.target as `0x${string}`,
data: transactionRequest.data,
value: transactionRequest.value,
value: overpaidFee.toFixed(0, 0),
gas: transactionRequest.gasLimit,
maxFeePerGas: maxFeePerGas.toString(),
maxPriorityFeePerGas: maxPriorityFeePerGas.toString(),
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/services/api/brla.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export class BrlaService {
* @param taxId The user's tax ID
* @returns The offramp status
*/
static async getOfframpStatus(taxId: string): Promise<BrlaEndpoints.GetOfframpStatusResponse> {
return apiRequest<BrlaEndpoints.GetOfframpStatusResponse>('get', `${this.BASE_PATH}/getOfframpStatus`, undefined, {
static async getRampStatus(taxId: string): Promise<BrlaEndpoints.GetRampStatusResponse> {
return apiRequest<BrlaEndpoints.GetRampStatusResponse>('get', `${this.BASE_PATH}/getRampStatus`, undefined, {
params: { taxId },
});
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/services/signingService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const fetchSep10Signatures = async ({
};

export const fetchOfframpStatus = async (taxId: string) => {
const statusResponse = await fetch(`${SIGNING_SERVICE_URL}/v1/brla/getOfframpStatus?taxId=${taxId}`);
const statusResponse = await fetch(`${SIGNING_SERVICE_URL}/v1/brla/getRampStatus?taxId=${taxId}`);

if (statusResponse.status !== 200) {
if (statusResponse.status === 404) {
Expand Down
6 changes: 3 additions & 3 deletions shared/src/endpoints/brla.endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ export namespace BrlaEndpoints {
kycLevel: number;
}

// GET /brla/getOfframpStatus?taxId=:taxId
export interface GetOfframpStatusRequest {
// GET /brla/getRampStatus?taxId=:taxId
export interface GetRampStatusRequest {
taxId: string;
}

export interface GetOfframpStatusResponse {
export interface GetRampStatusResponse {
type: string;
status: string;
}
Expand Down
Loading