LapTrinhBlockchain

Chia sẻ kiến thức về Lập Trình Blockchain

Kiến thức Blockchain, Kiến thức lập trình, Lập trình Blockchain, Lập trình Công cụ, Lập trình NodeJs, Nâng cao Kiến thức

Hướng dẫn lập trình bot tìm kiếm cơ hội giao dịch chênh lệch giá NFT trên blockchain Monad Testnet sử dụng nền tảng MagicEden

Hướng dẫn lập trình sử dụng NodeJs để mua bán NFT trên blockchain Monad Testnet với nền tảng MagicEden

Hướng dẫn lập trình sử dụng NodeJs để mua bán NFT trên blockchain Monad Testnet với nền tảng MagicEden

Chia sẻ bài viết
5
(68)

Gần đây tôi có trải nghiệm testnet trên mạng Monad, một phần để có cơ hội airdrop, một phần muốn tìm hiểu sâu hơn xem mạng lưới này hoạt động ổn định không và tìm hiểu các ứng dụng trên đó. Trong các ứng dụng đó có dapp MonadVerse, cứ một khoảng thời gian thì chính dự án tạo ra NFT mới để cho mọi người đúc, và mỗi lần tôi vào thì đều không còn, và tôi chưa mint được NFT nào cả. Tôi không hiểu sao các bộ NFT này rất HOT và tôi quyết định săn bằng được các NFT này. Tôi biết NFT này đang được giao dịch trên MagicEden.

Tìm hiểu về Monad

Giới thiệu về Monad blockchain

Monad là một dự án blockchain Layer 1 tương thích với EVM, được xây dựng để cải thiện hiệu suất so với Ethereum. Theo trang web chính thức của Monad, dự án này đạt được 10.000 giao dịch mỗi giây (TPS), với thời gian khối là 1 giây và tính cuối cùng trong một slot, nhờ vào các cải tiến như thực thi song song, thực thi bất đồng bộ, và cơ sở dữ liệu tùy chỉnh gọi là MonadDB. MonadDB cho phép lưu trữ trạng thái trên SSD thay vì RAM, giảm yêu cầu phần cứng và chi phí vận hành node, tạo điều kiện cho sự phi tập trung mở rộng.

Dự án được phát triển bởi Monad Labs, một đội ngũ có kinh nghiệm từ lĩnh vực giao dịch tần số cao (HFT), với sự hậu thuẫn từ các nhà đầu tư lớn. Theo CoinGecko, Monad đã huy động được 244 triệu USD qua hai vòng gọi vốn, dẫn đầu bởi ParadigmDragonFly, cho thấy sự tin tưởng vào tiềm năng của dự án.

Monad giữ tính tương thích 100% với EVM ở cấp bytecode, cho phép triển khai hợp đồng thông minh Ethereum mà không cần thay đổi mã, đồng thời giảm chi phí và tăng hiệu suất. Điều này làm cho nó hấp dẫn đối với nhà phát triển và người dùng trong hệ sinh thái Ethereum.

Testnet Và Hoạt Động Airdrop

Testnet của Monad được khởi động vào ngày 19 tháng 2 năm 2025, đánh dấu giai đoạn thử nghiệm trước khi ra mắt mainnet, dự kiến vào quý 1 năm 2025 theo Airdrop Alert. Giai đoạn này thu hút sự chú ý lớn từ cộng đồng crypto, đặc biệt vì tiềm năng airdrop token.

Theo CryptoRank, không có thông báo chính thức về airdrop, nhưng cộng đồng tin rằng Monad có thể phân phối token cho người dùng tham gia sớm, dựa trên tiền lệ của các dự án Layer 1 khác.

MonadVerse và MagicEden

Bộ NFT: MonadVerse NFTs

MonadVerse là một dự án NFT với các sự kiện mint theo giai đoạn (chapter). Dự án này tập trung vào trải nghiệm cộng đồng và thường có số lượng giới hạn, làm tăng tính “hot” trên testnet. Cho đến thời điểm hiện tại, MonadVerse đã ra mắt 6 bộ NFT và chúng đang được giao dịch trên MagicEden:

Các bộ NFT này đều có volume giao dịch hàng ngày khá lớn, chứng tỏ độ HOT của nó. Ngoài ra nó HOT có lẽ cũng bởi sự kỳ vọng nó là một trong các điều kiện để nhận được Airdrop trong tương lai.

Tôi kỳ vọng mua được các NFT này nhưng lại không có nhiều MON trong thay. Tôi cũng hay theo dõi các giao dịch hàng ngày và nhận thấy:

  • Có rất nhiều trường hợp xảy ra chênh lệch giá mua bán => Mua xong bán lại ngay là có thể kiếm được 1 MON đến 5 MON => Tôi đã làm thử bằng tay thì có lần được có lần không?
  • Có nhiều trường hợp giá NFT rất thấp, nếu có thể mua được ở thời điểm này thì khá ngon => Nhưng cơ hội này xảy ra rất nhanh và gần như không thể nào làm bằng tay được.

Và từ vấn đề trên tôi nảy ra ý tưởng viết một con bot để tự động mua bán các NFT này.

Giới thiệu về MagicEden

Cũng do cơ duyên với MonadVerse mà tôi biết đến MagicEden và có động lực tìm hiểu API của MagicEden, bình thường tôi cũng chưa để ý mảng NFT này lắm.

Magic Eden là một nền tảng NFT toàn diện, tập trung vào trải nghiệm người dùng, phí thấp, và mở rộng liên chuỗi, phù hợp cho cả nhà sưu tầm lẫn nhà sáng tạo.

Ra mắt vào tháng 9/2021, Magic Eden ban đầu là chợ NFT lớn nhất trên blockchain Solana, chiếm hơn 90% khối lượng giao dịch thứ cấp của hệ sinh thái này. Sau đó, nền tảng mở rộng hỗ trợ nhiều blockchain khác như Ethereum, Polygon, Bitcoin (gồm cả Ordinals và Runes), Base, Arbitrum, và gần đây là Monad Testnet.

Với hơn 22 triệu người dùng hoạt động hàng tháng và khối lượng giao dịch vượt 1,9 tỷ USD, Magic Eden đã huy động được hơn 170 triệu USD từ các quỹ lớn như Paradigm, Sequoia Capital, đạt định giá 1,6 tỷ USD (2022). Hiện tại, nó là chợ NFT đa chuỗi hàng đầu, vượt qua nhiều đối thủ như OpenSea về khối lượng giao dịch.

