Golang utilities used to generate and sign orders from Polymarket's CTFExchange
go get github.com/ivanzzeth/polymarket-go-order-utilspackage main
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/builder"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/model"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/signer"
)
func main() {
// Load your private key
privateKey, _ := crypto.HexToECDSA("your_private_key_hex")
// Create a signer
ethSigner := signer.NewEthPrivateKeySigner(privateKey)
// Create order builder for Polygon (chain ID 137)
chainId := big.NewInt(137)
orderBuilder := builder.NewExchangeOrderBuilderImpl(chainId, nil)
// Build and sign an order
signedOrder, err := orderBuilder.BuildSignedOrder(ethSigner, &model.OrderData{
Maker: "0xYourAddress",
Taker: "0x0000000000000000000000000000000000000000",
TokenId: "1234",
MakerAmount: "1000000", // 1 USDC (6 decimals)
TakerAmount: "500000", // 0.5 USDC worth of outcome tokens
Side: model.BUY,
FeeRateBps: "100", // 1% fee
Nonce: "0",
}, model.CTFExchange)
if err != nil {
panic(err)
}
fmt.Printf("Order signed successfully: %x\n", signedOrder.Signature)
}The Signer interface abstracts the signing mechanism, allowing you to use different signing strategies:
type Signer interface {
Sign(hashedData common.Hash) ([]byte, error)
GetAddress() (common.Address, error)
}OrderData is used to specify the parameters of an order:
- Maker: Address of the order maker (source of funds)
- Taker: Address of the order taker (
0x0for public orders) - TokenId: Token ID of the CTF ERC1155 asset
- MakerAmount: Maximum amount of tokens to be sold
- TakerAmount: Minimum amount of tokens to be received
- Side: Either
model.BUYormodel.SELL - FeeRateBps: Fee rate in basis points (100 = 1%)
- Nonce: Nonce for onchain cancellations
- Signer: Optional, defaults to maker address
- Expiration: Optional, timestamp after which order expires (0 = no expiration)
- SignatureType:
model.EOA,model.POLY_PROXY, ormodel.POLY_GNOSIS_SAFE
Two types of exchanges are supported:
model.CTFExchange: Standard CTF Exchangemodel.NegRiskCTFExchange: Negative Risk CTF Exchange
import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/signer"
)
// From hex string
privateKey, err := crypto.HexToECDSA("your_private_key_without_0x_prefix")
if err != nil {
panic(err)
}
ethSigner := signer.NewEthPrivateKeySigner(privateKey)
// Get the address associated with this signer
address, err := ethSigner.GetAddress()You can implement your own signer by implementing the Signer interface:
type MyCustomSigner struct {
// your fields
}
func (s *MyCustomSigner) Sign(hashedData common.Hash) ([]byte, error) {
// your signing logic
}
func (s *MyCustomSigner) GetAddress() (common.Address, error) {
// return your address
}Create an order builder and build an order without signing:
import (
"math/big"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/builder"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/model"
)
chainId := big.NewInt(137) // Polygon mainnet
orderBuilder := builder.NewExchangeOrderBuilderImpl(chainId, nil)
order, err := orderBuilder.BuildOrder(&model.OrderData{
Maker: "0xYourMakerAddress",
Taker: "0x0000000000000000000000000000000000000000",
TokenId: "1234",
MakerAmount: "1000000",
TakerAmount: "500000",
Side: model.BUY,
FeeRateBps: "100",
Nonce: "0",
})Build and sign an order in one step:
signedOrder, err := orderBuilder.BuildSignedOrder(
ethSigner,
&model.OrderData{
Maker: "0xYourMakerAddress",
Taker: "0x0000000000000000000000000000000000000000",
TokenId: "1234",
MakerAmount: "1000000",
TakerAmount: "500000",
Side: model.BUY,
FeeRateBps: "100",
Nonce: "0",
Expiration: "1735689600", // Optional: Unix timestamp
},
model.CTFExchange,
)
if err != nil {
panic(err)
}
// Access order details
fmt.Printf("Salt: %s\n", signedOrder.Salt.String())
fmt.Printf("Signature: %x\n", signedOrder.Signature)Generate the EIP-712 hash of an order:
order, _ := orderBuilder.BuildOrder(&model.OrderData{...})
orderHash, err := orderBuilder.BuildOrderHash(order, model.CTFExchange)
if err != nil {
panic(err)
}
fmt.Printf("Order hash: %x\n", orderHash)Sign an order hash separately:
orderHash, _ := orderBuilder.BuildOrderHash(order, model.CTFExchange)
signature, err := orderBuilder.BuildOrderSignature(ethSigner, orderHash)
if err != nil {
panic(err)
}
fmt.Printf("Signature: %x\n", signature)Verify that a signature is valid:
import "github.com/ivanzzeth/polymarket-go-order-utils/pkg/signer"
isValid, err := signer.ValidateSignature(
signerAddress,
orderHash,
signature,
)
if err != nil {
panic(err)
}
if isValid {
fmt.Println("Signature is valid!")
}Creates a new order builder instance.
chainId: The chain ID (e.g., 137 for Polygon, 80002 for Polygon Amoy testnet)saltGenerator: Optional function to generate salt values (defaults to random)
Creates an unsigned order from order data.
BuildSignedOrder(signer signer.Signer, orderData *model.OrderData, contract model.VerifyingContract) (*model.SignedOrder, error)
Builds and signs an order in one operation.
Generates the EIP-712 typed data hash for an order.
Signs an order hash.
Creates a new Ethereum private key signer.
Signs a hash and returns the signature.
Returns the Ethereum address associated with the signer.
Validates a signature against a hash and signer address.
package main
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/builder"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/model"
"github.com/ivanzzeth/polymarket-go-order-utils/pkg/signer"
)
func main() {
// Setup
privateKey, _ := crypto.HexToECDSA("your_private_key")
ethSigner := signer.NewEthPrivateKeySigner(privateKey)
makerAddress, _ := ethSigner.GetAddress()
chainId := big.NewInt(137) // Polygon
orderBuilder := builder.NewExchangeOrderBuilderImpl(chainId, nil)
// Create a buy order for outcome token
signedOrder, err := orderBuilder.BuildSignedOrder(ethSigner, &model.OrderData{
Maker: makerAddress.Hex(),
Taker: common.HexToAddress("0x0").Hex(), // Public order
TokenId: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
MakerAmount: "1000000", // 1 USDC
TakerAmount: "800000", // 0.8 outcome tokens (implies 0.8 probability)
Side: model.BUY,
FeeRateBps: "100", // 1% fee
Nonce: "0",
Expiration: "0", // No expiration
}, model.CTFExchange)
if err != nil {
panic(err)
}
fmt.Printf("Order created successfully!\n")
fmt.Printf("Salt: %s\n", signedOrder.Salt.String())
fmt.Printf("Maker: %s\n", signedOrder.Maker.Hex())
fmt.Printf("Token ID: %s\n", signedOrder.TokenId.String())
fmt.Printf("Maker Amount: %s\n", signedOrder.MakerAmount.String())
fmt.Printf("Taker Amount: %s\n", signedOrder.TakerAmount.String())
fmt.Printf("Signature: %x\n", signedOrder.Signature)
}signedOrder, err := orderBuilder.BuildSignedOrder(ethSigner, &model.OrderData{
Maker: makerAddress.Hex(),
Taker: "0x0000000000000000000000000000000000000000",
TokenId: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
MakerAmount: "800000", // 0.8 outcome tokens to sell
TakerAmount: "750000", // Minimum 0.75 USDC to receive
Side: model.SELL,
FeeRateBps: "100",
Nonce: "0",
}, model.CTFExchange)signedOrder, err := orderBuilder.BuildSignedOrder(ethSigner, &model.OrderData{
Maker: makerAddress.Hex(),
Taker: "0x0000000000000000000000000000000000000000",
TokenId: "1234",
MakerAmount: "1000000",
TakerAmount: "500000",
Side: model.BUY,
FeeRateBps: "100",
Nonce: "0",
}, model.NegRiskCTFExchange) // Use NegRisk exchange// Use a custom salt generator for deterministic salts
customSaltGenerator := func() int64 {
return 12345678
}
orderBuilder := builder.NewExchangeOrderBuilderImpl(chainId, customSaltGenerator)Before pushing changes please run make lint test to format the code and run the tests.
# Install dependencies
go mod download
# Run tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Format code
go fmt ./...
# Lint code (requires golangci-lint)
golangci-lint runSee LICENSE file for details.