0

I am trying to swap the erc721 token for erc20 using the 0x protocol which I deployed on the testnet.

Using the following script to perform the swap:

/**
 * Local, Data & Env imports
 * Exchange.json (from vendorArtifacts attached with this gist)
 * "@0x/contract-artifacts": "^3.15.0"
 */

const addr = require('./deployments.json')
const artifacts = require('@0x/contract-artifacts')
const vendorArtifacts = require('./vendor')
const dotenv = require('dotenv')

/**
 * 0x and ethers imports
 *
 * "ethers": "^5.4.6"
 * "@0x/contract-wrappers": "^13.17.6"
 * "@0x/subproviders": "^6.6.0"
 * "@0x/order-utils": "^10.4.28"
 */

const { providers, Wallet, ContractFactory, Contract, BigNumber } = require('ethers')
const { ContractWrappers } = require('@0x/contract-wrappers')
const { PrivateKeyWalletSubprovider, RPCSubprovider, Web3ProviderEngine } = require('@0x/subproviders')
const OrderUtils = require('@0x/order-utils')

dotenv.config()

/**
 * chainID - directly obtaining from connected provider to use relevant deployment addresses
 * providerEthers - General Ethers JSON-RPC provider to create maker and taker signer
 * wrapper - 0x ContractWrappers object
 * makerEthers - Signer object connected to maker's private key
 * takerEthers - Signer Object connected to taker's private key
 * engineMaker - RPC & maker private key provider for 0x
 * engineTaker - RPC & taker private key provider for 0x
 */

let chainId, providerEthers, wrapper
let makerEthers, takerEthers, engineMaker, engineTaker

const main = async () => {

    await init()

    const exchange = new Contract(addr[chainId].exchange, vendorArtifacts.Exchange.compilerOutput.abi, takerEthers)
    const devUtils = new Contract(addr[chainId].devUtils, artifacts.DevUtils.compilerOutput.abi, providerEthers)

    const erc20 = await deployERC20(takerEthers)
    const erc721 = await deployERC721(makerEthers)

    /**
     * approving erc20 and erc721 to their specific asset proxies
     * @notice proxies are already authorized to exchange v3 contract used above
     */
    await approveToProxies(
        erc20,
        BigNumber.from('1000000000000000000000000000'),
        erc721,
        1,
        addr[chainId]
    )

    wrapper = new ContractWrappers(engineTaker, {
        chainId: chainId
    })

    /**
     * @notice not using BigNumbers in order because will cause error when signing: "does not confront to order schema"
     */
    const order = {
        makerAddress: process.env.MAKER_FROM_ADDRESS,
        takerAddress: process.env.TAKER_FROM_ADDRESS,
        senderAddress: process.env.TAKER_FROM_ADDRESS,
        feeRecipientAddress: '0x0000000000000000000000000000000000000000',
        makerAssetAmount: '1',
        takerAssetAmount: '10000000000000',
        makerFee: '0',
        takerFee: '0',
        expirationTimeSeconds: '1947301711',
        salt: '510',
        makerAssetData: await devUtils.encodeERC721AssetData(erc721.address, BigNumber.from(1)),
        takerAssetData: await devUtils.encodeERC20AssetData(erc20.address),
        makerFeeAssetData: '0x',
        takerFeeAssetData: '0x',
        chainId: chainId,
        exchangeAddress: addr[chainId].exchange,
    }

    const sOrder = await OrderUtils.signatureUtils.ecSignOrderAsync(engineMaker, order, process.env.MAKER_FROM_ADDRESS)

    const valid = await wrapper.exchange.isValidOrderSignature(order, sOrder.signature).callAsync({
        to: addr[chainId].exchange
    })
    if (valid === true) console.log('- Maker signed order successfully')

    const abiEncoded = await wrapper.exchange.fillOrder(order, BigNumber.from('1'), sOrder.signature).getABIEncodedTransactionData()

    const output = await exchange.executeTransaction(
        ['510', '1947301711', '2000000000', process.env.MAKER_FROM_ADDRESS, abiEncoded],
        sOrder.signature,
        {
            gasLimit: 7000000,
            gasPrice: 2000000000,
            type: 1
        }
    )
    console.log(output)
}

