Last active
March 6, 2025 16:39
-
-
Save shaunlebron/7463f0003aa906ffe6f31dc18c408f73 to your computer and use it in GitHub Desktop.
Polymarket order signing in Clojure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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