Các bộ NFT HOT khác trên Monad Testnet

Tôi đã hỏi thêm Grok và đã biết ngoài bộ “MonadVerse NFTs” còn một số bộ NFT Hot khác trên Monad Testnet:

  • Monadians: Một bộ sưu tập PFP (profile picture) mang phong cách tương lai, được phát triển bởi các thành viên cộng đồng Monad. Dự án này nổi bật với các cơ hội whitelist và tiềm năng airdrop trong tương lai, thu hút nhiều sự chú ý từ người dùng testnet.
  • Lil Chogstars: Lấy cảm hứng từ “Chog” – một biểu tượng trong cộng đồng Monad, đây là bộ sưu tập PFP nghệ thuật với mục tiêu lan tỏa sáng tạo. Bộ sưu tập này gây sốt nhờ thiết kế độc đáo và sự kết nối với hệ sinh thái Monad.
  • Spiky Nads: Bộ sưu tập NFT pixel-art với cơ chế whitelist dựa trên mức độ tương tác của người dùng. Đây là một trong những dự án nổi bật nhờ sự gắn bó cộng đồng và tiềm năng phát triển khi mainnet ra mắt.
  • Fantasy Top Cards: Liên quan đến trò chơi Fantasy Top (mở rộng từ Blast sang Monad Testnet), bộ sưu tập này bao gồm các thẻ “hero” đại diện cho những nhân vật nổi tiếng trên mạng xã hội crypto. Người chơi có thể mint và sử dụng thẻ này trong các cuộc thi testnet, khiến nó trở nên rất phổ biến.
  • Purple Frens: Liên kết với Monad Pad, bộ sưu tập này khuyến khích người dùng tham gia các chiến dịch để được whitelist. Sự kết hợp giữa NFT và yếu tố gamification khiến nó thu hút nhiều người tham gia.

Ngoài ra bạn xem các bộ NFT nổi bật khác trên link: https://magiceden.io/monad-testnet, nhiều bộ sưu tập khá đắt đỏ.

Lập trình sử dụng API của MagicEden

Để có thể tự động giao dịch mua bán NFT trên MagicEden, chúng ta cần phải tìm hiểu sâu hơn về API của MagicEden. Chi tiết về API của MagicEden bạn xem tại liên kết Magic Eden Developer Home. Do chúng ta đang muốn chạy trên blockchain Monad Testnet, một blockchain dạng EVM nên chúng ta tập trung vào EVM APIs.

Tất cả các hàm cần thiết tôi sẽ đóng gói trong tệp: magiceden.js. Tôi mô tả một số hàm cơ bản như sau:

Hàm cơ bản truy vấn tới API của MagicEden

Đầu tiên chúng ta cần viết hàm cơ bản sử dụng để gọi API bất kỳ trên MagicEden, hàm này sẽ được sử dụng chung cho nhiều hàm khác sau này.

Theo tài liệu MagicEden thì để truy vấn chúng ta cần có EVM API Keys, nhưng rất may ở thời điểm hiện tại MagicEden vẫn cho sử dụng public nên không cần tới key này. Do đó code sẽ đơn giản như sau:

const axios = require('axios');
const qs = require('qs');

const baseApi = "https://api-mainnet.magiceden.dev";

async function _requestData(type, endPoint, params={}) {
    try {
        let link = `${baseApi}${endPoint}`;
        if (type=="GET") {
            let queryString = qs.stringify(params);
            if (queryString && queryString.length>0) {
                link += '?' + queryString;   
            }
        }
        let requestConfig = {
            method: type,
            url: link,
            timeout: 60000,
        };
        if (type=="POST") requestConfig.data = params;
        const response = await axios(requestConfig);
        if (response == null) {
            console.error("_requestData(): No response for link:", link);
            return null;
        }
        if (response.data==null) {
            console.error("_requestData(): No data return");
            return null;
        }
        // this.logger.debug("_requestPublic response", response.data);
        return response.data;
    } catch(ex) {
        console.error("_requestData(): Exception", type, endPoint, params, ex);
    }
    return null;
}

Lấy thông tin cơ bản của một NFT Collection

Để lấy thông tin một NFT Collection, chúng ta sẽ sử dụng api Collections. Chúng ta cài đặt hàm gọi API này như sau:

async function getCollectionInfo(collectionId) {
    let data = await _requestData("GET", `/v3/rtp/monad-testnet/collections/v7`, { id: collectionId });
    if (!data || !data.collections) return null;
    if (data.collections.length>0) return data.collections[0];
    return null;
}

Ban đầu khi tìm hiểu API này, cài đặt và chạy thử thực tế tôi thấy API này có thể lấy được luôn topBidfloorAsk. Hai thông tin này có thể giúp ta lấy ngay được được gia mua tốt nhất và giá bán tốt nhất, kiểm tra xem có sự chênh lệch giá hay không:

Tôi đã viết hàm test để lấy thông tin của “Monadverse: Chapter 1” có địa chỉ 0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e:

async function test() {
    let collectionInfo = await getCollectionInfo("0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e");
    console.log("CollectionInfo:", JSON.stringify(collectionInfo, null, 4));
}
test();

Tôi nhận được thông tin collection như sau:

