Skip to content

Instantly share code, notes, and snippets.

@shaunlebron
Last active March 6, 2025 16:39
Show Gist options
  • Save shaunlebron/7463f0003aa906ffe6f31dc18c408f73 to your computer and use it in GitHub Desktop.
Save shaunlebron/7463f0003aa906ffe6f31dc18c408f73 to your computer and use it in GitHub Desktop.
Polymarket order signing in Clojure
(ns example.polymarket.sign
(:require
[cheshire.core :as json]
[clojure.string :as str])
(:import
(org.web3j.crypto Sign Credentials) ;; org.web3j/crypto "4.12.2"
(org.web3j.utils Numeric)
(java.util Base64)
(javax.crypto Mac)
(javax.crypto.spec SecretKeySpec)))
(def ^:const polygon-chain-id 137) ;; Polygon Mainnet
(def ^:const zero-address "0x0000000000000000000000000000000000000000")
;; https://github.com/Polymarket/clob-client/blob/v4.11.0/src/config.ts
(def polygon-contracts
{137 ;; MATIC
{:exchange "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
:negRiskAdapter "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
:negRiskExchange "0xC5d563A36AE78145C45a50134d48A1215220f80a"
:collateral "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
:conditionalTokens "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"}
80002 ;; AMOY
{:exchange "0xdFE02Eb6733538f8Ea35D585af8DE5958AD99E40"
:negRiskAdapter "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
:negRiskExchange "0xC5d563A36AE78145C45a50134d48A1215220f80a"
:collateral "0x9c4e1703476e875070ee25b56a58b008cfb8fa78"
:conditionalTokens "0x69308FB512518e39F9b16112fA8d994F4e2Bf8bB"}})
(def EIP712-DOMAIN
;; https://github.com/Polymarket/clob-order-utils/blob/v2.1.0/src/exchange.order.const.ts#L15-L20
[{:name "name" :type "string"}
{:name "version" :type "string"}
{:name "chainId" :type "uint256"}
{:name "verifyingContract" :type "address"}])
(def ORDER-STRUCTURE
;; https://github.com/Polymarket/clob-order-utils/blob/v2.1.0/src/exchange.order.const.ts#L22-L35
[{: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"}])
;; Contracts
;; https://github.com/Polymarket/clob-order-utils/blob/v2.1.0/src/exchange.order.const.ts#L5-L6
(def PROTOCOL-NAME "Polymarket CTF Exchange")
(def PROTOCOL-VERSION "1")
(defn kw->caps-str [k]
(-> k name .toUpperCase))
(defn build-poly-hmac-sig [secret ts req-method req-path req-body]
(let [mesg (str ts (kw->caps-str req-method) req-path
(when req-body (json/generate-string req-body)))
base64-secret (.decode (Base64/getDecoder) secret) ;; byte[]
hmac (doto (Mac/getInstance "HmacSHA256")
(.init (SecretKeySpec. base64-secret "HmacSHA256")))
sig (.doFinal hmac (.getBytes mesg))] ;; byte[]
(-> (.encodeToString (Base64/getEncoder) sig)
(str/replace "+" "-")
(str/replace "/" "_"))))
(defonce credentials (Credentials/create PK))
(defn sign-typed-data [data]
(let [sig (Sign/signTypedData (json/generate-string data) (.getEcKeyPair credentials))
hex (Numeric/toHexString (byte-array (concat (.getR sig) (.getS sig) (.getV sig))))]
hex))
;; POST {clob-endpoint}/order (note: requires L2-headers)
;; -- Poly Signed Order Object --
;; Name Required Type Description
;; salt yes integer random salt used to create unique order
;; maker yes string maker address (funder)
;; signer yes string signing address
;; taker yes string taker address (operator)
;; tokenId yes string ERC1155 token ID of conditional token being traded
;; makerAmount yes string maximum amount maker is willing to spend
;; takerAmount yes string minimum amount taker must pay the maker in return
;; expiration yes string unix expiration timestamp
;; nonce yes string maker's Exchange nonce the order is associated with
;; feeRateBps yes string fee rate in basis points as required by the operator
;; side yes string buy or sell enum index
;; signatureType yes integer signature type enum index
;; signature yes string hex encoded signature
;; SignatureType's:
;; - https://github.com/Polymarket/python-order-utils/blob/main/py_order_utils/model/signatures.py
;;
;; # ECDSA EIP712 signatures signed by EOAs
;; EOA = 0
;; # EIP712 signatures signed by EOAs that own Polymarket Proxy wallets
;; POLY_PROXY = 1
;; # EIP712 signatures signed by EOAs that own Polymarket Gnosis safes
;; POLY_GNOSIS_SAFE = 2
;; ported from:
;; https://github.com/Polymarket/clob-order-utils/blob/v2.1.0/src/exchange.order.builder.ts
(def typed-data (atom nil))
(defn build-signed-order [order options]
(let [order (-> order
(select-keys [:maker :signer :taker :tokenId :makerAmount :takerAmount :expiration :nonce :feeRateBps :side :signatureType])
(assoc :salt (or (:signed-order-salt *repro-opts*)
(str (Math/round (* (Math/random)
(System/currentTimeMillis)))))
:signer (or (:signer order) (:maker order))
:expiration (or (:expiration order) 0)
:signatureType (or (:signatureType order) 0))) ;; EOA
chain-id polygon-chain-id
contract (get polygon-contracts chain-id)
contract-key (if (:neg-risk? options) :negRiskExchange :exchange)
order-typed-data {:primaryType "Order"
:types {:EIP712Domain EIP712-DOMAIN
:Order ORDER-STRUCTURE}
:domain {:name PROTOCOL-NAME
:version PROTOCOL-VERSION
:chainId chain-id
:verifyingContract (get contract contract-key)}
:message order}
order-signature (sign-typed-data order-typed-data)]
(-> order
(assoc :signature order-signature)
(update :salt parse-long)
(update :side {0 "BUY", 1 "SELL"}))))
;;; ————————————————————————————————————————————————————————————————————————————————
(comment
;;
;; from: https://docs.polymarket.com/?typescript#create-and-place-an-order
;;
;; const order = await clobClient.createOrder({
;; tokenID:
;; "71321045679252212594626385532706912750332728571942532289631379312455583992563",
;; price: 0.5,
;; side: Side.BUY,
;; size: 100,
;; feeRateBps: 100,
;; nonce: 1,
;; });
;;
;; sample data below generated after nodejs clob-client expands the above data?
(build-signed-order
{:maker "0x7CD8694f58740D231Ee081f5CC943A3ADA4A21E8",
:taker "0x0000000000000000000000000000000000000000",
:tokenId "71321045679252212594626385532706912750332728571942532289631379312455583992563",
:makerAmount "50000000",
:takerAmount "100000000",
:side 0,
:feeRateBps "100",
:nonce "1",
:signer "0x7CD8694f58740D231Ee081f5CC943A3ADA4A21E8",
:expiration "0",
:signatureType 0}
nil))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment