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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ ETHERLINK_TESTNET_USER_PRIVATE_KEY=***
MONAD_TESTNET_USER_PRIVATE_KEY=***
BASE_SEPOLIA_USER_PRIVATE_KEY=***
SEPOLIA_USER_PRIVATE_KEY=***
NILE_USER_PRIVATE_KEY=***
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

.idea
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 21inches Webapp

A cross-chain decentralized exchange web application built with Next.js, supporting multiple EVM networks including Ethereum Sepolia, Base Sepolia, Etherlink Testnet, Tron Nile, and Monad Testnet.
A cross-chain decentralized exchange web application built with Next.js, supporting multiple EVM networks including Ethereum Sepolia, Tron, Base Sepolia, Etherlink Testnet, Tron Testnet (Nile), and Monad Testnet.

## Installation

Expand Down Expand Up @@ -36,6 +36,7 @@ ETHERLINK_TESTNET_USER_PRIVATE_KEY=***
MONAD_TESTNET_USER_PRIVATE_KEY=***
BASE_SEPOLIA_USER_PRIVATE_KEY=***
SEPOLIA_USER_PRIVATE_KEY=***
NILE_USER_PRIVATE_KEY=***
```

## Available Scripts
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@
"@heroicons/react": "^2.2.0",
"@rainbow-me/rainbowkit": "^2.2.8",
"@tanstack/react-query": "^5.84.1",
"@tronweb3/tronwallet-abstract-adapter": "^1.1.9",
"@tronweb3/tronwallet-adapter-react-hooks": "^1.1.10",
"@tronweb3/tronwallet-adapter-react-ui": "^1.1.11",
"@tronweb3/tronwallet-adapters": "^1.2.12",
"dotenv": "^17.2.1",
"ethers": "^6.15.0",
"next": "15.4.4",
"pino-pretty": "^13.1.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"tronweb": "^6.0.4",
"viem": "~2.33.2",
"wagmi": "^2.16.1"
},
Expand Down
1,660 changes: 1,587 additions & 73 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Binary file added public/tron-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/tron-token.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/app/CombinedWalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { PropsWithChildren } from "react";
import { RainbowProvider } from "./RainbowProvider";
import { TronProvider } from "./TronProvider";

export function CombinedWalletProvider({ children }: PropsWithChildren) {
return (
<RainbowProvider>
<TronProvider>
{children}
</TronProvider>
</RainbowProvider>
);
}
45 changes: 45 additions & 0 deletions src/app/TronProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client"
import { Adapter } from '@tronweb3/tronwallet-abstract-adapter';
import { WalletProvider } from '@tronweb3/tronwallet-adapter-react-hooks';
import { WalletModalProvider } from '@tronweb3/tronwallet-adapter-react-ui';
import '@tronweb3/tronwallet-adapter-react-ui/style.css';
import { LedgerAdapter } from '@tronweb3/tronwallet-adapters';
import { PropsWithChildren, useCallback, useEffect, useState } from "react";

export function TronProvider({ children }: PropsWithChildren) {
//const adapters = useMemo(() => ([new TronLinkAdapter()]), []);
const onAccountsChanged = useCallback((curAddr:string, preAddr:string|undefined) => {
console.log('new address is: ', curAddr, ' previous address is: ', preAddr);
}, []);
const [adapters, setAdapters] = useState<Adapter[]>([]);
useEffect(() => {
import('@tronweb3/tronwallet-adapters').then((res) => {
const { BitKeepAdapter, OkxWalletAdapter, TokenPocketAdapter, TronLinkAdapter } = res;
const tronLinkAdapter = new TronLinkAdapter({
openTronLinkAppOnMobile: true,
openUrlWhenWalletNotFound: false,
checkTimeout: 3000,
});
const ledger = new LedgerAdapter({
accountNumber: 2,
});
const bitKeepAdapter = new BitKeepAdapter();
const tokenPocketAdapter = new TokenPocketAdapter();
const okxwalletAdapter = new OkxWalletAdapter();
setAdapters([
tronLinkAdapter,
bitKeepAdapter,
tokenPocketAdapter,
okxwalletAdapter,
ledger,
]);
});
}, [setAdapters]);
return (
<WalletProvider adapters={adapters} onAccountsChanged={onAccountsChanged}>
<WalletModalProvider>
{children}
</WalletModalProvider>
</WalletProvider>
);
}
17 changes: 16 additions & 1 deletion src/app/api/constants/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Address, EscrowFactory } from "@1inch/cross-chain-sdk";
import dotenv from "dotenv";
import { JsonRpcProvider } from "ethers";
import { parseEther } from "viem";
import { parseEther, parseUnits } from "viem";
import { getDestinationImpl, getSourceImpl } from "../order/escrow";
import { Wallet } from "../order/wallet";
dotenv.config();
Expand All @@ -11,6 +11,7 @@ export const ChainIds = {
BaseSepolia: 84532,
MonadTestnet: 10143,
EtherlinkTestnet: 128123,
TronNile: 3448148188,
};
export const ChainConfigs = {
[ChainIds.Sepolia]: {
Expand Down Expand Up @@ -73,6 +74,20 @@ export const ChainConfigs = {
ResolverPrivateKey: process.env.MONAD_TESTNET_USER_PRIVATE_KEY,
SafetyDeposit: parseEther("0.001"),
},
[ChainIds.TronNile]: {
LOP: "0x0656e98bf5b9457048b8ac0985cb48b1b6def4ac", // TAYjAyuKjKvkhkcvgJ7CgrJ8PVziU5vr4R
EscrowFactory: "0x527eb6a0f425c77722da1d92aa515f691606571b", // THVQCzNgJxTvBRH297tmHXuxVdcahipy3f
ResolverContractAddress: "0x9afd02fe7b017867e7468a0cacb3546c721edd84", // TQ6iAAL9oV4Xh6DrQwZ8iGa7q1QAcwhpui
BLT: "0xb7eac522e355549cf4820dfa75e8967ddf83432c", // TSjfuGqqg6XWqpDH4WukdvUPVZfXLAJDap
ITRC: "0x19fbfa920c9579bce1006d2d512d49e2dc47de1c", // 21Inches token "TCLbkeYSQR9zX8D7svdQ85NbdSRCDWVM5R",
EscrowSrcImplementationAddress: "0x810deb8c21a11f0f10977378d403c995480c2b8c", // TMjaqzSMeni2H8qSG2JyShS29dY8zgcm3V
EscrowDstImplementationAddress: "0x724132e32346b5199e7821025bcae3a20c5717fb", // TLPL921VcESVS3YKB1KnPxNmBENxTDB3jY
TrueERC20: "0xf8dfdf1ab75de04f485a9871d9298a070b9bebc6", // TYf8mVp2tC7K9AYbFFfv8gVH82JEkbKKDj
ChainName: "NILE",
RpcUrl: "https://nile.trongrid.io",
ResolverPrivateKey: process.env.NILE_USER_PRIVATE_KEY,
SafetyDeposit: parseUnits("1", 6), // TRX decimal is 6
},
};
export const getChainResolver = (chainId: number) => {
const chainConfig = ChainConfigs[chainId];
Expand Down
11 changes: 8 additions & 3 deletions src/app/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Image from "next/image";
"use client"
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { WalletActionButton } from "@tronweb3/tronwallet-adapter-react-ui";
import Image from "next/image";

export function Navbar() {
return (
Expand All @@ -12,8 +14,11 @@ export function Navbar() {
height={100}
/>
<div className="flex-1" />
<div className="flex items-center justify-end pr-4">
<ConnectButton></ConnectButton>
<div className="flex items-center justify-end pr-4 gap-2">
<ConnectButton label="EVM Wallet">
</ConnectButton>
<WalletActionButton>
</WalletActionButton>
</div>
</div>
</header>
Expand Down
101 changes: 101 additions & 0 deletions src/app/components/NetworkSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use client";

import { useWallet } from "@tronweb3/tronwallet-adapter-react-hooks";
import { useAccount, useSwitchChain } from "wagmi";
import { ALL_CHAINS, isTronChain } from "../constants/chains";

export function NetworkSelector() {
const { chain } = useAccount();
const { switchChain } = useSwitchChain();
const { wallet, connected, disconnect } = useWallet();

const handleNetworkSwitch = async (chainId: number) => {
if (isTronChain(chainId)) {
// Handle Tron network switch
if (connected) {
await disconnect();
}
// Tron networks are handled by the wallet adapter
console.log("Switching to Tron network:", chainId);
} else {
// Handle Ethereum network switch
if (switchChain) {
switchChain({ chainId });
}
}
};

const getCurrentNetwork = () => {
if (connected && wallet) {
return "Tron Network"; // Tron wallet doesn't expose chain info directly
}
return chain?.name || "Select Network";
};

return (
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-gray-700">
Current Network: {getCurrentNetwork()}
</div>

<div className="grid grid-cols-2 gap-2">
{/* Ethereum Networks */}
<div className="space-y-2">
<h3 className="text-xs font-medium text-gray-600 uppercase">Ethereum Networks</h3>
{ALL_CHAINS.filter(chain => !isTronChain(chain.id)).map((network) => (
<button
key={network.id}
onClick={() => handleNetworkSwitch(network.id)}
className={`w-full px-3 py-2 text-sm rounded-lg border transition-colors ${
chain?.id === network.id
? "bg-blue-500 text-white border-blue-500"
: "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
}`}
>
<div className="flex items-center gap-2">
<img src={network.logo} alt={network.name} className="w-4 h-4" />
{network.name}
</div>
</button>
))}
</div>

{/* Tron Networks */}
<div className="space-y-2">
<h3 className="text-xs font-medium text-gray-600 uppercase">Tron Networks</h3>
{ALL_CHAINS.filter(chain => isTronChain(chain.id)).map((network) => (
<button
key={network.id}
onClick={() => handleNetworkSwitch(network.id)}
className={`w-full px-3 py-2 text-sm rounded-lg border transition-colors ${
connected && isTronChain(network.id)
? "bg-red-500 text-white border-red-500"
: "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
}`}
>
<div className="flex items-center gap-2">
<img src={network.logo} alt={network.name} className="w-4 h-4" />
{network.name}
</div>
</button>
))}
</div>
</div>

{/* Tron Wallet Status */}
{connected && (
<div className="mt-4 p-3 bg-green-50 border border-green-200 rounded-lg">
<div className="text-sm text-green-800">
<div className="font-medium">Tron Wallet Connected</div>
<div className="text-xs opacity-75">
Address: {wallet?.adapter.address?.slice(0, 8)}...{wallet?.adapter.address?.slice(-6)}
</div>
<div className="text-xs opacity-75">
Network: Tron Network
</div>
</div>
</div>
)}
</div>
);
}
30 changes: 3 additions & 27 deletions src/app/components/OrderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ interface OrderCardProps {
}

export default function OrderCard({ order }: OrderCardProps) {
const formatAmount = (amount: string, decimals: number) => {
const formatAmount = (amount: string) => {
try {
return Number(amount).toFixed(6);
} catch (error) {
Expand Down Expand Up @@ -125,26 +125,6 @@ export default function OrderCard({ order }: OrderCardProps) {
}
};

const TransactionLink = ({
href,
tooltip,
className = "",
}: {
href: string;
tooltip: string;
className?: string;
}) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className={`p-1 text-gray-500 hover:text-blue-600 transition-colors ${className}`}
title={tooltip}
>
<ArrowTopRightOnSquareIcon className="w-4 h-4" />
</a>
);

return (
<div
className={`rounded-lg p-3 border transition-all duration-200 hover:shadow-md ${getStatusColor(order.status)}`}
Expand Down Expand Up @@ -177,9 +157,7 @@ export default function OrderCard({ order }: OrderCardProps) {
<span className="text-sm font-medium text-gray-900 dark:text-white">
{formatAmount(
((order.swapState as Record<string, unknown>)
?.fromAmount as string) || "0",
((order.fromToken as Record<string, unknown>)
?.decimals as number) || 18
?.fromAmount as string) || "0"
)}{" "}
{
(order.fromToken as Record<string, unknown>)
Expand Down Expand Up @@ -216,9 +194,7 @@ export default function OrderCard({ order }: OrderCardProps) {
<span className="text-sm font-medium text-gray-900 dark:text-white">
{formatAmount(
((order.swapState as Record<string, unknown>)
?.toAmount as string) || "0",
((order.toToken as Record<string, unknown>)
?.decimals as number) || 18
?.toAmount as string) || "0"
)}{" "}
{(order.toToken as Record<string, unknown>)?.symbol as string}
</span>
Expand Down
Loading