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 DApp

Bắt đầu lập trình tương tác với blockchain của Cardano

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

Sự khác biệt giữa Ethereum và Cardano

Nếu bạn đã từng là nhà phát triển trên mạng lưới Ethereum thì việc chuyển sang Cardano, bạn sẽ phải thay đổi hoàn toàn tư duy kiến trúc và bạn cũng cần phải học ngôn ngữ mới.

Dưới đây là 4 sự khác biệt cốt lõi mà mọi lập trình viên cần nắm vững:

Mô hình Trạng thái: Global State vs. Local State

Đây là sự khác biệt quan trọng nhất, ảnh hưởng đến mọi dòng code bạn viết.

  • Ethereum (Account-based): Hoạt động giống như một ngân hàng trung tâm. Một Smart Contract có một “trạng thái toàn cục” (Global State) mà bất kỳ ai cũng có thể gọi hàm để thay đổi. Điều này giúp việc kết hợp các hợp đồng rất dễ dàng, nhưng lại dễ dẫn đến lỗi bảo mật (như Reentrancy attack) vì trạng thái có thể bị thay đổi bất ngờ trong khi giao dịch đang thực hiện.
  • Cardano (eUTXO): Hoạt động giống như tiền mặt (tờ tiền/output). Trạng thái không nằm tập trung ở Contract mà nằm phân tán trong các UTXO. Bạn không “gọi một hàm” để sửa dữ liệu trên chuỗi; bạn “chi tiêu” một output cũ để tạo ra output mới. Điều này mang lại tính Deterministic (Xác định): Bạn có thể biết chính xác giao dịch sẽ thành công hay thất bại và phí là bao nhiêu ngay tại Local (off-chain) trước khi gửi lên mạng.

Ngôn ngữ và Hệ sinh thái Lập trình

  • Ethereum (Solidity):
    • Ngôn ngữ hướng đối tượng, cú pháp giống JavaScript, rất dễ tiếp cận nhưng cũng “dễ sai”.
    • Công cụ (Tooling) cực kỳ mạnh mẽ như Hardhat, Foundry, Remix.
  • Cardano (Haskell/Aiken):
    • Plutus: Dựa trên Haskell (lập trình hàm – functional programming). Nó đòi hỏi tư duy logic toán học chặt chẽ, giúp giảm thiểu lỗi runtime nhưng có rào cản gia nhập cao.
    • Aiken: Một ngôn ngữ mới (đang cực hot năm 2026) được thiết kế riêng cho Cardano. Nó có cú pháp hiện đại, dễ học hơn Plutus nhiều và tốc độ biên dịch cực nhanh, giúp tối ưu hóa kích thước script (script size) – một yếu tố sống còn trên Cardano.

Xử lý song song (Concurrency) và xử lý tuần tự (Sequential)

  • Ethereum:
    • Xử lý tuần tự.
    • Nếu 1.000 người cùng tương tác với một Pool, họ sẽ xếp hàng và người trả gas cao hơn sẽ đi trước.
  • Cardano:
    • Do mô hình eUTXO, một UTXO chỉ có thể bị chi tiêu một lần trong một block. Nếu 1.000 người cùng muốn chi tiêu 1 UTXO (ví dụ: một Liquidity Pool đơn giản), 999 người sẽ thất bại.
    • Giải pháp cho Dev Cardano: Bạn phải học các kỹ thuật như Batching (gom nhiều yêu cầu vào một transaction), Split UTXO, hoặc sử dụng các giải pháp Layer 2 như Hydra. Chú ý: Hydra là L2 của Cardana, chứ không phải Hydra Chain nhé.

Chi phí và Gas Fees

  • Ethereum: Phí Gas biến động kinh khủng theo nhu cầu mạng. Transaction có thể bị “fail” giữa chừng nhưng bạn vẫn mất phí.
  • Cardano: Phí giao dịch được tính dựa trên kích thước của transaction (bytes) và tài nguyên tính toán (ExUnits). Đặc biệt, nếu giao dịch thất bại ở giai đoạn kiểm tra off-chain, bạn không mất một đồng phí nào.

Lời khuyên: Nếu bạn muốn xây dựng nhanh, có sẵn nhiều thư viện mẫu, hãy chọn Ethereum. Nếu bạn muốn xây dựng các hệ thống tài chính yêu cầu độ an toàn cực cao, phí dự đoán được và tính toán song song, Cardano (với Aiken) là hướng đi đầy triển vọng hiện nay.

