# On-Chain Integration

This guide covers the on-chain transactions your integration must submit. For quote parameters and field mappings, see the [API Reference](/for-developers/api-reference.md). For the full flow end-to-end, see the [Quickstart](/for-developers/quickstart.md).

***

## 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-developers/websocket.md) for event payloads.

***

## Wallet types

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

* **Direct EOA** — covered below.
* **Polymarket Safe**, **Polymarket Proxy**, **Polymarket Deposit Wallet** — smart-contract wallets provisioned by Polymarket. Deposit wallets are the default for new API users and use a different opening flow. See [Smart Wallet Integration](/for-developers/smart-wallet-integration.md).

**Critical:** The `wallet_address` you pass to `POST /prediction-markets/quotes` must be the address that will be `msg.sender` on-chain. For smart-contract wallets that's the **contract address**, not the owner EOA. The wrong address fails the on-chain signature check.

***

## 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.

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

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

const usdc = new ethers.Contract(USDC_ADDRESS, ["function approve(address,uint256)"], signer);
const approvalAmount = BigInt(quote.total_user_amount_usd_pips) * 100n;
await usdc.approve(quote.polygon_vault_contract_address, approvalAmount);
```

{% endtab %}

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

```typescript
import {buildApproveTx} from "@dimes-dot-fi/sdk/contract";

const approveTx = buildApproveTx(usdcAddress, vaultAddress, approvalAmount);
await walletClient.writeContract(approveTx);
```

{% endtab %}
{% endtabs %}

### 2. Call `createPosition`

All parameters come directly from the [quote response](/for-developers/api-reference.md).

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

```javascript
const VAULT_ABI = [
  "function createPosition(bytes16,bytes32,uint256,uint256,uint32,uint256,uint16,uint16,uint16,uint256,bytes,uint256) external",
];

const vault = new ethers.Contract(quote.polygon_vault_contract_address, VAULT_ABI, signer);

const tx = await vault.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),
);
await tx.wait();
```

{% endtab %}

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

```typescript
import {buildCreatePositionTx, verifyOfferSignature} from "@dimes-dot-fi/sdk/contract";

// Verify the quote signature against the contract-info endpoint (cached per-client)
await verifyOfferSignature(client, offer, userAddress);

const createTx = buildCreatePositionTx(offer);
await walletClient.writeContract(createTx);
```

{% endtab %}
{% endtabs %}

### 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](/for-developers/websocket.md)

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`.

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

```javascript
const VAULT_ABI = ["function requestClose(bytes32) external"];
const vault = new ethers.Contract(vaultAddress, VAULT_ABI, signer);

const tx = await vault.requestClose(position.on_chain_position_key);
await tx.wait();
```

{% endtab %}

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

```typescript
import {buildRequestCloseTx} from "@dimes-dot-fi/sdk/contract";

const closeTx = buildRequestCloseTx(vaultAddress, positionKey);
await walletClient.writeContract(closeTx);
```

{% endtab %}
{% endtabs %}

Only the position owner (the wallet that called `createPosition`) can call `requestClose`. The example above is the Direct EOA path — for smart-contract wallets, wrap `requestClose` in the wallet's batch as shown in [Smart Wallet Integration](/for-developers/smart-wallet-integration.md).

After the transaction confirms, the backend sells tokens, distributes proceeds, and emits `position.closed` over [WebSocket](/for-developers/websocket.md).

***

## 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`.

***

## 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.json`](https://github.com/dimes-fi/dimes-demo-ui/blob/main/src/contract/canonical-leveraged-prediction-vault-v1-abi.json)

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:

```json
[
  "function createPosition(bytes16 positionSeed, bytes32 marketId, uint256 tokenId, uint256 collateralUsdcUnits, uint32 leverageBps, uint256 notionalUsdcUnits, uint16 originationFeeBps, uint16 lifetimeFeeAprBps, uint16 liquidationFeeBps, uint256 venueFeeUsdcUnits, bytes signature, uint256 signatureExpiry) external",
  "function requestClose(bytes32 positionKey) external"
]
```

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


---

# Agent Instructions: 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/on-chain-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.
