Elder Helper for Rollups
The Elder Helper class provides a convenient way to interact with rollups through a simple, object-oriented interface. This guide explains how to use the Elder Helper class in your applications to connect to the Elder network and send transactions to your rollup.
Understanding the Elder Helper Class
The Elder Helper class encapsulates the functionality needed to interact with rollups, including:
- Connecting to the Elder network
- Managing Elder addresses and public keys
- Sending transactions to rollups
- Handling configuration parameters
Class Structure
Here's the complete Elder Helper class implementation:
import {
ElderConfig,
eth_broadcastTx,
eth_getElderAccountInfoFromSignature,
eth_getElderMsgAndFeeTxRaw,
} from "elderjs";
import { Signer } from "ethers";
import { parseEther } from "ethers/lib/utils";
import { TransactionLike } from "./ethers";
const ChainName = "testnet-4";
export const ELDER_REST = process.env.NEXT_PUBLIC_ELDER_REST!;
export const ELDER_RPC = process.env.NEXT_PUBLIC_ELDER_RPC!;
export type BigNumberish = string | number | bigint;
export type CreateParams = {
rpc?: string;
rest?: string;
chainName?: string;
rollID: number;
rollChainID: bigint;
eth_rpc: string;
message?: string;
};
if (!ELDER_REST || !ELDER_RPC)
throw new Error(
'"NEXT_PUBLIC_ELDER_REST" or "NEXT_PUBLIC_ELDER_RPC" missing in environment'
);
export class Elder {
private _elder_rpc: string;
private _elder_rest: string;
private chainName: string;
private rollID: number;
private rollChainID: bigint;
private eth_rpc: string;
public elder_address: string | null = null;
public elder_pub_key: string | null = null;
constructor(params: CreateParams) {
this._elder_rest = params.rest || ELDER_REST;
this._elder_rpc = params.rpc || ELDER_RPC;
this.chainName = params.chainName || ChainName;
this.rollID = params.rollID;
this.rollChainID = params.rollChainID;
this.eth_rpc = params.eth_rpc;
}
static async connect(signer: Signer, params: CreateParams) {
const message =
params.message || `Connect to ${params.chainName || ChainName}`;
const signature = await signer.signMessage(message);
const { recoveredPublicKey, elderAddr } =
await eth_getElderAccountInfoFromSignature(message, signature);
const obj = new Elder(params);
obj.elder_address = elderAddr;
obj.elder_pub_key = recoveredPublicKey;
return obj;
}
async sendTransaction(
tx: TransactionLike<string>,
opts?: { gasLimit?: BigNumberish; value?: BigNumberish }
) {
opts = {
gasLimit: opts?.gasLimit || 10_000_000,
value: opts?.value || parseEther("0").toBigInt(),
};
if (
this.elder_address === null ||
this.elder_pub_key === null ||
typeof this.config === "undefined"
)
throw new Error("network not connected");
let { tx_hash, rawTx } = await eth_getElderMsgAndFeeTxRaw(
tx,
this.elder_address,
this.elder_pub_key,
opts.gasLimit!,
opts.value!,
this.config
);
let broadcastResult = await eth_broadcastTx(rawTx, this.config.rpc);
return { tx_hash, result: broadcastResult };
}
get config(): ElderConfig | undefined {
if (
typeof this.chainName !== "undefined" &&
typeof this.rollID !== "undefined" &&
typeof this.rollChainID !== "undefined" &&
typeof this.eth_rpc !== "undefined"
) {
return {
rest: this._elder_rest,
rpc: this._elder_rpc,
chainName: this.chainName,
rollID: this.rollID,
rollChainID: Number(this.rollChainID),
eth_rpc: this.eth_rpc,
};
}
return undefined;
}
get address(): string | undefined {
return this.elder_address || undefined;
}
}
How to Use the Elder Helper
Setting Up Environment Variables
Before using the Elder Helper class, make sure to set up the required environment variables:
NEXT_PUBLIC_ELDER_REST=https://your-elder-rest-url
NEXT_PUBLIC_ELDER_RPC=https://your-elder-rpc-url
Connecting to the Elder Network
To connect to the Elder network, use the static connect
method:
import { Elder } from "./path-to-elder-helper";
import { Wallet } from "ethers";
// Initialize your wallet with a private key
const wallet = new Wallet("your-private-key");
// Connect to Elder network
const elder = await Elder.connect(wallet, {
rollID: 1,
rollChainID: 1n, // Using BigInt for chain ID
eth_rpc: "https://your-eth-rpc-url",
// Optional parameters
chainName: "your-chain-name", // Defaults to "testnet-4"
message: "Custom connection message", // Custom message to sign
});
// Now you can access the Elder address
console.log("Elder address:", elder.address);
Sending Transactions
Once connected, you can send transactions to the rollup:
import { Contract } from "ethers";
import { parseEther } from "ethers/lib/utils";
// Prepare a transaction (example with a contract)
const contract = new Contract(contractAddress, contractABI, wallet);
const tx = await contract.populateTransaction.transfer(
recipientAddress,
parseEther("0.1")
);
// Send the transaction using the Elder helper
const { tx_hash, result } = await elder.sendTransaction(tx, {
gasLimit: 1000000,
value: parseEther("0.01").toBigInt(), // Amount of ETH to send with the transaction
});
console.log(`Transaction sent with hash: ${tx_hash}`);
console.log("Transaction result:", result);
Benefits of Using the Elder Helper Class
-
Simplified Interface: The Elder Helper provides a clean, object-oriented interface for interacting with rollups.
-
State Management: The class maintains the state of your connection, including Elder address and public key.
-
Configuration Handling: The class manages configuration parameters and provides sensible defaults.
-
Error Handling: Built-in validation ensures that required parameters are provided before sending transactions.
Complete Example
Here's a complete example showing how to use the Elder Helper class in a typical application:
import { Elder } from "./elder-helper";
import { Wallet, Contract } from "ethers";
import { parseEther } from "ethers/lib/utils";
async function main() {
// Initialize wallet
const wallet = new Wallet("your-private-key");
// Connect to Elder network
const elder = await Elder.connect(wallet, {
rollID: 1,
rollChainID: 1n,
eth_rpc: "https://your-eth-rpc-url",
});
console.log("Connected with Elder address:", elder.address);
// Create a contract instance
const tokenContract = new Contract(
"0xYourTokenContractAddress",
["function transfer(address to, uint256 amount) returns (bool)"],
wallet
);
// Prepare a transaction
const tx = await tokenContract.populateTransaction.transfer(
"0xRecipientAddress",
parseEther("1.0")
);
// Send the transaction
try {
const { tx_hash, result } = await elder.sendTransaction(tx);
console.log(`Transaction sent with hash: ${tx_hash}`);
console.log("Result:", result);
} catch (error) {
console.error("Failed to send transaction:", error);
}
}
main().catch(console.error);
useElder React Hook
For React applications, the useElder
hook provides a convenient way to integrate Elder functionality into your components. This hook encapsulates the Elder connection state and provides methods for connecting and sending transactions.
Implementation
Here's the complete implementation of the useElder
hook:
import {
ElderConfig,
eth_broadcastTx,
eth_getElderAccountInfoFromSignature,
eth_getElderMsgAndFeeTxRaw,
} from 'elderjs';
import { BrowserProvider, parseEther, Signer, TransactionLike } from 'ethers';
import { useEffect, useMemo, useState } from 'react';
import type { Account, Chain, Client, Transport } from 'viem';
export type BigNumberish = string | number | bigint;
export type CreateParams = {
roll_id: number;
chain_id: bigint;
eth_rpc: string;
message?: string;
};
export type ElderState = {
roll_id?: number;
chain_id?: bigint;
elder_address?: string;
elder_public_key?: string;
eth_address?: `0x${string}`;
eth_rpc?: string;
signer?: Signer;
};
// * Helper functions
async function clientToSigner(client: Client<Transport, Chain, Account | undefined>) {
const { account, chain, transport } = client;
const network = {
chainId: chain.id,
name: chain.name,
ensAddress: chain.contracts?.ensRegistry?.address,
};
const provider = new BrowserProvider(transport, network);
const signer = await provider.getSigner(account?.address);
return signer;
}
const ELDER_TESTNET = process.env.NEXT_PUBLIC_ELDER_TESTNET!;
const ELDER_REST_URL = process.env.NEXT_PUBLIC_ELDER_REST!;
const ELDER_RPC_URL = process.env.NEXT_PUBLIC_ELDER_RPC!;
if (!ELDER_REST_URL || !ELDER_RPC_URL)
throw new Error('"NEXT_PUBLIC_ELDER_REST" or "NEXT_PUBLIC_ELDER_RPC" missing in environment');
export function useElder() {
const [state, setState] = useState<ElderState>({});
const config = useMemo(() => {
if (
typeof state.roll_id !== 'undefined' &&
typeof state.chain_id !== 'undefined' &&
typeof state.eth_rpc !== 'undefined'
) {
return {
rest: ELDER_REST_URL,
rpc: ELDER_RPC_URL,
chainName: ELDER_TESTNET,
rollID: state.roll_id,
rollChainID: Number(state.chain_id),
eth_rpc: state.eth_rpc,
} as ElderConfig;
}
return undefined;
}, [state]);
async function connect(signer: Signer, params: CreateParams) {
const message = params.message || `Connect to Elder ${ELDER_TESTNET}`;
const signature = await signer.signMessage(message);
const { recoveredPublicKey, elderAddr } = await eth_getElderAccountInfoFromSignature(
message,
signature
);
const _s = {
roll_id: params.roll_id,
chain_id: params.chain_id,
eth_rpc: params.eth_rpc,
eth_address: (await signer.getAddress()) as `0x${string}`,
elder_address: elderAddr,
elder_public_key: recoveredPublicKey,
signer,
};
setState(_s);
return _s;
}
async function connectViem(
client: Client<Transport, Chain, Account | undefined>,
chain: Chain | IChain | IRollupChain
) {
if (typeof chain.custom?.rollId === 'number')
return await connect(await clientToSigner(client), {
chain_id: BigInt(chain.id),
eth_rpc: chain.rpcUrls.default.http[0],
roll_id: chain.custom.rollId,
});
return null;
}
async function sendTransaction(
tx: TransactionLike<string>,
opts?: { gasLimit?: BigNumberish; value?: BigNumberish }
) {
opts = {
gasLimit: opts?.gasLimit || 10_000_000,
value: opts?.value || parseEther('0'),
};
if (
typeof state.elder_address === 'undefined' ||
typeof state.elder_public_key === 'undefined' ||
typeof config === 'undefined'
)
throw new Error('network not connected');
let { tx_hash, rawTx } = await eth_getElderMsgAndFeeTxRaw(
tx,
state.elder_address,
state.elder_public_key,
opts.gasLimit!,
opts.value!,
config
);
let broadcastResult = await eth_broadcastTx(rawTx, config.rpc);
return { tx_hash, result: broadcastResult };
}
return ({
// state
address: state.eth_address,
elder_address: state.elder_address,
elder_public_key: state.elder_public_key,
signer: state.signer,
config,
// functions
connect,
connectViem,
sendTransaction,
});
}
export type UseElderReturnType = ReturnType<typeof useElder>;
Using the useElder Hook in React Components
Here's how to use the useElder
hook in your React components:
import { useElder } from './path-to-useElder';
import { useEffect, useState } from 'react';
import { BrowserProvider } from 'ethers';
function ElderConnectButton() {
const elder = useElder();
const [isConnected, setIsConnected] = useState(false);
async function handleConnect() {
try {
// Request access to the user's Ethereum wallet
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
// Connect to Elder network
await elder.connect(signer, {
roll_id: 1,
chain_id: 1n, // Using BigInt for chain ID
eth_rpc: "https://your-eth-rpc-url",
});
setIsConnected(true);
} catch (error) {
console.error("Failed to connect:", error);
}
}
return (
<div>
<button onClick={handleConnect} disabled={isConnected}>
{isConnected ? 'Connected to Elder' : 'Connect to Elder'}
</button>
{isConnected && (
<div>
<p>Ethereum Address: {elder.address}</p>
<p>Elder Address: {elder.elder_address}</p>
</div>
)}
</div>
);
}
Sending Transactions with useElder
Here's an example of sending a transaction using the useElder
hook:
import { useElder } from './path-to-useElder';
import { useState } from 'react';
import { Contract } from 'ethers';
import { parseEther } from 'ethers/lib/utils';
function SendTransactionComponent({ contractAddress, contractABI }) {
const elder = useElder();
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const [txHash, setTxHash] = useState('');
const [isLoading, setIsLoading] = useState(false);
async function handleSendTransaction() {
if (!elder.signer || !recipient || !amount) return;
setIsLoading(true);
try {
// Create contract instance
const contract = new Contract(contractAddress, contractABI, elder.signer);
// Prepare transaction
const tx = await contract.populateTransaction.transfer(
recipient,
parseEther(amount)
);
// Send transaction
const { tx_hash } = await elder.sendTransaction(tx);
setTxHash(tx_hash);
} catch (error) {
console.error("Transaction failed:", error);
} finally {
setIsLoading(false);
}
}
return (
<div>
<h2>Send Tokens</h2>
<div>
<input
type="text"
placeholder="Recipient Address"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
/>
</div>
<div>
<input
type="text"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<button
onClick={handleSendTransaction}
disabled={isLoading || !elder.signer}
>
{isLoading ? 'Sending...' : 'Send'}
</button>
{txHash && (
<div>
<p>Transaction sent! Hash: {txHash}</p>
</div>
)}
</div>
);
}
Using with Viem in React
You can also use the connectViem
method to connect with a Viem wallet client:
import { useElder } from './path-to-useElder';
import { useAccount, useConnect, useWalletClient } from 'wagmi';
import { mainnet } from 'wagmi/chains';
function ViemConnectComponent() {
const elder = useElder();
const { address } = useAccount();
const { data: walletClient } = useWalletClient();
const [isConnected, setIsConnected] = useState(false);
async function handleConnectViem() {
if (!walletClient) return;
try {
// Connect using Viem wallet client
await elder.connectViem(walletClient, {
...mainnet,
custom: {
rollId: 1
}
});
setIsConnected(true);
} catch (error) {
console.error("Failed to connect with Viem:", error);
}
}
return (
<div>
<button onClick={handleConnectViem} disabled={!walletClient || isConnected}>
Connect with Viem
</button>
{isConnected && (
<div>
<p>Connected with Elder address: {elder.elder_address}</p>
</div>
)}
</div>
);
}
Next Steps
Now that you understand how to use the Elder Helper class and the useElder React hook, you can:
- Integrate them into your dApps
- Create reusable transaction patterns
- Build higher-level abstractions on top of these tools
For more advanced usage and detailed API documentation, refer to the Elder.js documentation.