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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# backend

## 1.56.1

### Patch Changes

- 2a2ea8a: SOR - Fix unwrapRates scaling
- 4c84676: move pricing query to graphql
- f9a6468: pricing query cleanup
- 36aa8c9: add xlayer support

## 1.56.0

### Minor Changes
Expand Down
1 change: 1 addition & 0 deletions apps/api/gql/generated-schema-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const schema = gql`
POLYGON
SEPOLIA
SONIC
XLAYER
ZKEVM
}

Expand Down
1 change: 1 addition & 0 deletions apps/api/gql/generated-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export type GqlChain =
| 'POLYGON'
| 'SEPOLIA'
| 'SONIC'
| 'XLAYER'
| 'ZKEVM';

export interface GqlFeaturePoolGroupItemExternalLink {
Expand Down
1 change: 1 addition & 0 deletions apps/api/gql/schema/__mocks__/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export enum GqlChain {
POLYGON = 'POLYGON',
SEPOLIA = 'SEPOLIA',
SONIC = 'SONIC',
XLAYER = 'XLAYER',
ZKEVM = 'ZKEVM',
}

Expand Down
1 change: 1 addition & 0 deletions apps/api/gql/schema/base.gql
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ enum GqlChain {
FRAXTAL
MODE
SONIC
XLAYER
}
51 changes: 0 additions & 51 deletions apps/api/rest-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ import {
beetsGetCirculatingSupplySonic,
beetsGetTotalSupplySonic,
} from '../../modules/beets/lib/beets';
import { latestTokenPrice } from '../../modules/token/latest-token-price';
import config from '../../config';
import { Chain } from '@prisma/client';
import * as crypto from 'crypto';

const isHexAddress = (addr: any) =>
typeof addr === 'string' && addr.length === 42 && addr.startsWith('0x') && /^[0-9a-f]{40}$/i.test(addr.slice(2));

export function loadRestRoutes(app: Express) {
app.use('/health', (_, res) => res.sendStatus(200));
Expand All @@ -29,48 +22,4 @@ export function loadRestRoutes(app: Express) {
res.send(result);
});
});

app.get('/price', async (req, res) => {
res.type('application/json');

const chain = req.query.chain;
const tokens = req.query.tokens && (req.query.tokens as string).split(',');

// Validate params
if (typeof chain !== 'string' || !(chain in config)) {
res.status(400).end();
return;
}

if (!Array.isArray(tokens) || tokens.length === 0 || !tokens.every(isHexAddress) || tokens.length > 8) {
res.status(400).end();
return;
}

const prices = await latestTokenPrice(chain as Chain, tokens as string[]);

// Build response body
const responseBody = { prices };
const bodyString = JSON.stringify(responseBody);

// Generate strong ETag: MD5 hash of the body (fast for small JSON)
const etag = crypto.createHash('md5').update(bodyString).digest('hex');

// Set ETag header
res.set('ETag', `"${etag}"`);

// Check for conditional request
const clientEtag = req.get('If-None-Match');
if (clientEtag === `"${etag}"` || clientEtag === etag) {
// Handle quoted/unquoted client headers
res.status(304).end(); // Not modified: No body needed
return;
}

// Set caching headers (unchanged)
res.set('Cache-Control', 'public, max-age=600, s-maxage=600, stale-while-revalidate=30, stale-if-error=86400');

// Send full response
res.send(responseBody);
});
}
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import modeConfig from './mode';
import sonicConfig from './sonic';
import hyperevmConfig from './hyperevm';
import plasmaNetworkConfig from './plasma';
import xlayerNetworkConfig from './xlayer';

export const DAYS_OF_HOURLY_PRICES = 100;
export const BALANCES_SYNC_BLOCKS_MARGIN = 200;
Expand All @@ -34,4 +35,5 @@ export default {
[Chain.SONIC]: sonicConfig,
[Chain.HYPEREVM]: hyperevmConfig,
[Chain.PLASMA]: plasmaNetworkConfig,
[Chain.XLAYER]: xlayerNetworkConfig,
};
2 changes: 1 addition & 1 deletion config/plasma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default <NetworkData>{
v3: {
vaultAddress: '0xba1333333333a1ba1108e8412f11850a5c319ba9',
protocolFeeController: '0xcacc7e1efeea8bb3af6d5720d12c1876aa6ee76b',
routerAddress: '0xa8920455934da4d853faac1f94fe7bef72943ef1',
routerAddress: '0x9da18982a33fd0c7051b19f0d7c76f2d5e7e017c',
defaultSwapFeePercentage: '0.5',
defaultYieldFeePercentage: '0.1',
},
Expand Down
21 changes: 1 addition & 20 deletions config/sonic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,25 +170,6 @@ export default <NetworkData>{
{ type: 'path', token: '0x871a101dcf22fe4fe37be7b654098c801cba1c88', path: '$.beefy-besonic' },
],
},
{
url: 'https://api.goldsky.com/api/public/project_cmcccb4vz1nhh01x888di8lgk/subgraphs/mainstreet/0.0.1/gn',
body: JSON.stringify({
query: `{
smsUsdStats(id: "statsSmsUsd") {
apy
}
}`,
}),
headers: { 'Content-Type': 'application/json' },
scale: 100,
extractors: [
{
type: 'path',
token: '0xc7990369da608c2f4903715e3bd22f2970536c29',
path: '$.data.smsUsdStats.apy',
},
],
},
{
url: 'https://yields.llama.fi/chart/104b3467-bba3-4923-851d-aa9e6ff47611',
scale: 100,
Expand All @@ -201,7 +182,7 @@ export default <NetworkData>{
],
},
{
url: 'https://api.originprotocol.com/api/v2/os/apr/trailing/7?146',
url: 'https://api.originprotocol.com/api/v2/os/apr/trailing/7',
scale: 100,
extractors: [{ type: 'path', token: '0x9f0df7799f6fdad409300080cff680f5a23df4b1', path: '$.apr' }],
},
Expand Down
70 changes: 70 additions & 0 deletions config/xlayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { AaveV3Plasma } from '@bgd-labs/aave-address-book';
import { env } from '../apps/env';
import { NetworkData } from '../modules/network/network-config-types';

export default <NetworkData>{
chain: {
slug: 'xlayer',
id: 196,
nativeAssetAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
wrappedNativeAssetAddress: '0xe538905cf8410324e03a5a23c1c177a474d59b2b',
prismaId: 'XLAYER',
gqlId: 'XLAYER',
},
subgraphs: {
startDate: '2025-10-30',
balancer: ``,
balancerV3: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmX8LawQrPRVywFnjwZG57MomECJRZDw4nNBvwdEHJ3dS3`,
balancerPoolsV3: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmQp8YimZAcJTYTv8EASHC4b83U773pWnU9vDxWheDMM78`,
gauge: ``,
},
hooks: {
['0xa523f47a933d5020b23629ddf689695aa94612dc']: 'STABLE_SURGE',
},
eth: {
address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
addressFormatted: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
symbol: 'OKB',
name: 'OKB',
},
weth: {
address: '0xe538905cf8410324e03a5a23c1c177a474d59b2b',
addressFormatted: '0xe538905cf8410324e03A5A23C1c177a474D59b2b',
},
coingecko: {
nativeAssetId: 'okb',
platformId: 'x-layer',
excludedTokenAddresses: [],
},
rpcUrl: env.DRPC_API_KEY ? `https://lb.drpc.live/xlayer/${env.DRPC_API_KEY}` : 'https://rpc.xlayer.tech',
rpcMaxBlockRange: 1000,
acceptableSGLag: 30, // ~1min
protocolToken: 'bal',
balancer: {
v2: {
vaultAddress: '',
defaultSwapFeePercentage: '0.5',
defaultYieldFeePercentage: '0.5',
balancerQueriesAddress: '',
},
v3: {
vaultAddress: '0xba1333333333a1ba1108e8412f11850a5c319ba9',
protocolFeeController: '0xcacc7e1efeea8bb3af6d5720d12c1876aa6ee76b',
routerAddress: '0xc3ccace87f6d3a81724075adcb5ddd85a8a1bb68',
defaultSwapFeePercentage: '0.5',
defaultYieldFeePercentage: '0.1',
},
},
aprHandlers: {},
multicall: '0xca11bde05977b3631167028862be2a173976ca11',
multicall3: '0xca11bde05977b3631167028862be2a173976ca11',
avgBlockSpeed: 1,
monitoring: {
main: {
alarmTopicArn: 'arn:aws:sns:ca-central-1:118697801881:api_alarms',
},
canary: {
alarmTopicArn: 'arn:aws:sns:eu-central-1:118697801881:api_alarms',
},
},
};
2 changes: 2 additions & 0 deletions modules/network/chain-id-to-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const chainIdToChain: { [id: string]: Chain } = {
'146': Chain.SONIC,
'999': Chain.HYPEREVM,
'9745': Chain.PLASMA,
'196': Chain.XLAYER,
...(env.DEPLOYMENT_ENV !== 'production' ? { '11155111': Chain.SEPOLIA } : {}),
};

