A modern, user-friendly instant swap widget powered by Particle Network's Universal Accounts. This application demonstrates how to build a cross-chain trading interface with a simplified user experience using chain abstraction.
This widget is meant to be used as a component in a larger application, and it provides a simple way to add instant swap functionality to your app.
The main page in this app is a simple input bar where you manually add a contract address, the widget will fetch the token data and allow the user to instantly trade, the idea is to place it within a token's info page, so you can use the token address as a prop.
🎥 Find a quick video overview in this Tweet.
- Universal Account Integration: Wallet connection via Particle Connect and transaction handling through Universal Accounts
- Token Trading Interface: Clean, intuitive UI for trading tokens
- Real-time Price Data: Live token price fetching via Moralis API
- Transaction Fee Estimation: Preview gas costs before confirming trades
- Responsive Design: Works on desktop and mobile devices
- Solana-Only Buy/Sell Support: Currently only supports buy/sell Solana tokens due to:
- Token data API integration specifically for Solana (via Moralis)
- Universal Account configuration targeting Solana mainnet
- The user can still trade using the fully universal balance from any supported chain.
- Node.js 18+ and npm/yarn
- Particle Network Project credentials for Particle Connect and UA SDK(get one at dashboard.particle.network)
- Moralis API Key for token data (get one at moralis.io)
-
Clone the repository
git clone https://github.com/soos3d/instant-trade-widget.git cd ua-dex -
Install dependencies
npm install # or yarn install -
Create a
.env.localfile with your API keys
The Solana RPC is used to fetch the token balance.
NEXT_PUBLIC_PROJECT_ID=""
NEXT_PUBLIC_CLIENT_KEY=""
NEXT_PUBLIC_APP_ID=""
NEXT_PUBLIC_SERVER_KEY=""
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=""
MORALIS_API_KEY=""
SOLANA_RPC_URL=""
-
Start the development server
npm run dev # or yarn dev -
Open http://localhost:3000 in your browser
The widget is designed to be easily integrated into any React application. In the demo app, it's used in the main page (app/page.tsx) as follows:
// Import the widget component
import UniversalAccountsWidget from "./components/Widget";
export default function Home() {
const [tokenAddress, setTokenAddress] = useState(
"2nM6WQAUf4Jdmyd4kcSr8AURFoSDe9zsmRXJkFoKpump" // Default token address
);
// Form handling for token address input
const [inputValue, setInputValue] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (inputValue.trim()) {
setTokenAddress(inputValue.trim());
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-gray-900 p-4">
<div className="max-w-lg mx-auto pt-20">
{/* Page header and token input form */}
{/* The widget component */}
<UniversalAccountsWidget
projectId={process.env.NEXT_PUBLIC_UA_PROJECT_ID}
title="Universal Swap"
tokenAddress={tokenAddress}
/>
</div>
</div>
);
}The widget accepts the following props:
projectId: Your Particle Network Universal Accounts project IDtitle: The title displayed at the top of the widgettokenAddress: The address of the token to be traded
You can integrate this widget into any page of your application where you want to offer token trading functionality.
To extend this widget to support other blockchain networks, you'll need to implement chain detection logic in the BuyTabContent component. The transaction preview and execution are both handled in this component, making it the central place for adding multi-chain support.
The main change would be to include some logic to detect on witch chain the token is, so you can then adapt the createBuyTransaction function to use the correct chain id.
Modify the estimateFees function in BuyTabContent.tsx to use the detected chain:
const estimateFees = useCallback(
async (amount: string) => {
// ...
const chainId = detectTokenChain(tokenAddress);
const transaction = await universalAccount.createBuyTransaction({
token: { chainId: chainId, address: tokenAddress },
amountInUSD: amount,
});
// ...
},
[universalAccount, tokenAddress]
);Similarly, update the handleBuyToken function to use the detected chain:
const handleBuyToken = async (amount: string) => {
// ...
const chainId = detectTokenChain(tokenAddress);
const transaction = await universalAccount.createBuyTransaction({
token: { chainId: chainId, address: tokenAddress },
amountInUSD: amount,
});
// ...
};Create chain-specific API handlers for token data:
-
Create a new directory structure for multi-chain support:
/app/api/token/[chain]/metadata/route.ts /app/api/token/[chain]/price/route.ts /app/api/token/[chain]/balance/route.ts -
Implement chain-specific API routes:
// Example for Ethereum token metadata export async function GET( request: NextRequest, { params }: { params: { chain: string } } ) { const chain = params.chain; const searchParams = request.nextUrl.searchParams; const address = searchParams.get("address"); if (chain === "ethereum") { // Use Ethereum-specific API (e.g., Etherscan, Alchemy) // ... } else if (chain === "solana") { // Existing Solana implementation // ... } // ... }