Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Examples to integrate with Wyvern from a smart contract #55

Open
aramalipoor opened this issue Jun 24, 2021 · 13 comments
Open

Examples to integrate with Wyvern from a smart contract #55

aramalipoor opened this issue Jun 24, 2021 · 13 comments

Comments

@aramalipoor
Copy link

aramalipoor commented Jun 24, 2021

Hello there,

Thanks for the amazing platform! We're building a NFT platform with some special rules, because of these (specifically that "transferFrom" and "approve" cannot be called by owners themselves) we have to automatically list them on OpenSea.

From my understanding so far we need to call approveOrder_ from a contract who has the ability to do approve and transferFrom on those NFTs. We were thinking of having a "Marketer" contract which has a special access to do these operations on all NFTs on behalf of holders.

Assuming, above is correct there are a few questions I have:

  1. Do we need to have a proxy for this Marketer contract of ours? e.g. to call "registerProxy" on its constructor. If yes is it possible to implement required capabilities within our Marketer contract so we can avoid having a proxy?
  2. These orders need to be using ETH (and WETH maybe?) in terms of being asymmetrical, is it possible to do what we want to achieve? i.e. To put the NFT for sale on automatically behalf of holder for a certain price, and have it automatically show up on OpenSea?
  3. Is there any "solidity" examples available where a smart contract calls Wyvern exchange, there are some javascript examples but mapping them to solidity is a bit tricky.

Thank you so much in advance :)

@aramalipoor
Copy link
Author

aramalipoor commented Jun 26, 2021

For a PoC so far this is what I ended up with, but currently have 2 problems:

  1. How to generate replacementPattern?
  2. Should this operation automatically list the sale order on OpenSea? If so I don't see it on testnets.opensea at least.
    bytes constant emptyBytes = bytes("");
    bytes constant replacementPattern = bytes(hex"000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000");

    function putForSale(
        address owner,
        uint256 tokenId,
        uint256 salePrice
    ) public virtual isDirectory()
    {
        uint zero = 0;
        uint one = 1;

        address zeroAddr = address(0);
        bytes memory transferCalldata = abi.encodeWithSignature("transferFrom(address,address,uint256)", owner, zeroAddr, tokenId);
        address[7] memory addrs;
        uint[9] memory uints;

        {
            addrs = [
            // Exchange
            address(_wyvernExchange),
            // Maker
            address(this),
            // Taker
            zeroAddr,
            // Fee Recipient
            _openSeaWallet,
            // Target (Asset Contract)
            address(_directory),
            // Static Target
            zeroAddr,
            // Token used to pay for the order, or the zero-address as a sentinel value for Ether
            zeroAddr
            ];
        }
        {
            uints = [
            // Maker Relayer Fee
            500,
            // Taker Relayer Fee
            zero,
            // Maker Protocol Fee
            zero,
            // Taker Protocol Fee
            zero,
            // Base price of the order (in paymentTokens)
            salePrice,
            // Auction extra parameter - minimum bid increment for English auctions, starting/ending price difference
            zero,
            // Expiration timestamp - 0 for no expiry
            block.timestamp,
            zero,
            // Order salt, used to prevent duplicate hashes
            uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp)))
            ];
        }

        {
            _approveWyvernOrder(
                addrs,
                uints,
                transferCalldata
            );
        }
    }

    function _approveWyvernOrder(
        address[7] memory addrs,
        uint[9] memory uints,
        bytes memory transferCalldata
    ) internal {
        _wyvernExchange.approveOrder_(
        addrs,
        uints,
        // Fee Method (split fee): Maker fees are deducted from the token amount that the maker receives. Taker fees are extra tokens that must be paid by the taker.
        WyvernExchangeInterface.FeeMethod.SplitFee,
        // Side (sell)
        WyvernExchangeInterface.Side.Sell,
        // Sale Kind (fixed price)
        WyvernExchangeInterface.SaleKind.FixedPrice,
        // How to call (call)
        WyvernExchangeInterface.HowToCall.Call,
        // Call Data
        transferCalldata,
        // Replacement Pattern
        replacementPattern,
        // staticExtradata
        emptyBytes,
        // orderbookInclusionDesired
        true
        );
    }

@aramalipoor
Copy link
Author

@aramalipoor
Copy link
Author

aramalipoor commented Jun 26, 2021

Now I figured out I have to call OpenSea API to "post" the order...

There's a problem which I'm guessing is on OpenSea backend side. I'm using a different "maker" than the "owner" in the calldata...

So currently, I have marker = Our Marketer Contract, and owner = owner of tokenId, and I'm getting this error from OpenSea backend:

{
    "success": false,
    "errors": [
        "[\"Transfer calldata doesn't match.\"]"
    ]
}

And I think this is because OpenSea backend expects "owner == marketer" which is just one way of working with Wyvern protocol if I'm not mistaken...

For more context, calldata is for transferFrom(owner, null_address, tokenId), but the maker is our Marketer smart contract, so they are different. If I set maker = "owner" the contract fails due to maker != sender, but if set owner = "marker" then the contract fails because the Marketer (maker) is not the actual owner of tokenId...

