Skip to content
Open
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
2 changes: 1 addition & 1 deletion apps/api/src/api/controllers/moonbeam.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const executeXcmController = async (
try {
const { maxFeePerGas, maxPriorityFeePerGas } = await moonbeamClient.estimateFeesPerGas();
// Safe to send multiple times. Idempotent.
const hash = (await evmClientManager.sendTransactionWithBlindRetry(Networks.Moonbeam, moonbeamExecutorAccount, {
const hash = (await evmClientManager.sendTransaction(Networks.Moonbeam, moonbeamExecutorAccount, {
data,
maxFeePerGas,
maxPriorityFeePerGas,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class MoneriumOnrampSelfTransferHandler extends BasePhaseHandler {
],
functionName: "permit"
});
permitHash = await this.evmClientManager.sendTransactionWithBlindRetry(Networks.Polygon, account, {
permitHash = await this.evmClientManager.sendTransaction(Networks.Polygon, account, {
data: permitData,
to: ERC20_EURE_POLYGON_V2
});
Expand Down Expand Up @@ -160,7 +160,7 @@ export class MoneriumOnrampSelfTransferHandler extends BasePhaseHandler {
private async executeTransaction(txData: string): Promise<string> {
try {
const evmClientManager = EvmClientManager.getInstance();
const txHash = await evmClientManager.sendRawTransactionWithRetry(Networks.Polygon, txData as `0x${string}`);
const txHash = await evmClientManager.sendRawTransaction(Networks.Polygon, txData as `0x${string}`);
return txHash;
} catch (error) {
logger.error("Error sending raw transaction", error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class MoonbeamToPendulumPhaseHandler extends BasePhaseHandler {
const publicClient = evmClientManager.getClient(Networks.Moonbeam);

const isHashRegisteredInSplitReceiver = async () => {
const result = await evmClientManager.readContractWithRetry<bigint>(Networks.Moonbeam, {
const result = await evmClientManager.readContract<bigint>(Networks.Moonbeam, {
abi: splitReceiverABI,
address: MOONBEAM_RECEIVER_CONTRACT_ADDRESS,
args: [squidRouterReceiverHash],
Expand Down Expand Up @@ -99,7 +99,7 @@ export class MoonbeamToPendulumPhaseHandler extends BasePhaseHandler {
let attempt = 0;
while (attempt < 5 && (!receipt || receipt.status !== "success")) {
// blind retry for transaction submission
obtainedHash = await evmClientManager.sendTransactionWithBlindRetry(Networks.Moonbeam, moonbeamExecutorAccount, {
obtainedHash = await evmClientManager.sendTransaction(Networks.Moonbeam, moonbeamExecutorAccount, {
data,
maxFeePerGas,
maxPriorityFeePerGas,
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
.btn-vortex-accent:hover {
@apply bg-blue-100;
@apply text-blue-700;
@apply border-blue-300;
@apply border-blue-700;
}

.btn-vortex-primary-inverse {
Expand Down
43 changes: 39 additions & 4 deletions apps/frontend/src/machines/actors/register.actor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
AccountMeta,
API,
ApiManager,
EphemeralAccountType,
FiatToken,
getAddressForFormat,
Networks,
RampDirection,
RegisterRampRequest,
SubstrateApiNetwork,
signUnsignedTransactions
} from "@vortexfi/shared";
import { config } from "../../config";
Expand All @@ -15,7 +17,8 @@ import { RampState } from "../../types/phases";
import { RampContext } from "../types";

export enum RegisterRampErrorType {
InvalidInput = "INVALID_INPUT"
InvalidInput = "INVALID_INPUT",
ConnectionFailed = "CONNECTION_FAILED"
}

export class RegisterRampError extends Error {
Expand All @@ -26,6 +29,37 @@ export class RegisterRampError extends Error {
}
}

const API_CONNECTION_TIMEOUT = 15000; // 15 seconds

async function getApiWithTimeout(
apiManager: ApiManager,
network: SubstrateApiNetwork,
timeoutMs: number = API_CONNECTION_TIMEOUT
): Promise<API> {
const uuid = crypto.randomUUID();

const tryConnect = async (): Promise<API> => {
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error(`Connection to ${network} timed out after ${timeoutMs}ms`)), timeoutMs);
});

const api = await Promise.race([apiManager.getApiWithShuffling(network, uuid), timeoutPromise]);
return api;
};

for (let attempt = 1; attempt <= 3; attempt++) {
try {
return await tryConnect();
} catch {
if (attempt === 3) {
throw new RegisterRampError(`Failed to connect to ${network} after 3 attempts`, RegisterRampErrorType.ConnectionFailed);
}
}
}

throw new RegisterRampError(`Failed to connect to ${network}`, RegisterRampErrorType.ConnectionFailed);
}

export const registerRampActor = async ({ input }: { input: RampContext }): Promise<RampState> => {
const { executionInput, chainId, connectedWalletAddress, authToken, paymentData, quote } = input;

Expand All @@ -39,9 +73,10 @@ export const registerRampActor = async ({ input }: { input: RampContext }): Prom
}

const apiManager = ApiManager.getInstance();
const pendulumApiComponents = await apiManager.getApi(Networks.Pendulum);
const moonbeamApiComponents = await apiManager.getApi(Networks.Moonbeam);
const hydrationApiComponents = await apiManager.getApi(Networks.Hydration);

const pendulumApiComponents = await getApiWithTimeout(apiManager, Networks.Pendulum);
const moonbeamApiComponents = await getApiWithTimeout(apiManager, Networks.Moonbeam);
const hydrationApiComponents = await getApiWithTimeout(apiManager, Networks.Hydration);

if (!chainId) {
throw new RegisterRampError("Chain ID is required to register ramp.", RegisterRampErrorType.InvalidInput);
Expand Down
18 changes: 4 additions & 14 deletions apps/frontend/src/machines/kyc.states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,10 @@ export const kycStateNode = {
}),
onDone: [
{
actions: assign(({ context, event }: { context: RampContext; event: any }) => {
console.log("Monerium KYC completed with response:", event.output);
return {
...context,
authToken: event.output.authToken
};
}),
actions: assign(({ context, event }: { context: RampContext; event: any }) => ({
...context,
authToken: event.output.authToken
})),
guard: ({ event }: { event: any }) => !!event.output.authToken,
target: "VerificationComplete"
},
Expand Down Expand Up @@ -216,13 +213,6 @@ export const kycStateNode = {
VerificationComplete: {
always: {
target: "#ramp.KycComplete"
},
entry: {
actions: [
({ context }: any) => {
console.log("KYC verification completed successfully:", context.kycResponse);
}
]
}
}
}
Expand Down
17 changes: 11 additions & 6 deletions apps/frontend/src/machines/moneriumKyc.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,20 @@ export const moneriumKycMachine = setup({
id: "exchangeMoneriumCode",
input: ({ context }) => context,
onDone: {
actions: assign({
authToken: ({ event }) => event.output.authToken
}),
actions: [
assign({
authToken: ({ event }) => event.output.authToken
})
],
target: "Done"
},
onError: {
actions: assign({
error: () => new MoneriumKycMachineError("Error exchanging Monerium code", MoneriumKycMachineErrorType.UnknownError)
}),
actions: [
assign({
error: () =>
new MoneriumKycMachineError("Error exchanging Monerium code", MoneriumKycMachineErrorType.UnknownError)
})
],
target: "Failure"
},
src: "exchangeMoneriumCode"
Expand Down
8 changes: 5 additions & 3 deletions apps/frontend/src/machines/ramp.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,11 @@ export const rampMachine = setup({
invoke: {
input: ({ context }) => context,
onDone: {
actions: assign({
rampState: ({ event }) => event.output
}),
actions: [
assign({
rampState: ({ event }) => event.output
})
],
target: "UpdateRamp"
},
onError: {
Expand Down
35 changes: 0 additions & 35 deletions apps/frontend/src/services/api/price.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,39 +130,4 @@ export class PriceService {
}
});
}

/**
* @deprecated Use getAllPricesBundled instead for better error handling and performance
* Get price information from all providers
* @param sourceCurrency The source currency (crypto for offramp, fiat for onramp)
* @param targetCurrency The target currency (fiat for offramp, crypto for onramp)
* @param amount The amount to convert
* @param direction The direction of the conversion (onramp or offramp)
* @param network Optional network name
* @returns Price information from all providers
*/
static async getAllPrices(
sourceCurrency: Currency,
targetCurrency: Currency,
amount: string,
direction: RampDirection,
network?: string
): Promise<Record<PriceProvider, BundledPriceResult>> {
const providers: PriceProvider[] = ["alchemypay", "moonpay", "transak"];

const results = await Promise.allSettled(
providers.map(provider => this.getPrice(provider, sourceCurrency, targetCurrency, amount, direction, network))
);

return results.reduce(
(acc, result, index) => {
const provider = providers[index];
if (result.status === "fulfilled") {
acc[provider] = result.value;
}
return acc;
},
{} as Record<PriceProvider, BundledPriceResult>
);
}
}
42 changes: 25 additions & 17 deletions apps/frontend/src/wagmiConfig.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import { arbitrum, avalanche, base, bsc, mainnet, polygon, polygonAmoy } from "@reown/appkit/networks";
import { createAppKit } from "@reown/appkit/react";
import { WagmiAdapter } from "@reown/appkit-adapter-wagmi";
import { http } from "wagmi";
import { createSmartFallbackTransports } from "@vortexfi/shared";

import { config } from "./config";

// If we have an Alchemy API key, we can use it to fetch data from Polygon, otherwise use the default endpoint
const transports = config.alchemyApiKey
const chainRpcConfig = config.alchemyApiKey
? {
[arbitrum.id]: http(`https://arb-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`),
[avalanche.id]: http(`https://avax-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`),
[base.id]: http(`https://base-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`),
[bsc.id]: http(`https://bnb-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`),
[mainnet.id]: http(`https://eth-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`),
[polygon.id]: http(`https://polygon-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`),
[polygonAmoy.id]: http("")
[arbitrum.id]: [`https://arb-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`, "https://arb1.arbitrum.io/rpc"],
[avalanche.id]: [
`https://avax-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`,
"https://api.avax.network/ext/bc/C/rpc"
],
[base.id]: [`https://base-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`, "https://mainnet.base.org"],
[bsc.id]: [`https://bnb-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`, "https://bsc-dataseed.binance.org"],
[mainnet.id]: [`https://eth-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`, "https://eth.llamarpc.com"],
[polygon.id]: [`https://polygon-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`, "https://polygon-rpc.com"],
[polygonAmoy.id]: ["https://polygon-amoy.api.onfinality.io/public", "https://rpc-amoy.polygon.technology"]
}
: {
[arbitrum.id]: http(""),
[avalanche.id]: http(""),
[base.id]: http(""),
[bsc.id]: http(""),
[mainnet.id]: http(""),
[polygon.id]: http(""),
[polygonAmoy.id]: http("")
[arbitrum.id]: ["https://arb1.arbitrum.io/rpc"],
[avalanche.id]: ["https://api.avax.network/ext/bc/C/rpc"],
[base.id]: ["https://mainnet.base.org"],
[bsc.id]: ["https://bsc-dataseed.binance.org"],
[mainnet.id]: ["https://eth.llamarpc.com"],
[polygon.id]: ["https://polygon-rpc.com"],
[polygonAmoy.id]: ["https://polygon-amoy.api.onfinality.io/public", "https://rpc-amoy.polygon.technology"]
};

// Create smart fallback transports with automatic retry and RPC switching
const transports = createSmartFallbackTransports(chainRpcConfig, {
initialDelayMs: 500,
timeout: 10_000
});

const metadata = {
description: "Vortex",
icons: [],
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/src/services/evm/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function getEvmTokenBalance({ tokenAddress, ownerAddress, chain }:
try {
const evmClientManager = EvmClientManager.getInstance();

const balanceResult = await evmClientManager.readContractWithRetry<string>(chain, {
const balanceResult = await evmClientManager.readContract<string>(chain, {
abi: erc20ABI,
address: tokenAddress,
args: [ownerAddress],
Expand Down Expand Up @@ -56,7 +56,7 @@ export function checkEvmBalancePeriodically(

const checkBalance = async () => {
try {
const result = await evmClientManager.readContractWithRetry<string>(chain, {
const result = await evmClientManager.readContract<string>(chain, {
abi: erc20ABI,
address: tokenAddress as EvmAddress,
args: [brlaEvmAddress],
Expand Down
Loading