Get the dispute resolution interface running in your app in under 5 minutes.
npm install @stellar-split/sdk react react-domimport { InvoiceDetailPage } from '@stellar-split/sdk/ui';
import { StellarSplitClient } from '@stellar-split/sdk';const client = new StellarSplitClient({
rpcUrl: 'https://soroban-testnet.stellar.org',
networkPassphrase: 'Test SDF Network ; September 2015',
contractId: 'YOUR_CONTRACT_ID',
});import { create } from 'ipfs-http-client';
const ipfs = create({ url: 'https://ipfs.infura.io:5001' });
const handleIPFSUpload = async (file: File): Promise<string> => {
const result = await ipfs.add(file);
return result.path; // Returns CID
};function MyInvoicePage() {
const userAddress = "GABC...XYZ"; // From wallet connection
return (
<InvoiceDetailPage
invoiceId="123"
client={client}
userAddress={userAddress}
uploadToIPFS={handleIPFSUpload}
/>
);
}import '@stellar-split/sdk/ui/styles/dispute.css';Override CSS variables:
:root {
--dispute-warning-color: #f59e0b;
--dispute-success-color: #10b981;
--dispute-danger-color: #ef4444;
}Or provide custom classes:
<InvoiceDetailPage
className="my-custom-invoice-page"
// ... other props
/>-
Real-time Updates
- Automatic polling every 5 seconds
- SSE support (when available)
- Manual refresh capability
-
Dispute Panel (appears only when disputed)
- Dispute information (reason, timestamp, arbitrator)
- Evidence upload to IPFS
- Arbitrator voting (approve/reject)
- Real-time resolution display
-
Timeline
- Chronological event history
- Evidence CIDs with metadata
- Vote tracking
- Transaction hashes
-
Security
- Wallet-based arbitrator verification
- File validation (10MB max)
- Transaction signing with user wallet
import { useInvoiceStream } from '@stellar-split/sdk/ui';
const { invoice, disputeStatus } = useInvoiceStream({
invoiceId: '123',
client,
pollingInterval: 10000, // 10 seconds
onDisputeUpdate: (status) => {
console.log('Dispute updated:', status);
},
});<InvoiceDetailPage
invoiceId="123"
client={client}
userAddress={userAddress}
uploadToIPFS={handleIPFSUpload}
// Custom handlers
onVote={async (approve) => {
console.log('Vote cast:', approve);
// Analytics, notifications, etc.
}}
onEvidenceUploaded={(cid) => {
console.log('Evidence CID:', cid);
// Track evidence submissions
}}
/><InvoiceDetailPage
invoiceId="123"
client={client}
// No userAddress = read-only mode
uploadToIPFS={async () => ''}
/><InvoiceDetailPage
invoiceId="123"
client={client}
userAddress={arbitratorAddress}
uploadToIPFS={handleIPFSUpload}
/><InvoiceDetailPage
invoiceId="123"
client={client}
userAddress={participantAddress}
uploadToIPFS={handleIPFSUpload}
/>import { connectWallet } from '@stellar-split/sdk';
const userAddress = await connectWallet();
<InvoiceDetailPage
invoiceId="123"
client={client}
userAddress={userAddress}
uploadToIPFS={handleIPFSUpload}
/>const client = new StellarSplitClient({
rpcUrl: 'https://soroban-testnet.stellar.org',
networkPassphrase: 'Test SDF Network ; September 2015',
contractId: 'YOUR_CONTRACT_ID',
adapter: walletConnectAdapter, // Your WalletConnect adapter
});import { create } from 'ipfs-http-client';
const ipfs = create({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
headers: {
authorization: 'Basic ' + btoa(projectId + ':' + projectSecret),
},
});
const uploadToIPFS = async (file: File) => {
const result = await ipfs.add(file);
return result.path;
};import { PinataSDK } from '@pinata/sdk';
const pinata = new PinataSDK({ apiKey, apiSecret });
const uploadToIPFS = async (file: File) => {
const result = await pinata.pinFileToIPFS(file);
return result.IpfsHash;
};import { NFTStorage } from 'nft.storage';
const storage = new NFTStorage({ token: API_TOKEN });
const uploadToIPFS = async (file: File) => {
const cid = await storage.storeBlob(file);
return cid;
};const uploadToIPFS = async (file: File): Promise<string> => {
try {
const cid = await ipfsClient.add(file);
return cid.path;
} catch (error) {
console.error('IPFS upload failed:', error);
throw new Error('Failed to upload to IPFS. Please try again.');
}
};const handleVote = async (approve: boolean) => {
try {
await client.voteDispute({
invoiceId,
arbiter: userAddress,
approve,
});
} catch (error) {
console.error('Vote failed:', error);
// Show user-friendly error message
}
};interface InvoiceDetailPageProps {
invoiceId: string;
client: StellarSplitClient;
userAddress?: string;
uploadToIPFS: (file: File) => Promise<string>;
className?: string;
}interface DisputeStatus {
invoiceId: string;
disputed: boolean;
arbiter: string;
resolved: boolean;
resolution: 'approved' | 'rejected' | null;
reason?: string;
openedBy?: string;
openedAt?: number;
}interface DisputeTimelineEvent {
id: string;
type: 'dispute_opened' | 'evidence_submitted' | 'vote_cast' | 'dispute_resolved';
timestamp: number;
actor: string;
description: string;
metadata?: {
evidenceCid?: string;
vote?: 'approve' | 'reject';
resolution?: 'approved' | 'rejected';
txHash?: string;
};
}const mockUploadToIPFS = async (file: File): Promise<string> => {
await new Promise(resolve => setTimeout(resolve, 500));
return 'QmMockCID123ABC';
};import { createMockClient } from '@stellar-split/sdk/testing';
const mockClient = createMockClient({
getDisputeStatus: async () => ({
invoiceId: '123',
disputed: true,
arbiter: 'GABC...ARBITER',
resolved: false,
resolution: null,
}),
});-
Lazy Load Component
const InvoiceDetailPage = lazy(() => import('@stellar-split/sdk/ui'));
-
Memoize Upload Handler
const uploadToIPFS = useCallback(async (file: File) => { return await ipfs.add(file); }, [ipfs]);
-
Debounce Polling
pollingInterval: 10000 // Increase for less frequent updates
- ✅ Check invoice status is disputed
- ✅ Verify
disputeStatusis being fetched - ✅ Check browser console for errors
- ✅ Ensure
userAddressis provided - ✅ Verify user is the assigned arbitrator
- ✅ Check dispute is not already resolved
- ✅ Check file size (< 10MB)
- ✅ Verify IPFS service is reachable
- ✅ Check API credentials
- ✅ Review browser console for CORS errors
- ✅ Check polling is enabled
- ✅ Verify client is properly initialized
- ✅ Check network connectivity
- ✅ Look for errors in useInvoiceStream hook
- Customize Styling: Override CSS variables or provide custom classes
- Add Notifications: Hook into
onDisputeUpdatefor push notifications - Extend Timeline: Add custom event types for your use case
- Integrate Analytics: Track dispute metrics and user behavior
- 📖 Full Implementation Guide
- 📋 Verification Checklist
- 📊 Implementation Summary
- 🧪 Test Examples
- 🎨 Style Guide
- Documentation:
docs/API.md - Examples:
examples/ - Issues: GitHub Issues
- Discord: StellarSplit Community
That's it! You now have a fully functional dispute resolution interface. 🎉
Start with the basic setup and gradually customize as needed. The component handles all the complex state management, real-time updates, and security checks automatically.