0

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')

2 Answers 2

0

It looks like there's an error reading the properties of 'data' which is set to 'calldata'. Can you log 'data' and 'calldata' to check if they are set properly?

0

The above code is no longer returning a 500 error.

Its now returning 200 which means it was successful.

For anyone looking to place limit orders in python I hope it will be a good reference.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.