Skip to content

An Open Action for embeding Polymarket trading within Lens Protocol Publications

License

Notifications You must be signed in to change notification settings

iPaulPro/PolymarketAttestActionModule

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Polymarket Attestation Open Action Module

PolymarketAttestActionModule is an Open Action Module for Lens Protocol that facilitates an embedded Polymarket trading experience, directly within publications.

The module serves as an attestation layer for Polymarket orders, ensuring that the Lens actor is the signer of the Polymarket order. It also provides the necessary data to display the market in the publication.

Using the PolymarketAttestActionModule Contract

To use the live PolymarketAttestActionModule you can use the address and metadata below:

Network Chain ID Deployed Contract Metadata
Mumbai 80001 0x077afD867F1F6144677F39416797D64d743db31D link

For instructions on how to initialize and process the Open Action Module, see the Open Action Modules section.

Table of Contents

Polymarket Brief

Polymarket allows users to bet on the outcome of future events in a wide range of topics, like sports, politics, and pop culture. Polymarket's Order Book, also referred to as the "CLOB" (Central Limit Order Book) or "BLOB" (Binary Limit Order Book), is hybrid-decentralized wherein there is an operator that provides off-chain matching/ordering/execution services while settlement happens on-chain, non-custodially according to instructions provided by users in the form of signed order messages.

Underlying the exchange system is a custom Exchange contract that facilitates atomic swaps (settlement) between binary Outcome Tokens (both CTF ERC1155 assets and ERC20 PToken assets) and a collateral asset (ERC20) according to signed limit orders.

Orders are represented as signed typed structured data (EIP712). When orders are matched, one side is considered the maker and the other side is considered the taker. The Operator is responsible for matching, ordering, and submitting matched trades to the underlying blockchain network for execution. As such, order placement and canellation can happen immediately off-chain while only the settlement action must occur on-chain.

The docs for Polymarket can be found at https://docs.polymarket.com.

Proposed User Flow

  1. A user adds a Polymarket link while composing a Lens publication.
  2. The Lens client queries the Polymarket Gamma Markets API to get the Question ID and other market data.
  3. The Lens client prompts the user to embed the market in the publication, optionally showing a preview.
  4. The Lens client uses the Question ID to initiate the Open Action for the publication.
  5. The Lens client displays the Polymarket trading widget on publications with the Open Action attached, allowing the user to place an order on Polymarket.
  6. The Lens client uses the Polymarket CLOB API to place the order.
  7. The Lens client uses the PolymarketAttestActionModule to validate and attest that the actor is the signer of the Polymarket order.

Polymarket Deployments

Polymarket Exchange:

Network Address
Mumbai 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E
Polygon 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E

UMA Oracle (V2.0.0):

Network Address
Mumbai 0x6A9D222616C90FcA5754cd1333cFD9b7fb6a4F74
Polygon 0x6A9D222616C90FcA5754cd1333cFD9b7fb6a4F74

Conditional Tokens Framework (CTF):

Network Address
Mumbai 0x7D8610E9567d2a6C9FBf66a5A13E9Ba8bb120d43
Polygon 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045

Collateral Tokens (USDC.e):

Network Address
Mumbai 0x2E8DCfE708D44ae2e406a1c02DFE2Fa13012f961
Polygon 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174

Polymarket.com URLs

Polymarket.com URLs are in the form of

https://polymarket.com/event/presidential-election-winner-2024/will-donald-trump-win-the-2024-us-presidential-election

or

https://polymarket.com/event/presidential-election-winner-2024

or

https://polymarket.com/market/will-donald-trump-win-the-2024-us-presidential-election

Here presidential-election-winner-2024 is the event.slug, while will-donald-trump-win-the-2024-us-presidential-election is the market.slug.

Events: https://docs.polymarket.com/?typescript#events

Markets: https://docs.polymarket.com/?typescript#markets

Gamma Markets API (GraphQL)

NOTE: The Gamma Markets API is currently being rebuilt and has been deprecated. The new API will be available soon. In the meantime similar REST queries can be made at https://strapi-matic.poly.market.

