GitBond Escrow is an upgradeable ERC-20 escrow contract for refundable contribution deposits. It supports both the standard allowance-based deposit flow and a permit-backed single-transaction flow for self-submitted deposits.
createEscrow(key, counterparty, beneficiary, token, amount)- Pulls
amountfrommsg.sender. - Keeps the approval-based path for tokens that do not use
permit().
- Pulls
createEscrowWithPermit(key, payer, counterparty, beneficiary, token, amount, permit_)- Requires
msg.sender == payer. - Validates GitBond-specific inputs before calling the token.
- Calls
IERC20Permit(token).permit(payer, address(this), amount, ...). - Pulls
amountfrompayerin the same transaction. - Preferred path whenever the token supports
permit().
- Requires
refundEscrow(key),slashEscrow(key), andclaim(token)refundEscrow(key)can be called by the owner, the escrow counterparty, or a refund delegate elected by that counterparty.slashEscrow(key)can be called by the owner, the escrow counterparty, or a slash delegate elected by that counterparty.- Refunds credit the beneficiary claim balance for that token; slashes credit the counterparty and treasury claim balances for that token.
claim(token)lets the caller withdraw their full claim balance for that token.
addRefundDelegate(delegate),removeRefundDelegate(delegate),addSlashDelegate(delegate),removeSlashDelegate(delegate)- Lets each counterparty manage its own settlement delegates.
setCounterparty(key, newCounterparty)- Owner-only update to the slash destination for an active escrow.
claimableAmount(claimant, token)- Returns the current aggregate claim balance for that claimant and token.
treasuryClaimableAmount(token)- Returns the current aggregate claim balance for the treasury and token.
isRefundDelegate(counterparty, delegate)/isSlashDelegate(counterparty, delegate)- Returns whether a delegate is currently authorized by that counterparty.
pause()/unpause()- Owner-only circuit breaker for user-facing flows.
Both creation entrypoints reuse the same internal _createEscrowFrom(...) path, so validation, accounting, token transfer, and event emission stay identical.
All token movements use standard ERC-20 transfers:
- escrow creation uses
transferFrom(...) - claim payouts use
transfer(...)
createEscrowWithPermit(...) uses the token's own audited permit() implementation. GitBond does not implement custom EIP-712 domain logic or signature recovery.
If the token does not support permit() or the provided permit parameters are wrong, createEscrowWithPermit(...) reverts with the token's native error behavior.
PermitParams is:
struct PermitParams {
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}Recommended client behavior:
- Prefer
createEscrowWithPermit(...)when the token and network supportpermit(). - Use
createEscrow(...)as the compatibility path whenpermit()is unavailable. - Call
createEscrowWithPermit(...)directly from the payer account; relayed permit submissions are intentionally rejected.
Example permit-backed call:
GitBondEscrow.PermitParams memory permit_ = GitBondEscrow.PermitParams({
deadline: deadline,
v: v,
r: r,
s: s
});
escrow.createEscrowWithPermit(
key,
payer,
counterparty,
beneficiary,
token,
amount,
permit_
);Fallback approval-based call:
IERC20(token).approve(address(escrow), amount);
escrow.createEscrow(key, counterparty, beneficiary, token, amount);setTreasury(address)updates the treasury address.setTokenWhitelist(address, bool)whitelists or un-whitelists six-decimal tokens.removeTokenFromWhitelist(address)removes a token from the whitelist.setPlatformCut(uint256)sets the slash treasury cut in basis points, capped at3000.setMinEscrowAmount(uint256)sets the deposit floor and must remain non-zero.
All admin methods are onlyOwner.
Deployment note:
- The contract does not whitelist tokens automatically at initialization unless you pass them in
initialize(currently via deploy scripts). - After deployment in standard mode, the owner must explicitly whitelist each supported ERC-20.
Deployment mode:
- The deploy script uses OpenZeppelin Foundry Upgrades
Upgrades.deployUUPSProxy(...). - The library validates the implementation, deploys the implementation contract, then deploys the UUPS proxy.
Whitelist trust assumption:
- The whitelist is a trusted-token allowlist, not a complete defense against arbitrary ERC-20 implementations.
- GitBond assumes whitelisted tokens implement
transfer,transferFrom, balance accounting, andpermitsemantics honestly when those paths are used. - The on-chain whitelist check enforces six decimals, but safe operation still depends on the owner only whitelisting known-good stablecoins.
- Storage is isolated in
GitBondEscrowStorageusing an ERC-7201-style namespaced storage slot. - This branch removes an unused pre-launch escrow field, so treat it as the version to deploy rather than an in-place storage-compatible upgrade from earlier drafts.
USD_TOKEN_DECIMALS = 6DEFAULT_MIN_ESCROW_AMOUNT = 20_000
Initial deployment uses script/GitBondEscrow.s.sol:GitBondEscrowScript.
Required environment variables:
ESCROW_TREASURYESCROW_OWNER- Optional:
ESCROW_IMPLEMENTATIONwith defaultGitBondEscrow.sol
Upgrade uses script/GitBondEscrow.s.sol:UpgradeGitBondEscrowScript.
Required environment variables:
ESCROW_PROXY- Optional:
ESCROW_IMPLEMENTATIONwith defaultGitBondEscrow.sol
The upgrade script runs OpenZeppelin upgrade validation before upgrading the proxy.
Build:
forge buildTest:
forge testFormat:
forge fmt- Contract:
src/GitBondEscrow.sol - Unit tests:
test/GitBondEscrow.t.sol - Invariant tests:
test/GitBondEscrow.invariant.t.sol - Deployment and upgrade scripts:
script/GitBondEscrow.s.sol - Operational scripts:
script/CreateEscrow.s.sol,script/RefundEscrow.s.sol,script/SlashEscrow.s.sol,script/ClaimEscrow.s.sol - Product context:
PLAN.md