CollectionInfo: {
    "chainId": 10143,
    "id": "0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
    "slug": null,
    "createdAt": "2025-02-19T13:28:50.671Z",
    "updatedAt": "2025-04-06T13:27:06.081Z",
    "name": "Monadverse: Chapter 1",
    "symbol": null,
    "contractDeployedAt": "2025-02-19T13:23:48.000Z",
    "image": "https://img.reservoir.tools/images/v2/monad-testnet/u1%2F3W9ZeQYBsdTpG%2FLdkUOlukCnCr%2By2AJK%2F96iWbU5iIYM6l2WKnR8S0cR6FF9eDOnt7N6LWmYXo2Yw%2FcGcAZHVpac2HL1y%2BVOt197QyctAE3VZ9H1dHF8yQO6eXwzkMD%2B464FyqcYlb8E8nlbMP7hTAofMTFHC%2FFwGp7Gb9AyrsluW7S26WrmPDpiyvR3Edc93gPl48Ga7FzfivLdxnHqv9yFhguAq5MIaZQt1pyxGAozXjlyufM5zwX24K49P?width=250",
    "banner": null,
    "twitterUrl": null,
    "discordUrl": "https://discord.com/invite/wf29GFzE5Z",
    "externalUrl": "https://monadverse.land/",
    "twitterUsername": "monadverse",
    "openseaVerificationStatus": null,
    "magicedenVerificationStatus": "verified",
    "description": "Monadverse used to be a lively world full of adventures and belonging. Now, it’s in chaos and darkness. A hated, outcast ruler named Mongrod has gained power. But one unlikely hero has been chosen to travel back in time. Can he bring light and rewrite destiny?\n\nOwn this Chapter 1 to be a part of the official Monadverse lore!",
    "metadataDisabled": false,
    "isSpam": false,
    "isNsfw": false,
    "isMinting": false,
    "sampleImages": [
        "https://img.reservoir.tools/images/v2/monad-testnet/i9YO%2F4yHXUdJsWcTqhqvf2ME9ilfqTiN3oLgxc6lg8oJPFyFYK1sI6Rx7ZlHO%2Fp1%2FOSIAsp2ceTM%2BIx1HK5Q%2BpaFxZ43SQF0SSPHGMs%2BC3IBEd7LE6KvBPxo3e3o6uQy.png"
    ],
    "tokenCount": "1",
    "onSaleCount": "1",
    "primaryContract": "0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
    "tokenSetId": "contract:0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
    "creator": null,
    "isSharedContract": false,
    "royalties": null,
    "allRoyalties": {
        "custom": []
    },
    "floorAsk": {
        "id": "0x39ca71d76e5c5841b054eb5ee10dbd41837b680011faf01f089f82a9bf252c53",
        "sourceDomain": "magiceden.io",
        "price": {
            "currency": {
                "contract": "0x0000000000000000000000000000000000000000",
                "name": "Monad",
                "symbol": "MON",
                "decimals": 18
            },
            "amount": {
                "raw": "14200000000000000000",
                "decimal": 14.2,
                "usd": null,
                "native": 14.2
            }
        },
        "maker": "0x8b30badf5cd3d04b5bce33fcd6db887450062cce",
        "validFrom": 1743945011,
        "validUntil": 1746537071,
        "token": {
            "contract": "0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
            "tokenId": "1",
            "name": null,
            "image": "https://img.reservoir.tools/images/v2/monad-testnet/i9YO%2F4yHXUdJsWcTqhqvf2ME9ilfqTiN3oLgxc6lg8oJPFyFYK1sI6Rx7ZlHO%2Fp1%2FOSIAsp2ceTM%2BIx1HK5Q%2BpaFxZ43SQF0SSPHGMs%2BC3J696O563J3CFkiiBCQY4Dh?width=512"
        }
    },
    "topBid": {
        "id": "0x204f68b9cb437b7ef981b165bae79084f35222edf97404c4127e7569a9dd5c8a",
        "sourceDomain": "magiceden.io",
        "price": {
            "currency": {
                "contract": "0x760afe86e5de5fa0ee542fc7b7b713e1c5425701",
                "name": "Wrapped Monad",
                "symbol": "WMON",
                "decimals": 18
            },
            "amount": {
                "raw": "12100000000000000000",
                "decimal": 12.1,
                "usd": null,
                "native": 12.1
            },
            "netAmount": {
                "raw": "11858000000000000000",
                "decimal": 11.858,
                "usd": null,
                "native": 11.858
            }
        },
        "maker": "0x7baae4eab0e08ebb47f0b39acd77e390d4f321cf",
        "validFrom": 1743935953,
        "validUntil": 1743957600
    },
    "rank": {
        "1day": 23,
        "7day": 35,
        "30day": 17,
        "allTime": 26
    },
    "volume": {
        "1day": 5239.09908,
        "7day": 37903.0081,
        "30day": 131277.83051,
        "allTime": 150460.64426
    },
    "volumeChange": {
        "1day": 1.2003304161441648,
        "7day": 1.1192312792416168,
        "30day": 8.357624133388835
    },
    "floorSale": {
        "1day": 4,
        "7day": 7,
        "30day": 2
    },
    "floorSaleChange": {
        "1day": 3.55,
        "7day": 2.0285714285714285,
        "30day": 7.1
    },
    "collectionBidSupported": true,
    "ownerCount": 297375,
    "contractKind": "erc1155",
    "mintedTimestamp": 1739971695,
    "lastMintTimestamp": 1740411724,
    "mintStages": [],
    "supply": "50003",
    "remainingSupply": "50003"
}

Hai thông tin quan trọng nhất chúng ta cần lấy sẽ là topBidfloorAsk:

  • Giá mua sẽ là giá trị lấy trong dữ liệu floorAsk:
    collectionInfo.floorAsk.price.amount.decimal
    Như trên là 12.1 MON
  • Giá bán sẽ là giá trị lấy trong topBid:
    collectionInfo.topBid.price.netAmount.decimal
    Do khi bán xong chúng ta phải trả thêm phí, nên lượng MON thực nhận ở trong trường netAmount. Như trên sẽ là 11.858 MON

Như dữ liệu trên thì không có sự chênh lệch giá. Tôi kiểm tra dữ liệu từ API này khá khớp với dữ liệu hiển thị trên giao diện. Ban đầu tôi sử dụng API trên để kiểm tra sự chênh lệch giá, nhưng sau khi chạy một thời gian thì rất nhiều lệnh mua hay bán bị lỗi, mỗi khi bị lỗi tôi kiểm tra giao diện cũng không thực hiện mua bán được. Sau khi tìm hiểu tôi đã phát hiện ra là floorAsk và topBid không được cập nhật realtime, nhiều khi nó là các order đã được khớp rồi. Chính vì lỗi cập nhật từ API này, dẫn đến UI không mua bán được nên người dùng đành phải đặt lệnh Limit với giá tốt hơn giá thị trường, do đó hay xảy ra chênh lệch giá.

Sau thời gian kiểm tra tôi tìm ra hai API giúp lấy được floorAsktopBid chuẩn hơn và theo thời gian thực. Đó là api: Asks (listings)Bids (offers).

Hàm lấy danh sách những lệnh mua nft (Bids)

Để lấy thông tin này, chúng ta sử dụng api Bids (offers). Chi tiết cài đặt hàm lấy thông tin này như sau:

async function getCollectionBids(collectionId) {
    let url = `/v3/rtp/monad-testnet/orders/bids/v6`;
    let param = {
        collection: collectionId,
        status: "active"
    };
    let data = await _requestData("GET", url, param);
    if (!data || !data.orders) return null;
    return data.orders;
}

Dữ liệu trả về có dạng như sau:

[
    {
        "id": "0x5ebabf796747073eb8e8402ab98f94edd757ff3c60e63e3e1fb27d34ba66d825",
        "kind": "seaport-v1.6",
        "side": "buy",
        "status": "active",
        "tokenSetId": "contract:0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
        "tokenSetSchemaHash": "0xbbda67c48e97b87793e59fdc6587a83f3edefc001d3734ce2827c35ab7f8cd87",
        "contract": "0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
        "contractKind": "erc1155",
        "maker": "0x4e20e84f1133943aa7e0ce6aab2f44b261f42263",
        "taker": "0x0000000000000000000000000000000000000000",
        "price": {
            "currency": {
                "contract": "0x760afe86e5de5fa0ee542fc7b7b713e1c5425701",
                "name": "Wrapped Monad",
                "symbol": "WMON",
                "decimals": 18
            },
            "amount": {
                "raw": "11601000000000000000",
                "decimal": 11.601,
                "usd": null,
                "native": 11.601
            },
            "netAmount": {
                "raw": "11368980000000000000",
                "decimal": 11.36898,
                "usd": null,
                "native": 11.36898
            }
        },
        "validFrom": 1743949847,
        "validUntil": 1744036260,
        "quantityFilled": 0,
        "quantityRemaining": 1,
        "criteria": {
            "kind": "collection",
            "data": {
                "collection": {
                    "id": "0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e"
                }
            }
        },
        "source": {
            "id": "0xa41c2f89d4ab789b298e04216505617328e9ab7a",
            "domain": "magiceden.io",
            "name": "Magic Eden",
            "icon": "https://raw.githubusercontent.com/reservoirprotocol/assets/main/sources/magiceden-logo.svg"
        },
        "feeBps": 200,
        "feeBreakdown": [
            {
                "kind": "marketplace",
                "recipient": "0x6fa303e72bed54f515a513496f922bc331e2f27e",
                "bps": 200
            }
        ],
        "expiration": 1744036260,
        "isReservoir": true,
        "createdAt": "2025-04-06T14:32:12.506Z",
        "updatedAt": "2025-04-06T14:32:12.506Z",
        "originatedAt": null,
        "isNativeOffChainCancellable": true
    },
    ...
]

Lấy danh sách các lệnh bán nft (Asks)

Để lấy thông tin này, chúng ta sử dụng api Asks (listings). Chi tiết cài đặt hàm lấy thông tin này như sau:

async function getCollectionAsks(collectionId) {
    let url = `/v3/rtp/monad-testnet/orders/asks/v5`;
    let param = {
        tokenSetId: "contract:" + collectionId,
        status: "active"
    };
    let data = await _requestData("GET", url, param);
    if (!data || !data.orders) return null;
    return data.orders;
}

Dữ liệu trả về có dạng như sau:

[
    {
        "id": "0x251e4fa808281f17295694a5ab477c45d1ce0a66445038ee41ec83aaaf70603b",
        "kind": "seaport-v1.6",
        "side": "sell",
        "status": "active",
        "tokenSetId": "token:0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e:1",
        "tokenSetSchemaHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "contract": "0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
        "contractKind": "erc1155",
        "maker": "0x4a8352b5ba77a08ed95ce0557b1a5188bf806495",
        "taker": "0x0000000000000000000000000000000000000000",
        "price": {
            "currency": {
                "contract": "0x0000000000000000000000000000000000000000",
                "name": "Monad",
                "symbol": "MON",
                "decimals": 18
            },
            "amount": {
                "raw": "13900000000000000000",
                "decimal": 13.9,
                "usd": null,
                "native": 13.9
            },
            "netAmount": {
                "raw": "13622000000000000000",
                "decimal": 13.622,
                "usd": null,
                "native": 13.622
            }
        },
        "validFrom": 1743950300,
        "validUntil": 1746542359,
        "quantityFilled": 0,
        "quantityRemaining": 1,
        "dynamicPricing": null,
        "criteria": {
            "kind": "token",
            "data": {
                "token": {
                    "tokenId": "1"
                }
            }
        },
        "source": {
            "id": "0xa41c2f89d4ab789b298e04216505617328e9ab7a",
            "domain": "magiceden.io",
            "name": "Magic Eden",
            "icon": "https://raw.githubusercontent.com/reservoirprotocol/assets/main/sources/magiceden-logo.svg",
            "url": "https://magiceden.io/item-details/monad-testnet/0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e/1"
        },
        "feeBps": 200,
        "feeBreakdown": [
            {
                "kind": "marketplace",
                "recipient": "0x6fa303e72bed54f515a513496f922bc331e2f27e",
                "bps": 200
            }
        ],
        "expiration": 1746542359,
        "isReservoir": true,
        "isDynamic": false,
        "createdAt": "2025-04-06T14:39:45.562Z",
        "updatedAt": "2025-04-06T14:39:45.562Z",
        "originatedAt": null,
        "isNativeOffChainCancellable": true
    },
    ...
]

Sinh dữ liệu để phục vụ mua một NFT

Để mua một NFT, trước tiên chúng ta cần phải gọi tới API: Buy tokens (fill listings). API này sẽ trả về đầy đủ dữ liệu để từ đó chúng ta gọi contract để thực hiện lệnh buy. Chúng ta viết hàm getBuyNftOrder() để lấy thông tin theo API này:

async function generateBuyNftOrder(askId, recipent, skipBalanceCheck=false) {
    let params = {
        items: [{
            quantity: 1,
            orderId: askId,
            fillType: "trade",
        }],
        taker: recipent,
        relayer: recipent,
        skipBalanceCheck: skipBalanceCheck
    };
    let endPoint = `/v3/rtp/monad-testnet/execute/buy/v7`;
    let orderInfo = await _requestData("POST", endPoint, params);
    return orderInfo;
}

Khi các bạn gọi thử hàm này với askId lấy từ danh sách asks, bạn sẽ thấy dữ liệu trả về như dưới:

