EDUCHAIN Agent Kit
- src
import { ethers } from 'ethers';
import * as subgraph from './subgraph.js';
// Default RPC URL and chain ID
let rpcUrl = 'https://rpc.edu-chain.raas.gelato.cloud';
const EDUCHAIN_CHAIN_ID = 41923;
// ERC20 ABI for token balance and metadata queries
const ERC20_ABI = [
'function balanceOf(address owner) view returns (uint256)',
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
'function name() view returns (string)',
'function totalSupply() view returns (uint256)',
'function transfer(address to, uint256 value) returns (bool)'
];
// ERC721 ABI for NFT balance queries
const ERC721_ABI = [
'function balanceOf(address owner) view returns (uint256)',
'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)',
'function tokenURI(uint256 tokenId) view returns (string)',
'function name() view returns (string)',
'function symbol() view returns (string)'
];
// ERC1155 ABI for NFT balance queries
const ERC1155_ABI = [
'function balanceOf(address account, uint256 id) view returns (uint256)',
'function balanceOfBatch(address[] accounts, uint256[] ids) view returns (uint256[])',
'function uri(uint256 id) view returns (string)'
];
// Helper function to safely convert BigInt to string
function bigIntToString(value: any): string {
if (typeof value === 'bigint') {
return value.toString();
} else if (typeof value === 'object' && value !== null && typeof value.toString === 'function') {
return value.toString();
}
return String(value);
}
// Get provider instance
export function getProvider(): ethers.JsonRpcProvider {
return new ethers.JsonRpcProvider(rpcUrl, EDUCHAIN_CHAIN_ID);
}
// Set RPC URL
export function setRpcUrl(url: string): void {
rpcUrl = url;
}
// Get current RPC URL
export function getRpcUrl(): string {
return rpcUrl;
}
// Get wallet address from private key
export function getWalletAddressFromPrivateKey(privateKey: string): string {
try {
const wallet = new ethers.Wallet(privateKey);
return ethers.getAddress(wallet.address); // Format to checksum address
} catch (error) {
console.error('Error getting wallet address from private key:', error);
throw error;
}
}
// Get EDU balance
export async function getEduBalance(address: string): Promise<{ balance: string, balanceInEdu: string }> {
try {
const provider = getProvider();
const balance = await provider.getBalance(address);
const balanceInEdu = ethers.formatEther(balance);
return {
balance: bigIntToString(balance),
balanceInEdu
};
} catch (error) {
console.error('Error fetching EDU balance:', error);
throw error;
}
}
// Get token balance
export async function getTokenBalance(
tokenAddress: string,
walletAddress: string
): Promise<{
balance: string,
decimals: number,
symbol: string,
name: string,
formattedBalance: string,
usdValue?: string
}> {
try {
const provider = getProvider();
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
// Get token details
const [balance, decimals, symbol, name] = await Promise.all([
tokenContract.balanceOf(walletAddress),
tokenContract.decimals(),
tokenContract.symbol(),
tokenContract.name()
]);
// Convert BigInt to string and number
const balanceStr = bigIntToString(balance);
const decimalsNum = Number(decimals);
const formattedBalance = ethers.formatUnits(balance, decimalsNum);
// Try to get USD value from SailFish
let usdValue: string | undefined;
try {
const tokenPrice = await subgraph.getTokenPrice(tokenAddress);
if (tokenPrice) {
const valueInUsd = parseFloat(formattedBalance) * parseFloat(tokenPrice);
usdValue = valueInUsd.toString();
}
} catch (error) {
console.error('Error fetching token price:', error);
// Continue without USD value
}
return {
balance: balanceStr,
decimals: decimalsNum,
symbol: String(symbol),
name: String(name),
formattedBalance,
usdValue
};
} catch (error) {
console.error('Error fetching token balance:', error);
throw error;
}
}
// Get multiple token balances
export async function getMultipleTokenBalances(
tokenAddresses: string[],
walletAddress: string
): Promise<Array<{
tokenAddress: string,
balance: string,
decimals: number,
symbol: string,
name: string,
formattedBalance: string,
usdValue?: string
}>> {
try {
const results = await Promise.all(
tokenAddresses.map(async (tokenAddress) => {
try {
const tokenBalance = await getTokenBalance(tokenAddress, walletAddress);
return {
tokenAddress,
...tokenBalance
};
} catch (error) {
console.error(`Error fetching balance for token ${tokenAddress}:`, error);
return {
tokenAddress,
balance: '0',
decimals: 18,
symbol: 'UNKNOWN',
name: 'Unknown Token',
formattedBalance: '0'
};
}
})
);
return results;
} catch (error) {
console.error('Error fetching multiple token balances:', error);
throw error;
}
}
// Get ERC721 NFT balance
export async function getERC721Balance(
nftAddress: string,
walletAddress: string,
fetchTokenIds: boolean = true
): Promise<{
contractAddress: string,
name: string,
symbol: string,
balance: string,
tokenIds?: string[]
}> {
try {
const provider = getProvider();
const nftContract = new ethers.Contract(nftAddress, ERC721_ABI, provider);
// Get NFT details
const [balance, name, symbol] = await Promise.all([
nftContract.balanceOf(walletAddress),
nftContract.name(),
nftContract.symbol()
]);
const balanceNumber = Number(balance);
// Get token IDs if requested and balance > 0
let tokenIds: string[] | undefined;
if (fetchTokenIds && balanceNumber > 0) {
tokenIds = [];
for (let i = 0; i < balanceNumber; i++) {
try {
const tokenId = await nftContract.tokenOfOwnerByIndex(walletAddress, i);
tokenIds.push(bigIntToString(tokenId));
} catch (error) {
console.error(`Error fetching token ID at index ${i}:`, error);
// Continue with next token ID
}
}
}
return {
contractAddress: nftAddress,
name: String(name),
symbol: String(symbol),
balance: bigIntToString(balance),
tokenIds
};
} catch (error) {
console.error('Error fetching ERC721 balance:', error);
throw error;
}
}
// Get ERC1155 NFT balance for a specific token ID
export async function getERC1155Balance(
nftAddress: string,
walletAddress: string,
tokenId: string
): Promise<{
contractAddress: string,
tokenId: string,
balance: string,
uri?: string
}> {
try {
const provider = getProvider();
const nftContract = new ethers.Contract(nftAddress, ERC1155_ABI, provider);
// Get NFT details
const balance = await nftContract.balanceOf(walletAddress, tokenId);
let uri: string | undefined;
try {
uri = await nftContract.uri(tokenId);
} catch (error) {
console.error(`Error fetching URI for token ID ${tokenId}:`, error);
// Continue without URI
}
return {
contractAddress: nftAddress,
tokenId,
balance: bigIntToString(balance),
uri
};
} catch (error) {
console.error('Error fetching ERC1155 balance:', error);
throw error;
}
}
// Send EDU native token to another address
export async function sendEdu(
privateKey: string,
toAddress: string,
amount: string
): Promise<{
hash: string,
from: string,
to: string,
amount: string
}> {
try {
const provider = getProvider();
const wallet = new ethers.Wallet(privateKey, provider);
// Convert amount to wei
const amountWei = ethers.parseEther(amount);
// Create and send transaction
const tx = await wallet.sendTransaction({
to: toAddress,
value: amountWei
});
// Wait for transaction to be mined
const receipt = await tx.wait();
if (!receipt) {
throw new Error('Transaction failed');
}
return {
hash: tx.hash,
from: wallet.address,
to: toAddress,
amount
};
} catch (error) {
console.error('Error sending EDU:', error);
throw error;
}
}
// Send ERC20 token to another address
export async function sendErc20Token(
privateKey: string,
tokenAddress: string,
toAddress: string,
amount: string
): Promise<{
hash: string,
from: string,
to: string,
tokenAddress: string,
amount: string
}> {
try {
const provider = getProvider();
const wallet = new ethers.Wallet(privateKey, provider);
// Create contract instance
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, wallet);
// Get token decimals
const decimals = await tokenContract.decimals();
// Convert amount to token units
const amountInTokenUnits = ethers.parseUnits(amount, decimals);
// Send tokens
const tx = await tokenContract.transfer(toAddress, amountInTokenUnits);
// Wait for transaction to be mined
const receipt = await tx.wait();
if (!receipt) {
throw new Error('Transaction failed');
}
return {
hash: tx.hash,
from: wallet.address,
to: toAddress,
tokenAddress,
amount
};
} catch (error) {
console.error('Error sending ERC20 token:', error);
throw error;
}
}
// Get wallet overview with EDU, tokens, and NFTs
export async function getWalletOverview(
walletAddress: string,
tokenAddresses: string[] = [],
nftAddresses: string[] = []
): Promise<{
address: string,
eduBalance: { balance: string, balanceInEdu: string },
tokens: Array<{
tokenAddress: string,
balance: string,
decimals: number,
symbol: string,
name: string,
formattedBalance: string,
usdValue?: string
}>,
nfts: Array<{
contractAddress: string,
name?: string,
symbol?: string,
balance: string,
tokenIds?: string[]
}>
}> {
try {
// Get EDU balance
const eduBalance = await getEduBalance(walletAddress);
// Get token balances
const tokens = await getMultipleTokenBalances(tokenAddresses, walletAddress);
// Get NFT balances
const nfts = await Promise.all(
nftAddresses.map(async (nftAddress) => {
try {
return await getERC721Balance(nftAddress, walletAddress);
} catch (error) {
console.error(`Error fetching NFT balance for ${nftAddress}:`, error);
return {
contractAddress: nftAddress,
balance: '0'
};
}
})
);
return {
address: walletAddress,
eduBalance,
tokens,
nfts
};
} catch (error) {
console.error('Error fetching wallet overview:', error);
throw error;
}
}