Sự khác biệt trong Smart Contract giữa Ethereum và Cardano

Bây giờ chúng ta sẽ viết một Smart Contract với nhiệm vụ như sau:
Khóa một số tiền (ETH/ADA), ai biết được “mật mã” (Secret Phrase) thì mới có thể rút được tiền đó ra.

Trên Ethereum

Trên Ethereum chúng ta sử dụng Solidity để viết Contract với tư duy như sau:
Ta viết một hàm, hàm đó kiểm tra dữ liệu gửi lên và thực hiện chuyển tiền từ tài khoản của Hợp động sang tài khoản của tôi.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract GuessingGame {
    // Trạng thái lưu trữ trực tiếp trên Contract
    bytes32 public secretHash;

    constructor(bytes32 _secretHash) {
        secretHash = _secretHash;
    }

    // Hàm thay đổi trạng thái và gửi tiền
    function takeMoney(string memory _guess) public {
        require(keccak256(abi.encodePacked(_guess)) == secretHash, "Sai mat ma!");
        
        // Gửi toàn bộ số dư của hợp đồng cho người gọi
        payable(msg.sender).transfer(address(this).balance);
    }
}

Trên Cardano

Trên Cardano chúng ta sử dụng Aiken để viết Contract với tư duy như sau:
Tôi tạo một giao dịch tiêu thụ cái UTXO đang nằm ở địa chỉ Script này. Để làm được việc đó, tôi phải cung cấp một cái Redeemer (mật mã) sao cho khi chạy Script, kết quả trả về là True.

use aiken/hash.{Blake2b_224, Hash}
use aiken/transaction.{Transaction, ScriptContext}

// Định nghĩa kiểu dữ liệu cho Datum (Dữ liệu đính kèm với UTXO)
type Datum {
  secret_hash: Hash<Blake2b_224, ByteArray>
}

// Định nghĩa kiểu dữ liệu cho Redeemer (Dữ liệu người dùng gửi lên để mở khóa)
type Redeemer {
  guess: ByteArray
}

validator {
  fn spend(datum: Datum, redeemer: Redeemer, _context: ScriptContext) -> Bool {
    // Kiểm tra xem mã hash của từ khóa đoán có khớp với hash trong Datum không
    let guess_hash = blake2b_224(redeemer.guess)
    
    // Trả về True nếu khớp, False nếu sai (giao dịch sẽ thất bại)
    guess_hash == datum.secret_hash
  }
}

Điểm khác biệt lớn dưới góc độ của Dev

  1. Dữ liệu nằm ở đâu?
    • Solidity: secretHash nằm trong Contract.
    • Aiken: secret_hash nằm trong chính cái UTXO đang chứa tiền (gọi là Datum). Mỗi UTXO có thể có một mật mã khác nhau dù dùng chung một Script.
  2. Cách tiếp cận logic:
    • Solidity (Imperative): Làm cái này, rồi làm cái kia, nếu sai thì revert.
    • Aiken (Declarative/Functional): Định nghĩa một điều kiện logic (Predicate). Script chỉ đóng vai trò là một “người gác cổng” trả lời Đúng hoặc Sai.
  3. Tối ưu hóa:
    • Với mã Aiken trên, khi biên dịch ra mã máy (Plutus Core), nó cực kỳ nhẹ. Điều này giúp bạn nhồi nhét được nhiều logic hơn vào một giao dịch mà không lo chạm ngưỡng giới hạn kích thước (block size limit) của Cardano.
  4. Kiểm thử (Testing):
    • Với Aiken, bạn có thể viết Unit Test ngay trong file code và chạy:
      aiken check
      Bạn không cần giả lập cả một blockchain nặng nề chỉ để test một điều kiện logic đơn giản.

Như vậy, Cardano tiếp cận dựa trên “Điều kiện mở khóa” còn Ethereum thì tiếp cận theo hướng “Thực thi hàm”.

Token trên Cardano

Trong hệ sinh thái Cardano, token không được quản lý bằng địa chỉ hợp đồng (Contract Address) như Ethereum, mà dựa trên cấu trúc Asset ID.

