Skip to content

Commit 32314ae

Browse files
release: L2 swaps (#1129)
* feat: use hardcoded chains (#1117) * feat: hardcoded chains for keplr * chore: self review * feat (HQI-1973): L2 bridge (#1119) * chore: savepoint * chore: savepoint * feat: erc20 bridge * chore: self review * fix: devnet url * feat: add devnet * chore: self review * fix: addresses * feat: tokens fetcher * chore: savepoint * chore: api for tokens * fix: bridge allowance * chore: savepoint * feat: use bridge state * chore: self review * fix: balances from sepolia * chore: prettier * fix: explorer for devnet * feat: https * feat: token deployment page * fix: rpc url * fix: balances and token deployment * chore: cleanup logs * feat: logs parser * feat: correct handle remote token address * chore: self review * chore: precommit hook * chore: handle allowance with timers * chore: savepoint * feat: l2 to l1 orders list * chore: self review for bridge tokens/links * chore: self review * feat: correct devnet configs for time to prove checks * chore: correct chain switch for proving * chore: correct finilize steps * chore: review * feat: self review UI for bridge page * fix: header btns for bridge page * fix: reloading timers * chore: self review * chore: replace local storage usage * chore: review comments * feat: upd wagmi lib (#1121) * feat: upd wagmi lib * fix: build providers * feat: restore tx by hash (#1122) * feat: restore withdraw by tx hash * chore: savecommit * feat: recover page with order creation * fix: linter issues * fix: types * fix: upd addresses * Potential fix for code scanning alert no. 390: Useless assignment to local variable Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Rinat Fihtengolts <9-b-rinat@rambler.ru> --------- Signed-off-by: Rinat Fihtengolts <9-b-rinat@rambler.ru> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * feat: review fixes for L2 swap page (#1123) * fix: approve btn disable and chains in header * chore: savepoint refactored bridge page * feat: share by url support * fix: bridge (#1124) * feat: new testethic (#1125) * feat: use new testethic * chore: savepoint * chore: upd addresses * fix: build * fix: search tokens * chore: self review * fix: allowance checking * fix: bridge * chore: savepoint * feat/pre release fixes (#1126) * fix: pre release fixes * chore: savepoint * chore: reset token on chain id change * fix: validators count * fix: chain switch problem * fix: links * Fix/decrease rpc calls (#1127) * fix: decrease RPC calls * fix: rename ISLM to ETH * fix: eth transfer fee calculation (#1128) --------- Signed-off-by: Rinat Fihtengolts <9-b-rinat@rambler.ru> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 82141e3 commit 32314ae

70 files changed

Lines changed: 7437 additions & 565 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/shell/messages/common/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
"unbonding-amount": "Unbonding: {amount}",
9696
"undelegate": "Undelegate",
9797
"unsupported-network-message": "Your current action cannot be performed as the application is connected to an unsupported network. Please select one of the supported networks from the list below to proceed.",
98-
"unsupported-network-title": "Unsupported <br /> Network",
98+
"unsupported-network-title": "Unsupported Network",
9999
"version": "version: ",
100100
"vested-amount": "Vested: {amount}",
101101
"vote-option-abstain": "Abstain",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {
2+
HydrationBoundary,
3+
QueryClient,
4+
dehydrate,
5+
} from '@tanstack/react-query';
6+
import { TokenDeploymentPage } from '@haqq/shell-bridge';
7+
8+
export const dynamic = 'force-dynamic';
9+
export const fetchCache = 'force-no-store';
10+
11+
export default async function TokenDeployment() {
12+
const queryClient = new QueryClient();
13+
14+
const dehydratedState = dehydrate(queryClient);
15+
16+
return (
17+
<HydrationBoundary state={dehydratedState}>
18+
<TokenDeploymentPage />
19+
</HydrationBoundary>
20+
);
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {
2+
HydrationBoundary,
3+
QueryClient,
4+
dehydrate,
5+
} from '@tanstack/react-query';
6+
import { BridgePage } from '@haqq/shell-bridge';
7+
8+
export const dynamic = 'force-dynamic';
9+
export const fetchCache = 'force-no-store';
10+
11+
export default async function ValidatorList() {
12+
const queryClient = new QueryClient();
13+
14+
const dehydratedState = dehydrate(queryClient);
15+
16+
return (
17+
<HydrationBoundary state={dehydratedState}>
18+
<BridgePage />
19+
</HydrationBoundary>
20+
);
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { WithdrawalRecoveryPage } from '@haqq/shell-bridge';
2+
3+
export default function RecoveryPage() {
4+
return <WithdrawalRecoveryPage />;
5+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { haqqMainnet, haqqTestedge2, sepolia } from 'viem/chains';
3+
import { haqqTestethic } from '@haqq/shell-shared';
4+
5+
export interface TokenBalance {
6+
symbol: string;
7+
address: string;
8+
name: string;
9+
balance: string;
10+
decimals: number;
11+
formattedBalance: number;
12+
}
13+
14+
export interface ExplorerToken {
15+
value: string;
16+
token: {
17+
symbol: string;
18+
address_hash?: string;
19+
address?: string;
20+
name: string;
21+
decimals: string;
22+
};
23+
}
24+
25+
export interface ExplorerApiResponse {
26+
items: ExplorerToken[];
27+
}
28+
29+
export async function GET(request: NextRequest) {
30+
try {
31+
const { searchParams } = new URL(request.url);
32+
const address = searchParams.get('address');
33+
const chainId = searchParams.get('chainId');
34+
35+
if (!address || !chainId) {
36+
return NextResponse.json(
37+
{ error: 'Missing required parameters: address and chainId' },
38+
{ status: 400 },
39+
);
40+
}
41+
42+
// Get chain configuration
43+
const chainConfig = getChainConfig(Number(chainId));
44+
if (!chainConfig) {
45+
return NextResponse.json(
46+
{ error: `Unsupported chain ID: ${chainId}` },
47+
{ status: 400 },
48+
);
49+
}
50+
51+
// Make the API request to the explorer
52+
const url = `${chainConfig.apiUrl}/v2/addresses/${address}/tokens?type=ERC-20`;
53+
54+
console.log('Explorer API URL:', url);
55+
56+
const response = await fetch(url, {
57+
headers: {
58+
Accept: 'application/json',
59+
},
60+
});
61+
62+
console.log('Explorer API response status:', response.status);
63+
64+
if (!response.ok) {
65+
console.log('Explorer API error:', response.status, response.statusText);
66+
67+
return NextResponse.json(
68+
{
69+
error: `Explorer API error: ${response.status} ${response.statusText}`,
70+
},
71+
{ status: response.status },
72+
);
73+
}
74+
75+
const data = (await response.json()) as ExplorerApiResponse;
76+
77+
console.log('Explorer API data items count:', data.items?.length || 0);
78+
79+
// Filter out tokens with zero balance
80+
const filteredTokens: ExplorerToken[] = data.items.filter((item) => {
81+
return item.value !== '0' && item.value !== '0x0';
82+
});
83+
84+
console.log(
85+
`Found ${filteredTokens.length} tokens with non-zero balance out of ${data.items.length} total tokens`,
86+
);
87+
88+
// Process token balances
89+
const tokenBalances: TokenBalance[] = filteredTokens.map((item) => {
90+
const decimals = Number(item.token.decimals);
91+
const balanceWei = BigInt(item.value);
92+
const formattedBalance = Number(balanceWei) / Math.pow(10, decimals);
93+
94+
return {
95+
symbol: item.token.symbol,
96+
address: item.token.address_hash || item.token.address || '',
97+
name: item.token.name,
98+
balance: item.value,
99+
decimals,
100+
formattedBalance,
101+
};
102+
});
103+
104+
console.log(
105+
'Processed token balances:',
106+
tokenBalances.map((t) => {
107+
return `${t.symbol}: ${t.formattedBalance}`;
108+
}),
109+
);
110+
111+
return NextResponse.json({
112+
tokens: tokenBalances,
113+
total: filteredTokens.length,
114+
});
115+
} catch (error) {
116+
console.error('Error in token balances API:', error);
117+
return NextResponse.json(
118+
{ error: 'Internal server error' },
119+
{ status: 500 },
120+
);
121+
}
122+
}
123+
124+
interface ChainConfig {
125+
apiUrl: string;
126+
nativeSymbol: string;
127+
nativeName: string;
128+
}
129+
130+
function getChainConfig(chainId: number): ChainConfig | null {
131+
const configs: Record<number, ChainConfig> = {
132+
[haqqTestethic.id]: {
133+
// HAQQ Devnet1
134+
apiUrl: haqqTestethic.blockExplorers.default.apiUrl,
135+
nativeSymbol: 'ISLM',
136+
nativeName: 'Islamic Coin',
137+
},
138+
[sepolia.id]: {
139+
// Sepolia
140+
apiUrl: 'https://eth-sepolia.blockscout.com/api',
141+
nativeSymbol: 'ETH',
142+
nativeName: 'Ethereum',
143+
},
144+
[haqqMainnet.id]: {
145+
// HAQQ Mainnet
146+
apiUrl: haqqMainnet.blockExplorers.default.apiUrl,
147+
nativeSymbol: 'ISLM',
148+
nativeName: 'Islamic Coin',
149+
},
150+
[haqqTestedge2.id]: {
151+
// HAQQ Testedge2
152+
apiUrl: haqqTestedge2.blockExplorers.default.apiUrl,
153+
nativeSymbol: 'ISLM',
154+
nativeName: 'Islamic Coin',
155+
},
156+
};
157+
158+
return configs[chainId] || null;
159+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { createPublicClient, http, formatEther, Chain } from 'viem';
3+
import { haqqMainnet, haqqTestedge2, sepolia } from 'viem/chains';
4+
import { haqqTestethic } from '@haqq/shell-shared';
5+
6+
export async function GET(request: NextRequest) {
7+
try {
8+
const { searchParams } = new URL(request.url);
9+
const address = searchParams.get('address');
10+
const chainId = searchParams.get('chainId');
11+
12+
if (!address || !chainId) {
13+
return NextResponse.json(
14+
{ error: 'Missing required parameters: address and chainId' },
15+
{ status: 400 },
16+
);
17+
}
18+
19+
// Get chain configuration
20+
const chainConfig = getChainConfig(parseInt(chainId));
21+
if (!chainConfig) {
22+
return NextResponse.json(
23+
{ error: `Unsupported chain ID: ${chainId}` },
24+
{ status: 400 },
25+
);
26+
}
27+
28+
// Create RPC client for the chain
29+
const client = createPublicClient({
30+
chain: chainConfig.chain,
31+
transport: http(chainConfig.rpcUrl),
32+
});
33+
34+
try {
35+
// Get native token balance using RPC
36+
const balance = await client.getBalance({
37+
address: address as `0x${string}`,
38+
});
39+
40+
const formattedBalance = parseFloat(formatEther(balance));
41+
42+
const nativeToken = {
43+
symbol: chainConfig.nativeSymbol,
44+
address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', // ETH placeholder
45+
name: chainConfig.nativeName,
46+
balance: balance.toString(),
47+
decimals: 18,
48+
formattedBalance,
49+
};
50+
51+
return NextResponse.json({
52+
token: nativeToken,
53+
});
54+
} catch {
55+
return NextResponse.json(
56+
{ error: 'Failed to fetch native token balance via RPC' },
57+
{ status: 500 },
58+
);
59+
}
60+
} catch {
61+
return NextResponse.json(
62+
{ error: 'Internal server error' },
63+
{ status: 500 },
64+
);
65+
}
66+
}
67+
68+
interface ChainConfig {
69+
chain: Chain;
70+
rpcUrl: string;
71+
nativeSymbol: string;
72+
nativeName: string;
73+
}
74+
75+
function getChainConfig(chainId: number): ChainConfig | null {
76+
const configs: Record<number, ChainConfig> = {
77+
[haqqTestethic.id]: {
78+
chain: haqqTestethic,
79+
rpcUrl: haqqTestethic.rpcUrls.default.http[0],
80+
nativeSymbol: 'ETH',
81+
nativeName: 'Ethereum',
82+
},
83+
[sepolia.id]: {
84+
chain: sepolia,
85+
rpcUrl: sepolia.rpcUrls.default.http[0],
86+
nativeSymbol: 'ETH',
87+
nativeName: 'Ethereum',
88+
},
89+
[haqqMainnet.id]: {
90+
chain: haqqMainnet,
91+
rpcUrl: haqqMainnet.rpcUrls.default.http[0],
92+
nativeSymbol: 'ISLM',
93+
nativeName: 'Islamic Coin',
94+
},
95+
[haqqTestedge2.id]: {
96+
chain: haqqTestedge2,
97+
rpcUrl: haqqTestedge2.rpcUrls.default.http[0],
98+
nativeSymbol: 'ISLM',
99+
nativeName: 'Islamic Coin',
100+
},
101+
};
102+
103+
return configs[chainId] || null;
104+
}

0 commit comments

Comments
 (0)