Skip to content

Deposit Wallet Lifecycle

The deposit wallet has a defined lifecycle. Polygolem handles every phase, and go-bot’s live loop automates the full startup sequence so a restart always recovers to a ready state.

Lifecycle Phases

┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ DERIVE │ ──→ │ DEPLOY │ ──→ │ APPROVE │ ──→ │ FUND │
│ (offline)│ │ (relayer)│ │ (relayer)│ │ (onchain)│
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │
└───────────────────────────────────────────────────▼
┌──────────┐ ┌──────────┐
│ TRADE │ ──→ │ REDEEM │
│ (CLOB) │ │ (relayer)│
└──────────┘ └──────────┘

Phase 1: Derive (offline, always succeeds)

The deposit wallet address is computed from the EOA address using CREATE2. No network calls. The same EOA always produces the same address.

wallet = CreateAddress2(
factory = 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07,
salt = keccak256(abi.encode(factory, LeftPad32(EOA))),
hash = keccak256(proxyInitCode)
)

Phase 2: Deploy (relayer, idempotent)

The relayer deploys a clone proxy via the deposit wallet factory and funds the deployment gas. The go-bot checks deployment status before submitting.

WALLET-CREATE { from: <EOA>, to: <factory> }

On-chain code is the source of truth. The relayer’s /deployed endpoint can return false even when the deposit wallet is fully deployed on Polygon (for example, after a stale WALLET-CREATE row marked STATE_FAILED despite the underlying transaction landing). Polygolem and the go-bot SDK both fall back to eth_getCode at the derived address; if bytecode exists, the wallet is treated as deployed and WALLET-CREATE is skipped. polygolem deposit-wallet status --json exposes which path answered via relayerDeployed, onchainCodeDeployed, and deploymentStatusSource.

Phase 3: Fund (onchain)

pUSD must be transferred from the EOA to the deposit wallet. The EOA’s pUSD balance does not count for deposit wallet trading. Only pUSD held by the deposit wallet contract is available for CLOB orders.

Two-step funding:

  1. EOA acquires pUSD (bridge POL or swap)
  2. EOA transfers pUSD to deposit wallet (ERC-20 transfer)

Phase 4: Approve (relayer WALLET batches)

The deposit wallet must approve trading contracts before CLOB orders can settle. That trading readiness batch is six calls:

  • pUSD approve for CTFExchangeV2
  • CTF setApprovalForAll for CTFExchangeV2
  • pUSD approve for NegRiskExchangeV2
  • CTF setApprovalForAll for NegRiskExchangeV2
  • pUSD approve for NegRiskAdapterV2
  • CTF setApprovalForAll for NegRiskAdapterV2

V2 split, merge, and redeem readiness is separate. The collateral adapters also need a one-time WALLET batch:

  • pUSD approve for CtfCollateralAdapter
  • CTF setApprovalForAll for CtfCollateralAdapter
  • pUSD approve for NegRiskCtfCollateralAdapter
  • CTF setApprovalForAll for NegRiskCtfCollateralAdapter

Redeem itself requires the CTF approval leg; pUSD approval is included so the same adapter-readiness batch also covers future split flows.

Phase 5: Trade (CLOB)

With the wallet deployed, funded, and approved, orders are placed with deposit-wallet signing automatically. The EOA signs the order, but the CLOB sees the deposit wallet as both maker and signer in the V2 order payload.

Phase 6: Redeem Winners (relayer WALLET batch)

After a market resolves, query Data API positions for the deposit wallet and look for redeemable=true. That is the readiness signal; there is no separate resolved boolean in the current Data API position schema.

V2 redeem routes through collateral adapters, not ConditionalTokens directly. The owner signs an EIP-712 WALLET batch, and the relayer submits that batch through the deposit-wallet factory:

Market TypeAdapter
Standard binaryCtfCollateralAdapter
Negative-riskNegRiskCtfCollateralAdapter

The adapter uses conditionId, detects the wallet’s CTF balances, redeems the winning side, wraps proceeds back into pUSD, and returns pUSD to the deposit wallet.

Do not use direct EOA calls, raw CTF calls, or SAFE/PROXY relayer examples as fallbacks for deposit-wallet positions. If the relayer rejects the adapter-targeted WALLET batch, first verify the adapter addresses against Polymarket’s current contracts reference. If the addresses are current, surface the upstream blocker and stop.

The operator surface is polygolem deposit-wallet approve-adapters, redeemable, and redeem. Every signing path is dry-run by default; submission requires both --submit and a typed --confirm token (APPROVE_ADAPTERS for adapter approvals, REDEEM_WINNERS for redeem). See Redeeming Winning Positions for the full runbook.

Recovery Modes

On every startup, go-bot walks the lifecycle and fills any gaps. Every step is idempotent — running it twice on an already-ready wallet is a no-op.