Cấu trúc Asset ID (Định danh duy nhất)

Một token được xác định bằng sự kết hợp giữa:

  • Policy ID: ID của bộ quy tắc đúc (minting script). Nó là một chuỗi Hex dài 56 ký tự.
  • Asset Name (Hex): Tên của token được chuyển đổi sang định dạng Hex.

Công thức: AssetID = PolicyID + AssetNameHex

Ví dụ với token NIGHT:

  • Policy ID: 0691b2fecca1ac4f53cb6dfb00b7013e561d1f34403b957cbb5af1fa
  • Token Name: NIGHT (Dạng hex: 4e49474854)
  • Full Asset ID: 0691b2fecca1ac4f53cb6dfb00b7013e561d1f34403b957cbb5af1fa4e49474854

Các thông số kỹ thuật của một token

Khi lập trình, bạn cần quan tâm một số thông tin của một token như sau:

  • AssetID: Như đã nói ở trên
  • Decimals: Số thập phân => Xác định mức phân chia nhỏ nhất của 1 token
  • Total Supply: Tổng cung để tính toán vốn hóa
  • Fingerprint: Định dạng rút gọn để người dùng dễ tra cứu trên Explorer, thường bắt đầu bằng asset1…
  • Script Type: Chính là “bản quy tắc” quy định việc đúc (minting) hoặc chi tiêu (spending) của một token. Có hai loại:
    • Native Script: Đây là loại script đơn giản, hỗ trợ từ giai đoạn Shelley, được tích hợp trực tiếp vào sổ cái của Cardano mà không cần máy ảo phức tạp. Có các đặc điểm sau:
      • Cấu trúc: Thường là các logic logic đơn giản như:
        • Sig: Phải có chữ ký của người A mới được đúc.
        • All/Any: Cần chữ ký của tất cả hoặc một trong số những người trong danh sách.
        • Timelock: Chỉ được đúc trước hoặc sau một thời điểm nhất định (Slot).
      • Đặc điểm: Không có trạng thái (stateless), phí giao dịch rẻ và cực kỳ an toàn vì không có lỗi logic lập trình.
      • Ứng dụng: Hầu hết các NFT đời đầu và các token đơn giản thường dùng loại này.
      • Tính minh bạch: Nếu một Native Script có quy tắc “không cho phép đúc thêm sau slot X”, thì token đó trở thành hard-capped vĩnh viễn, không ai (kể cả chủ dự án) có thể tạo thêm.
    • Plutus Script: Hỗ trợ từ giai đoạn Goguen. Đây là các kịch bản lập trình (Smart Contract) thực thụ dựa trên ngôn ngữ Haskell, chạy trên máy ảo Plutus (EUTXO model).
      • Cấu trúc: Chứa các hàm logic phức tạp (Validator). Nó có thể kiểm tra các điều kiện on-chain, đọc dữ liệu từ DatumRedeemer.
      • Đặc điểm: Cực kỳ linh hoạt. Nó có thể tạo ra các cơ chế như:
        • Burn-to-mint: Đốt token A để lấy token B.
        • Dynamic Supply: Cung tiền thay đổi dựa trên giá oracle hoặc các biến số kinh tế khác.
      • Ứng dụng: Các token của các giao thức DeFi thường sử dụng Plutus Script để xử lý các logic nghiệp vụ phức tạp.
      • Rủi ro: Vì là code do con người viết, Plutus Script có thể có lỗi (bugs) hoặc “cửa sau” (backdoor) nếu không được kiểm toán kỹ càng.

Để xem Script của một Token bạn làm như sau (Sử dụng token NIGHT làm ví dụ):
– Đầu tiên, trên Cardano Explorer bạn vào trang thông tin token NIGHT
– Trong vùng Policy Id, kích vào link ứng với Policy Id của token NIGHT => Bạn sẽ thấy giao diện Policy Details
– Trong giao diện Policy Details, trong vùng có chữ Script, kích vào chữ View ngay bên dưới, bạn sẽ thấy giao diện Script Details:

  • Nếu bạn thấy một đoạn mã JSON ngắn gọn với các từ khóa all, before, sig => Đó là Native Script.
  • Nếu bạn thấy dòng chữ Plutus V1, Plutus V2 hoặc Plutus V3 kèm theo một mã băm (Script Hash) phức tạp => Đó là Smart Contract.
