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.
Mục lục
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 Paradigm và DragonFly, 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:
- Monadverse: Chapter 1
- Monadverse: Chapter 2
- Monadverse: Chapter 3
- Monadverse: Chapter 4
- Monadverse Chapter V: The Heroes Assemble
- Monadverse: Chapter 6
- Monadverse: Chapter 7
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.
- OPEN EDITION CHOGSTAR: Mint on Monad Testnet => Mua bán trên MagicEden
- Lil Chogstars Testnet Starlist: Bộ này đang cho mint nhưng chỉ với 120 ví trong Whitelist https://magiceden.io/launchpad/monad-testnet/lil-chogstars => Mua bán trên MagicEden
- 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 topBid và floorAsk. 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à topBid và floorAsk:
- 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 floorAsk và topBid chuẩn hơn và theo thời gian thực. Đó là api: Asks (listings) và 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 đó xxxxxxx là Private 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:

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:


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à topBid và floorAsk, 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á mua là 3.2 MON và giá bán đã trừ phí là 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 và ĐÚ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.
Trả lời