{
    "requestId": "bad01283-7975-49fc-986a-73736793d10d",
    "steps": [
        {
            "id": "sale",
            "action": "Confirm transaction in your wallet",
            "description": "To purchase this item you must confirm the transaction and pay the gas fee",
            "kind": "transaction",
            "items": [
                {
                    "status": "incomplete",
                    "orderIds": [
                        "0xe97e8f1df208f556965a72d1cedb0726137fd751d9422e45d647d4ca61845f1a"
                    ],
                    "data": {
                        "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
                        "to": "0x0000000000000068f116a894984e2db1123eb395",
                        "data": "0xe7acab2400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000600f3d63166f0ca56c3c1a3508fce03ff0cf3fb691e000000000000000000000000000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000dee37c0e3950299a3703cd19d2c7835747b2ac74000000000000000000000000088d937f241702de1d8379e7667826a3bbcb6da3000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000006800ab00000000000000000000000000000000000000000000000000000000006828383e00000000000000000000000000000000000000000000000000000000000000000e1c0c381d4da48b0000000000000000e281867ca191f8a5182933ff4bb14189f3d63166f0ca56c3c1a3508fce03ff0cf3fb691e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000e25c57ff3eea05d0f8be9aaae3f522ddc803ca4e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000edde39165b268000000000000000000000000000000000000000000000000000edde39165b268000000000000000000000000000dee37c0e3950299a3703cd19d2c7835747b2ac7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004dabd3f266e800000000000000000000000000000000000000000000000000004dabd3f266e80000000000000000000000000006fa303e72bed54f515a513496f922bc331e2f27e0000000000000000000000000000000000000000000000000000000000000041790ab17561c94518b5755c5e8b06fa85332cdb450fc24e667f8cb2a916a35b607349e8c1b53fb04416dc8c866b630d1aa2eba3c0ab5fac765878b367b3841d341c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e000000000000000000000000000000000000000000000000006800b479812c4ee5441e628ef501c164aa14b94900ab49e667e4f3133e82555c9610497c661bc8d33146bd42a17422a9cbc61bef3dab952894016aeb7027d08c985cdace00e556ea32f590e887cf112236cd7d33d9ab4fc66851b7bc54149fcf1efbaec701000000000000000000000000000000000000000000000000000000000000000000001d4da48b00000000",
                        "value": "0xf2b8f65581950000"
                    },
                    "check": {
                        "endpoint": "/execute/status/v1",
                        "method": "POST",
                        "body": {
                            "kind": "transaction"
                        }
                    },
                    "gasEstimate": 160000
                }
            ]
        }
    ],
    "errors": [],
    "path": [
        {
            "orderId": "0xe97e8f1df208f556965a72d1cedb0726137fd751d9422e45d647d4ca61845f1a",
            "contract": "0xe25c57ff3eea05d0f8be9aaae3f522ddc803ca4e",
            "tokenId": "1",
            "quantity": 1,
            "source": "magiceden.io",
            "currency": "0x0000000000000000000000000000000000000000",
            "currencySymbol": "MON",
            "currencyDecimals": 18,
            "quote": 17.48999,
            "rawQuote": "17490000000000000000",
            "builtInFees": [
                {
                    "kind": "marketplace",
                    "recipient": "0x6fa303e72bed54f515a513496f922bc331e2f27e",
                    "bps": 200,
                    "amount": 0.3498,
                    "rawAmount": "349800000000000000"
                }
            ],
            "isNativeOffChainCancellable": true,
            "feesOnTop": [],
            "totalPrice": 17.48999,
            "totalRawPrice": "17490000000000000000"
        }
    ],
    "fees": {
        "gas": {
            "currency": {
                "contract": "0x0000000000000000000000000000000000000000",
                "name": "Monad",
                "symbol": "MON",
                "decimals": 18
            },
            "amount": {
                "raw": "8000000000000000",
                "decimal": 0.008,
                "usd": null,
                "native": null
            }
        }
    }
}

Chúng ta thấy mảng step chứa dữ liệu chúng ta cần gửi lên blockchain để tạo giao dịch mua NFT.

Sinh dữ liệu phục vụ bán một NFT

Trước khi thực hiện bán một NFT chúng ta phải gọi tới API: Sell tokens (accept bids). API này sẽ trả về đầy đủ dữ liệu để từ đó chúng ta gọi contract để thực hiện lệnh buy. Chúng ta viết hàm getSellNftOrder() để lấy thông tin theo API này:

async function generateSellNftOrder(bidId, recipent, token, skipBalanceCheck=false) {
    let params = {
        items: [{
            token: token,
            quantity: 1,
            orderId: bidId,
            fillType: "trade",
        }],
        taker: recipent,
        relayer: recipent,
        skipBalanceCheck: skipBalanceCheck
    };
    let endPoint = `/v3/rtp/monad-testnet/execute/sell/v7`;
    let orderInfo = await _requestData("POST", endPoint, params);
    return orderInfo;
}

Định dạng dữ liệu trả về tương tự như hàm generateBuyNftOrder().

Một số hàm cần thiết để tương tác với blockchain

Để có thể thực hiện được mua bán NFT, thì chúng ta cần phải tương tác với blockchain và chúng ta sẽ sử dụng thư viện Web3 để làm điều này.

Trong phần này, tôi viết một số hàm cơ bản cần dùng khi mua bán NFT. Toàn bộ tôi đóng gói trong tệp: web3-utils.js. Bạn cần chú ý là sử dụng web3 phiên bản 1.10.4

Tôi không đi chi tiết code phần này, vì hầu hết mọi người quan tâm bài viết này thì đều phải biết cách sử dụng thư viện web3 rồi. Một số hàm cơ bản cần sử dụng:

  • getWeb3(): Trả về đối tượng web3 sử dụng để tương tác với blockchain Monad Testnet.
  • gasEstimate(): Sử dụng để ước lượng lượng gas tiêu thụ.
  • sendTransaction(): Sử dụng để gửi một giao dịch lên blockchain.
  • waitForTransaction(): Hàm chờ một transaction được đưa vào blockchain.
  • Và một số hàm khác…

Viết bot để kiểm tra cơ hội và thực hiện mua bán khi có chênh lệch giá

Luồng thực hiện của bot

Về cơ bản luồng thực hiện của bot như sau:

  • Bước 1: Lấy danh sách bids và asks
  • Bước 2: Tìm bid tốt nhất (Người mua có giá cao nhất) và ask tốt nhất (Người bán có giá thấp nhất)
  • Bước 3: So sánh giá giữ bid tốt nhất (bestBid) và ask tốt nhất (bestAsk) để tìm kiếm cơ hội chênh lệch giá. Nếu có không có cơ hội thì quay về Bước 1, nếu có cơ hội sang Bước 4.
  • Bước 4: Thực hiện mua NFT theo thông tin trong bestAsk. Thông thường khi mua chúng ta sẽ sử dụng MON để mua.
  • Bước 5: Thực hiện bán NFT vừa mua. Thông thường sau khi bán xong chúng ta thu được WMON.
  • Bước 6: Thực hiện chuyển WMON sang MON
  • Bước 7: Quay lại Bước 1