Thông tin Script của một Token

Cách lấy thông tin token bằng Code (Node.js)

Nếu bạn không muốn cài SDK nặng nề, hãy dùng axios để gọi trực tiếp vào hệ thống dữ liệu của Cardano qua Koios (một trong những API miễn phí và mạnh mẽ nhất hiện nay). Code ví dụ như sau:

const axios = require('axios');

async function showTokenInfo(policyId, tokenNameHex) {
    try {
        let link = `https://api.koios.rest/api/v1/asset_info`;
        let params = {
            _asset_list: [[policyId, tokenNameHex]]
        };
        let resp = await axios.post(link, params);
        let data = resp.data;
        console.log("Resp", JSON.stringify(data, null, 4));
    } catch(ex) {
        console.error("Error to showTokenInfo()", addr, ex);
    }
}

async function main() {
    await showTokenInfo("0691b2fecca1ac4f53cb6dfb00b7013e561d1f34403b957cbb5af1fa", "4e49474854");
}
main();

Tạo một ví trên Cardano

Phân biệt các loại địa chỉ khác nhau trên cùng một ví của Cardano

  • Trên Cardano, một ví có nhiều loại địa chỉ khác nhau, đây là một đặc sản của Cardano, phản ánh sự tách biệt rõ rệt giữa quyền sở hữu tài sảnquyền tham gia quản trị/ủy thác. Với Developer khi lập trình, chúng ta cần chú ý:
  • Nếu bạn làm dApp cho người dùng: Hãy luôn lấy baseAddressBech32. Đây là địa chỉ mà họ thấy trên ví của họ.
  • Nếu bạn làm hệ thống trả thưởng tự động hoặc Bot: Có thể cân nhắc dùng enterpriseAddressBech32 để quản lý quỹ riêng biệt, không cần quan tâm đến việc staking.
  • Sự khác biệt giữa định dạng thường và Bech32:
    • baseAddress: Thường là đối tượng (Object) hoặc mã Hex dùng cho logic tính toán sâu.
    • baseAddressBech32: Là chuỗi ký tự dễ đọc (addr1...) dùng để hiển thị hoặc copy/paste.

Chi tiết hơn về các loại địa chỉ ví trên Cardano như sau:

Base Address (Địa chỉ cơ bản)

  • Cấu trúc: Kết hợp giữa Payment Key (Khóa Thanh toán để tiêu tiền) và Stake Key (Khóa Staking để ủy thác/staking).
  • Mục đích: Đây là loại địa chỉ phổ biến nhất mà người dùng cuối (như ví Eternl, Lace và Yoroi) sử dụng.
  • Tại sao cần: Khi bạn giữ ADA trong địa chỉ này, tiền của bạn được bảo mật bằng Payment Key, nhưng quyền nhận thưởng staking được liên kết với Stake Key của bạn.

Enterprise Address (Địa chỉ doanh nghiệp)

  • Cấu trúc: Chỉ có Payment Key, không có Stake Key.
  • Mục đích: Thường dùng bởi các sàn giao dịch (CEX) hoặc các ứng dụng Backend/Bot.
  • Tại sao cần: Các tổ chức lớn không muốn số dư của khách hàng bị tự động tính vào quyền biểu quyết hoặc quyền ủy thác của riêng sàn đó (để tránh tập trung quyền lực hoặc vì lý do kế toán). Nó cũng giúp giao dịch nhẹ hơn một chút vì không phải mang theo phần dữ liệu của Stake Key.

Reward Address (Địa chỉ phần thưởng)

  • Cấu trúc: Một địa chỉ đặc biệt bắt đầu bằng stake1
  • Mục đích: Đây không phải địa chỉ để bạn nhận tiền từ người khác gửi vào, mà là nơi mạng lưới Cardano “trả lương” (staking rewards) cho bạn.
  • Tại sao cần: Tiền thưởng staking không tự động nhập vào số dư khả dụng ngay lập tức mà nằm ở “tài khoản” này. Bạn cần một giao dịch rút (withdraw) để chuyển tiền từ Reward Address sang Base Address nếu muốn chi tiêu.

DRep (Decentralized Representative) – Kỷ nguyên Voltaire

