On-Chain Integration

How to submit on-chain transactions to open and close leveraged positions, and what the backend handles automatically after each step.

This guide covers the on-chain transactions your integration must submit. For quote parameters and field mappings, see the API Reference. For the full flow end-to-end, see the Quickstart.


Position lifecycle

A position involves two user-initiated on-chain transactions. Everything else is handled by the Multiply backend.

User action                          Backend (automatic)
───────────                          ───────────────────
1. Approve USDC
2. Call createPosition() ──────────► Detect event
                                     Place exchange order
                                     Finalize on-chain
                                     ──► position.opened WebSocket event

3. Call requestClose() ────────────► Detect event
                                     Sell tokens on exchange
                                     Distribute proceeds on-chain
                                     ──► position.closed WebSocket event

Other exits (no user action needed):
  Market resolves ─────────────────► Settle all positions
  Price breaches threshold ────────► Liquidate position

See WebSocket Events for event payloads.


Wallet types

The vault doesn't care whether msg.sender is an EOA or a smart-contract wallet. Four patterns are supported:

Pattern

Description

msg.sender to vault

Direct EOA

Plain wallet (no contract wrapper)

The EOA itself

Polymarket Safe

Standard 1-of-1 Gnosis Safe provisioned by Polymarket for some users

The Safe contract

Polymarket Proxy

EIP-1167 minimal proxy provisioned by Polymarket for some users

The Proxy contract

Polymarket Deposit Wallet

ERC-1967 proxy deployed via Polymarket's deposit wallet factory (new users)

The deposit wallet contract

Polymarket provisions one of these contract wallets depending on how the user registered. Deposit wallets are the default for new API users. To determine which type a given address is, call eth_getCode — a Safe has ~250 bytes of bytecode, a Proxy has ~92 bytes, a deposit wallet has ~252 bytes, and an EOA has none. You can also try calling getOwners() on it — Safes respond, others revert.

Critical: The wallet_address you pass to POST /prediction-markets/quotes must be the address that will be msg.sender on-chain. For Safe, Proxy, and deposit wallets, that's the contract address, not the underlying owner EOA. If you pass the wrong address, the on-chain signature check will revert.


Opening a position (Direct EOA)

1. Approve USDC

The vault pulls USDC from the caller via transferFrom. Approve the vault to spend the required amount first.

2. Call createPosition

All parameters come directly from the quote response.

3. What happens next (automatic)

  1. Backend detects the PositionCreated event

  2. Places an exchange order for prediction market tokens

  3. Finalizes on-chain via finalizeOpen

  4. Emits position.opened over WebSocket

If the exchange order fails, the backend reverts the position and refunds collateral + origination fee + venue fee. A position.reverted event is emitted.


Closing a position

The position owner calls requestClose on the vault. The positionKey is the on_chain_position_key field from GET /positions.

Only the position owner (the wallet that called createPosition) can call requestClose.

After the transaction confirms, the backend sells tokens, distributes proceeds, and emits position.closed over WebSocket.


Other exit paths

No user action required for these — the backend handles them automatically.

  • Settlement: When a market resolves, all positions are settled. Emits position.settled.

  • Liquidation: When margin health approaches zero, the position is liquidated. Emits position.liquidated. The liquidation price is on every position object at risk.current_liquidation_price_usd.


Wallet integration patterns

Polymarket Safe

Encode the vault calldata normally, wrap it in a SafeTx, have the owner sign, submit execTransaction.

USDC approval must be a separate SafeTx before createPosition, or batched via Safe's MultiSend.

Polymarket Proxy

The owner EOA calls factory.proxy() directly. USDC approval can be batched in the same call.

The owner EOA must call factory.proxy() directly — the proxy does not support off-chain signatures. Polymarket's hosted relayer will not relay vault calls.

Polymarket Deposit Wallet

Deposit wallets are Polymarket's newest wallet type for API users. They work with our vault the same way as Safes and Proxies — the deposit wallet is msg.sender to the vault, and USDC + position payouts flow through it.

The owner signs an EIP-712 Batch containing the vault calldata, then submits it through the Polymarket relayerarrow-up-right. USDC approval and createPosition can be batched in a single Batch.

The requestClose call follows the same pattern — encode requestClose(positionKey) as the inner call. See Polymarket's deposit wallet docsarrow-up-right for wallet deployment, relayer setup, and contract addresses.


Contract ABIs

Vault

The full canonical vault ABI — including every external function, event, and custom error — is published in the official UI repo:

canonical-leveraged-prediction-vault-v1-abi.jsonarrow-up-right

Use this file directly so your client can decode revert reasons (e.g. InsufficientCapital, SignatureExpired, UserCapitalExceeded) into named errors instead of raw selectors. The two methods you'll typically call are summarized below for reference:

The vault address is returned in every quote response as polygon_vault_contract_address, and from GET /prediction-markets/contract-info.

Last updated