const init = async () => {

    engineMaker = new Web3ProviderEngine()
    engineMaker.addProvider(new PrivateKeyWalletSubprovider(process.env.MAKER_PRIVATE_KEY))
    engineMaker.addProvider(new RPCSubprovider(process.env.RPC));
    engineMaker.start();

    engineTaker = new Web3ProviderEngine()
    engineTaker.addProvider(new PrivateKeyWalletSubprovider(process.env.TAKER_PRIVATE_KEY))
    engineTaker.addProvider(new RPCSubprovider(process.env.RPC));
    engineTaker.start();

    providerEthers = new providers.JsonRpcProvider(process.env.RPC)
    chainId = await (await providerEthers.getNetwork()).chainId

    makerEthers = new Wallet(process.env.MAKER_PRIVATE_KEY, providerEthers)
    takerEthers = new Wallet(process.env.TAKER_PRIVATE_KEY, providerEthers)

    console.log('- Providers Initialized Successfully')
}

const deployERC20 = async (signer) => {
    const erc20Factory = new ContractFactory(
        artifacts.DummyERC20Token.compilerOutput.abi,
        artifacts.DummyERC20Token.compilerOutput.evm.bytecode.object,
        signer
    )
    const erc20 = await erc20Factory.deploy(
        'Test ERC20',
        'TST',
        18,
        BigNumber.from('1000000000000000000000000000')
    )
    console.log('- ERC20 deployed successfully')
    return erc20
}

const deployERC721 = async (signer) => {
    const erc721Factory = new ContractFactory(
        artifacts.DummyERC721Token.compilerOutput.abi,
        artifacts.DummyERC721Token.compilerOutput.evm.bytecode.object,
        signer
    )
    const erc721 = await erc721Factory.deploy(
        'Test ERC721',
        'TST'
    )
    await erc721.mint(signer.address, '1')
    console.log('- ERC721 deployed successfully')
    return erc721
}

const approveToProxies = async (erc20, amount, erc721, tokenId, addrs) => {
    await erc20.approve(addrs.erc20Proxy, amount)
    await erc721.approve(addrs.erc721Proxy, tokenId)
    console.log('- Approved to proxies successfully')
}

main()

But the transaction is getting reverted each time after using 50k gas (you can check all failed attempts here: https://mumbai.polygonscan.com/address/0xAc63b037242D498483C8ad18A0A71aE56e939C22 , source code is not verified on polygonscan since it is causing trouble because of usage of abi encoder v2 experimental)

I have tried different signature mechansims for signing order (eth_sign, sign typed v3 & v4), tried officially deployed 0x v3 contracts on kovan (https://0x.org/docs/guides/0x-cheat-sheet), changing order parameters, swapping maker and taker, etc.

I have also tried to match my approach with 0x protocol's integrations test to make sure everything is correct.

Here are env & deployment files to reproduce to complete environment: https://gist.github.com/hack3r-0m/71102da691183574222fcb90241a07e8

Any help would be highly appericiated, thanks!

1

It sounds like you want to just fill an order but your code is attempting to do it through meta transactions, which is what executeTransaction() does.

To execute a fill through a meta-transaction, the taker will need to sign the meta-transaction object as well, and that is the signature that goes into executeTransaction(). @0x/order-utils has helpers for this as well.

To fill an order directly, the taker just has to call fillOrder() on the exchange contract. You are also encoding the fillOrder() call for too low a taker asset fill amount. I think you mean to pass in 10000000000000, not 1, because anything less than 10000000000000 will round down to 0 of the maker asset, due to the exchange rate the order specifies.

9
  • thanks, understood. first maker signs, then taker signs ABI encoded data for fillOrder but can tweak parameters salt, expiration time and gasPrice or it needs to be same as a maker?
    – hack3r_0m
    Sep 17 at 7:26
  • also, is there a way to verify exchange v3 contracts on etherscan based explorers? bytecode of contract matches but still it does not verify. how did 0x verified it on mainnet?
    – hack3r_0m
    Sep 17 at 7:31
  • the transaction went through: mumbai.polygonscan.com/tx/… but the balance for maker and taker is as it is, swap didn't occur, what could be reason for it?
    – hack3r_0m
    Sep 17 at 9:22
  • The taker cannot change the salt or expiration time of the order because the maker already signed it. But the taker can choose whatever salt and expiration time they want for the metatransaction object that they sign. Tbh I still feel like you want to just call fillOrder() directly without metatransactions. All metatransactions do is let someone who isn't the taker call fillOrder() on their behalf. Sep 17 at 16:18
  • Verifying the contracts entirely depends on how you compiled it. If you compiled it with using our tooling, you need to verify using "Standard JSON input" and find the input artifact in contracts/exchange/test/generated-artifacts/Exchange.input.json. You might have to also remove some extraneous fields from it as well. Sep 17 at 16:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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