Skip to content

Public beta on the Base Sepolia test network. Payments use test USDC with no real value: do not send real funds. The escrow contract has not had its external security audit yet, and the terms are drafts pending legal review. What the beta means

OpenVPT

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:

PartyWhere the address comes fromWhat their signature means
BuyerThe 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"
SellerThe wallet the seller verified on OpenVPT with SIWE (Sign-In with Ethereum)"I agree to this split of the sale proceeds"
PlatformThe 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 CreateOrder message 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:

text
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:

  1. Readable. EIP-712-aware wallets show you toSeller, toBuyer and toPlatform field by field.
  2. Not replayable elsewhere. Because the domain pins the chain and contract, the signature is worthless on any other chain or contract.
  3. 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 1200000000 in 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:

  1. The order exists. The orderId must have been funded, otherwise the call reverts with OrderNotFound.
  2. 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.
  3. 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.
  4. The fee has a pro-rata floor. toPlatform must be at least fee × 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.
  5. Both signatures are valid. A malformed signature is rejected (InvalidSignature).
  6. 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).
  7. 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.

ScenarioSeller receivesBuyer refundPlatform feeContract's fee floor
Normal completion (100% to seller)1,14006060 × 1,140 ÷ 1,200 = 57
Full refund (0% to seller)01,20000
Arbitration awards seller 30%3428401860 × 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:

SignersTypical situationWho signs firstOutcome
Buyer + platformBuyer clicks "Confirm receipt"Platform signs automatically, buyer signs in their walletSeller is paid (minus the fee)
Buyer + platformSeller misses the delivery deadlinePlatform signs the refund, buyer signs to submitFull refund to the buyer
Seller + platformInspection window (default 3 days after delivery) ends with no confirmation and no disputePlatform signs, seller signs to submitSeller is paid (minus the fee)
Platform + buyer or sellerA dispute is resolved by arbitration (e.g. 30/70)Platform signs after the arbitrator's ruling, then the buyer or seller co-signsFunds split per the ruling
Buyer + sellerBoth parties agree directly on one splitEach signsPaid 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. Settle signatures 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 canThe owner cannot
Pause and unpause new fund() paymentsCall settle() on any order
Rotate the platformSigner address (e.g. routine key rotation)Withdraw or move any escrowed funds
Set the token allowlistChange the buyer, seller, amount or fee of a funded order
Change the fee recipient addressRenounce 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 platformSigner immediately.
  • 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 Settle payload 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":

  1. The cancel time is recorded and no new payment signatures are issued.
  2. 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.
  3. Before finalising, it checks the chain for a payment on this order.
  4. If the payment landed first, payment wins: the order proceeds as funded and the cancel request is voided.
  5. 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, check orderId, toSeller, toBuyer and toPlatform (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