I'm trying to get limit orders working using the 0x reference: https://docs.0x.org/market-makers/guides/0x-v4-rfq-orders-code-examples
looking to swap some WETH for some ZRX via placing a limit order.
I'm getting the error:
500
{'reason': "Cannot read properties of undefined (reading 'data')"}
here is my request:
{
"makerToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"takerToken": "0xe41d2489571d322189246dafa5ebde1f4699f498",
"makerAmount": "50000000000000000",
"takerAmount": "220000000000000000000",
"takerTokenFeeAmount": "0",
"maker": "0xMYADDRESSS",
"taker": "0x0000000000000000000000000000000000000000",
"sender": "0x0000000000000000000000000000000000000000",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"pool": "0x0000000000000000000000000000000000000000000000000000000000000000",
"expiry": "1648687063",
"salt": "1648686703",
"chainId": 1,
"verifyingContract": "0xDef1C0ded9bec7F1a1670819833240f027b25EfF",
"signature": {
"signatureType": 3,
"v": 27,
"r": "0xbec1e45ecba34aa17158ebfd672d6c6fdd418cc4ba576efe23aad24af35d85d6",
"s": "0x7cbd78a796c3fad5b87e75cc96c6e5f2963dd78265724e700e30049af41cd927"
}
}
I'm posting it to:
https://api.0x.org/orderbook/v1/order
below is the full code, sorry its long, this is the only way I can figure out without wrapper classes:
import eth_abi
import web3
from eth_utils import keccak, remove_0x_prefix, to_bytes
import hashlib
from bitcoin import ecdsa_raw_sign
from typing import NamedTuple, Tuple
import time
import os
import personal
# Create 2 private keys. These keys have been funded with some Ropsten ETH. For these scripts to work, the accounts below need to have some Ropsten ETH
private_key_maker = web3.Web3.toBytes(hexstr=personal.MY_PK)
private_key_maker_address = personal.MY_ETH_ADY
# Fetch the RPC URL and the Exchange Proxy address
MAIN_URL = personal.MYINFURA
ZERO_EX_EP = '0xdef1c0ded9bec7f1a1670819833240f027b25eff'
ZERO_EX_EP_CS = web3.Web3.toChecksumAddress('0xdef1c0ded9bec7f1a1670819833240f027b25eff')
TOKEN_A = web3.Web3.toChecksumAddress('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') #WETH
TOKEN_B = web3.Web3.toChecksumAddress('0xe41d2489571d322189246dafa5ebde1f4699f498') #ZRX
print(TOKEN_A)
provider = web3.HTTPProvider(MAIN_URL)
client = web3.Web3(provider)
print(f"Latest block is: {client.eth.getBlock('latest')['number']}")
import sha3
private_key_address = web3.Web3.toChecksumAddress(personal.MY_ETH_ADY)
private_key = personal.MY_PK
null_eth_address = '0x0000000000000000000000000000000000000000'
ORDER_URL = 'https://api.0x.org/orderbook/v1/order'
from eip712_structs import EIP712Struct, Address, String, Bytes, Address, Uint
from eip712_structs import Bytes
def pad_20_bytes_to_32(twenty_bytes: bytes):
return bytes(12) + twenty_bytes
def int_to_32_big_endian_bytes(i: int):
return i.to_bytes(32, byteorder="big")
order_field_types = [
"address",
"address",
"uint128",
"uint128",
"uint128",
"address",
"address",
"address",
"address",
"bytes32",
"uint64",
"uint256",
]
class LimitOrder(NamedTuple):
makerToken: str
takerToken: str
makerAmount: int
takerAmount: int
takerTokenFeeAmount: int
maker: str
taker: str
sender: str
feeRecipient: str
pool: int
expiry: int
salt: int
order = LimitOrder(
makerToken= TOKEN_A.lower(),
takerToken= TOKEN_B.lower(),
makerAmount= int(0.05 * 1e18),
takerAmount= int(220 * 1e18),
takerTokenFeeAmount = 0,
maker= private_key_maker_address,
taker= null_eth_address,
sender = null_eth_address,
feeRecipient = null_eth_address,
pool = 0,
expiry=int(time.time() + 360), #6 mins
salt=int(time.time())
)
eip191_header = b"\x19\x01"
eip712_domain_separator_schema_hash = client.keccak(
b"EIP712Domain("
+ b"string name,"
+ b"string version,"
+ b"uint256 chainId,"
+ b"address verifyingContract"
+ b")"
)
eip712_order_schema_hash = client.keccak(
b"LimitOrder("
+ b"address makerToken,"
+ b"address takerToken,"
+ b"uint128 makerAmount,"
+ b"uint128 takerAmount,"
+ b"uint128 takerTokenFeeAmount,"
+ b"address maker,"
+ b"address taker,"
+ b"address sender,"
+ b"address feeRecipient,"
+ b"bytes32 pool,"
+ b"uint64 expiry,"
+ b"uint256 salt"
+ b")"
)
def pad_20_bytes_to_32(twenty_bytes: bytes):
return bytes(12) + twenty_bytes
def int_to_32_big_endian_bytes(i: int):
return i.to_bytes(32, byteorder="big")
def make_eip712_domain_struct_header_hash(chain_id: int, verifying_contract: str) -> str:
return keccak(
eip712_domain_separator_schema_hash
+ keccak(b"ZeroEx")
+ keccak(b"1.0.0")
+ int_to_32_big_endian_bytes(int(chain_id))
+ pad_20_bytes_to_32(to_bytes(hexstr=verifying_contract))
)
def get_order_hash_0xv4(order: LimitOrder, chain_id: int, exchange_proxy_address: str):
"""
Returns an order hash for a given LIMIT order. The order hash will not contain the `0x` prefix.
:params order: a RFQ order
:params chain_id: the Chain ID. Ropsten is 3, while Mainnet is 1
:params exchange_proxy_address: the address of the Exchange Proxy. This address must be checksummed.
"""
order_struct = eip712_order_schema_hash + pad_20_bytes_to_32(to_bytes(hexstr=order.makerToken)) + pad_20_bytes_to_32(to_bytes(hexstr=order.takerToken))+ int_to_32_big_endian_bytes(int(order.makerAmount))+ int_to_32_big_endian_bytes(int(order.takerAmount))+ int_to_32_big_endian_bytes(int(order.takerTokenFeeAmount))+ pad_20_bytes_to_32(to_bytes(hexstr=order.maker))+ pad_20_bytes_to_32(to_bytes(hexstr=order.taker))+ pad_20_bytes_to_32(to_bytes(hexstr=order.sender))+ pad_20_bytes_to_32(to_bytes(hexstr=order.feeRecipient))+ int_to_32_big_endian_bytes(int(order.pool))+ int_to_32_big_endian_bytes(int(order.expiry))+ int_to_32_big_endian_bytes(int(order.salt))
eip712_order_struct_hash = client.keccak(order_struct)
eip712_domain_struct_hash = make_eip712_domain_struct_header_hash(
chain_id=chain_id,
verifying_contract=exchange_proxy_address
)
#print(f"Domain hash: {eip712_domain_struct_hash.hex()}")
#print(f"Order schema hash: {eip712_order_schema_hash.hex()}")
return keccak(
eip191_header
+ eip712_domain_struct_hash
+ eip712_order_struct_hash
)
def get_order_info(order: LimitOrder) -> Tuple[bytes, int, int]:
function_signature = web3.Web3.keccak(text=f'getLimitOrderInfo(({",".join(order_field_types)}))').hex()[:10]
encoded_args = eth_abi.encode_abi(order_field_types, [
order.makerToken,
order.takerToken,
order.makerAmount,
order.takerAmount,
order.takerTokenFeeAmount,
order.maker,
order.taker,
order.sender,
order.feeRecipient,
int_to_32_big_endian_bytes(order.pool), # Pool is represented as an unsigned integer, however it is encoded as a `bytes32`
order.expiry,
order.salt
])
calldata = function_signature + encoded_args.hex()
tx = {
"to": client.toChecksumAddress(ZERO_EX_EP),
"data": calldata,
}
# Perform an `eth_call` to read the transaction data result. The response structure can be found here:
# https://0xprotocol.readthedocs.io/en/latest/basics/functions.htbml#getrfqorderinfo
response = client.eth.call(tx)
on_chain_order_hash, fillable_status, amount_filled = eth_abi.decode_abi(['bytes32', 'uint8', 'uint128'], bytes.fromhex(response.hex()[2:]))
print(f"ONLINE Hash hex: {on_chain_order_hash.hex()}")
#print(f"Fillable status: {fillable_status}")
#print(f"Amount filled: {amount_filled / 1e18}")
return on_chain_order_hash, fillable_status, amount_filled
import json
def order_to_sra(order: LimitOrder, chain_id: int, verifying_contract: str, v: int, r: bytes, s: bytes):
obj = {
"makerToken": order.makerToken,
"takerToken": order.takerToken,
"makerAmount": str(order.makerAmount),
"takerAmount": str(order.takerAmount),
"takerTokenFeeAmount": str(order.takerTokenFeeAmount),
"maker": order.maker,
"taker": order.taker,
"sender": order.sender,
"feeRecipient": order.feeRecipient,
"pool": '0x' + order.pool.to_bytes(32, 'big').hex(),
"expiry": str(order.expiry),
"salt": str(order.salt),
"chainId": int(chain_id),
"verifyingContract": str(verifying_contract),
"signature": {
"signatureType": 3,
"v": v,
"r": f"0x{r.hex()}",
"s": f"0x{s.hex()}",
}
}
return json.dumps(obj, indent=4) # Indent is added simply for prettifying
def SignHashNew(hash_bytesArray, privateKey):
from eth_account.messages import encode_defunct
message = encode_defunct(hash_bytesArray)
signed_message = client.eth.account.sign_message(message, private_key=privateKey)
return (
signed_message.v,
'0x' + signed_message.r.to_bytes(length=32, byteorder='big').hex(),
'0x' + signed_message.s.to_bytes(length=32, byteorder='big').hex()
)
off_chain_order_hash = get_order_hash_0xv4(order, 1, ZERO_EX_EP)
off_chain_order_hash.hex()
print('OFFCHAIN Hash hex: ', off_chain_order_hash.hex())
on_chain_order_hash = get_order_info(order)
v, r, s = SignHashNew(off_chain_order_hash, private_key_maker)
r = bytes.fromhex(r[2:])
s = bytes.fromhex(s[2:])
json_order = order_to_sra(order=order, chain_id=1, verifying_contract=ZERO_EX_EP_CS, v=v, r=r, s=s)
json_order = json.loads(json_order)
print(json_order)
import requests
response = requests.post(
url=ORDER_URL,
json=json_order,
headers = {
"Content-Type": "application/json"
}
)
if response.status_code != 200:
try:
print(response.status_code)
response_json = response.json()
print(response_json)
except:
print('error getting response')