If the user is sharing a link with only the event slug, they should be presented a list of available markets (or shown a market directly if there's only one). Example query for an event slug:

https://strapi-matic.poly.market/events?slug=presidential-election-winner-2024

returns (most fields omitted for brevity):

[
  {
    "id": 903193,
    "slug": "presidential-election-winner-2024",
    "title": "Presidential Election Winner 2024",
    "description": "This is a market group on who will win the 2024 US Presidential Election (POTUS)",
    "end_date": "2024-11-05",
    "markets": [
      {
        "id": 253591,
        "question": "Will Donald Trump win the 2024 US Presidential Election?",
        "conditionId": "0xdd22472e552920b8438158ea7238bfadfa4f736aa4cee91a6b86c39ead110917",
        "questionID": "0xe3b1bc389210504ebcb9cffe4b0ed06ccac50561e0f24abb6379984cec030f00",
        "slug": "will-donald-trump-win-the-2024-us-presidential-election",
        "outcomes": [
          "Yes",
          "No"
        ],
        "outcomePrices": [
          "0.545",
          "0.455"
        ],
        "questionID": "0xe3b1bc389210504ebcb9cffe4b0ed06ccac50561e0f24abb6379984cec030f00",
        "clob_token_ids": [
          "21742633143463906290569050155826241533067272736897614950488156847949938836455",
          "48331043336612883890938759509493159234755048973500640148014422747788308965732"
        ],
        "accepting_orders": true
      }
    ]
  }
]

If the shared link contains a market slug, look up directly by slug:

https://strapi-matic.poly.market/markets?slug=will-donald-trump-win-the-2024-us-presidential-election

Returns something like (most fields omitted for brevity):

[
  {
    "id": "253591",
    "active": true,
    "question": "Will Donald Trump win the 2024 US Presidential Election?",
    "conditionId": "0xdd22472e552920b8438158ea7238bfadfa4f736aa4cee91a6b86c39ead110917",
    "questionID": "0xe3b1bc389210504ebcb9cffe4b0ed06ccac50561e0f24abb6379984cec030f00",
    "slug": "will-donald-trump-win-the-2024-us-presidential-election",
    "clob_token_ids": [
      "21742633143463906290569050155826241533067272736897614950488156847949938836455",
      "48331043336612883890938759509493159234755048973500640148014422747788308965732"
    ]
  }
]

Notes

  • The main thing we're interested in from the Gamma Markets API is the questionID. This is all that is needed to initiate the Open Action Module.
  • The conditionId is the main identifier used for Markets in the CLOB API.
  • The clob_token_ids field contains the ERC1155 Outcome Token IDs for the market. These are used to create orders.
  • The Gamma Markets API can be used without authentication, while the CLOB API requires an API key for any market-related calls.
  • The data returned from the Gamma Markets API is in a different shape than the data returned from the Polymarket CLOB API. For example, the Token IDs are returned in this form from the CLOB API:
{
   "tokens": [
       {
           "token_id": "45192470599548595159090094230221802571282664878863077053730623624685503357046",
           "outcome": "Yes",
           "winner": false
       },
       {
           "token_id": "104708316105117381625931018009191103815070143757312220500640690567229192101989",
           "outcome": "No",
           "winner": false
       }
   ]
}

Open Action Module

All that's needed for initialization of the Open Action Module is the Question ID of the market being attached to the publication.

The initialization calldata ABI is:

[
    {
        "type": "bytes32",
        "name": "questionId"
    }
]

The initializePublicationAction returns the conditionId, the ancillary question data (the actual question, itself), and the ERC1155 Binary Outcome Token IDs for the market, which are used to create orders. All of this can also be retrieved from the CLOB API.

Here's the ABI of the initialize result data:

[
    {
        "type": "bytes32",
        "name": "conditionId"
    }, 
    {
        "type": "bytes",
        "name": "questionData"
    },
    {
        "type": "uint256[]",
        "name": "clobTokenIds"
    }
]

Clients can use this to determine the market to display as part of the publication. To validate and register an order, the process calldata ABI expects an instance of an Order:

[
    {
        "type": "tuple(uint256,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,uint8,uint8,bytes)",
        "name": "order",
        "components": [
          { "name": "salt", "type": "uint256" },
          { "name": "maker", "type": "address" },
          { "name": "signer", "type": "address" },
          { "name": "taker", "type": "address" },
          { "name": "tokenId", "type": "uint256" },
          { "name": "makerAmount", "type": "uint256" },
          { "name": "takerAmount", "type": "uint256" },
          { "name": "expiration", "type": "uint256" },
          { "name": "nonce", "type": "uint256" },
          { "name": "feeRateBps", "type": "uint256" },
          { "name": "side", "type": "uint8" },
          { "name": "signatureType", "type": "uint8" },
          { "name": "signature", "type": "bytes" }
        ]
    }
]

Here's the full signed Order struct:

struct Order {
    /// @notice Unique salt to ensure entropy
    uint256 salt;
    /// @notice Maker of the order, i.e the source of funds for the order
    address maker;
    /// @notice Signer of the order
    address signer;
    /// @notice Address of the order taker. The zero address is used to indicate a public order
    address taker;
    /// @notice Token Id of the CTF ERC1155 asset to be bought or sold
    /// If BUY, this is the tokenId of the asset to be bought, i.e the makerAssetId
    /// If SELL, this is the tokenId of the asset to be sold, i.e the takerAssetId
    uint256 tokenId;
    /// @notice Maker amount, i.e the maximum amount of tokens to be sold
    uint256 makerAmount;
    /// @notice Taker amount, i.e the minimum amount of tokens to be received
    uint256 takerAmount;
    /// @notice Timestamp after which the order is expired
    uint256 expiration;
    /// @notice Nonce used for onchain cancellations
    uint256 nonce;
    /// @notice Fee rate, in basis points, charged to the order maker, charged on proceeds
    uint256 feeRateBps;
    /// @notice The side of the order: BUY or SELL
    Side side;
    /// @notice Signature type used by the Order: EOA, POLY_PROXY or POLY_GNOSIS_SAFE
    SignatureType signatureType;
    /// @notice The order signature
    bytes signature;
}

Central Limit Order Book (CLOB) API

The Polymarket CLOB API plays the role of "Operator" and matches open orders. The @polymarkey/clob-client library can be used to place orders, as well as query Markets and open orders.

Here's an example of how to set up the CLOB client to be able to place orders:

import { ClobClient } from "@polymarket/clob-client";
import { polygon, polygonMumbai } from "viem/chains";
import { providers } from "ethers";

const chain = polygonMumbai;
const host =
        chain.id.valueOf() === polygon.id ? "https://clob.polymarket.com/" : "https://clob-staging.polymarket.com/";
const provider = new providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();

// Initialize the Level 1 CLOB client
const clobClient = new ClobClient(host, chain.id, signer);

// Create the required API key for the Level 2 Client (if not already created)
// This creates a wallet signing prompt
const apiKeyCreds = await clobClient.createApiKey();

// Initialize the L2 CLOB Client
const l2ClobClient = new ClobClient(host, chain.id, signer, apiKeyCreds);

Notes

  • A "Level 1" CLOB Client is created with a wallet. It's required for making orders and creating API Keys.
  • For all other CLOB API calls, a "Level 2" CLOB Client is required.
  • The Level 2 CLOB Client is initialized with an API Key, which can be created by signing a message with a L1 Client.

Placing Orders

The @polymarket/clob-client library can be used to create and place market and limit orders.

// A Level 2 CLOB Client (with API credentials) is required to query markets (which includes placing "market" orders)
const clobClient = new ClobClient(host, chain, signer, creds);

// Get the Market by Condition ID
const market = await clobClient.getMarket(conditionId);

// A "YES" Binary Outcome Token ID
const tokenID = market.tokens.find(token => token.outcome === "Yes").token_id;

// Create a market buy order (no price specified) for $15 worth of shares
const order = await clobClient.createMarketBuyOrder({
  tokenId,
  amount: 15, // 15 USDC (collateral)
  // price: 0.50 // If not specified, the best available price will be used. If no price is available, this must be specified
});

// Place the order
const resp = await clobClient.postOrder(order);

Note that you can also create limit orders (specify the amount of shares to buy at a specific price). If no market price is available (clobClient.getPrice() returns "0") then orders must be placed as a limit order with the "midpoint" price, otherwise, the order will fail. Eg:

const midRes = await clobClient.getMidpoint(tokenID);
const price = Number.parseFloat(midRes.mid);
const order = await clobClient.createOrder({
  tokenID,
  size: 100, // 100 shares
  price,
  side: Side.BUY,
})

You can also use the API directly to query the Market:

GET {clob-endpoint}/markets/{condition_id}

And place the order:

POST {clob-endpoint}/order

Request Payload Parameters

Name Required Type Description
order yes Order signed order object
owner yes string api key of oder owner
orderType yes string order type ("FOK", "GTC", "GTD")

Proxy Wallets

Each user has their own proxy wallet (and thus proxy wallet address) but the factories are available at the following deployed addresses on the Polygon network:

Contract Address Factory
0xaacfeea03eb1561c4e67d661e40682bd20e3541b Gnosis safe
0xaB45c5A4B0c941a2F231C04C3f49182e1A254052 Polymarket proxy

Gnosis safes are used for all MetaMask users, while Polymarket custom proxy contracts are used for all MagicLink users. Using proxy wallets allows Polymarket to provide an improved UX where multi-step transactions can be executed atomically and transactions can be relayed by relayers on the gas station network. It's not strictly required for the user to use a proxy wallet, but it's recommended.

The @polymarket/sdk library can be used to interact with the proxy wallets. Proxy addresses are calculated using the following code:

import { getProxyWalletAddress } from "@polymarket/sdk";

const safeAddress = '0xaacfeea03eb1561c4e67d661e40682bd20e3541b';
const proxyAddress = getProxyWalletAddress(
  safeAddress, // proxy wallet factory contract
  '0x...', // address which owns the proxy wallet we want to calculate
);

Here's an example of creating a CLOB Client for a Gnosis Safe proxy wallet:

const [account] = await window.ethereum.request({ method: "eth_requestAccounts" });
const proxyWalletAddress = getProxyWalletAddress(safeAddress, account);
const clobClient = new ClobClient(
  host,
  chain,
  signer,
  creds,
  SignatureType.POLY_GNOSIS_SAFE,
  proxyWalletAddress,
);

Prices and Books

Spot Price

You can get the best available price

With the @polymarket/clob-client library:

const price = await clobClient.getPrice(tokenId, Side.Buy);

or from the CLOB API using:

https://clob.polymarket.com/price?token_id=[TOKEN_ID]&side=buy

Which returns

{
    "price": "0.48"
}

Or the midpoint:

// Get the midpoint price for a market (halfway between best bid or best ask)
const midpoint = await clobClient.getMidpoint(tokenId);

and

https://clob.polymarket.com/midpoint?token_id=[TOKEN_ID]

Order Book

You can also retrieve the current order book and prices for a specific market.

With the @polymarket/clob-client library:

const book = await clobClient.getOrderBook(tokenId);

or the API:

https://clob.polymarket.com/book?token_id=[TOKEN_ID]

Which returns

{
    "market": "0xdd22472e552920b8438158ea7238bfadfa4f736aa4cee91a6b86c39ead110917",
    "asset_id": "21742633143463906290569050155826241533067272736897614950488156847949938836455",
    "bids": [
        {
            "price": "0.03",
            "size": "300"
        },
        {
            "price": "0.12",
            "size": "5000"
        },
        {
            "price": "0.16",
            "size": "15000"
        },
        {
            "price": "0.17",
            "size": "3583"
        },
        {
            "price": "0.45",
            "size": "20522"
        },
        {
            "price": "0.46",
            "size": "256530.05"
        },
        {
            "price": "0.47",
            "size": "299948.42"
        },
        {
            "price": "0.48",
            "size": "158512.24"
        }
    ],
    "asks": [
        {
            "price": "0.99",
            "size": "55000"
        },
        {
            "price": "0.97",
            "size": "300"
        },
        {
            "price": "0.58",
            "size": "5000"
        },
        {
            "price": "0.55",
            "size": "5000"
        },
        {
            "price": "0.53",
            "size": "1000"
        },
        {
            "price": "0.52",
            "size": "2000"
        },
        {
            "price": "0.51",
            "size": "261145.35"
        },
        {
            "price": "0.5",
            "size": "289848"
        },
        {
            "price": "0.49",
            "size": "167976.65"
        }
    ],
    "hash": "a06bacc555bfa7386279e6b9373be56f77ac0c81"
}

Allowances

If you're using an EOA wallet, instead of a proxy wallet, there are multiple approvals that need to be made. The @polymarket/sdk library can be used to get the CTF Exchange, ERC20 collateral token, and the ERC1155 Conditional Tokens addresses.

  1. Set collateral token (USDC.e) allowance for ERC1155 Conditional Tokens (CTF).
  2. Set USDC.e allowance for the Exchange.
  3. Set CTF "approval for all" for the Exchange.
import { getContractConfig } from "@polymarket/clob-client";
import { polygon } from "viem/chains";

const approveSpend = async () => {
  const config = getContractConfig(polygon.id);

  const { localWalletClient } = await getWalletClient();
  try {
    let hash;
    hash = await localWalletClient.writeContract({
      address: config.collateral,
      abi: usdcAbi,
      functionName: "approve",
      args: [config.conditionalTokens, constants.MaxUint256],
    });
    console.log(`approveSpend: Setting USDC allowance for CTF: ${hash}`);

    hash = await localWalletClient.writeContract({
      address: config.collateral,
      abi: usdcAbi,
      functionName: "approve",
      args: [config.exchange, constants.MaxUint256],
    });
    console.log(`approveSpend: Setting USDC allowance for Exchange: ${hash}`);

    hash = await localWalletClient.writeContract({
      address: config.conditionalTokens,
      abi: conditionalTokenAbi,
      functionName: "setApprovalForAll",
      args: [config.exchange, true],
    });
    console.log(`approveSpend: Setting Conditional Tokens allowance for Exchange: ${hash}`);
  } catch (e) {
    console.error("approveSpend: error", e);
  }

About

An Open Action for embeding Polymarket trading within Lens Protocol Publications

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published