Disputes
startDisputeSigned
async startDisputeSigned(
walletClient: WalletClient,
escrowId: bigint,
signature: Hex,
deadline: bigint,
nonce: bigint
): Promise<Hex>
startDispute() — buyer or seller signs a typed message off-chain, then anyone (user, relayer, or dApp) can submit it on-chain to open a dispute.
Perfect for zero-gas UX where users don't need native tokens to initiate disputes.
Parameters
walletClient: WalletClient– Wallet submitting the transaction (can be different from signer)escrowId: bigint– The escrow IDsignature: Hex– EIP-712 signature from buyer or sellerdeadline: bigint– Unix timestamp when signature expiresnonce: bigint– User's nonce (prevents replay attacks)
Returns
Promise<Hex> – Transaction hash
Full Flow
import { createPalindromeSDK } from '@/lib/createSDK';
const { sdk, walletClient } = await connectAndInitSDK();
const escrowId = 42n;
try {
// Step 1: Get user's current nonce for this escrow
const userAddress = walletClient.account.address;
const nonce = await sdk.getUserNonce(escrowId, userAddress);
// Step 2: Create signature deadline (e.g. 60 minutes)
const deadline = await sdk.createSignatureDeadline(60);
// Step 3: User signs the StartDispute message (NO GAS)
const signature = await sdk.signStartDispute(
walletClient,
escrowId,
deadline,
nonce
);
console.log("User signed dispute request (no gas paid)");
console.log("Signature:", signature);
// Step 4: Submit signature on-chain (relayer or user pays gas)
const txHash = await sdk.startDisputeSigned(
walletClient, // Could be relayer's wallet
escrowId,
signature,
deadline,
nonce
);
console.log("Dispute opened successfully!");
console.log("Transaction:", txHash);
console.log("Escrow state → DISPUTED");
// Next: both parties submit evidence via submitDisputeMessage()
} catch (error: any) {
if (error.code === "SIGNATURE_EXPIRED") {
alert("Signature expired — please sign again");
} else if (error.code === "INVALID_STATE") {
alert("Cannot dispute in current state (must be AWAITING_DELIVERY)");
} else if (error.code === "INVALID_ROLE") {
alert("Only buyer or seller can start a dispute");
} else {
console.error("Signed dispute failed:", error.shortMessage || error.message);
}
}
Helper Methods
The SDK provides convenience methods for the signing flow:
// Create a deadline (default 10 minutes, or specify custom)
const deadline = await sdk.createSignatureDeadline(60); // 60 minutes
// Get user's current nonce
const nonce = await sdk.getUserNonce(escrowId, userAddress);
// Sign the dispute start request (EIP-712)
const signature = await sdk.signStartDispute(
walletClient,
escrowId,
deadline,
nonce
);
// Check if signature is still valid
const isValid = !sdk.isSignatureDeadlineExpired(deadline);
Signature Structure (EIP-712)
The signed message follows this structure:
{
escrowId: bigint;
buyer: Address;
seller: Address;
arbiter: Address;
token: Address;
amount: bigint;
depositTime: bigint;
deadline: bigint; // Signature expiration
nonce: bigint; // Anti-replay
}
Security Features
✅ Deadline protection – Signatures expire after specified time
✅ Nonce system – Prevents replay attacks
✅ EIP-712 – Structured, readable signatures
✅ Domain separation – Signatures only valid for your contract
✅ Role verification – Only buyer/seller can sign
Relayer Pattern
For true transactions, set up a relayer:
// Frontend: User signs (no gas)
const { signature, deadline, nonce } = await createDisputeSignature();
// Send to backend
await fetch('/api/relay-dispute', {
method: 'POST',
body: JSON.stringify({ escrowId, signature, deadline, nonce })
});
// Backend: Relayer submits (pays gas)
await sdk.startDisputeSigned(
relayerWallet, // Relayer pays gas
escrowId,
signature,
deadline,
nonce
);
Why Use Disputes?
User Benefits:
- No native token (ETH/BNB) needed to open disputes
- Instant protection even if user has no gas
- Better UX during emergencies
Platform Benefits:
- Subsidize dispute costs for better user experience
- Control when/how disputes are opened
- Can add additional validation before submitting
Flow After Dispute Opens
- ✅ Escrow state →
DISPUTED - 📝 Buyer submits evidence via
submitDisputeMessage() - 📝 Seller submits evidence via
submitDisputeMessage() - ⚖️ Arbiter reviews and calls
submitArbiterDecision() - 💰 Funds released to winner (seller or buyer)
See also → startDispute() · signStartDispute() · submitDisputeMessage() · getUserNonce()
