Smart contracts
work2own V1 is two Solidity contracts (compiler 0.8.37) on Robinhood Chain mainnet, chain ID 4663.
Both contracts are source-verified on Blockscout, so anyone can read the deployed code: Work2OwnEscrow and StockPayout.
Addresses
| Name | Address |
|---|---|
| Work2OwnEscrow | 0xB20703EB380d40601fC57b05a1965e6131Df422b |
| StockPayout | 0xEC5fe4d31d35FCECD7FC9b3D6A1B6eDAA1493d8d |
| Admin (deployer) | 0x5Dd2868AC5039fFb7597146363ba4EA03Ce8D562 |
| Operator | 0x0eF95c4aA8095De41332646eD433dc183c99231c |
| Arbiter and treasury | 0xf4af5Cc55D7dcDAEb958B7582d7e9d5E758D2Aeb |
External contracts used:
| Name | Address |
|---|---|
| USDG (6 decimals) | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 |
| Robinhood stock token registry | 0xe10b6f6B275de231345c20D14Ab812db62151b00 |
| Uniswap SwapRouter02 | 0xCaf681a66D020601342297493863E78C959E5cb2 |
| Uniswap QuoterV2 (used off-chain for quotes) | 0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7 |
Stock tokens have 18 decimals. In the contracts, a payout token of 0x0000000000000000000000000000000000000000 means USDG.
Roles
| Role | Contract | Held by | Can do |
|---|---|---|---|
DEFAULT_ADMIN_ROLE | Both | Admin | Pause and unpause the escrow; set the fee (max 5%), the maximum deposit and the treasury; add, change or disable payout tokens; set the slippage (max 5%) and the per-payout cap for quote-protected tokens; rescue tokens sent by mistake (never USDG from the escrow); grant and revoke all roles |
OPERATOR_ROLE | Escrow | Operator | Verify or reject automatic quests; run pending payouts with a price quote |
ARBITER_ROLE | Escrow | Arbiter | Settle disputed gigs |
ESCROW_ROLE | StockPayout | Escrow | Call swapAndDeliver |
Work2OwnEscrow
Constants
| Constant | Value |
|---|---|
SLOT_RESERVATION | 24 hours |
REVIEW_WINDOW | 7 days |
PAYOUT_FALLBACK_DELAY | 3 days |
MAX_FEE_BPS | 500 (5%) |
BPS_DENOMINATOR | 10,000 |
Quest functions
| Function | Who | Rules |
|---|---|---|
createCampaign(rewardPerSlot, slots, deadline, manualReview) | Anyone | Reward and slots above 0, deadline in the future, reward × slots ≤ maxDeposit. Takes reward × slots into the escrow and the fee to the treasury |
claimSlot(campaignId, payoutToken) | Anyone but the employer | Quest open (not closed, before deadline), a slot free, token enabled or USDG, and this wallet never claimed this quest |
releaseExpiredSlot(campaignId, worker) | Anyone | Reservation older than 24 hours, or deadline passed. The claim becomes Expired |
submitQuest(campaignId, proofHash) | The reserving worker | Within 24 hours of reserving and before the deadline |
approveQuest(campaignId, worker) | Employer | Manual quests only |
verifyQuest(campaignId, worker, quotedMinOut) | Operator | Automatic quests only |
releaseQuest(campaignId, worker) | Anyone | Manual quests, at least 7 days after submission |
rejectQuest(campaignId, worker) | Employer within 7 days (manual), or operator (automatic) | Frees the slot |
closeCampaign(campaignId) | Anyone | After the deadline, no submission waiting. Refunds reward × (slots − completed) to the employer |
Gig functions
| Function | Who | Rules |
|---|---|---|
createGig(worker, budget, deadline) | Anyone | Worker is not the caller, budget above 0 and ≤ maxDeposit, deadline in the future. Takes the budget into the escrow and the fee to the treasury |
submitGig(gigId, deliverableHash, payoutToken) | The named worker | Gig funded, before the deadline, token enabled or USDG |
approveGig(gigId) | Employer | Gig delivered |
releaseGig(gigId) | Anyone | At least 7 days after delivery |
rejectGig(gigId, reasonHash) | Employer | Within 7 days of delivery. The gig becomes Disputed |
resolveDispute(gigId, workerShareBps) | Arbiter | Gig disputed. Worker gets budget × share / 10,000; the rest is refunded to the employer |
cancelGig(gigId) | Employer | Gig not delivered and deadline passed. Refunds the full budget |
Payout and refund functions
| Function | Who | Rules |
|---|---|---|
retryPayout(payoutId, quotedMinOut) | The payout's worker, or the operator | Payout pending |
changePayoutToken(payoutId, newPayoutToken, quotedMinOut) | The payout's worker | Payout pending for at least 3 days. Switches the token (or to USDG) and tries again at once |
withdrawRefund() | Anyone with a pending refund | Sends the caller's own pending refund to the caller |
Pause
Pause stops every function that moves USDG or finalizes work. These keep working while paused, so a pause never uses up a deadline or a review window: claimSlot, releaseExpiredSlot, submitQuest, rejectQuest, submitGig, rejectGig.
Views
getCampaign, getClaim, getGig, getPayout, pendingRefunds(account), feeBps, maxDeposit, treasury, campaignCount, gigCount, payoutCount, paused.
Statuses: claims are None, Reserved, Submitted, Completed, Rejected or Expired. Gigs are None, Funded, Submitted, Disputed, Completed, Cancelled or Resolved. Payouts are None, Pending or Paid.
Events
CampaignCreated, SlotClaimed, SlotReleased, QuestSubmitted, QuestCompleted, QuestRejected, CampaignClosed, GigCreated, GigSubmitted, GigCompleted, GigRejected, GigCancelled, DisputeResolved, PayoutCreated, PayoutPaid, PayoutDeferred (with the revert reason), PayoutTokenChanged, RefundDeferred, RefundWithdrawn, TreasuryUpdated, FeeUpdated, MaxDepositUpdated, TokenRescued, Paused, Unpaused.
StockPayout
| Function | Who | What it does |
|---|---|---|
swapAndDeliver(stockToken, recipient, usdgAmount, quotedMinOut) | Escrow only | Runs the safety checks, swaps USDG for the stock token on the token's Uniswap V3 pool with the worker as recipient, and checks the delivered amount |
previewOracleMinOut(stockToken, usdgAmount) | View | The minimum an oracle-protected payout would accept right now |
tokenConfig(stockToken) | View | Enabled flag, tier, decimals, pool fee, price feed and maximum price age |
slippageBps(), tierQuoteMaxUsdg() | View | Current slippage and per-payout cap for quote-protected tokens |
setToken, disableToken, setSlippageBps, setTierQuoteMaxUsdg, rescueToken | Admin | Configuration |
The checks and the price formula are explained in Stock payouts.
StockPayout never keeps stock tokens and holds USDG only inside a single swap transaction.