How 2-of-3 Multisig Protects Your Money: Inside the OpenVPT Escrow Contract
Buyer, seller, platform: any two must sign before funds move. EIP-712 signatures, the contract's checks, every signing combination and the platform's limits.
- Author
- OpenVPT Team
- Published
- Reading
- 11 min read
- Tags
- multisig
- escrow
- EIP-712
- USDC
- smart contracts
- fund safety
Contents
TL;DR When you pay for an order on OpenVPT, your USDC doesn't go into a platform account. It's locked in
VPTEscrow, a smart contract on Base. Getting it out takes signatures from two different parties out of three (buyer, seller, platform), and both have to sign the exact same payout. The platform can never move funds on its own. The contract owner can pause new orders, rotate the platform's signing key and manage which tokens are accepted, but no function lets it settle or withdraw. The trade-off: if no two parties are willing to sign, the funds stay locked. We cover that honestly below, along with what's planned.
The short version: where is your money?
On many marketplaces, "escrow" means the money sits in the platform's own account, and you have to trust it not to misuse the funds, get hacked or go under.
OpenVPT works differently. When you pay, your own wallet calls the escrow contract's fund() function and transfers USDC (a dollar-backed stablecoin with 6 decimals) straight into the contract. From that moment:
- The money is in the contract, not in a platform wallet.
- The contract has exactly one way out: the
settle()function. settle()requires two valid EIP-712 signatures from two different parties.
In other words, the platform has no path to move funds on its own.
Three keys: who are the three parties?
For every order, the contract records three addresses:
| Party | Where the address comes from | What their signature means |
|---|---|---|
| Buyer | The wallet that called fund(), which must be the SIWE-verified wallet chosen at checkout, since the platform's CreateOrder signature is bound to it | "I agree to this split of the money I paid" |
| Seller | The wallet the seller verified on OpenVPT with SIWE (Sign-In with Ethereum) | "I agree to this split of the sale proceeds" |
| Platform | The platformSigner address configured in the contract | "The platform endorses this split under its rules (confirmation, timeout or arbitration)" |
Each "key" is a wallet's private key. Buyers and sellers keep theirs; OpenVPT never sees them. Two more details:
- Seller requirements. To sell on OpenVPT you need a KYC status of VERIFIED and a wallet you've proven control of through SIWE. That's how we know the seller address in the contract really belongs to the seller.
- Terms locked at checkout. When the buyer pays, the platform signs a
CreateOrdermessage that binds the order ID, buyer, seller, token, amount, fee and an expiry time together. The contract verifies it, so nobody can quietly swap the seller address or inflate the fee at payment time.
EIP-712 in plain English
Some wallet signing prompts show an unreadable blob of hex; others show a neat list of named fields. The second kind is EIP-712 typed structured data: instead of signing mystery bytes, you sign a form with labelled fields. OpenVPT's settlement form looks like this:
Settle(
bytes32 orderId, // which order
uint256 toSeller, // amount paid to the seller
uint256 toBuyer, // amount refunded to the buyer
uint256 toPlatform // platform fee
)Every form also carries a "domain": the name VPTEscrow, version 1, the chain ID (8453 on Base mainnet) and the contract address. That gives you three guarantees:
- Readable. EIP-712-aware wallets show you
toSeller,toBuyerandtoPlatformfield by field. - Not replayable elsewhere. Because the domain pins the chain and contract, the signature is worthless on any other chain or contract.
- Tamper-evident. Change any amount by even one base unit and the signature no longer matches, so the contract rejects it.
Tip: USDC has 6 decimals, so
1200000000in the contract means 1,200 USDC. If your wallet shows raw integers, shift the decimal point six places left before comparing.
What the contract actually checks before paying out
Here's what VPTEscrow.settle() checks, in order:
- The order exists. The
orderIdmust have been funded, otherwise the call reverts withOrderNotFound. - It settles once. An already-settled order is rejected (
OrderAlreadySettled). The contract marks the order settled before transferring anything, and it's protected against reentrancy. - The parts add up exactly.
toSeller + toBuyer + toPlatform == amount, to the last base unit (AmountMismatch). Nothing is left behind in the contract and nothing appears from thin air. - The fee has a pro-rata floor.
toPlatformmust be at leastfee × toSeller ÷ amount(integer division, rounded down). Whatever share the seller receives, the platform's cut can be no lower than that same share of the fee. On a full refund the floor is zero. - Both signatures are valid. A malformed signature is rejected (
InvalidSignature). - Each signer is one of this order's three parties: the order's buyer, its seller or the current
platformSigner. Anyone else is rejected (UnauthorizedSigner). - The two signers are different. One party signing twice doesn't count (
DuplicateSigner).
Only then does the contract pay the seller, the buyer and the platform's fee recipient. Anyone can submit the transaction (in practice the platform relays it and pays the gas), because authority comes from the signatures, not from the sender.
A worked example: a 1,200 USDC order
Say an item costs 1,200 USDC at the default 5% fee (feeBps = 500):
fee = 1,200 × 500 ÷ 10,000 = 60 USDC
OpenVPT's split formula (splitSettlement) works out the seller's gross share first, then takes the fee from that share pro rata (rounded down). Whatever isn't in the seller's gross goes back to the buyer.
| Scenario | Seller receives | Buyer refund | Platform fee | Contract's fee floor |
|---|---|---|---|---|
| Normal completion (100% to seller) | 1,140 | 0 | 60 | 60 × 1,140 ÷ 1,200 = 57 |
| Full refund (0% to seller) | 0 | 1,200 | 0 | 0 |
| Arbitration awards seller 30% | 342 | 840 | 18 | 60 × 342 ÷ 1,200 = 17.1 |
The 30% case step by step: seller gross = 1,200 × 30% = 360; platform = 60 × 360 ÷ 1,200 = 18; seller receives 360 − 18 = 342; buyer refund = 1,200 − 360 = 840. Total: 342 + 840 + 18 = 1,200. The platform's 18 clears the contract's 17.1 floor, so the settlement is valid.
The takeaway: the platform fee follows only what the seller actually receives. On a full refund, the platform takes nothing.
Every signing combination, and when it's used
Two out of three gives three possible pairs. Here's how each one shows up in practice:
| Signers | Typical situation | Who signs first | Outcome |
|---|---|---|---|
| Buyer + platform | Buyer clicks "Confirm receipt" | Platform signs automatically, buyer signs in their wallet | Seller is paid (minus the fee) |
| Buyer + platform | Seller misses the delivery deadline | Platform signs the refund, buyer signs to submit | Full refund to the buyer |
| Seller + platform | Inspection window (default 3 days after delivery) ends with no confirmation and no dispute | Platform signs, seller signs to submit | Seller is paid (minus the fee) |
| Platform + buyer or seller | A dispute is resolved by arbitration (e.g. 30/70) | Platform signs after the arbitrator's ruling, then the buyer or seller co-signs | Funds split per the ruling |
| Buyer + seller | Both parties agree directly on one split | Each signs | Paid out as they both signed; no platform signature needed |
Some details:
- Confirming receipt. The platform co-signs automatically, so the buyer's signature completes the pair and is relayed straight away. The seller doesn't need to sign.
- Timeouts. Scheduled jobs handle missed deliveries and expired inspection windows. The platform signs first, and the second signature normally comes from whoever is receiving the money (the buyer for a refund, the seller for a payout), since they have the most reason to sign. Technically, a valid signature from either the buyer or the seller completes the pair.
- Arbitration. The buyer can open a dispute during the inspection window. An assigned arbitrator reviews both sides' evidence and rules as a split ("X% to the seller"). The platform signs that split and the buyer or seller (usually whoever receives funds) co-signs. There is no appeal step today; the ruling is final.
- The platform signs once.
Settlesignatures don't expire and can't be revoked, so our current policy is that the platform signs at most one settlement per order. Any later request for a different split is refused, logged in the audit trail and handed to manual review. That rules out two conflicting splits both carrying a platform signature.
What the platform can and can't do
VPTEscrow has an owner, the operator's admin address. Its powers are few, and they're all in the contract where anyone can verify them on-chain:
| The owner can | The owner cannot |
|---|---|
Pause and unpause new fund() payments | Call settle() on any order |
Rotate the platformSigner address (e.g. routine key rotation) | Withdraw or move any escrowed funds |
| Set the token allowlist | Change the buyer, seller, amount or fee of a funded order |
| Change the fee recipient address | Renounce ownership (deliberately disabled so pausing and key rotation can't be lost forever) |
Note that pausing only blocks new payments. settle() isn't affected, so orders already in escrow can still settle with two valid signatures even while the contract is paused.
Ownership transfers take two steps (Ownable2Step): the new owner must accept, so a mistyped address can't take control.
Being straight about it: what 2-of-3 doesn't protect against
Multisig removes a lot of risk, not all of it:
- Two parties colluding. 2-of-3 assumes at least two of the three are honest. Any two acting together can sign any split that adds up. On the platform side we limit this with the sign-once policy, the arbitration process and audit logs, but the contract itself can't tell who's lying.
- What you sign is what you get. Signatures can't be taken back. Always check the order ID and all three amounts in your wallet, and never sign on an unfamiliar site or through a link someone sent you in chat.
- Platform key security. A leaked platform key gives an attacker the platform's one vote, but they'd still need the buyer's or seller's signature to move anything, and the owner can rotate
platformSignerimmediately. - Contract audit. A third-party audit of the contract before launch is planned. Until it's done, size your trades to the risk you're comfortable with.
What if the platform disappears?
With the current design, escrowed funds stay in the contract until two different parties sign the same split.
- If buyer and seller agree, they can bypass the platform entirely: both sign the same
Settlepayload and anyone submits it. The contract checks signatures, not who sent the transaction. That does take some technical skill (producing EIP-712 signatures yourself and paying gas), and OpenVPT doesn't currently offer a dedicated "platform offline" interface. - If buyer and seller can't agree and the platform can't sign, there is no timeout refund or unilateral withdrawal in the contract. The funds stay locked.
- Nobody, the platform included, can redirect the money, because no function bypasses the two-signature rule.
Improving this scenario (for example, adding expiry to settlement signatures and a way out that doesn't depend on the platform) is on the roadmap for a future contract version. It's planned, not built. Until then, keep individual trades to amounts you can afford to have tied up.
Two safeguards at checkout
There's a subtler risk just before money reaches the contract: you hit cancel, but your payment transaction was already broadcast and is still waiting to be mined. OpenVPT has two safeguards.
Safeguard 1: short-lived CreateOrder signatures
Every payment needs a platform-signed CreateOrder, and it expires at whichever comes first: the payment deadline (24 hours after the order is placed) or 15 minutes from issue. The contract rejects expired ones. The payment page fetches a fresh signature every time it loads the order, so normal use isn't affected, but no signature stays valid for more than 15 minutes.
Safeguard 2: deferred cancellation
If you cancel before paying, the order doesn't flip to cancelled straight away. It shows as "cancelling":
- The cancel time is recorded and no new payment signatures are issued.
- The system waits at least 15 minutes plus a grace period, so any signature you might still hold has expired and any broadcast transaction has had time to land.
- Before finalising, it checks the chain for a payment on this order.
- If the payment landed first, payment wins: the order proceeds as funded and the cancel request is voided.
- If a payment is somehow confirmed after cancellation (the safety net), the system takes a refund-only path: the platform signs a full refund, you co-sign, and the money comes back to you with no fee.
Checklist before and after you trade
- Before paying, check that your wallet shows Base, USDC and the right amount, and that you're on the official OpenVPT order page, not a link from chat.
- Before signing a
Settle, checkorderId,toSeller,toBuyerandtoPlatform(remember the 6 decimals). - If a seller asks you to "pay off-site" or "move to another messaging app," stop. Off-platform payments get no contract protection, and OpenVPT chat flags messages that share contact details.
- Inspect what you received within the inspection window, and open a dispute in time if something's wrong. Delivered files are recorded with a SHA-256 hash, which helps establish what was actually delivered; license keys are stored encrypted.
- Only click "Confirm receipt" once you're satisfied; it releases the payment and can't be undone.
FAQ
Can the platform freeze my money? It can pause new payments, but it can't block a settlement that already has two valid signatures, and it can't move the money on its own. As noted above, though, without two signatures the funds stay locked.
What happens with banned users? A ban doesn't cancel or alter orders in progress; scheduled jobs and arbitration keep running. If a settlement needs the banned user's own signature (a refund to them, say), it waits in "settling" until an admin handles it.
Wouldn't it be faster if the platform could refund me by itself? Yes, but "the platform alone can move money" is exactly the risk we set out to remove. Requiring the buyer or the seller to sign the same split as well means nobody can redirect the funds without you knowing.
Disclaimer: This article describes the technical design of the OpenVPT escrow contract. It isn't legal, tax or investment advice. Blockchain transactions are irreversible and carry risk; please assess your own situation carefully.
Next steps
- Related reading: Disputes and arbitration
- Related reading: Why OpenVPT settles in USDC on Base
- See the full flow: How it works
- Understand the fees: Pricing
- Start browsing: Listings
- Still have questions? Visit the Help center or contact us