> 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/wallet-integration/on-chain-integration.md).

# On-Chain Integration

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

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

> **Runnable examples:** [`03-open-position-onchain.ts`](https://github.com/dimes-fi/dimes-sdk/blob/main/examples/03-open-position-onchain.ts) (approve → verify signature → `createPosition` with viem) and [`04-positions-and-close.ts`](https://github.com/dimes-fi/dimes-sdk/blob/main/examples/04-positions-and-close.ts) (list positions, request close). Both are type-checked against the SDK in CI.

***

## Position lifecycle

Two user-initiated on-chain transactions. Everything else is handled by the Multiply backend.

```
User action                          Backend (automatic)
───────────                          ───────────────────
1. Approve pUSD
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/api-and-events/websocket.md) for event payloads.

***

## Wallet types

The vault doesn't care whether `msg.sender` is an EOA or 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/wallet-integration/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 pUSD

The vault pulls pUSD 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-and-events/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, verifyQuoteSignature} from "@dimes-dot-fi/sdk/contract";

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

const createTx = buildCreatePositionTx(quote);
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/api-and-events/websocket.md)

If the exchange order fails, the backend reverts the position, refunds collateral + origination fee + venue fee, and emits `position.reverted`.

***

## 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 ([Smart Wallet Integration](/for-developers/wallet-integration/smart-wallet-integration.md)).

After the transaction confirms, the backend sells tokens, distributes proceeds, and emits `position.closed` over [WebSocket](/for-developers/api-and-events/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 — 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 it 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:

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