Skip to main content
Polyshield
TESTNET · POLYGON AMOY
HOW IT WORKS

Private prediction markets, step by step.

Polyshield uses ZK proofs to hide which depositor placed which bet. The vault acts as a single Polymarket account shared by all depositors. Here's the full flow.

01

Deposit USDC into the shared vault

You transfer USDC to the Vault contract. A local spending note (secret, balance, nonce, owner_address) is generated in your browser. The secret is derived from a wallet signature — you never need to back anything up. The Poseidon commitment of that note becomes a new leaf in the vault's Merkle tree. Your deposit amount is public; the note contents are not.

C = Poseidon4(secret, balance, nonce, owner_address)
02

Choose a Polymarket position

Browse markets and pick a side. Your browser generates a ZK proof (BET_AUTH) proving: (a) your note has sufficient balance, (b) the nullifier belongs to your note, (c) the new note after spending is correctly formed. No secret ever leaves your device.

nullifier = poseidon(secret, nonce)
03

Proof relay submits the authorization

Your ZK proof goes to the Proof Relay — not your wallet. The relay submits Vault.authorizeBet() from its own EOA. Your wallet address never appears in any bet-related transaction. Gas is paid by the relay.

Vault.authorizeBet(proof, publicInputs)
04

Vault EOA submits the CLOB order

The Signing Layer detects the BetAuthorized event and submits a Fill-Or-Kill order to Polymarket's CLOB using the vault's single EOA. All traders in the pool share this one address. No CLOB observer can tell which depositor authorized which bet.

POST /order { tokenId, price, size, type: "FOK" }
05

Settle winnings as a new private note

When a market resolves, you generate a SETTLE_CRED proof locally. This proof binds your original position to the market outcome without revealing your note's identity. The settlement credit becomes a fresh private note in your vault.

new_commitment = Poseidon4(secret, balance + credit, nonce+1, owner_address)
06

Withdraw to your own address

The WITHDRAW proof proves you know a note's secret, and commits to a recipient address via its Poseidon hash (a private input). The relay submits the withdrawal. You can only withdraw to the wallet that made the original deposit — this is enforced inside the ZK circuit via the owner_address field. The link from deposit to withdrawal is still unlinkable on-chain because no identifying data appears in the withdrawal transaction.

recipient_hash = poseidon(recipient_address, 0)
THREAT MODEL SUMMARY
ThreatMitigated?How
Observer sees which EOA placed the Polymarket order✓ YESAll orders from vault's single EOA; depositor never appears
On-chain observer links nullifier to depositor address✓ YESNullifier = poseidon(secret, nonce); not derivable without secret
Relay learns which depositor authorized which bet✓ YESRelay only sees the ZK proof; public inputs contain no depositor ID
Timing correlation between deposit and withdrawal✓ YESRelay adds random jitter (3–60 min depending on posture)
Deposit amount is private✗ NOVault.deposit() is a public ERC-20 transfer; amount is on-chain
That a wallet used Polyshield at all✗ NOVault.deposit() is public — only post-deposit activity is private