polyoxide-relay
Rust client library for Polymarket Relayer API.
The Relay API enables submitting gasless on-chain transactions through Polymarket's relayer service on Polygon. The relayer pays gas fees on behalf of users, supporting two wallet types:
- Safe wallets -- Gnosis Safe multisig contracts (must be deployed before first use)
- Proxy wallets -- lightweight proxy contracts that auto-deploy on first transaction
More information about this crate can be found in the crate documentation.
Installation
[dependencies]
polyoxide-relay = "0.17"
The relay client is not bundled into the unified polyoxide crate (which covers CLOB, Gamma, and Data). Depend on polyoxide-relay directly to use it.
Feature Flags
| Feature |
Default |
Description |
keychain |
No |
Enables OS keychain storage for credentials via keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service) |
Authentication
Relay operations require a private key for EIP-712 transaction signing and one of two authentication schemes for relay submission:
Builder API Credentials (HMAC-SHA256)
use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
let config = BuilderConfig::new(
"your-api-key".into(),
"your-secret".into(),
Some("your-passphrase".into()),
);
let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
let client = RelayClient::from_account(account)?;
# let _ = client;
# Ok(())
# }
Relayer API Key (static headers)
use polyoxide_relay::{RelayClient, BuilderAccount};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
let account = BuilderAccount::with_relayer_api_key(
"0xprivatekey...",
"your-relayer-api-key".into(),
"0xyour-address".into(),
)?;
let client = RelayClient::from_account(account)?;
# let _ = client;
# Ok(())
# }
OS Keychain (feature keychain)
use polyoxide_relay::{RelayClient, BuilderAccount};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
let account = BuilderAccount::from_keychain()?;
let client = RelayClient::from_account(account)?;
let account = BuilderAccount::from_keychain_relayer_api_key()?;
let client = RelayClient::from_account(account)?;
# let _ = client;
# Ok(())
# }
Usage
Builder Pattern
use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig, WalletType};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
let config = BuilderConfig::new("key".into(), "secret".into(), None);
let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
let client = RelayClient::builder()?
.with_account(account)
.wallet_type(WalletType::Safe)
.chain_id(137) .max_concurrent(2)
.build()?;
# let _ = client;
# Ok(())
# }
Or pull settings from environment variables (RELAYER_URL, CHAIN_ID):
# use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
# let config = BuilderConfig::new("key".into(), "secret".into(), None);
# let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
let client = RelayClient::default_builder()?
.with_account(account)
.build()?;
# let _ = client;
# Ok(())
# }
Gasless Redemption
# use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig};
use alloy::primitives::U256;
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
# let config = BuilderConfig::new("key".into(), "secret".into(), None);
# let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
# let client = RelayClient::from_account(account)?;
let condition_id = [0u8; 32]; let index_sets = vec![U256::from(1)];
let response = client
.submit_gasless_redemption(condition_id, index_sets)
.await?;
println!("Transaction ID: {}", response.transaction_id);
# Ok(())
# }
Gasless Redemption with Gas Estimation
# use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig};
# use alloy::primitives::U256;
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
# let config = BuilderConfig::new("key".into(), "secret".into(), None);
# let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
# let client = RelayClient::from_account(account)?;
# let condition_id = [0u8; 32];
# let index_sets = vec![U256::from(1)];
let response = client
.submit_gasless_redemption_with_gas_estimation(
condition_id,
index_sets,
true, )
.await?;
# let _ = response;
# Ok(())
# }
Execute Arbitrary Transactions
use polyoxide_relay::SafeTransaction;
use alloy::primitives::{Address, U256, Bytes};
# use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
# let config = BuilderConfig::new("key".into(), "secret".into(), None);
# let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
# let client = RelayClient::from_account(account)?;
# let calldata: Vec<u8> = vec![];
let tx = SafeTransaction {
to: "0x...".parse().unwrap(),
value: U256::ZERO,
data: Bytes::from(calldata),
operation: 0, };
let response = client.execute(vec![tx], None).await?;
# let _ = (response, Address::ZERO);
# Ok(())
# }
Query Operations (no auth required)
use alloy::primitives::Address;
# use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
# let config = BuilderConfig::new("key".into(), "secret".into(), None);
# let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
# let client = RelayClient::from_account(account)?;
# let safe_address = Address::ZERO;
# let address = Address::ZERO;
let deployed = client.get_deployed(safe_address).await?;
let nonce = client.get_nonce(address).await?;
let status = client.get_transaction("tx-id").await?;
println!("State: {}", status.state);
let latency = client.ping().await?;
println!("Relay API latency: {}ms", latency.as_millis());
# let _ = (deployed, nonce);
# Ok(())
# }
Wallet Address Derivation
# use polyoxide_relay::{RelayClient, BuilderAccount, BuilderConfig};
# async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
# let config = BuilderConfig::new("key".into(), "secret".into(), None);
# let account = BuilderAccount::new("0xprivatekey...", Some(config))?;
# let client = RelayClient::from_account(account)?;
let safe_address = client.get_expected_safe()?;
let proxy_address = client.get_expected_proxy_wallet()?;
# let _ = (safe_address, proxy_address);
# Ok(())
# }
Environment Variables
| Variable |
Description |
POLYMARKET_PRIVATE_KEY |
Hex-encoded private key for EIP-712 signing |
BUILDER_API_KEY |
Builder API key (HMAC auth) |
BUILDER_SECRET |
Builder API secret (HMAC auth) |
BUILDER_PASS_PHRASE |
Builder API passphrase (HMAC auth, optional) |
RELAYER_API_KEY |
Relayer API key (static auth) |
RELAYER_API_KEY_ADDRESS |
Address associated with relayer API key |
RELAYER_URL |
Custom relayer URL (default: https://relayer-v2.polymarket.com) |
CHAIN_ID |
Target chain ID (default: 137 for Polygon mainnet) |
API Coverage
- Transaction Submission: Sign and submit gasless transactions via Safe or Proxy wallets
- Gasless Redemptions: Redeem CTF positions without holding MATIC
- Gas Estimation: Simulate redemptions against Polygon RPC for accurate gas limits
- Nonce Management: Fetch current nonce from the relayer
- Deployment Check: Verify whether a Safe wallet is deployed on-chain
- Transaction Status: Query the state of submitted relay transactions
- Wallet Derivation: Compute expected Safe and Proxy wallet addresses via CREATE2
- Multi-Send Batching: Automatically batch multiple transactions via Gnosis Safe MultiSend
Supported Chains
| Chain |
ID |
Notes |
| Polygon mainnet |
137 |
Full support (Safe + Proxy) |
| Amoy testnet |
80002 |
Safe only (Proxy not available) |
License
Licensed under either of MIT or Apache-2.0 at your option.