Đây là phần mới nhất trong các thuộc tính mà bạn thấy (pubDRepKey, dRepIDBech32, dRepIDCip105).

  • Mục đích: Phục vụ cho cơ chế quản trị phi tập trung (CIP-1694).
  • Tại sao cần: Khi Cardano chuyển sang giai đoạn Voltaire, người giữ ADA có quyền bầu chọn các Đại diện (DRep) để quyết định các thay đổi của hệ thống. Các ID này định danh bạn (hoặc người bạn ủy quyền) trong hệ thống biểu quyết on-chain.

Định dạng Bech32

Trong hệ sinh thái Cardano, Bech32 được dùng cho hầu hết các thực thể quan trọng như địa chỉ ví (addr1…), khóa bảo mật (xprv…), hay mã nhận diện Stake Pool (pool1…). Vậy Bech32 là gi?

Bech32 là một tiêu chuẩn mã hóa dữ liệu giúp biến các chuỗi dữ liệu nhị phân hoặc Hex phức tạp thành những xâu ký tự mà con người có thể đọc, sao chép và kiểm tra lỗi một cách an toàn nhất.

Cấu trúc một chuỗi Bech32 như sau:

  • Human-Readable Part (HRP): Tiền tố xác định loại dữ liệu.
    • addr: Địa chỉ ví Mainnet.
    • addr_test: Địa chỉ ví Testnet.
    • xprv: Extended Private Key.
    • pool: Stake pool
  • Dấu phân cách (Separator): Luôn luôn là ký tự số 1. Nó giúp máy tính biết đâu là phần tiền tố và đâu là phần dữ liệu đã mã hóa.
  • Data Part (Mã hóa Base32): Đây là phần dữ liệu chính, sử dụng bảng chữ cái gồm 32 ký tự (không bao gồm các ký tự dễ gây nhầm lẫn). 6 ký tự cuối cùng trong phần này là Checksum (mã kiểm tra lỗi).

Tại sao lại dùng Bech32 thay vì Hex (Base16) hay Base64?
Bởi vì Bech32 giải quyết được 3 vấn đề “nhức nhối” sau:

  • Chống nhầm lẫn (Visual Ambiguity): Bảng chữ cái của Bech32 loại bỏ hoàn toàn các ký tự dễ nhìn nhầm như:
    • 1, i, l, I (số 1, chữ i, chữ l)
    • 0, o, O (số 0, chữ o)
  • Khả năng phát hiện lỗi (Error Detection): Đây là tính năng “ăn tiền” nhất. Bech32 sử dụng thuật toán kiểm tra mã lỗi BCH.
    • Nếu bạn vô tình gõ sai hoặc thiếu 1-2 ký tự, thuật toán Checksum ở cuối chuỗi sẽ không khớp.
    • Các thư viện như MeshJS sẽ báo lỗi ngay lập tức khi bạn gọi init() hoặc from_bech32(), ngăn chặn việc gửi tiền vào một địa chỉ không tồn tại.
  • Tối ưu cho QR Code: Dữ liệu Bech32 chỉ bao gồm các chữ cái viết thường và số, giúp các mã QR Code được tạo ra có mật độ điểm ảnh thấp hơn, dễ quét hơn trong điều kiện ánh sáng yếu so với Base64 (vốn phân biệt chữ hoa/thường).

Trên NodeJs bạn có thể sử dụng thư viện bech32, một thư viện siêu nhẹ giúp mã hóa và giải mã theo chuẩn này.

Tạo/Khôi phục ví với cụm Seed Phrase trên Cardano

Tạo ví với cụm Seed Phrase

Chúng ta sử dụng thư viện @meshsdk/core trên NodeJs để tạo cụm Seed Phrase và hiển thị các loại địa chỉ tương ứng. Hãy nhớ bảo mật TUYỆT ĐỐI với cụm từ Seed Phrase.

const MeshWallet = require('@meshsdk/core').MeshWallet;