Expand All @@ -34,5 +35,6 @@ export const chainToChainId: { [chain: string]: string } = {
SONIC: '146',
HYPEREVM: '999',
PLASMA: '9745',
XLAYER: '196',
...(env.DEPLOYMENT_ENV !== 'production' ? { SEPOLIA: '11155111' } : {}),
};
4 changes: 4 additions & 0 deletions modules/network/network-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { modeNetworkConfig } from './mode';
import { sonicNetworkConfig } from './sonic';
import { hyperevmNetworkConfig } from './hyperevm';
import { plasmaNetworkConfig } from './plasma';
import { xlayerNetworkConfig } from './xlayer';

export const AllNetworkConfigs: { [chainId: string]: NetworkConfig } = {
'250': fantomNetworkConfig,
Expand All @@ -32,6 +33,7 @@ export const AllNetworkConfigs: { [chainId: string]: NetworkConfig } = {
'146': sonicNetworkConfig,
'999': hyperevmNetworkConfig,
'9745': plasmaNetworkConfig,
'196': xlayerNetworkConfig,
};

export const AllNetworkConfigsKeyedOnChain: { [chain in Chain]: NetworkConfig } = {
Expand All @@ -50,6 +52,7 @@ export const AllNetworkConfigsKeyedOnChain: { [chain in Chain]: NetworkConfig }
SONIC: sonicNetworkConfig,
HYPEREVM: hyperevmNetworkConfig,
PLASMA: plasmaNetworkConfig,
XLAYER: xlayerNetworkConfig,
};

export const BalancerChainIds = [
Expand All @@ -65,5 +68,6 @@ export const BalancerChainIds = [
'34443',
'999',
'9745',
'196',
];
export const BeethovenChainIds = ['250', '10', '146'];
76 changes: 76 additions & 0 deletions modules/network/xlayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ethers } from 'ethers';
import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types';
import { every } from '../../apps/scheduler/intervals';
import { env } from '../../apps/env';
import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service';
import config from '../../config';

const xlayerNetworkData: NetworkData = config.XLAYER;

export const xlayerNetworkConfig: NetworkConfig = {
data: xlayerNetworkData,
provider: new ethers.providers.JsonRpcProvider({ url: xlayerNetworkData.rpcUrl, timeout: 60000 }),
userStakedBalanceServices: [],
services: {
balancerSubgraphService: new BalancerSubgraphService(
xlayerNetworkData.subgraphs.balancer,
xlayerNetworkData.chain.prismaId,
),
},
workerJobs: [
{
name: 'update-liquidity-for-inactive-pools',
interval: every(10, 'minutes'),
},
{
name: 'update-pool-apr',
interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(6, 'minutes') : every(2, 'minutes'),
},
{
name: 'user-sync-wallet-balances-for-all-pools',
interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(5, 'minutes') : every(20, 'seconds'),
},
{
name: 'update-fee-volume-yield-all-pools',
interval: every(10, 'minutes'),
},
// V3 Jobs
{
name: 'add-pools-v3',
interval: every(30, 'seconds'),
},
{
name: 'sync-pools-v3',
interval: every(30, 'seconds'),
},
{
name: 'sync-join-exits-v3',
interval: every(1, 'minutes'),
},
{
name: 'sync-swaps-v3',
interval: every(1, 'minutes'),
},
{
name: 'sync-snapshots-v3',
interval: every(10, 'minutes'),
},
{
name: 'forward-fill-snapshots-v3',
interval: every(1, 'hours'),
},

{
name: 'sync-hook-data',
interval: every(1, 'hours'),
},
{
name: 'sync-erc4626-onchain-data',
interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(60, 'minutes') : every(20, 'minutes'),
},
{
name: 'sync-lbps',
interval: every(1, 'minutes'),
},
],
};
5 changes: 2 additions & 3 deletions modules/sor/utils/data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Cache } from 'memory-cache';
import { Address, parseUnits } from 'viem';
import { Address, parseEther, parseUnits } from 'viem';
import { Chain, PrismaPoolType, PrismaToken } from '@prisma/client';