Quá trình mua bán, chúng ta cần phải có “Private Key“, vì vậy để đảm bảo tính bảo mật chúng ta sẽ lấy “Private Key” từ biến môi trường BOT_PK. Hầu hết các hàm cần tương tác với blockchain đều cần dữ liệu này.

Bây giờ chúng ta sẽ từng bước viết các hàm để hoàn thành đầy đủ luồng ở trên.

Viết hàm chuyển từ WMON sang MON

Nhiệm vụ hàm này sẽ chuyển toàn bộ WMON nếu có sang MON. Luồng thực hiện hàm này như sau:

  • Bước 1: Lấy balance của WMON, nếu giá trị này dương thì thực hiện sang bước 2
  • Bước 2: Tạo thông số giao dịch và tiến hành ước lượng gas tiêu thụ
  • Bước 3: Tạo và gửi giao dịch tới blockchain để thực hiện chuyển đổi từ WMON sang MON. Thực chất của quá trình này là ta gọi hàm withdraw() của contract WMON.

Chi tiết cài đặt như sau:

async function convertWmon2Mon(web3, botPk) {
    try {
        // Bot address
        let botAddr = Web3Utils.privateKeyToAddress(web3, botPk);

        // Get balance of WMON
        let contract = Web3Utils.getErc20Contract(web3, WMON);
        let balance = await contract.methods.balanceOf(botAddr).call();
        console.log("WMON Balance:", balance);
        if (Number(balance)<=0) return false;

        // Gas estimate
        let data = web3.eth.abi.encodeFunctionCall(
            {
                name: "withdraw",
                type: "function",
                inputs: [{
                    type: "uint256",
                    name: "amount"
                }]
            }, [ balance ]
        );
        let opts = {
            from: botAddr,
            chainId: chainId,
            to: WMON,
            data: data
        };
        let gasEstimate = await Web3Utils.gasEstimate(web3, opts);
        console.log("GasEstimate:", opts, gasEstimate);

        // Convert WMON to MON
        opts.gasLimit = Web3Utils.toHex(Web3Utils.addNumber(gasEstimate, 5000));
        let result = await Web3Utils.sendTransaction(web3, botPk, opts);
        if (!result || result.status!=true || !result.transactionHash) return false;
        let txId = result.transactionHash;
        console.log(`Convert ${balance} WMON to MON: ${txId}`);

        // Check transaction
        let receipt = await Web3Utils.waitForTransaction(web3, txId, 5000);
        let ret = (receipt && receipt.status);
        // console.log("Receipt:", receipt);
        console.log(`    Convert ${balance} WMON to MON`, botAddr, (ret?"SUCCESS":"FAIL"));

        return true;
    } catch(ex) {
        console.error(`    Convert WMON to MON`, ex);
    }
    return false;
}

Viết hàm thực hiện mua 1 NFT

Để mua NFT chúng ta cần phải có thông tin của askId, nó xác định chúng ta mua NFT từ lệnh ask nào. Luồng thực hiện như sau:

  • Bước 1: Gọi API của MagicEden để lấy thông tin cho lệnh mua NFT. Một lệnh mua NFT có thể bao gồm 1 hoặc nhiều giao dịch.
  • Bước 2: Từ dữ liệu trả về, ta duyệt mảng thông tin các dữ liệu giao dịch, với mỗi một thông tin giao dịch chúng ta sẽ thực hiện:
    • Bước 2.1: Tạo thông số giao dịch và tiến hành ước lượng gas tiêu thụ
    • Bước 2.2: Tạo và gửi giao dịch tới blockchain để thực hiện giao dịch

Chi tiết cài đặt như sau:

async function buyNft(web3, botPk, askId) {
    try {
        // Bot address
        let botAddr = Web3Utils.privateKeyToAddress(web3, botPk);

        // Buy Order
        let buyOrder = await MagicEden.generateBuyNftOrder(askId, botAddr, skipBalanceCheck);
        // console.log("BuyOrder:", JSON.stringify(buyOrder, null, 4));
        if (!buyOrder) return false;
        if (buyOrder.statusCode) {
            console.error("Error to generate buy order", buyOrder);
            return false;
        }

        // Send transactions to blockchain
        let steps = buyOrder.steps;
        for (let idx=0; idx<steps.length; idx++) {
            let step = steps[idx];
            // console.log("Step", idx, step);
            let items = (step.items??[]);
            for (let idx1=0; idx1<items.length; idx1++) {
                let item = items[idx1];
                // console.log("Item", item);

                // Gas estimate
                let opts = {
                    from: botAddr,
                    chainId: chainId,
                    to: item.data.to,
                    data: item.data.data
                };
                if (item.data.value) {
                    opts.value = Web3Utils.hexaToDecimal(item.data.value);
                }
                let gasEstimate = await Web3Utils.gasEstimate(web3, opts);
                console.log("GasEstimate:", opts, gasEstimate);

                // Send transaction to blockchain
                opts.gasLimit = Web3Utils.toHex(Web3Utils.addNumber(gasEstimate, 10000));
                let result = await Web3Utils.sendTransaction(web3, botPk, opts);
                if (!result || result.status!=true || !result.transactionHash) return false;
                let txId = result.transactionHash;
                console.log(`[BUY] Steps[${idx}] - Items[${idx1}]: TxId: ${txId}`);

                // Check transaction
                let receipt = await Web3Utils.waitForTransaction(web3, txId, 5000);
                // console.log("Receipt:", receipt);
                let ret = (receipt && receipt.status);
                let msg = `Buy NFT at step ${idx} and item ${idx1}: ${ret?"SUCCESS":"FAIL"} (${txId})`;
                console.log(new Date(), msg);

                if (!ret) return false;
            }
        }
        return true;
    } catch(ex) {
        console.log("buyNft() EXCEPTION", ex);
    }
    return false;
}

Viết hàm thực hiện bán 1 NFT

Để bán NFT chúng ta cần phải có thông tin của bidId, nó xác định chúng ta sẽ bán NFT cho người mua nào. Ngoài ra chúng ta cần có tham số để xác định chúng ta sẽ bán NFT nào.

Luồng thực hiện như sau:

  • Bước 1: Gọi API của MagicEden để lấy thông tin cho lệnh bán NFT. Một lệnh bán NFT có thể bao gồm 1 hoặc nhiều giao dịch.
  • Bước 2: Từ dữ liệu trả về, ta duyệt mảng thông tin các dữ liệu giao dịch, với mỗi một thông tin giao dịch chúng ta sẽ thực hiện:
    • Bước 2.1: Tạo thông số giao dịch và tiến hành ước lượng gas tiêu thụ
    • Bước 2.2: Tạo và gửi giao dịch tới blockchain để thực hiện giao dịch

Chi tiết cài đặt như sau:

async function sellNft(web3, botPk, bidId, token) {
    try {
        // Bot address
        let botAddr = Web3Utils.privateKeyToAddress(web3, botPk);

        // Sell order
        let sellOrder = await MagicEden.generateSellNftOrder(bidId, botAddr, token, skipBalanceCheck);
        // console.log("SellOrder:", JSON.stringify(sellOrder, null, 4));
        if (!sellOrder) return false;
        if (sellOrder.statusCode) {
            console.error("Error to generate sell order", sellOrder);
            return false;
        }

        // Send transactions to blockchain
        let steps = sellOrder.steps;
        for (let idx=0; idx<steps.length; idx++) {
            let step = steps[idx];
            // console.log("Step", idx, step);
            let items = (step.items??[]);
            for (let idx1=0; idx1<items.length; idx1++) {
                let item = items[idx1];
                // console.log("Item", item);

                // Gas estimate
                let opts = {
                    from: botAddr,
                    chainId: chainId,
                    to: item.data.to,
                    data: item.data.data
                };
                if (item.data.value) {
                    opts.value = Web3Utils.hexaToDecimal(item.data.value);
                }
                let gasEstimate = await Web3Utils.gasEstimate(web3, opts);
                console.log("GasEstimate:", opts, gasEstimate, token);

                // Generate transaction
                opts.gasLimit = Web3Utils.toHex(Web3Utils.addNumber(gasEstimate, 10000));
                let result = await Web3Utils.sendTransaction(web3, botPk, opts);
                if (!result || result.status!=true || !result.transactionHash) return false;
                let txId = result.transactionHash;
                console.log(`[SELL] Steps[${idx}] - Items[${idx1}]: TxId: ${txId}`);

                // Check transaction
                let receipt = await Web3Utils.waitForTransaction(web3, txId, 5000);
                // console.log("Receipt:", receipt);
                let ret = (receipt && receipt.status);
                let msg = `Sell NFT at step ${idx} and item ${idx1}: ${ret?"SUCCESS":"FAIL"} (${txId})`;
                console.log(new Date(), msg);

                if (!ret) return false;
            }
        }
        return true;
    } catch(ex) {
        console.log("sellNft() EXCEPTION", ex);
    }
    return false;
}

Cài đặt toàn bộ luồng giao dịch chênh lệch giá NFT

Sau khi đã có các hàm cơ bản, chúng ta sẽ cài đặt đầy đủ luồng giao dịch như sau:

async function arbitrageNft(web3) {
    // Get Bid/Ask
    let promises = [
        MagicEden.getCollectionAsks(collectionId),
        MagicEden.getCollectionBids(collectionId)
    ];
    let results = await Promise.all(promises);
    let asks = results[0];
    let bids = results[1];
    if (!asks | !bids) return false;
    if (asks.length==0 || bids.length==0) {
        console.log(new Date(), "No bids or no asks data!");
        return false;
    }

    // Get best ask
    asks = asks.map(item => {
        return { id: item.id, symbol: item.price.currency.symbol, price: item.price.amount.decimal, tokenId: item.criteria.data.token.tokenId }
    });
    asks.sort(function(a, b) { return a.price - b.price; });
    let bestAsk = asks[0];
    console.log(new Date(), "Best Ask:", JSON.stringify(bestAsk));

    // Get best bids
    bids = bids.map(item => {
        return { id: item.id, symbol: item.price.currency.symbol, price: item.price.netAmount.decimal }
    });
    bids.sort(function(a, b) { return b.price - a.price; });
    let bestBid = bids[0];
    console.log(new Date(), "Best Bid:", JSON.stringify(bestBid));

    // Check arbitrage
    let diffPrice = bestBid.price - bestAsk.price;
    console.log(new Date(), "DiffPrice", diffPrice);
    if (diffPrice >= diffPriceMin) {
        console.log(new Date(), "There is an opportunity for arbitrage!");

        // Buy NFT
        if (!await buyNft(web3, botPk, bestAsk.id)) return false;

        // Sell NFT
        let token = `${collectionId}:${bestAsk.tokenId}`
        if (!await sellNft(web3, botPk, bestBid.id, token)) return false;

        // Convert WMON to MON
        if (await convertWmon2Mon(web3, botPk)) {
            let msg = `Arbitrage DONE: buyPrice=${bestAsk.price} - sellPrice=${bestBid.price} => Profit=${diffPrice} MON`;
            console.log(new Date(), msg);
        }

        return true;
    } else {
        console.log(new Date(), "There is no opportunity for arbitrage!");
    }
    return false;
}

Chúng ta viết thêm hàm autoArbitrageNft() để thực hiện lặp lại liên tục luồng ở trên:

async function autoArbitrageNft(web3) {
    try {
        await arbitrageNft(web3);
    } catch(ex) {
        console.error(new Date(), "Error to arbitrage NFT", ex);
    }

    setTimeout(async function() {
        await autoArbitrageNft(web3);
    }, 15000);
}

Đến đây chúng ta đã có cài đặt đầy dủ cho bot. Toàn bộ mã nguồn bạn có thể xem tại: magiceden-trade-nft

Triển khai bot giao dịch chênh lệch giá NFT

Toàn bộ mã nguồn, tôi đã để trên Github tại: magiceden-trade-nft. Bây giờ tôi hướng dẫn các bạn chạy bot này.

Đầu tiên chúng ta cần tải mã nguồn về bằng lệnh và thực hiện cài đặt thư viện:

git clone https://github.com/laptrinhbockchain/magiceden-trade-nft.git
cd magiceden-trade-nft
npm i

Ta tạo tệp .env với nội dung như sau:

BOT_PK = xxxxxxx

Trong đó xxxxxxxPrivate Key của bot dưới dạng mã hexa và không có 0x ở đầu. Và bạn nhớ rằng bạn cũng phải có sẵn một lượng MON cần thiết cho bot đủ để mua 1 NFT.

Sau đó chúng ta sẽ sửa một số biến trong tệp index.js:

const diffPriceMin = 0.0001;
const collectionId = "0x7bbd69551c73d149846df55cd5ff5f0a691bd0f7";