async function createCardanoWallet() {
    // Generate Seed phrase with 24 words
    const mnemonic = MeshWallet.brew(false);
    console.log("Mnemonic:", mnemonic.join(' '));

    // Init Wallet from Mnemonic
    // networkId=0 for Testnet (Preprod/Preview)
    // networkId=1 for Mainnet
    const wallet = new MeshWallet({
        networkId: 1,
        key: {
            type: 'mnemonic',
            words: mnemonic,
        },
    });
    await wallet.init();

    // Show addresses
    const addressInfo = wallet.getAddresses();
    console.log("Addresses:");
    console.log("    Base Address      :", addressInfo.baseAddressBech32);
    console.log("    Enterprise Address:", addressInfo.enterpriseAddressBech32);
    console.log("    Reward Address    :", addressInfo.rewardAddressBech32);
    console.log("    DRep Address      :", addressInfo.dRepIDBech32);
}

createCardanoWallet().catch(console.error);

Cụm Seed Phrase này trên Cardano sinh ra theo chuẩn BIP32 nên bạn có thể sử dụng thư viện BIP32 để tạo.

Khôi phục ví từ cụm Seed Phrase

Bây giờ chúng ta sẽ viết code để khôi phục ví từ cụm Seed Phrase, để tiện chúng ta sẽ yêu cầu nhập cụm Seed Phrase này:

const readline = require('node:readline');
const MeshWallet = require('@meshsdk/core').MeshWallet;

async function restoreCardanoWallet(seedPhrase) {
    // Convert Seed Phrase to array
    const mnemonic = seedPhrase.split(" ");

    // Init Wallet from Mnemonic
    // networkId=0 for Testnet (Preprod/Preview)
    // networkId=1 for Mainnet
    const wallet = new MeshWallet({
        networkId: 1,
        key: {
            type: 'mnemonic',
            words: mnemonic,
        },
    });
    await wallet.init();

    // Show addresses
    const addressInfo = wallet.getAddresses();
    console.log("Addresses:");
    console.log("    Base Address      :", addressInfo.baseAddressBech32);
    console.log("    Enterprise Address:", addressInfo.enterpriseAddressBech32);
    console.log("    Reward Address    :", addressInfo.rewardAddressBech32);
    console.log("    DRep Address      :", addressInfo.dRepIDBech32);
}

async function main() {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });

    rl.question('Enter the Seed Phrase:', async (seedPhrase) => {
        rl.close();
        await restoreCardanoWallet(seedPhrase);
    });
}
main().catch(console.error);

Tạo/Khôi phục ví từ Extended Private Key

Cấu trúc Extended Private Key trên Cardano

Trên Cardano, một Private Key có độ dài 96 bytes, nên nó được gọi là Extended Private Key, gồm 3 phần chính:

  • 64 bytes đầu tiên: Là khóa bí mật Ed25519 mở rộng (Extended Ed25519 secret key). Cardano sử dụng thuật toán Ed25519 để có tốc độ ký nhanh và bảo mật cao.
  • 32 bytes tiếp theo:Chain Code. Đây là “nguyên liệu” để thực hiện phép toán dẫn xuất các địa chỉ theo mô hình HD Wallet (Hierarchical Deterministic).

Trên Cardano, Extended Private Key này thường được hiển tị dưới dạng Bech32, là xâu bắt đầu bằng cụm kí tự “xprv“, gọi là xâu xprv cho tiện. Xâu xprv này có giá trị tương đương với 24 từ Seed Phrase. Ai có được xâu này sẽ có toàn quyền rút tiền từ tất cả các địa chỉ (Base, Enterprise, Reward) thuộc về ví đó, nên hãy TUYỆT ĐỐI bảo mật xâu này.

Tạo ví với Extended Private Key

Code để tạo xprv như sau:

const MeshWallet = require('@meshsdk/core').MeshWallet;

async function createCardanoWallet() {
    // Generate Extended Private Key
    const xprv = MeshWallet.brew(true);
    console.log("Extended Private Key:", xprv);

    // Init Wallet from Mnemonic
    // networkId=0 for Testnet (Preprod/Preview)
    // networkId=1 for Mainnet
    const wallet = new MeshWallet({
        networkId: 1,
        key: {
            type: 'root',
            bech32: xprv,
        },
    });
    await wallet.init();

    // Show addresses
    const addressInfo = wallet.getAddresses();
    console.log("Addresses:");
    console.log("    Base Address      :", addressInfo.baseAddressBech32);
    console.log("    Enterprise Address:", addressInfo.enterpriseAddressBech32);
    console.log("    Reward Address    :", addressInfo.rewardAddressBech32);
    console.log("    DRep Address      :", addressInfo.dRepIDBech32);
}

