> For the complete documentation index, see [llms.txt](https://docs.dimes.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.dimes.fi/for-developers/smart-wallet-integration.md).

# Smart Wallet Integration

Most users hold a Polymarket smart-contract wallet rather than a plain EOA. The vault does not care whether `msg.sender` is an EOA or a contract, but each wallet type submits transactions differently. This guide covers all three Polymarket wallet patterns.

For the core position lifecycle, the Direct EOA flow, and contract ABIs, see [On-Chain Integration](/for-developers/on-chain-integration.md).

***

## Which wallet type?

| Pattern                       | Description                                                                 | `msg.sender` to vault       |
| ----------------------------- | --------------------------------------------------------------------------- | --------------------------- |
| **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 depending on how the user registered. **Deposit wallets are the default for new API users.**

To detect a type, call `eth_getCode`: a Safe has \~250 bytes of bytecode, a Proxy \~92 bytes, a deposit wallet \~252 bytes, an EOA none. Or call `getOwners()` — 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 all three patterns that is the **contract address**, not the owner EOA. The wrong address fails the on-chain signature check.

***

## Polymarket Safe

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

```javascript
import {ethers} from "ethers";

const SAFE_ABI = [
  "function nonce() view returns (uint256)",
  "function getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256) view returns (bytes32)",
  "function execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes) returns (bool)",
];

const safe = new ethers.Contract(safeAddress, SAFE_ABI, provider);
const vault = new ethers.Contract(vaultAddress, VAULT_ABI, provider);

// Encode the vault call
const data = vault.interface.encodeFunctionData("createPosition", [
  quote.position_seed_hex,
  quote.polymarket_market_id,
  BigInt(quote.polymarket_token_id),
  BigInt(quote.collateral_usdc_units),
  quote.leverage_bps,
  BigInt(quote.notional_usdc_units),
  quote.origination_fee_bps,
  quote.lifetime_fee_apr_bps,
  quote.liquidation_fee_bps,
  BigInt(quote.expected_open_trading_fee_usdc_units),
  quote.contract_signature,
  BigInt(quote.signature_expiry),
]);

// Build SafeTx and get the hash
const nonce = await safe.nonce();
const safeTxHash = await safe.getTransactionHash(
  vaultAddress, 0, data, 0, 0, 0, 0, ethers.ZeroAddress, ethers.ZeroAddress, nonce,
);

// Owner signs the hash
const signature = await ownerSigner.signMessage(ethers.getBytes(safeTxHash));

// Anyone can submit (owner, partner backend, or relayer)
const tx = await safe.connect(submitterSigner).execTransaction(
  vaultAddress, 0, data, 0, 0, 0, 0, ethers.ZeroAddress, ethers.ZeroAddress, signature,
);
await tx.wait();
```

pUSD approval must be a separate SafeTx before `createPosition`, or batched via Safe's MultiSend. `requestClose` follows the same pattern — encode `requestClose(positionKey)` as the inner call.

***

## Polymarket Proxy

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

```javascript
import {ethers} from "ethers";

const PROXY_FACTORY = "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052";
const PROXY_FACTORY_ABI = [
  "function proxy(tuple(address to, uint256 typeCode, bytes data, uint256 value)[]) payable returns (bytes[])",
];

const factory = new ethers.Contract(PROXY_FACTORY, PROXY_FACTORY_ABI, ownerSigner);
const vault = new ethers.Contract(vaultAddress, VAULT_ABI, provider);
const usdc = new ethers.Contract(usdcAddress, ["function approve(address,uint256)"], provider);

const approveData = usdc.interface.encodeFunctionData("approve", [vaultAddress, approvalAmount]);
const createData = vault.interface.encodeFunctionData("createPosition", [
  quote.position_seed_hex,
  quote.polymarket_market_id,
  BigInt(quote.polymarket_token_id),
  BigInt(quote.collateral_usdc_units),
  quote.leverage_bps,
  BigInt(quote.notional_usdc_units),
  quote.origination_fee_bps,
  quote.lifetime_fee_apr_bps,
  quote.liquidation_fee_bps,
  BigInt(quote.expected_open_trading_fee_usdc_units),
  quote.contract_signature,
  BigInt(quote.signature_expiry),
]);

// Batch approve + createPosition in one transaction
const tx = await factory.proxy([
  {to: usdcAddress, typeCode: 1, data: approveData, value: 0},
  {to: vaultAddress, typeCode: 1, data: createData, value: 0},
]);
await tx.wait();
```

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

***

## Polymarket Deposit Wallet

Polymarket's newest wallet type and the default for new API users. They require a different opening flow than Safe and Proxy.

### Why deposit wallets need the push-funded flow

The legacy `createPosition` path pulls pUSD with `transferFrom`, which requires the caller to first `approve` the vault. **The Polymarket relayer blocks `approve` to non-whitelisted spenders**, so a deposit wallet can never grant that allowance.

Instead, the vault exposes a **push-funded** path: the deposit wallet *pushes* pUSD to the vault inside an atomic 3-call batch, and the vault verifies the exact balance delta. No approval needed.

```
Deposit wallet batch (atomic — all-or-nothing):

  1. vault.reserveDeposit(totalUserAmount)    snapshot the vault pUSD balance
  2. pUSD.transfer(vault, totalUserAmount)    push the funds directly
  3. vault.createPositionPushFunded(...)      verify the delta, create the position
```

`reserveDeposit` records a transient snapshot of the vault's pUSD balance. `createPositionPushFunded` re-reads the balance and requires the delta to **exactly** equal the position's total amount — any over- or under-funding reverts. The three calls are signed and submitted together, so they cannot be split or reordered.

`createPositionPushFunded` takes the **same 12 arguments** as `createPosition` and accepts the **same `contract_signature`** from the quote. Only the funding mechanism differs.

### The deposit wallet batch

A deposit wallet executes calls through Polymarket's factory:

```solidity
factory.proxy(Batch[] batches, bytes[] signatures)

struct Batch {address wallet; uint256 nonce; uint256 deadline; Call[] calls;}
struct Call  {address target; uint256 value; bytes data;}
```

The wallet owner signs each `Batch` with EIP-712. The relayer submits `factory.proxy(...)` on-chain.

| Constant         | Value                                                                                              |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| Factory address  | `0x00000000000Fb5C9ADea0298D729A0CB3823Cc07`                                                       |
| EIP-712 domain   | `{ name: "DepositWallet", version: "1", chainId: 137, verifyingContract: <depositWalletAddress> }` |
| Relayer endpoint | `POST https://relayer-v2.polymarket.com/submit`                                                    |

### Opening a position

{% tabs %}
{% tab title="SDK (viem)" %}

```typescript
import {
  buildPushFundedCreateCalls,
  buildDepositWalletBatch,
  getDepositWalletBatchTypedData,
  buildRelayerSubmitPayload,
  buildRelayerAuthHeaders,
  polymarketRelayerBaseUrl,
} from "@dimes-dot-fi/sdk/contract";

// 1. Build the 3-call push-funded batch (reserveDeposit + transfer + createPositionPushFunded).
//    `pUsdAddress` is the pUSD token address from GET /prediction-markets/contract-info.
const calls = buildPushFundedCreateCalls(offer, pUsdAddress);

// 2. Read the deposit wallet nonce, assemble and sign the batch.
const nonce = await publicClient.readContract({
  address: depositWalletAddress,
  abi: [{ type: "function", name: "nonce", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] }],
  functionName: "nonce",
});
const deadline = BigInt(Math.floor(Date.now() / 1000) + 240);
const batch = buildDepositWalletBatch({ depositWalletAddress, nonce, deadline, calls });
const signature = await walletClient.signTypedData(getDepositWalletBatchTypedData(batch));

// 3. Submit to the Polymarket relayer (builder API credentials required).
const body = JSON.stringify(buildRelayerSubmitPayload({ ownerAddress, batch, signature }));
const headers = await buildRelayerAuthHeaders({ apiKey, apiSecret, apiPassphrase, body });
const res = await fetch(`${polymarketRelayerBaseUrl}/submit`, { method: "POST", headers, body });
const { transactionID } = await res.json();
```

{% endtab %}

{% tab title="ethers.js" %}

```javascript
import {ethers} from "ethers";

const FACTORY = "0x00000000000Fb5C9ADea0298D729A0CB3823Cc07";
const vault = new ethers.Contract(vaultAddress, VAULT_ABI, provider);
const usdc = new ethers.Contract(usdcAddress, ["function transfer(address,uint256)"], provider);
const dw = new ethers.Contract(depositWalletAddress, ["function nonce() view returns (uint256)"], provider);

// reserveDeposit / createPositionPushFunded are not in the legacy VAULT_ABI — add them:
const PUSH_FUNDED_ABI = new ethers.Interface([
  "function reserveDeposit(uint256 expectedAmountUsdcUnits)",
  "function createPositionPushFunded(bytes16,bytes32,uint256,uint256,uint32,uint256,uint16,uint16,uint16,uint256,bytes,uint256)",
]);

const totalUserAmount = BigInt(quote.total_user_amount_usdc_units);

// 1. Encode the three inner calls
const reserveData = PUSH_FUNDED_ABI.encodeFunctionData("reserveDeposit", [totalUserAmount]);
const transferData = usdc.interface.encodeFunctionData("transfer", [vaultAddress, totalUserAmount]);
const createData = PUSH_FUNDED_ABI.encodeFunctionData("createPositionPushFunded", [
  quote.position_seed_hex,
  quote.polymarket_market_id,
  BigInt(quote.polymarket_token_id),
  BigInt(quote.collateral_usdc_units),
  quote.leverage_bps,
  BigInt(quote.notional_usdc_units),
  quote.origination_fee_bps,
  quote.lifetime_fee_apr_bps,
  quote.liquidation_fee_bps,
  BigInt(quote.expected_open_trading_fee_usdc_units),
  quote.contract_signature,
  BigInt(quote.signature_expiry),
]);

const calls = [
  {target: vaultAddress, value: 0, data: reserveData},
  {target: usdcAddress,  value: 0, data: transferData},
  {target: vaultAddress, value: 0, data: createData},
];

// 2. Build and sign the Batch (EIP-712)
const nonce = await dw.nonce();
const deadline = BigInt(Math.floor(Date.now() / 1000) + 240);
const batch = {wallet: depositWalletAddress, nonce, deadline, calls};

const signature = await ownerSigner.signTypedData(
  {name: "DepositWallet", version: "1", chainId: 137, verifyingContract: depositWalletAddress},
  {
    Batch: [
      {name: "wallet", type: "address"}, {name: "nonce", type: "uint256"},
      {name: "deadline", type: "uint256"}, {name: "calls", type: "Call[]"},
    ],
    Call: [
      {name: "target", type: "address"}, {name: "value", type: "uint256"},
      {name: "data", type: "bytes"},
    ],
  },
  batch,
);

// 3. Submit via the Polymarket relayer (see "Relayer submission" below)
await submitToRelayer({ownerAddress, depositWalletAddress, batch, signature});
```

{% endtab %}
{% endtabs %}

### Closing a position

Same batch mechanics with a single inner call: `requestClose(positionKey)`. The `positionKey` is the `on_chain_position_key` field from `GET /positions`.

{% tabs %}
{% tab title="SDK (viem)" %}

```typescript
import {
  buildRequestCloseCalls,
  buildDepositWalletBatch,
  getDepositWalletBatchTypedData,
  buildRelayerSubmitPayload,
  buildRelayerAuthHeaders,
  polymarketRelayerBaseUrl,
} from "@dimes-dot-fi/sdk/contract";

const calls = buildRequestCloseCalls(vaultAddress, position.on_chain_position_key);

const deadline = BigInt(Math.floor(Date.now() / 1000) + 240);
const batch = buildDepositWalletBatch({ depositWalletAddress, nonce, deadline, calls });
const signature = await walletClient.signTypedData(getDepositWalletBatchTypedData(batch));

const body = JSON.stringify(buildRelayerSubmitPayload({ ownerAddress, batch, signature }));
const headers = await buildRelayerAuthHeaders({ apiKey, apiSecret, apiPassphrase, body });
await fetch(`${polymarketRelayerBaseUrl}/submit`, { method: "POST", headers, body });
```

{% endtab %}

{% tab title="ethers.js" %}

```javascript
const closeData = vault.interface.encodeFunctionData("requestClose", [position.on_chain_position_key]);
const calls = [{target: vaultAddress, value: 0, data: closeData}];

const nonce = await dw.nonce();
const deadline = BigInt(Math.floor(Date.now() / 1000) + 240);
const batch = {wallet: depositWalletAddress, nonce, deadline, calls};

// Sign the Batch with the same EIP-712 types as the opening flow, then submit to the relayer.
const signature = await ownerSigner.signTypedData(domain, types, batch);
await submitToRelayer({ownerAddress, depositWalletAddress, batch, signature});
```

{% endtab %}
{% endtabs %}

Only the position owner — the deposit wallet that called `createPositionPushFunded` — can close the position. After confirmation the backend sells the tokens, distributes proceeds, and emits `position.closed` over [WebSocket](/for-developers/websocket.md).

### Relayer submission

The relayer authenticates every request with an HMAC over the request body. The SDK's `buildRelayerSubmitPayload` and `buildRelayerAuthHeaders` produce both; the manual shape is:

```javascript
import {createHmac} from "node:crypto";

async function submitToRelayer({ownerAddress, depositWalletAddress, batch, signature}) {
  const payload = {
    type: "WALLET",
    from: ownerAddress,
    to: "0x00000000000Fb5C9ADea0298D729A0CB3823Cc07", // factory
    nonce: batch.nonce.toString(),
    signature,
    depositWalletParams: {
      depositWallet: depositWalletAddress,
      deadline: batch.deadline.toString(),
      calls: batch.calls.map((c) => ({target: c.target, value: c.value.toString(), data: c.data})),
    },
  };
  const body = JSON.stringify(payload);

  // HMAC-SHA256 over `timestamp + "POST" + "/submit" + body`, base64url-encoded.
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const sig = createHmac("sha256", Buffer.from(builderApiSecret, "base64"))
    .update(timestamp + "POST" + "/submit" + body)
    .digest("base64")
    .replaceAll("+", "-").replaceAll("/", "_");

  const res = await fetch("https://relayer-v2.polymarket.com/submit", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      POLY_BUILDER_API_KEY: builderApiKey,
      POLY_BUILDER_TIMESTAMP: timestamp,
      POLY_BUILDER_PASSPHRASE: builderApiPassphrase,
      POLY_BUILDER_SIGNATURE: sig,
    },
    body,
  });
  const {transactionID} = await res.json();

  // Poll GET /transaction?id=<transactionID> until state is STATE_MINED / STATE_CONFIRMED.
  return transactionID;
}
```

Submission returns a `transactionID`. Poll `GET https://relayer-v2.polymarket.com/transaction?id=<transactionID>` until `state` is `STATE_MINED` or `STATE_CONFIRMED` (terminal failures: `STATE_FAILED`, `STATE_CANCELLED`, `STATE_INVALID`).

### Constraints

* **One push-funded create per transaction.** The `reserveDeposit` lock allows a single push-funded create per batch; do not bundle two.
* **Exact funding.** The pushed amount must equal `total_user_amount_usdc_units` exactly. The SDK helper sources this from the offer, so reuse it for both `reserveDeposit` and `transfer`.
* **Signature expiry.** `signature_expiry` from the quote is short-lived — sign and submit the batch promptly, and keep the batch `deadline` tight (e.g. now + 240s).
* The relayer can refuse to submit a batch but cannot split, reorder, or alter it — the EIP-712 batch signature commits to the whole `Call[]` array.

See [Polymarket's deposit wallet docs](https://docs.polymarket.com/trading/deposit-wallet-migration) for wallet deployment and builder API key setup.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.dimes.fi/for-developers/smart-wallet-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
