Setoff

Design

Smart contract

Setoff.sol: one contract, no owner, no upgrades. It stores IOUs, holds deposits, verifies and applies cycles, resolves disputes by agreement, tracks credit lines, and applies opt-in USDC↔EURC swaps.

The IOU

solidity
struct IOU {
    address debtor;
    address creditor;
    address token;     // USDC or EURC, fixed at deployment
    uint128 amount;    // 6 decimals
    uint64  deadline;  // last timestamp it may settle
    uint256 nonce;     // makes otherwise identical IOUs distinct
    bytes32 ref;       // invoice reference, carried into events
}
// id = EIP-712 hash, domain ("Setoff", "1", chainId, contract)

An IOU's id is its EIP-712 typed-data hash, so the same struct the debtor signs in their wallet is the key it's stored under. Signatures are checked with OpenZeppelin's SignatureChecker, which also accepts EIP-1271 smart-contract wallets.

Functions

FunctionWhoWhat it does
submit(iou, signature)anyoneAdds a debtor-authorised IOU to the pool. The debtor may submit with an empty signature.
cancel(id)debtor or creditorWithdraws (debtor) or rejects (creditor) a pending IOU.
deposit(token, amount)anyoneMoves tokens in and credits your balance.
withdraw(token, amount)balance ownerMoves tokens out, up to your balance.
settle(ids, amounts, parties, draws, fx)anyoneApplies a cycle: pays each IOU the given amount (partial or full), applies credit draws, nets, then applies opt-in USDC↔EURC swaps. Reverts unless every net debtor is covered and the swaps balance per token.
dispute(id)debtor or creditorFreezes an open IOU so no cycle can pay it.
offer(id, remaining)debtor or creditorProposes what is still owed on a disputed IOU. Matching offers reopen it at that amount; 0 cancels it.
setFxPreference(sell, buy, minRate)anyoneOpts in to having your leftover sell converted into buy in cycles, never below minRate (buy per sell, 1e6 = 1:1). 0 opts out.
setCreditLine(borrower, token, limit)lenderLets a borrower overdraw up to limit, funded from the lender's deposit. 0 stops new draws.
repay(lender, token, amount)borrowerRepays credit from the borrower's balance.
getIOU(id)viewReturns the IOU (current amount), its status, the last cycle that paid it, and how much is paid.
creditLine(lender, borrower, token)viewReturns the limit and how much is drawn and not yet repaid.
hashIOU(iou)viewThe EIP-712 id, for wallets and solvers.

What settle checks

Events

solidity
event IOUSubmitted(bytes32 indexed id, address indexed debtor, address indexed creditor,
                   address token, uint128 amount, uint64 deadline, uint256 nonce, bytes32 ref);
event IOUCancelled(bytes32 indexed id, address indexed by);
event IOUSettled(bytes32 indexed id, uint64 indexed cycle, address indexed debtor,
                 address creditor, address token, uint128 amount, uint128 remaining, bytes32 ref);
event IOUDisputed(bytes32 indexed id, address indexed by);
event IOUOffer(bytes32 indexed id, address indexed by, uint128 amount);
event IOUResolved(bytes32 indexed id, uint128 remaining);
event CreditLineSet(address indexed lender, address indexed borrower, address indexed token, uint128 limit);
event CreditDrawn(uint64 indexed cycle, address indexed borrower, address indexed lender, address token, uint128 amount);
event CreditRepaid(address indexed borrower, address indexed lender, address indexed token, uint128 amount);
event NetPosition(uint64 indexed cycle, address indexed account, address indexed token, int256 net);
event CycleSettled(uint64 indexed cycle, address indexed solver, uint256 iouCount,
                   uint256[] gross, uint256[] netFunded);
event Deposited(address indexed account, address indexed token, uint256 amount);
event Withdrawn(address indexed account, address indexed token, uint256 amount);

Guarantees

These are enforced by the contract and checked by its test suite, including fuzz and stateful invariant tests, in the repository.

Security model

Deployment

Current network: Arc Mainnet, contract 0x9C09F52cFB6f2B563FD0fC27cf585DE33dD820A6.