createCardanoWallet().catch(console.error);

Khôi phục ví từ Extended Private Key

const readline = require('node:readline');
const MeshWallet = require('@meshsdk/core').MeshWallet;

async function restoreCardanoWallet(xprv) {
    // Init Wallet from Mnemonic
    // networkId=0 for Testnet (Preprod/Preview)
    // networkId=1 for Mainnet
    const wallet = new MeshWallet({
        networkId: 1,
        key: {
            type: 'root',
            bech32: xprv,
        },
    });
    await wallet.init();

    // Show addresses
    const addressInfo = wallet.getAddresses();
    console.log("Addresses:");
    console.log("    Base Address      :", addressInfo.baseAddressBech32);
    console.log("    Enterprise Address:", addressInfo.enterpriseAddressBech32);
    console.log("    Reward Address    :", addressInfo.rewardAddressBech32);
    console.log("    DRep Address      :", addressInfo.dRepIDBech32);
}

async function main() {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });

    rl.question('Enter the xprv: ', async (xprv) => {
        rl.close();
        await restoreCardanoWallet(xprv);
    });
}
main().catch(console.error);

Lập trình tương tác với Minswap

Tìm hiểu về Minswap

Minswap là dex đầu tiên trên Cardano và hiện tại cũng đang là dex top đầu với TVL lớn nhất trên Cardano. Giao diện của Minswap hỗ trợ trên cả Mainnet và Testnet:

Để bắt đầu, chúng ta nên sử dụng môi trường Testnet trước để tránh mất tiền oan. Bạn lấy tADA cho Testnet tại: Cardano Faucet. Trước khi bắt đầu tìm hiểu về code, bạn cũng nên đọc trước tài liệu của Minswap Minswap Documentation và xem qua các repo trên Minswap Github.

Riêng về phần Dex, thì Minswap có 3 phiên bản:

  • Minswap V1: Phí cố định là 0.3% cho tất cả các Pool. Công thức toán học thì giống với Uniswap V2: Minswap AMM V2 Formula
  • Minswap V2:
    • Được xây dựng sau khi có Plutus V2 (Phiên bản nâng cấp của ngôn ngữ lập trình hợp đồng thông minh trên blockchain Cardano, được giới thiệu cùng với bản nâng cấp Vasil vàotháng 9/2022).
    • Phí linh hoạt hơn, giao diện đẹp hơn, hỗ trợ tính năng gom lệnh để tối ưu giá và nâng cao hiệu suất nhờ chuyển sang Plutus V2.
    • Còn về công thức toán học thì vẫn như phiên bản V1.
  • Minswap Stableswap: Công thức toán học bạn xem tại Minswap Stableswap Formula (Giống với Curve)

Để tương tác với Minswap thì thông thường có 3 cách:

  • Sử dụng API của Minswap: Đây là cách đơn giản nhất nhưng có thể chưa đáp ứng hết các yêu cầu của bạn. Thường chúng ta sử dụng 2 API sau: Minswap APIsAggregator API
  • Sử dụng Minswap SDK: Cách này cũng khá tiện nhưng phụ thuộc vào ngôn ngữ mà được Minswap hỗ trợ có SDK.
  • Tự viết code từ đầu: Cách này rất phức tạp nhưng bạn có thể làm được nhiều thứ cho nhiều bài toán khác nhau.

Chúng ta sẽ đi vào từng cách:

Sử dụng API của Minswap

Minswap hỗ trợ API ở cả hai môi trường Mainnet và Testnet:

  • Testnet:
    • Minswap API Base URL: https://monorepo-testnet-preprod.minswap.org
    • Aggregator API Base URL: https://aggr.monorepo-testnet-preprod.minswap.org/aggregator
  • Mainnet:
    • Minswap API Base URL: https://api-mainnet-prod.minswap.org
    • Aggregator API Base URL: https://agg-api.minswap.org/aggregator

Nếu bạn chạy được cho Testnet thì chỉ cần đổi Base URL là sẽ chạy được cho Mainnet. Để tiện trong bài viết tôi chỉ sử dụng môi trường Testnet, bạn tự đổi Base URL để chạy cho mainnet.

Thông tin các API, tham số vào, dữ liệu ra đều có hết trong tài liệu API: Minswap APIsAggregator API.

Lấy thông tin các tokens

