React/Next.js Implementation Examples
The PwnDAO SDK provides comprehensive integration with React and Next.js applications. Here are the key implementation examples:
1. Listing Strategies
Example of listing all available strategies:
// app/strategies/page.tsx
'use client'
import Link from "next/link"
import { SupportedChain } from "@pwndao/sdk-core"
import { useStrategies } from "@pwndao/sdk-v1-react"
import { formatUnits } from "viem"
export default function StrategiesPage() {
const { data: strategies, isLoading } = useStrategies(SupportedChain.World)
if (isLoading) {
return <div>Loading strategies...</div>
}
return (
<div className="container">
<h1>Strategies</h1>
<div className="grid">
{strategies?.map((strategy) => (
<div key={strategy.id}>
<h2>{strategy.name}</h2>
<p>{strategy.description}</p>
<div>
<div>
<span>Total Committed:</span>
<span>{formatUnits(strategy.lendingStats.totalCommittedAmount, 18)} USD</span>
</div>
<div>
<span>Credit Assets:</span>
<span>{strategy.terms.creditAssets.map(asset => asset.symbol).join(", ")}</span>
</div>
<div>
<span>APR:</span>
<span>
{strategy.terms.apr[`${strategy.terms.collateralAssets[0].address}/${strategy.terms.collateralAssets[0].chainId}-${strategy.terms.creditAssets[0].address}/${strategy.terms.creditAssets[0].chainId}`]}%
</span>
</div>
<div>
<span>LTV:</span>
<span>
{strategy.terms.ltv[`${strategy.terms.collateralAssets[0].address}/${strategy.terms.collateralAssets[0].chainId}-${strategy.terms.creditAssets[0].address}/${strategy.terms.creditAssets[0].chainId}`]}%
</span>
</div>
</div>
<Link href={`/strategy/${strategy.id}`}>
View Details
</Link>
</div>
))}
</div>
</div>
)
}2. Strategy Detail Page
Example of displaying detailed strategy information:
// app/strategy/[id]/page.tsx
'use client';
import { useParams } from 'next/navigation';
import { useStrategy } from '@pwndao/sdk-v1-react';
import { formatUnits } from 'viem';
export default function StrategyDetailPage() {
const params = useParams();
const strategyId = params.id as string;
const { data: strategy, isLoading, error } = useStrategy(strategyId);
if (isLoading) return <div>Loading strategy...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!strategy) return <div>Strategy not found</div>;
const firstCollateralAsset = strategy.terms.collateralAssets[0];
const firstCreditAsset = strategy.terms.creditAssets[0];
const pairKey = `${firstCollateralAsset.address}/${firstCollateralAsset.chainId}-${firstCreditAsset.address}/${firstCreditAsset.chainId}`;
return (
<div>
<h1>{strategy.name}</h1>
<p>Strategy ID: {strategyId}</p>
{/* Strategy Overview */}
<div>
<h2>Overview</h2>
<p>{strategy.description}</p>
{/* Lending Stats */}
<div>
<h3>Lending Stats</h3>
<div>
<div>Total Committed: {formatUnits(strategy.lendingStats.totalCommittedAmount, 18)} USD</div>
<div>Total Utilized: {formatUnits(strategy.lendingStats.totalUtilizedAmount, 18)} USD</div>
<div>Total Available: {formatUnits(strategy.lendingStats.totalAvailableAmount, 18)} USD</div>
</div>
</div>
{/* Strategy Terms */}
<div>
<h3>Terms</h3>
<div>APR: {strategy.terms.apr[pairKey]}%</div>
<div>LTV: {strategy.terms.ltv[pairKey]}%</div>
<div>Duration: {strategy.terms.durationDays} days</div>
<div>Expiration: {strategy.terms.expirationDays} days</div>
</div>
</div>
</div>
);
}3. Creating Strategy Commitments
Example of a component for creating strategy commitments:
// components/StrategyCommitmentCreator.tsx
'use client';
import { useState } from 'react';
import { useAccount, useConfig } from 'wagmi';
import { sepolia } from 'wagmi/chains';
import { useMakeProposals, useUserWithNonce } from '@pwndao/sdk-v1-react';
import { createElasticProposals } from '@pwndao/v1-core';
import type { Strategy } from '@pwndao/v1-core';
interface Props {
strategy: Strategy;
}
export default function StrategyCommitmentCreator({ strategy }: Props) {
const { address, isConnected } = useAccount();
const [creditAmount, setCreditAmount] = useState('100');
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const config = useConfig();
const { userWithNonce: user } = useUserWithNonce([sepolia.id]);
const {
mutateAsync: makeProposal,
isPending: isLoading,
isSuccess,
error,
data: txHash,
} = useMakeProposals(user);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErrorMessage(null);
if (!isConnected || !address || !user) {
return;
}
try {
// Create proposals with proper parameters
const proposalsToCreate = createElasticProposals(
strategy,
address,
creditAmount,
config,
);
const res = await makeProposal(proposalsToCreate);
console.log("Proposals created successfully:", res);
} catch (err) {
console.error("Error creating commitment:", err);
setErrorMessage(err instanceof Error ? err.message : "An unknown error occurred");
}
};
return (
<div>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Credit Amount Input */}
<div>
<label>Credit Amount</label>
<input
type="text"
value={creditAmount}
onChange={(e) => setCreditAmount(e.target.value)}
placeholder="Enter amount"
required
/>
</div>
{/* Strategy Terms Display */}
<div>
<h3>Strategy Terms</h3>
<div>
<div>APR: {strategy.terms.apr[Object.keys(strategy.terms.apr)[0]]}%</div>
<div>LTV: {strategy.terms.ltv[Object.keys(strategy.terms.ltv)[0]]}%</div>
<div>Duration: {strategy.terms.durationDays} days</div>
<div>Expiration: {strategy.terms.expirationDays} days</div>
</div>
</div>
{/* Submit Button */}
<button
type="submit"
disabled={isLoading || (!isConnected && !address)}
>
{isLoading
? "Creating Commitment..."
: !isConnected
? "Connect Wallet to Continue"
: "Create Commitment"}
</button>
</form>
{/* Transaction Status */}
{isLoading && (
<div>
<p>Transaction in progress...</p>
</div>
)}
{isSuccess && (
<div>
<p>Commitment created successfully!</p>
{txHash && (
<p>
Transaction Hash:
<a
href={`https://sepolia.etherscan.io/tx/${txHash}`}
target="_blank"
rel="noopener noreferrer"
>
{JSON.stringify(txHash)}
</a>
</p>
)}
</div>
)}
{(error || errorMessage) && (
<div>
<p>Error creating commitment:</p>
<p>{errorMessage || error?.message || String(error)}</p>
</div>
)}
</div>
);
}4. Custom Authentication with useUserNonces
When using World App's MiniKit for authentication instead of Wagmi, you can use the useUserNonces hook to manage user nonces across different chains. This is particularly useful when building mini apps for World App:
import { useUserNonces } from '@pwndao/sdk-v1-react';
import { SupportedChain } from '@pwndao/sdk-core';
import { MiniKit } from '@worldcoin/minikit-js';
import { useEffect, useState } from 'react';
function WorldAppAuthComponent() {
const [address, setAddress] = useState<string>();
const chainIds = [SupportedChain.World, SupportedChain.Sepolia];
useEffect(() => {
// Initialize MiniKit
MiniKit.install();
}, []);
// Get user address using MiniKit's Wallet Auth
const handleAuth = async () => {
if (!MiniKit.isInstalled()) {
console.error('Please open this app in World App');
return;
}
try {
const response = await MiniKit.walletAuth();
setAddress(response.address);
} catch (err) {
console.error('Authentication failed:', err);
}
};
const { data: userWithNonces, isLoading, error } = useUserNonces(
address,
chainIds
);
if (!MiniKit.isInstalled()) {
return <div>Please open this app in World App</div>;
}
if (!address) {
return (
<button onClick={handleAuth}>
Authenticate with World App
</button>
);
}
if (isLoading) return <div>Loading user nonces...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>User Nonces</h2>
<div>Connected Address: {address}</div>
{userWithNonces && (
<div>
{chainIds.map(chainId => (
<div key={chainId}>
Chain {chainId}: Nonce {userWithNonces.nonces[chainId]}
</div>
))}
</div>
)}
</div>
);
}The useUserNonces hook is particularly useful when:
- Building mini apps for World App using MiniKit
- Need to track user nonces across multiple chains
- Implementing custom authentication flows outside of Wagmi
- Managing proposal sequences with proper nonce tracking
The hook automatically handles:
- Fetching user nonces for specified chains
- Keeping nonces in sync across multiple chains
- Caching nonce data using TanStack Query
For more information about MiniKit integration, visit the World App Mini Apps documentation.
Important Considerations
-
Multi-Proposal Support
- Uses
createElasticProposalsto generate multiple proposals - Handles batch proposal creation in a single transaction
- Manages nonces automatically across chains
- Uses
-
Error Handling
- Implements comprehensive error handling
- Shows user-friendly error messages
- Handles both transaction and validation errors
-
Transaction Status
- Displays loading state during transaction
- Shows success message with transaction hash
- Provides etherscan link for transaction tracking
-
Type Safety
- Uses TypeScript for type safety
- Leverages SDK types for consistency
- Provides proper type inference
For more detailed examples and advanced usage patterns, check out our Next.js Example App.