State on RestartWhat Happens
No deploymentIsDeployed() → false and eth_getCode empty → WALLET-CREATE → poll until mined
Deployed (relayer says so)IsDeployed() → true → skip deploy
Deployed (relayer false-negative)IsDeployed() → false but eth_getCode non-empty → treat as deployed, skip WALLET-CREATE
Deployed, no pUSDWallet has code, balance zero → fund EOA pUSD → transfer to wallet
Deployed, pUSD present, no approvalsBalance OK, allowances empty → SubmitWalletBatchSafe with fresh nonce
Deployed, trade approvals present, adapter approvals missingTrading can work, but split/merge/redeem must fail closed until adapter approvals are submitted
Fully operationalAll checks pass → skip to live trading loop
Nonce conflict (lost response after a previous batch)SubmitWalletBatchSafe re-fetches nonce from relayer, signs with new nonce, retries
Wallet lost (never happens — it’s a contract on Polygon)Re-derive address (same result) → on-chain code still present → skip deploy

Nonce Safety

Every WALLET batch consumes one nonce tracked by the relayer per EOA. The nonce is never cached or reused:

  1. Fetch fresh nonce from GET /nonce?address=<EOA>&type=WALLET
  2. Sign the batch EIP-712 with that nonce
  3. Submit to POST /submit (type=WALLET)
  4. If submission succeeds → poll for confirmation
  5. If submission fails with nonce conflict (a previous lost response consumed it) → re-fetch (step 1) and retry
  6. If submission fails with any other error → report and stop

This guarantees:

  • No duplicate nonce submission
  • Recovery from lost responses without operator intervention
  • Max 3 retries before failing with a clear error

First-Time Onboard

First-time setup has one upstream account step: create or verify the builder profile at polymarket.com/settings?tab=builder. After that, polygolem can derive CLOB credentials, validate relayer HMAC credentials, and run the wallet lifecycle from the CLI.

Terminal window
# Derive CLOB credentials for account reads and same-address order auth.
POLYMARKET_PRIVATE_KEY="0x..." \
polygolem builder auto
# Mint a V2 relayer API key for deposit-wallet deploy and approval batches.
POLYMARKET_PRIVATE_KEY="0x..." \
polygolem auth headless-onboard
# Full wallet onboarding (deploy + trading approve + fund).
POLYMARKET_PRIVATE_KEY="0x..." \
RELAYER_API_KEY="..." \
RELAYER_API_KEY_ADDRESS="..." \
polygolem deposit-wallet onboard --fund-amount 50 --json

builder auto signs a ClobAuth EIP-712 message locally and posts to clob.polymarket.com/auth/api-key. auth headless-onboard performs SIWE login and mints the V2 relayer key used by deposit-wallet deploy and deposit-wallet approve. Non-zero order attribution is configured separately with POLYMARKET_BUILDER_CODE or CLI --builder-code.

This single command currently runs: derive → deploy → trading approve (6 calls) → fund (transfer). V2 redeem readiness adds the separate collateral-adapter approval batch described in Phase 4.

Then sync the CLOB balance:

Terminal window
POLYMARKET_PRIVATE_KEY="0x..." \
polygolem clob update-balance --asset-type collateral

Automated Startup (go-bot live)

go-bot’s live command automates the full lifecycle at startup:

go-bot live startup:
1. derive deposit wallet address (offline)
2. check IsDeployed(); if false, fall back to eth_getCode → deploy only when both say not deployed
3. check deposit wallet pUSD balance → fund if needed
4. check collateral allowances → approve if needed
5. update CLOB balance cache
6. enter live trading loop with deposit-wallet signing

With persistence, a restart after a crash recovers cleanly:

crash → restart → derive (same address) → IsDeployed or eth_getCode=true
→ skip deploy → balance OK → skip fund → allowances OK → skip approve
→ start trading

Checking Status

Terminal window
# Is the wallet deployed? (relayer + on-chain dual-source check)
polygolem deposit-wallet status --json
# {
# "deployed": true,
# "deploymentStatusSource": "polygon_code",
# "relayerDeployed": false,
# "onchainCodeDeployed": true,
# "depositWallet": "0x21999a..."
# }
# What's the deposit wallet balance?
polygolem clob balance --asset-type collateral
# Check EOA onchain funds with wallet/RPC tooling; CLOB balance reads use
# the deposit wallet cache.

deploymentStatusSource is one of:

  • relayer — relayer /deployed returned true (normal happy path).
  • polygon_code — relayer said false, but eth_getCode returned non-empty bytecode. The wallet is deployed; the relayer row is stale.
  • relayer_and_polygon_code — both reported not deployed. Real first-deploy state; polygolem deposit-wallet deploy --wait will submit WALLET-CREATE (and itself skips the call if eth_getCode becomes non-empty before the request is made, e.g. mid-retry).

pUSD held by the EOA and pUSD held by the deposit wallet are separate balances. Only the deposit wallet balance counts for POLY_1271 trading.

Redeem Readiness Checklist

Before submitting a V2 redeem batch:

  1. The wallet is deployed according to Polygon eth_getCode.
  2. Data API /positions?user=<depositWallet> returns at least one redeemable=true row.
  3. The position includes conditionId, asset, outcome, outcomeIndex, and negativeRisk.
  4. CTF isApprovedForAll(wallet, adapter) is true for the required adapter.
  5. The WALLET batch is dry-run and inspected before any live submission.

go-bot must call the Polygolem SDK package directly for this flow. The Polygolem CLI is for humans and operator runbooks, not the production bot integration boundary.