Bạn sử dụng API để lấy thông tin tokens mà Minswap đang hỗ trợ:

Lấy thông tin tài sản trong ví Cardano

Chúng ta sử dụng API /aggregator/wallet như sau:

const axios = require('axios');

async function showBalances(addr) {
    try {
        let link = `https://aggr.monorepo-testnet-preprod.minswap.org/aggregator/wallet?address=${addr}`;
        let resp = await axios.get(link);
        let data = resp.data;
        console.log("Resp", JSON.stringify(data, null, 4));
        console.log(`${data.wallet}:`);
        let balances = data.balance;
        for (let idx=0; idx<balances.length; idx++) {
            let item = balances[idx];
            let asset = item.asset;
            let decimals = asset.decimals;
            let amount = Number(item.amount)/10**decimals;
            console.log(`    ${asset.ticker} (${asset.project_name}): ${amount}`);
        }
    } catch(ex) {
        console.error("Error to showBalances()", addr, ex);
    }
}

async function main() {
    await showBalances("addr_test1qqc9uc557cryhtzj7hn60r49qdt8pghywaw0mt2vu9lmklgu3yy39gfaa6e6euuamje5ev9ky075vja7navywdnq63xshjp3mg");
}
main();

Kết quả trả về như sau:

Lấy giá trước khi thực hiện hoán đổi

Chúng ta sử dụng api /aggregator/estimate để lấy giá trước khi hoán đổi. Nhưng API này chỉ lấy giá theo 1 chiều:

  • Bạn biết hoán đổi 100 ADA sẽ nhận được bao nhiêu NIGHT
  • Nhưng bạn không biết cần bao nhiêu ADA để nhận được 1000 NIGHT
const axios = require('axios');

const ADA = "lovelace";
const ADA_DECIMALS = 6;

const NIGHT = "aa1865f70fd0798c226142258d4e237cc249867971380859279ab5960014df104e49474854";
const NIGHT_DECIMALS = 6;

function toNetworkAmount(num, decimals) {
    let n = BigInt(num);
    let k10 = BigInt(10);
    let d = BigInt(decimals);
    let a = n*k10**d;
    return a.toString(10);
}

async function showBestPriceInfos(adaAmount) {
    try {
        let link = "https://aggr.monorepo-testnet-preprod.minswap.org/aggregator/estimate";
        let payload = {
            amount: toNetworkAmount(adaAmount, ADA_DECIMALS),
            token_in: ADA,
            token_out: NIGHT,
            allow_multi_hops: true,
            allow_non_atomic_multi_hops: true,
            slippage: 0.1,
            exclude_protocols: [ "MuesliSwap" ]
        };
        let resp = await axios.post(link, payload);
        let data = resp.data;
        // console.log("Resp", JSON.stringify(data, null, 4));
        let numOfPath = data.paths.length;
        let amountOut = data.amount_out/10**NIGHT_DECIMALS;
        console.log(`Best Price: ${numOfPath} paths`);
        console.log(`    ${adaAmount} ADA => ${amountOut} NIGHT`);
    } catch(ex) {
        console.error("Error to getPriceInfos()", ex);
    }
}

async function showMinswapPriceInfos(adaAmount) {
    try {
        let link = "https://aggr.monorepo-testnet-preprod.minswap.org/aggregator/estimate";
        let payload = {
            amount: toNetworkAmount(adaAmount, ADA_DECIMALS),
            token_in: ADA,
            token_out: NIGHT,
            allow_multi_hops: false,
            allow_non_atomic_multi_hops: false,
            slippage: 0.1,
            include_protocols: [ "Minswap" ]
        };
        let resp = await axios.post(link, payload);
        let data = resp.data;
        // console.log("Resp", JSON.stringify(data, null, 4));
        let numOfPath = data.paths.length;
        let amountOut = data.amount_out/10**NIGHT_DECIMALS;
        console.log(`Minswap Price: ${numOfPath} paths`);
        console.log(`    ${adaAmount} ADA => ${amountOut} NIGHT`);
    } catch(ex) {
        console.error("Error to getPriceInfos()", ex);
    }
}

async function main() {
    let adaAmount = 100;
    await showBestPriceInfos(adaAmount);
    await showMinswapPriceInfos(adaAmount);
}
main();

Kết quả sẽ như sau:

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