curl --location --request POST 'https://rinkeby-api.opensea.io/wyvern/v1/orders/post/' --header 'Content-Type: application/json' --data-raw '{
    "exchange": "0x5206e78b21ce315ce284fb24cf05e0585a93b1d9",
    "maker": "0x3f539db004cb22c96d69d0e3eb8974d46ed93254",
    "taker": "0x0000000000000000000000000000000000000000",
    "makerRelayerFee": "250",
    "takerRelayerFee": "0",
    "makerProtocolFee": "0",
    "takerProtocolFee": "0",
    "makerReferrerFee": "0",
    "feeMethod": 1,
    "feeRecipient": "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073",
    "side": 1,
    "saleKind": 0,
    "target": "0xda25087323285ae25e00e8c373a9184fb558d831",
    "howToCall": 0,
    "calldata": "0x23b872dd000000000000000000000000431922e8ede1ce07ed80c89ac4c302e23dc6ba2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064",
    "replacementPattern": "0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000",
    "staticTarget": "0x0000000000000000000000000000000000000000",
    "staticExtradata": "0x",
    "paymentToken": "0x0000000000000000000000000000000000000000",
    "quantity": "1",
    "basePrice": "550000000000000000",
    "extra": "0",
    "listingTime": "1624714038",
    "expirationTime": "0",
    "salt": "108978690099335258361352357558901730935341035577208231443103633475927170030228",
    "metadata": {
        "asset": {
            "id": "100",
            "address": "0xda25087323285ae25e00e8c373a9184fb558d831"
        },
        "schema": "ERC721"
    },
    "hash": "0xb48cd508ec5caab9ab9b4fcb4d83afeeb53b009dea3393ca9a167158bb38d1fe"
}'

@aramalipoor
Copy link
Author

@cwgoes @MarvinJanssen would you be able to help me on above?

@Rotemy
Copy link

Rotemy commented Aug 22, 2021

@aramalipoor Hey Aram, we are also trying to initiate a sell and buy orders via smart contract. Have you manage to make any progress?

@priviprotocol
Copy link

Hi @Rotemy @aramalipoor -- we are facing a similar issue
Were you able to find a solution?

@Rotemy
Copy link

Rotemy commented Nov 19, 2021

Yes, but the seller or buyer should either be the smart contract or the user has to sign the sell/buy order in advanced

@priviprotocol
Copy link

priviprotocol commented Nov 19, 2021

Hi @Rotemy,

Thanks a lot for your reply.

Then, this would be correct?

bytes memory transferCalldata = abi.encodeWithSignature("transferFrom(address,address,uint256)", address(this), zeroAddr, tokenId);

where address(this) would be the contract address and then the buyer.

@Rotemy
Copy link

Rotemy commented Nov 19, 2021 via email

@priviprotocol
Copy link

priviprotocol commented Nov 19, 2021

Thanks again for the answer @Rotemy. Let me state better our problem. We are trying to list a selling offer on Opensea from our contract.

This is the post request we are doing:

{
    "exchange": "0x5206e78b21ce315ce284fb24cf05e0585a93b1d9",
    "maker": "0x568A0f32BC48f278fe3C822E9992dD478598b3B1",
    "taker": "0x0000000000000000000000000000000000000000",
    "makerRelayerFee": "250",
    "takerRelayerFee": "0",
    "makerProtocolFee": "0",
    "takerProtocolFee": "0",
    "makerReferrerFee": "0",
    "feeMethod": 0,
    "feeRecipient": "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073",
    "side": 1,
    "saleKind": 0,
    "target": "0x8385c95eb77ceab24f8bb05b33dc7602840c0ba1",
    "howToCall": 0,
    "calldata": "23B872DD000000000000000000000000568A0F32BC48F278FE3C822E9992DD478598B3B100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003",
    "replacementPattern": "000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000",
    "staticTarget": "0x0000000000000000000000000000000000000000",
    "staticExtradata": "0x",
    "paymentToken": "0x0000000000000000000000000000000000000000",
    "quantity": "1",
    "basePrice": "100000000000000000000",
    "extra": "0",
    "listingTime": "1637328412",
    "expirationTime": "0",
    "salt": "70178943407415076223843289749154878162928002278408319126229368582024804639243",
    "metadata": {
        "asset": {
            "id": "3",
            "address": "0x8385c95eb77ceab24f8bb05b33dc7602840c0ba1"
        },
        "schema": "ERC721"
    },
    "hash": "0xced35f245951d3622eeee38066d3a71ab1218e7fcc7d451d213d133d5d0dcef1"
}

after calling approveOrder on Wyvern Exchange https://rinkeby.etherscan.io/tx/0x2153ca6f7cc1aac8f08e7a65df424bab1b2d4b3209dccd9d9a6f0c4885cb6602#eventlog

However the response we are getting is:

"errors": [ "['You must approve assets #3 for trading before creating an order']"

I would be great if you could give some guidance or support on that. Thanks in advance.

@atsmsmr
Copy link

atsmsmr commented Nov 27, 2021

Hi @Rotemy, @priviprotocol, I am also trying to initiate sell order via smart contract.
I tested approveOrder_() based on aramalipoor's code above, but it did not work.
Did you achieve that in a completely different way than aramalipoor?
Could you give me some advice?

@Ghijo1
Copy link

Ghijo1 commented Jan 21, 2022

Hello guys, do you know how to get the orderbook of a token from the wyvern exchange?

@qaiszero
Copy link

Hello guys, do you know how to get the orderbook of a token from the wyvern exchange?

you mean how to get order data for available atomic matches on Wyvern? I want to know the same

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants