NFT trong Game (Claynosaurz)
Thiết kế backend để gắn một NFT Solana với một character trong game, lấy luật class selection của Claynosaurz làm ví dụ: liên kết ví bằng chữ ký, xác minh sở hữu qua DAS API, character gắn với mint, chọn class nguyên tử và đồng bộ khi NFT đổi chủ.
- Crypto
- Backend
- Architecture
Claynosaurz là bộ sưu tập khoảng 10.000 NFT khủng long 3D trên Solana, ra mắt tháng 11/2022, và đã được Gameloft công bố làm thành game mobile. Câu hỏi kỹ thuật thú vị nhất của một game như vậy không nằm ở blockchain, mà ở chỗ nối hai thế giới: người chơi sở hữu một NFT trong ví, còn game cần một character có class, chỉ số, tiến trình và lịch sử trận đấu.
Bài này là ghi chép về cách mình thiết kế phần "map NFT với 1 character" theo hướng một kiến trúc tham khảo. Ví dụ lấy từ luật class selection công khai của Claynosaurz; chi tiết triển khai bên dưới là thiết kế mình đề xuất, không phải mô tả hệ thống production của bất kỳ công ty nào.
Bài toán: class selection của Claynosaurz#
Theo tài liệu chính thức, mỗi Clayno ban đầu là một nhân vật "trần", chưa có vai trò. Người sở hữu chọn class cho nó:
- Mỗi loài chỉ được chọn 1 trong 3 class của loài đó (concept art có Defender, Warrior, Mender, Mystic).
- Cần nguyên liệu: 3 Clay đúng màu với class và 1 lượt dùng Claymaker. Nguyên liệu bị đốt trong quá trình chọn.
- Lựa chọn là vĩnh viễn: "class selection is binding".
Từ ba luật này đã thấy đủ các yêu cầu khó của backend: phải chắc người gọi thật sự sở hữu NFT, phải xác minh nguyên liệu đã bị đốt, phải đảm bảo một NFT không bao giờ có hai class, và character phải đi theo NFT khi NFT đổi chủ.
Kiến trúc tổng quan#
Có bốn mảnh ghép:
- Liên kết ví với tài khoản game bằng chữ ký.
- Xác minh sở hữu NFT qua RPC hoặc DAS API, có cache.
- Character record gắn với địa chỉ mint của NFT, không gắn với người chơi.
- Đồng bộ chuyển nhượng: khi NFT đổi ví, character đổi chủ theo.
1. Liên kết ví bằng chữ ký#
Client không gửi "ví của tôi là X". Server phát một nonce ngắn hạn, người chơi ký bằng ví, server kiểm tra chữ ký ed25519 rồi mới ghi liên kết.
Message nên chứa cả playerId và nonce, để chữ ký không thể bị dùng lại cho tài khoản khác hay lần sau.
2. Xác minh sở hữu#
Với Solana, cách gọn nhất là DAS API (Digital Asset Standard) mà các RPC provider hỗ trợ. Một lần gọi getAsset trả về chủ sở hữu, collection và metadata, kể cả với compressed NFT.
Hai lưu ý:
- Luôn kiểm tra collection đã verify, không dựa vào tên hay symbol. Bất kỳ ai cũng mint được một NFT tên "Claynosaurz".
- Cache có thời hạn: lưu kết quả ownership vào Redis khoảng vài phút để danh sách nhân vật load nhanh, nhưng kiểm tra lại không qua cache trước các hành động quan trọng như chọn class, vào trận xếp hạng hay nhận thưởng.
3. Character gắn với NFT, không gắn với người chơi#
Quyết định thiết kế quan trọng nhất: khoá chính của character là địa chỉ mint. Người chơi chỉ là chủ hiện tại.
Khi NFT đổi chủ, cần quyết định cái gì đi theo NFT và cái gì ở lại với tài khoản:
| Đi theo NFT (character) | Ở lại với tài khoản |
|---|---|
| Class, level, chỉ số, trang bị gắn với nhân vật | Bạn bè, clan, lịch sử chat |
| Thành tích của chính nhân vật đó | Tiền tệ trong game, vật phẩm chung, rank của người chơi |
Chia như vậy thì NFT mang giá trị thật khi giao dịch (một Clayno đã có class và level cao đáng giá hơn), mà người bán không mất các thứ thuộc về chính họ.
4. Chọn class: nguyên tử và chỉ một lần#
Class selection có ba điều kiện phải cùng đúng: còn sở hữu NFT, nguyên liệu đã bị đốt, và NFT chưa có class. Cách an toàn là để giao dịch đốt diễn ra trên chain trước, rồi server xác minh giao dịch đó và ghi class bằng một câu update có điều kiện.
- assertBurnTx đọc giao dịch đã confirm, kiểm tra người ký là chủ NFT và đúng loại, đúng số lượng token bị đốt.
- Câu update có điều kiện className: { $exists: false } đảm bảo hai request đồng thời không thể ghi hai class.
- Bảng burnReceipts với khoá là chữ ký giao dịch chặn việc dùng lại một lần đốt cho NFT khác.
5. Đồng bộ khi NFT đổi chủ#
Không thể chờ người chơi đăng nhập mới phát hiện NFT đã bị bán. Kết hợp hai cơ chế:
- Webhook từ RPC provider cho các giao dịch chuyển nhượng trong collection: nhận event, cập nhật ownerWallet, gỡ character khỏi đội hình và hàng chờ ghép trận của chủ cũ.
- Reconcile định kỳ: một job quét các character có ownershipCheckedAt cũ, gọi lại DAS theo lô để bắt các event webhook bị lỡ.
Event webhook phải xử lý idempotent theo chữ ký giao dịch, vì provider có thể gửi lặp.
Những điều mình rút ra#
- Tách rõ hai nguồn sự thật. Chain quyết định ai sở hữu, server quyết định nhân vật mạnh cỡ nào. Trộn hai thứ này là nguồn của hầu hết bug và lỗ hổng.
- Không tin client. Ví, metadata, kết quả trận: mọi thứ client gửi lên đều phải xác minh lại ở server.
- Cache cho trải nghiệm, kiểm tra lại cho hành động có giá trị.
- Thiết kế cho đổi chủ ngay từ đầu. Gắn tiến trình vào mint thay vì vào người chơi là quyết định khó đảo ngược về sau.
Tham khảo#
Enjoyed this? Get the next one by email.
Keep reading
Sự thật thú vị: Không phải code đẹp mới là code tốt
Làm việc trong ngành phát triển phần mềm được một thời gian, tôi nhận ra một điều thú vị: không phải lúc nào code đẹp cũng đồng nghĩa với thành công về mặt kinh doanh. Hãy cùng tôi đi sâu vào một vài ví dụ điển hình và bài học từ thực tế.
Gaming Industry from the Inside: My Gameloft Experience
After facing challenges in job hunting and going through a rigorous 5-round interview process at Gameloft, I found myself in a professional environment that values work-life balance, documentation, and team bonding. The experience at Gameloft, from the welcoming first day to memorable team trips to Vinh Hy and Cam Ranh, has shown me that a workplace can be more than just about work - it can be a place where lasting relationships are built and professional growth is nurtured.