import { prisma } from '../../../prisma/prisma-client';
Expand Down Expand Up @@ -253,7 +253,6 @@ export async function getBufferPoolsFromDBPools(pools: SORDbPool[], chain: Chain
// check if underlying token exists in the database
const underlyingToken = tokensMap[poolToken.token.underlyingTokenAddress];
if (underlyingToken) {
const unwrapRateDecimals = 18 - poolToken.token.decimals + underlyingToken.decimals;
bufferPools.push({
poolId: pool.id,
address: poolToken.address.toLowerCase() as Address,
Expand All @@ -269,7 +268,7 @@ export async function getBufferPoolsFromDBPools(pools: SORDbPool[], chain: Chain
balance: parseUnits(poolToken.token.bufferBalanceUnderlying, underlyingToken.decimals),
},
poolType: 'Buffer',
unwrapRate: parseUnits(poolToken.token.unwrapRate, unwrapRateDecimals),
unwrapRate: parseEther(poolToken.token.unwrapRate), // the way we store rates already takes into account main/underlying decimals differences
maxWithdraw: parseUnits(poolToken.token.maxWithdraw, poolToken.token.decimals),
maxDeposit: parseUnits(poolToken.token.maxDeposit, poolToken.token.decimals),
});
Expand Down
2 changes: 2 additions & 0 deletions modules/sources/viem-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
mode,
sonic,
plasma,
xLayer,
} from 'viem/chains';
import { Chain } from '@prisma/client';
import config from '../../config';
Expand All @@ -40,6 +41,7 @@ const chain2ViemChain = {
[Chain.FRAXTAL]: fraxtal,
[Chain.MODE]: mode,
[Chain.SONIC]: sonic,
[Chain.XLAYER]: xLayer,
[Chain.HYPEREVM]: defineChain({
id: 999,
name: 'hyperevm',
Expand Down
Loading