Trong đó:

  • diffPriceMin: Xác định mức giá chênh lệch mà bạn sẽ thực hiện mua bán. Khi chênh lệch giá lớn hơn hoặc bằng giá trị này thì quá trình mua bán được thực hiện.
  • collectionId: Địa chỉ của một NFT Collection mà bạn muốn theo dõi và thực hiện giao dịch

Và giờ chúng ta đánh lệnh sau để chạy bot:

node index.js

Việc còn lại bây giờ là chờ đợi. Và đây là kết quả khi tôi chạy thử với collection 0x7bbd69551c73d149846df55cd5ff5f0a691bd0f7 (Bitcoin to the moon), một NFT tôi tự tạo ra để tiện cho việc test:

Kết quả chạy bot giao dịch chênh lệch giá cho NFT

Bot trên chỉ cài đặt luồng cơ bản nhất, các bạn hãy chỉnh sửa nâng cấp nó để nó hoạt mượt mà với độ trễ thấp hơn.

Thực tế đang chạy một bot khác, cơ bản luồng tương tự như trên, đã kiếm được 1000 MON trong 3 ngày đầu tiên, còn bình thường mỗi ngày cũng được vài chục MON để chơi testnet. Dưới là ảnh giao diện chạy bot của mình:

Thực hiện lệnh mua bằng bot

Thực hiện lệnh bán sau đó bằng bot

Một số kinh nghiệm khi làm việc với MagicEden

API getcollectionsv7 không thể hiện dữ liệu mới nhất

Tôi đã sử dụng hàm getcollectionsv7 để lấy thông tin của một collection, trong đó thông tin quan trọng nhất mà tôi quan tâm là topBidfloorAsk, tôi dùng thông tin này để kiểm tra độ chênh lệch giá mua và bán để từ đó tìm kiếm cơ hội kiếm MON từ sự chênh lệch giá. Nhưng sau thời gian test thử thôi phát hiện ra dữ liệu của API không được cập nhật mới nhất.

Cụ thể thôi theo dõi chênh lệch giá của bộ sưu tập Monadverse: Chapter 5, thì API này trả về thông tin thì giá mua3.2 MONgiá bán đã trừ phí4.41 MON:

Trên giao diện cũng hiện thị tương tự với giá mua là 3.2 MON:

Nếu bạn sử dụng giao diện để mua NFT này thì giao dịch sẽ không thành công và đốt một lượng phí cực lớn:

Sau đó tôi đã sử dụng API để lấy thông tin Asks (listings), thì API này trả về giá bán tốt nhất là 3.8 MON chứ không phải 3.2 MON:

Vì vậy khi mua NFT, mọi người nên chú ý kiểm trâ phí tiêu thụ, nếu phí tiêu thụ lớn thì chắc chắn giao dịch có vấn đề và không nên thực hiện giao dịch. Nếu thực hiện để mất lượng MON này thì rất phí vì kiếm được MON cũng không hề dễ dàng.

Cẩn thận với các giao dịch lỗi trên MONAD TESTNET

Tôi đã bị khá nhiều lần liên quan đến lỗi này, các giao dịch FAIL trên MONAD TESTNET, Metamask không estimate được nên để lượng GasLimit rất lớn là 52,500,000. Và trên Monad Testnet, nếu giao dịch FAIL thì bạn đặt GasLimit bao nhiêu đi nữa nó cũng sẽ tiêu hết của bạn. Và cuối cùng bạn phải trả phí giao dịch rất lớn, mất nhiều MON cho 1 giao dịch FAIL trong khi kiếm được MON rất khó.

Giao dịch ở phần trên là một ví dụ điển hình, hay gặp trên MagicEden, thường ở các thao tác MUA, BÁNĐÚC NFT. Đây là ví dụ về giao dịch ĐÚC (MINT) một NFT, giao dịch LỖI và tiêu mất 2.73 MON: 0x8352d3375fb1a6483c699c41eb9bd839ed70d32d66e8ac5d719fd8c42a874307

Cẩn thận với việc chênh lệch giá trên giao diện MagicEden

Một số NFT hot trên Monad Testnet rất hay thấy cơ hội chênh lệch giá, cụ thể như ví dụ với trường hợp Monadverse: Chapter 5 này:

Rõ ràng nhìn vào giao dịch này ta thấy có cơ hội ăn chênh lệch giá trong trường hợp này:

  • Thực hiện mua NFT với giá 3.2 MON
  • Sau đó bán ngay NFT với giá 4.6 MON => Trừ đi 2% phí Marketplace thì ta được 4.5 MON
  • Như vậy ta thu được (4.5 – 3.2) = 1.3 MON => Nhìn khá đơn giản

Nhưng thực tế, đời không như mơ, phải chạy thực tế bạn mới thấy được:

  • Case 1: Giao diện hiển thị không đúng, như đã nói trong phần trước, bạn nhanh tay nhấn mua vừa không có được NFT lại mất rất nhiều MON vô nghĩa.
  • Case 2: Bạn vừa thực hiện mua xong thì đối phương hủy lệnh bán, giá bán mới nhất lúc này thấp hơn cả giá bạn mua => Tôi nghĩ đây là một cách để đối thủ bán NFT đi => Họ đặt lệnh bán NFT với mức giá tương đối, sau đó đặt lệnh mua với giá cao hẳn giá bán, và khi phát hiện NFT được bán họ sẽ hủy luôn lệnh mua.
  • Case 3: Tôi gặp nhiều trường hợp mà lệnh bán giá rất cao nhưng không thể bán được, cứ bán là thông báo lỗi. Đây cũng là một thủ thuật để đối thủ bán NFT của họ mà không sợ rủi ro bị mua lại. Tôi chưa rõ chính xác đối thủ đặt lệnh Bid kiểu gì mà không thể bán cho họ được nhưng theo tôi có khả năng như sau: Đối phương có cách nào đó thiết lập địa chỉ nhận NFT là một contract hoặc đối phương tạo lệnh Bids này từ 1 contract, và contract đó không cài đặt hàm onERC1155Received() nên không thể nhận được NFT theo chuẩn ERC1155 (Xem thêm tại fulfilladvancedorder) => Đây cũng chỉ là dự đoán và tôi chưa thể kiểm chứng được.

Bài viết này có hữu ích với bạn?

Kích vào một biểu tượng ngôi sao để đánh giá bài viết!

Xếp hạng trung bình 5 / 5. Số phiếu: 68

Bài viết chưa có đánh giá! Hãy là người đầu tiên đánh giá bài viết này.

Trả lời

Giao diện bởi Anders Norén