Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
8f96dac
add first draft
gianfra-t Jan 15, 2026
16885b4
move dynamic token config to shared, adjust checks and getters
gianfra-t Jan 15, 2026
9edce1e
test react window
gianfra-t Jan 16, 2026
e75470c
solve cyclic import issue
gianfra-t Jan 19, 2026
d0d9564
use balances from alchemy endpoint
gianfra-t Jan 19, 2026
c30b3de
Merge branch 'dynamic-onchain-tokens' of github.com:pendulum-chain/vo…
gianfra-t Jan 19, 2026
8c0d7f3
fetch balances from portfolio endpoint
gianfra-t Jan 19, 2026
02dda7c
order balance by usd value
gianfra-t Jan 19, 2026
2eeded6
replace react-window with react-virtual
gianfra-t Jan 19, 2026
b1b5d5b
improve list ordering, performance
gianfra-t Jan 19, 2026
0ba8c1c
add proper logo to Onramp selected token
gianfra-t Jan 19, 2026
42960cf
re-arrange import
gianfra-t Jan 20, 2026
40dd15a
remove unused coins imgs
Sharqiewicz Jan 21, 2026
13ca8a7
implement TokenIconWithNetwork
Sharqiewicz Jan 21, 2026
41a8c11
fix lint issues
Sharqiewicz Jan 21, 2026
a2eaa7d
refactor getting asset icons
Sharqiewicz Jan 21, 2026
0c43f6b
refactor PopularTokens tokens definition
Sharqiewicz Jan 21, 2026
33a0b90
remove unused tokens from useGetAssetIcon
Sharqiewicz Jan 21, 2026
f7febd3
refactor tanstack virtualizer definition
Sharqiewicz Jan 21, 2026
9f98bf0
optimize sorting tokens by balance
Sharqiewicz Jan 21, 2026
a57c21b
add new components stories
Sharqiewicz Jan 21, 2026
47aee76
improve animations and introduce reduced-animation setting for users
Sharqiewicz Jan 21, 2026
41c80c4
change logo redirect to stay under /widget when user is in the widget…
Sharqiewicz Jan 22, 2026
5c58390
Merge branch 'staging' into chore/change-logo-redirect
Sharqiewicz Jan 22, 2026
663d331
fix type issues
Sharqiewicz Jan 22, 2026
c5b5bdc
fix: normalize token keys to uppercase to prevent duplicates
Sharqiewicz Jan 28, 2026
eeb5d24
feat: add fallbackLogoURI property to EVM tokens
Sharqiewicz Jan 28, 2026
71e0c5a
feat: implement fallback image handling in token selection
Sharqiewicz Jan 28, 2026
e1606f1
feat: implement fallback image handling in ramp inputs
Sharqiewicz Jan 28, 2026
0f25c6a
feat: implement fallback image handling in transaction summary
Sharqiewicz Jan 28, 2026
8075f76
Add loading placeholder for token images
Sharqiewicz Jan 28, 2026
e28930a
feat: add TokenImage component with loading and fallback support
Sharqiewicz Jan 28, 2026
2c07cb0
feat: add getTokenLogoURIs helper function
Sharqiewicz Jan 28, 2026
98f5d33
refactor: use TokenImage component for token icons
Sharqiewicz Jan 28, 2026
1cf91f7
fix type issues
Sharqiewicz Jan 28, 2026
c5f9581
change hex colors to oklch
Sharqiewicz Jan 28, 2026
471088c
fix token filtering to only our present chains
Sharqiewicz Jan 28, 2026
5cfae2b
change isOnChainToken type guard
Sharqiewicz Jan 28, 2026
b8a13a9
Initial plan
Copilot Jan 28, 2026
ae4c582
Fix import ordering, remove unused imports, and address code review f…
Copilot Jan 28, 2026
a846f51
Address dynamic token keying and type consistency issues
Copilot Jan 28, 2026
41bb805
Add deterministic tie-breaker for token deduplication
Copilot Jan 28, 2026
e4b05e7
Merge pull request #1014 from pendulum-chain/dynamic-onchain-tokens
ebma Jan 28, 2026
9db6085
feat: enhance useTokenIcon to accept token details objects
Sharqiewicz Jan 28, 2026
e54986c
refactor: migrate ramp components to useTokenIcon
Sharqiewicz Jan 28, 2026
9a69042
refactor: migrate UI components to useTokenIcon
Sharqiewicz Jan 28, 2026
f22676f
chore: remove unused tokenHelpers.ts
Sharqiewicz Jan 28, 2026
ac3fa36
Fix assethubToPendulum transaction being filtered out when EVM wallet…
Sharqiewicz Jan 29, 2026
40a78aa
fix translations
Sharqiewicz Jan 29, 2026
8e6cdb1
Fix small type-casting issue
ebma Jan 30, 2026
f3df258
Improve check for substrate transaction
ebma Jan 30, 2026
55f3498
rename TokenImage into TokenIcon
Sharqiewicz Jan 30, 2026
6f2a04a
show TokenIconWithNetwork instead of TokenImage
Sharqiewicz Jan 30, 2026
b6b66ef
refactor dynamicEvmTokens
Sharqiewicz Jan 30, 2026
d9b964c
implement low liquidity error
Sharqiewicz Jan 30, 2026
838b91b
Merge branch 'staging' into chore/change-logo-redirect
ebma Jan 30, 2026
611b588
Amend merge
ebma Jan 30, 2026
4ebf431
Initial plan
Copilot Jan 30, 2026
ff216ff
Address review feedback: Add motion-reduce classes, fix z-index, corr…
Copilot Jan 30, 2026
c995fe2
Fix type issue
ebma Jan 30, 2026
4db0429
Add fallback logo URI generation and logging for address mismatches
ebma Jan 30, 2026
60242a5
Merge pull request #1049 from pendulum-chain/fix/assethubToPendulum-f…
ebma Jan 30, 2026
bd69be3
Update animations.ts
ebma Jan 30, 2026
580c2ea
Merge pull request #1050 from pendulum-chain/copilot/sub-pr-1028
ebma Jan 30, 2026
8073b8d
Merge pull request #1048 from pendulum-chain/dynamic-onchain-tokens
ebma Jan 30, 2026
a48b406
Merge pull request #1028 from pendulum-chain/chore/change-logo-redirect
ebma Jan 30, 2026
f1d728c
Merge staging branch and resolve conflicts
Copilot Jan 30, 2026
ceea6e9
Revert "Merge staging branch and resolve conflicts"
ebma Jan 30, 2026
6184275
Merge branch 'staging' into copilot/sub-pr-1014
ebma Jan 30, 2026
2839e36
Small amend
ebma Jan 30, 2026
56485b6
Merge pull request #1046 from pendulum-chain/copilot/sub-pr-1014
ebma Jan 30, 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
10 changes: 5 additions & 5 deletions apps/api/src/api/controllers/admin/partnerApiKeys.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { generateApiKey, getKeyPrefix, hashApiKey } from "../../middlewares/apiK
* Create a new API key pair (public + secret) for a partner
* POST /v1/admin/partners/:partnerName/api-keys
*/
export async function createApiKey(req: Request, res: Response): Promise<void> {
export async function createApiKey(req: Request<{ partnerName: string }>, res: Response): Promise<void> {
try {
const { partnerName } = req.params;
const partnerName = req.params.partnerName as string;
const { name, expiresAt } = req.body;

// Verify at least one partner with this name exists and is active
Expand Down Expand Up @@ -110,9 +110,9 @@ export async function createApiKey(req: Request, res: Response): Promise<void> {
* List all API keys for a partner (by name)
* GET /v1/admin/partners/:partnerName/api-keys
*/
export async function listApiKeys(req: Request, res: Response): Promise<void> {
export async function listApiKeys(req: Request<{ partnerName: string }>, res: Response): Promise<void> {
try {
const { partnerName } = req.params;
const partnerName = req.params.partnerName as string;

// Verify partner exists
const partners = await Partner.findAll({
Expand Down Expand Up @@ -180,7 +180,7 @@ export async function listApiKeys(req: Request, res: Response): Promise<void> {
* Revoke (soft delete) an API key
* DELETE /v1/admin/partners/:partnerName/api-keys/:keyId
*/
export async function revokeApiKey(req: Request, res: Response): Promise<void> {
export async function revokeApiKey(req: Request<{ partnerName: string; keyId: string }>, res: Response): Promise<void> {
try {
const { partnerName, keyId } = req.params;

Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/api/controllers/maintenance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ export const getAllMaintenanceSchedules: RequestHandler = async (_, res) => {
* @returns {Object} 404 - Schedule not found
* @returns {Object} 500 - Internal server error
*/
export const updateScheduleActiveStatus: RequestHandler = async (req, res) => {
export const updateScheduleActiveStatus: RequestHandler<{ id: string }> = async (req, res) => {
try {
const { id } = req.params;
const id = req.params.id as string;
const { isActive } = req.body;

if (typeof isActive !== "boolean") {
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/api/controllers/metrics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const zeroVolume = (key: string, keyName: "day" | "month"): any => ({
});

async function getMonthlyVolumes(): Promise<MonthlyVolume[]> {
const cacheKey = `monthly`;
const cacheKey = "monthly";
const cached = cache.get<MonthlyVolume[]>(cacheKey);
if (cached) return cached;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class DistributeFeesHandler extends BasePhaseHandler {
logger.info(`Found existing distribute fee hash for ramp ${state.id}: ${existingHash}`);

const status = await this.checkExtrinsicStatus(existingHash).catch((_: unknown) => {
throw this.createRecoverableError(`Failed to check extrinsic status`);
throw this.createRecoverableError("Failed to check extrinsic status");
});

if (status === ExtrinsicStatus.Success) {
Expand Down Expand Up @@ -212,10 +212,14 @@ export class DistributeFeesHandler extends BasePhaseHandler {
reject(this.handleDispatchError(api, dispatchError, systemExtrinsicFailedEvent, "distributeFees"));
}

if (status.isBroadcast || status.isInBlock) {
if (status.isBroadcast) {
logger.info(`Transaction broadcasted: ${status.asBroadcast.toString()}`);
resolve(txHash.toHex());
}
if (status.isInBlock) {
logger.info(`Transaction in block: ${status.asInBlock.toString()}`);
resolve(txHash.toHex());
}
})
.catch((error: unknown) => {
logger.error("Error submitting transaction to distribute fees:", error);
Expand Down
25 changes: 23 additions & 2 deletions apps/api/src/api/services/priceFeed.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
EvmToken,
getPendulumDetails,
getTokenOutAmount,
getTokenUsdPrice,
isFiatToken,
normalizeTokenSymbol,
PENDULUM_USDC_AXL,
Expand Down Expand Up @@ -488,6 +489,16 @@ export class PriceFeedService {
}

private async convertUsdToCrypto(amount: string, toCurrency: RampCurrency, decimals: number): Promise<string> {
// Try dynamic token price first
const dynamicPrice = getTokenUsdPrice(toCurrency);
if (dynamicPrice !== undefined && dynamicPrice > 0) {
const result = new Big(amount).div(dynamicPrice).toFixed(decimals);
logger.debug(`Converted ${amount} USD to ${result} ${toCurrency} using dynamic price: ${dynamicPrice}`);
return result;
}

// Fall back to CoinGecko
logger.debug(`No dynamic price for ${toCurrency}, falling back to CoinGecko`);
const tokenId = this.getCoinGeckoTokenId(toCurrency);
if (!tokenId) {
throw new Error(`No CoinGecko token ID mapping for ${toCurrency}`);
Expand All @@ -499,19 +510,29 @@ export class PriceFeedService {
}

const result = new Big(amount).div(cryptoPriceUSD).toFixed(decimals);
logger.debug(`Converted ${amount} USD to ${result} ${toCurrency} using price: ${cryptoPriceUSD}`);
logger.debug(`Converted ${amount} USD to ${result} ${toCurrency} using CoinGecko price: ${cryptoPriceUSD}`);
return result;
}

private async convertCryptoToUsd(amount: string, fromCurrency: RampCurrency, decimals: number): Promise<string> {
// Try dynamic token price first
const dynamicPrice = getTokenUsdPrice(fromCurrency);
if (dynamicPrice !== undefined && dynamicPrice > 0) {
const result = new Big(amount).mul(dynamicPrice).toFixed(decimals);
logger.debug(`Converted ${amount} ${fromCurrency} to ${result} USD using dynamic price: ${dynamicPrice}`);
return result;
}

// Fall back to CoinGecko
logger.debug(`No dynamic price for ${fromCurrency}, falling back to CoinGecko`);
const tokenId = this.getCoinGeckoTokenId(fromCurrency);
if (!tokenId) {
throw new Error(`No CoinGecko token ID mapping for ${fromCurrency}`);
}

const cryptoPriceUSD = await this.getCryptoPrice(tokenId, "usd");
const result = new Big(amount).mul(cryptoPriceUSD).toFixed(decimals);
logger.debug(`Converted ${amount} ${fromCurrency} to ${result} USD using price: ${cryptoPriceUSD}`);
logger.debug(`Converted ${amount} ${fromCurrency} to ${result} USD using CoinGecko price: ${cryptoPriceUSD}`);
return result;
}
}
Expand Down
14 changes: 12 additions & 2 deletions apps/api/src/api/services/quote/core/squidrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,18 @@ export async function calculateEvmBridgeAndNetworkFee(request: EvmBridgeRequest)
outputTokenDecimals
};
} catch (error) {
logger.error(`Error calculating EVM bridge and network fee: ${error instanceof Error ? error.message : String(error)}`);
// We assume that the error is due to a low input amount
const errorMessage = error instanceof Error ? error.message : String(error);
logger.error(`Error calculating EVM bridge and network fee: ${errorMessage}`);

// Check for specific SquidRouter error types
if (errorMessage.toLowerCase().includes("low liquidity") || errorMessage.toLowerCase().includes("reduce swap amount")) {
throw new APIError({
message: QuoteError.LowLiquidity,
status: httpStatus.BAD_REQUEST
});
}

// Default to generic error for other cases
throw new APIError({
message: QuoteError.InputAmountTooLow,
status: httpStatus.BAD_REQUEST
Expand Down
5 changes: 4 additions & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dotenv.config({
path: [path.resolve(process.cwd(), ".env"), path.resolve(process.cwd(), "../.env")]
});

import { ApiManager, EvmClientManager, setLogger } from "@vortexfi/shared";
import { ApiManager, EvmClientManager, initializeEvmTokens, setLogger } from "@vortexfi/shared";
import { config, testDatabaseConnection } from "./config";
import cryptoService from "./config/crypto";
import app from "./config/express";
Expand Down Expand Up @@ -53,6 +53,9 @@ const initializeApp = async () => {
// Initialize RSA keys for webhook signing
cryptoService.initializeKeys();

// Initialize dynamic EVM tokens from SquidRouter API (falls back to static config on failure)
await initializeEvmTokens();

// Test database connection
await testDatabaseConnection();

Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { dirname, join } from "path";
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): any {
function getAbsolutePath(value: string) {
return dirname(require.resolve(join(value, "package.json")));
}
const config: StorybookConfig = {
Expand Down
32 changes: 16 additions & 16 deletions apps/frontend/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
@plugin "daisyui";

:root {
--color-primary: #0f4dc0;
--color-primary-content: #fff;
--color-secondary: #f4f5f6;
--color-secondary-content: #58667e;
--color-accent: #db2777;
--color-accent-content: #000;
--color-neutral: #eff2f5;
--color-neutral-content: #58667e;
--color-base-100: #f5f9fa;
--color-base-200: #fff;
--color-base-300: #e3e7eb;
--color-base-content: #58667e;
--color-primary: oklch(0.45 0.2 260);
--color-primary-content: oklch(1 0 0);
--color-secondary: oklch(0.97 0.003 260);
--color-secondary-content: oklch(0.5 0.04 250);
--color-accent: oklch(0.55 0.22 350);
--color-accent-content: oklch(0 0 0);
--color-neutral: oklch(0.96 0.005 250);
--color-neutral-content: oklch(0.5 0.04 250);
--color-base-100: oklch(0.98 0.005 210);
--color-base-200: oklch(1 0 0);
--color-base-300: oklch(0.92 0.008 250);
--color-base-content: oklch(0.5 0.04 250);

--radius-field: 9px;
--border: 1px;

--text: #111;
--bg-modal: #fff;
--modal-border: #e5e5e5;
--text: oklch(0.15 0 0);
--bg-modal: oklch(1 0 0);
--modal-border: oklch(0.91 0 0);
--rounded-btn: 9px;
--btn-text-case: none;
}
Expand Down Expand Up @@ -75,7 +75,7 @@
.input-ghost[aria-readonly="true"]:focus-within {
background-color: transparent !important;
color: var(--color-base-content);
border-color: #0000;
border-color: oklch(0 0 0 / 0);
box-shadow: none;
}

Expand Down
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@tanstack/react-query": "^5.64.2",
"@tanstack/react-router": "^1.136.8",
"@tanstack/react-router-devtools": "^1.136.8",
"@tanstack/react-virtual": "^3.13.18",
"@tanstack/zod-adapter": "^1.144.0",
"@types/crypto-js": "^4.2.2",
"@vitejs/plugin-react": "^4.3.4",
Expand Down
38 changes: 0 additions & 38 deletions apps/frontend/src/assets/coins/DOT_ASSETHUB.svg

This file was deleted.

13 changes: 0 additions & 13 deletions apps/frontend/src/assets/coins/ETH.svg

This file was deleted.

37 changes: 0 additions & 37 deletions apps/frontend/src/assets/coins/ETH_ARBITRUM.svg

This file was deleted.

30 changes: 0 additions & 30 deletions apps/frontend/src/assets/coins/ETH_BASE.svg

This file was deleted.

Loading