npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@guardian-sdk/cardano

v1.1.0

Published

Guardian SDK for Cardano

Downloads

329

Readme

@guardian-sdk/cardano — Cardano

Native staking support for Cardano, part of the Guardian SDK.

Abstracts Blockfrost API calls and CBOR transaction construction behind a clean, type-safe API so you can build staking features without dealing with bech32 decoding, stake certificate encoding, UTXO coin selection, or Cardano-specifics.

Limitation — pure-ADA wallets only (native token support coming in a future release). This release only supports wallets whose UTXOs contain ADA exclusively. If your wallet holds native tokens (NFTs, fungible tokens) and there is not enough pure-ADA to cover the transaction, signing and fee estimation will throw ValidationError("UNSUPPORTED_OPERATION"). Move native tokens to a separate address before staking, or use a dedicated ADA-only wallet for staking operations.

Table of Contents


How Cardano Native Staking Works

Cardano uses Ouroboros — a proof-of-stake consensus protocol where block production rights are proportional to the amount of ADA delegated to a stake pool. ADA holders delegate their stake to pools to participate in network security and earn rewards without locking or transferring any tokens.

Stake Pools

A stake pool is an always-on server that participates in block production on behalf of its delegators. Pools are identified by a bech32 pool ID (e.g. pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy).

Each pool sets two fee parameters that reduce delegator rewards:

  • Fixed cost — a flat ADA amount taken from rewards each epoch (currently minimum 170 ADA on mainnet). Pools must cover their operating costs before distributing to delegators.
  • Margin — a percentage of the remaining rewards the pool operator keeps (0–100%). The rest goes to delegators.

The return on stake (ROS) a delegator earns is approximately:

delegator ROS ≈ protocol_yield × (1 − margin) − fixed_cost / pool_active_stake

The protocol targets roughly 4–5% annual yield across all stake. Actual returns depend on pool performance (blocks produced vs expected) and pool saturation.

Saturation: Each pool has a target size determined by the k parameter (currently 500 optimal pools, each ideally holding 1/500 of total ADA staked). Pools above saturation earn reduced rewards — this design incentivises delegators to spread ADA across many pools rather than concentrating in a few.

As of 2026, the network has approximately 3,000 pools, of which ~2,400 are active.

Stake Keys and Addresses

Every Cardano wallet has two independent key pairs derived from the same root seed:

root seed (mnemonic)
├── payment key  (m/1852'/1815'/0'/0/0)  ──► addr1...  (payment addresses)
└── staking key  (m/1852'/1815'/0'/2/0)  ──► stake1... (stake address)

| Key | Authorises | Derived address | |---|---|---| | Payment key | Spending UTXOs; paying fees | addr1... payment addresses | | Staking key | Delegation certificates; reward withdrawals | stake1... stake address |

The stake address (stake1...) is a single identifier for your staking account regardless of how many payment addresses you use. It is the handle for all staking operations: registering the key, delegating to a pool, and withdrawing rewards.

Both keys are required to sign any staking transaction. The SDK accepts them as separate paymentPrivateKey and stakingPrivateKey fields in sign().

Key format: The SDK accepts standard Ed25519 private keys — 32 bytes as 64 hex characters. BIP32-Ed25519 HD wallet keys are 64-byte extended keys; pass only the first 32 bytes (the private scalar). Use deriveCardanoKeys(rootKeyHex) to derive both keys correctly from a BIP32 root key.

UTXO Model — Delegation Without Locking

Cardano uses an Unspent Transaction Output (UTXO) model. Every ADA sits in a specific UTXO, not in an account balance. When you delegate, you are not moving or locking any of your UTXOs — you are registering a preference on-chain that says "assign the stake weight of all UTXOs controlled by my staking key to pool X".

This has two important consequences:

  1. Nothing is locked. Your ADA is fully spendable at all times, even while actively delegating. Receiving, sending, or spending ADA does not interrupt delegation — the pool earns rewards on whatever ADA you hold at each epoch snapshot.

  2. No unbonding period. Changing or stopping delegation takes effect in the next epoch with no waiting period. You pay only the regular transaction fee.

getDelegations() reports amount as your total controlled ADA (including any rewards currently in the reward account) — the economic weight your delegation carries. Available and Staked in getBalances() are derived by subtracting withdrawable_amount from controlled_amount, so they reflect the actual spendable UTXO balance. Staked equals Available while actively delegating to a pool, and drops to zero when the stake key is not delegating — only delegated ADA earns rewards.

Rewards

Rewards do not auto-compound into your staked balance. They accumulate separately in your reward account and must be explicitly withdrawn using a ClaimRewards transaction.

Rewards become spendable two epochs after they are earned (see Epochs and Reward Timing). The Rewards balance in getBalances() is the amount available to withdraw right now.

When you withdraw rewards, the ADA moves from the reward account to a payment address you control. You can withdraw and redelegate simultaneously in the same transaction, or withdraw separately.

Lifecycle of a Stake

                                          redelegate()
                                  ┌─────────────────────────────────┐
                                  │                                  ▼
  register + delegate() ──► [ Delegating ]               [ Delegating — new pool ]
                                  │
                             undelegate()
                                  │
                                  ▼
                            [ Deregistered ] ──► 2 ADA deposit returned to wallet
  [ Delegating ] ──► rewards accumulate each epoch ──► [ Rewards available ]
                                                               │
                                                           claim()
                                                               │
                                                               ▼
                                                       ADA in payment address

| Stage | Description | |---|---| | Unregistered | Stake key does not exist on-chain. A 2 ADA deposit is required to register it. | | Registered, not delegating | Stake key registered but no pool selected. Key deposit is locked on-chain. No rewards earned. | | Delegating | ADA earns rewards every epoch. Delegation is active for the leader schedule as of epoch N+2 after the delegation transaction. | | Rewards | Rewards distributed at the end of each epoch, available to withdraw from epoch N+4 onward (visible as the Rewards balance). | | Deregistered | Stake key removed from chain. The 2 ADA deposit is refunded in the same transaction. Any unclaimed rewards are lost — always claim before deregistering. |

Delegation cycle in detail

First-time delegation (most common path):

  1. Submit delegation tx with:
     ├── StakeRegistration certificate  → 2 ADA deposit deducted from wallet
     └── StakeDelegation certificate   → pool assignment recorded on-chain

  2. End of current epoch N:
     └── On-chain snapshot taken. Your ADA counted toward the pool.

  3. Epoch N+1:
     └── Snapshot is used to determine the leader schedule for epoch N+2.

  4. Epoch N+2:
     └── Your stake participates in the leader schedule.

  5. Epoch N+3:
     └── Pool produces blocks (if selected) using the snapshot that includes your stake.
         Rewards are earned for this epoch's performance.

  6. Epoch N+4:
     └── Rewards for epoch N+3 are calculated and distributed.
         Your `Rewards` balance increases.

  7. Repeat from step 2 every epoch (~5 days).

Redelegation (switching pool):

  1. Submit redelegate tx with:
     └── StakeDelegation certificate to new pool (no deposit needed, no fee beyond tx fee)

  2. Change takes effect at end of current epoch.
     Old pool: earns rewards until snapshot.
     New pool: earns rewards from next epoch.

  3. No rewards are lost. No unbonding period.

Undelegation (stop staking):

  1. Claim any unclaimed rewards first — they are lost on deregistration.

  2. Submit undelegate tx with:
     └── StakeDeregistration certificate → 2 ADA deposit refunded in same tx

  3. Pool stops earning on your behalf from next epoch.
     Your ADA remains fully spendable throughout.

  4. If you want to delegate again later, pay the 2 ADA deposit again.

Claiming rewards:

  1. Check Rewards balance: getBalances() → `Rewards` amount

  2. Submit claim tx with:
     └── Withdrawal entry: reward account → lovelace amount
         ADA moves from reward account to your payment address.

  3. Delegation continues uninterrupted. Claiming does not affect your pool assignment.

Epochs and Reward Timing

A Cardano epoch spans 5 days (432,000 slots at 1 second per slot). Epochs are numbered sequentially and the exact reward schedule is deterministic:

  Epoch N       Epoch N+1      Epoch N+2      Epoch N+3      Epoch N+4
  ─────────────────────────────────────────────────────────────────────────────
  │              │              │              │              │
  ▼              ▼              ▼              ▼              ▼
  Tx confirmed   Snapshot       Stake in       Blocks         Rewards
  (delegation    taken          leader         produced       distributed
   included)                     schedule       (rewards       (visible in
                                                 earned)        `withdrawable_amount`)

| Event | Timing | |---|---| | Delegation submitted | Epoch N (any slot) | | Snapshot taken | End of Epoch N | | Stake participates in leader schedule | Epoch N+2 | | Pool produces blocks with your stake | Epoch N+3 | | First rewards calculated | End of Epoch N+3 | | First rewards spendable | Epoch N+4 (15–20 days after delegation) | | Subsequent rewards | Every epoch (~5 days) |

The first reward after first-time delegation therefore arrives 15–20 days after the transaction is confirmed, depending on where in the epoch you delegated. After that, rewards are added to your Rewards balance at the start of each new epoch.

Fee Model

Cardano fees are calculated from the size of the transaction, not from gas:

fee = minFeeA × txSizeInBytes + minFeeB

Current mainnet parameters (protocol version 9):

| Parameter | Value | Description | |---|---|---| | minFeeA | 44 lovelaces/byte | Per-byte coefficient | | minFeeB | 155,381 lovelaces | Base fee | | Typical tx size | 300–400 bytes | Staking transactions | | Typical fee | ~0.17–0.18 ADA | For a delegation tx |

Because the fee depends on the transaction size and the transaction size depends on the fee (it's a field in the body), the SDK estimates the size using mock 32/64-byte witnesses (same byte length as real signatures) and applies the formula once. No iteration is needed.

There is no fee priority mechanism. All valid transactions with the correct minimum fee are treated equally by block producers. Transactions cannot be accelerated by offering a higher fee.

Gas estimation is not used. Every operation has a predictable, deterministic fee based on its serialised size.

Typical fees observed on mainnet:

| Operation | Approx. fee | Notes | |---|---|---| | Delegate (first time) | ~0.18 ADA + 2 ADA deposit | Registration + delegation certificates | | Redelegate | ~0.17 ADA | Delegation certificate only | | Undelegate | ~0.17 ADA − 2 ADA deposit returned | Net cost is negative (deposit refunded) | | Claim rewards | ~0.17 ADA | Withdrawal only |

Key Protocol Parameters

| Parameter | Value | |---|---| | Stake key registration deposit | 2 ADA (refunded on deregistration) | | Unbonding period | None | | Redelegate fee | None (standard tx fee only) | | Min delegation amount | No protocol minimum (deposit is the effective minimum) | | Epoch duration | 5 days | | Target number of pools (k) | 500 | | Approximate protocol yield | ~4–5% annually | | Active pools (approx.) | ~2,400 | | Total registered pools | ~3,000 | | Mainnet staking explorer | https://cardanoscan.io | | Preprod staking explorer | https://preprod.cardanoscan.io |


Blockfrost Setup

All on-chain queries go through Blockfrost — the most widely used Cardano API service. No local node is required.

  1. Register at blockfrost.io
  2. Create a Cardano mainnet project
  3. Copy the project_id key (format: mainnet...)
cardano({ apiKey: "mainnetXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" })

Rate limits:

| Tier | Requests/day | |---|---| | Free | 50,000 | | Starter | 500,000 | | Professional | 5,000,000 |


Installation

npm install @guardian-sdk/cardano \
  @guardian-sdk/sdk \
  @cardano-sdk/[email protected] \
  @cardano-sdk/[email protected] \
  @cardano-sdk/[email protected]

If you already have @guardian-sdk/bsc installed, @guardian-sdk/sdk is already present — you only need to add the Cardano-specific peers:

npm install @guardian-sdk/cardano \
  @cardano-sdk/[email protected] \
  @cardano-sdk/[email protected] \
  @cardano-sdk/[email protected]

Dependencies

| Package | Version | Role | |---|---|---| | @guardian-sdk/sdk | workspace:^ | Peer — chain-agnostic core, shared types and interfaces | | @cardano-sdk/core | 0.46.12 | Peer — Cardano primitives: addresses, transactions, certificates | | @cardano-sdk/crypto | 0.4.5 | Peer — Ed25519 key operations and Blake2b hashing | | @cardano-sdk/util | 0.17.1 | Peer — shared utilities for the Cardano SDK family |

Why exact peer versions? The @cardano-sdk family has no stability guarantees between minor versions and CBOR serialisation is sensitive to the exact release. The versions above are the ones this package was built and tested against. These pins will be loosened to ^ in a future release.


Quick Start

import { GuardianSDK } from "@guardian-sdk/sdk";
import { cardano, chains } from "@guardian-sdk/cardano";

const sdk = new GuardianSDK([
  cardano({ apiKey: "mainnetXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }),
]);

const PAYMENT_ADDRESS = "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae";
const STAKE_ADDRESS   = "stake1ux3g2c9dx2nhhehyrezy4uvtyvgmndp3v4kplasjan2fcgfv7jyfa";

// Derive your payment and staking keys from a BIP32 root key (192 hex chars).
// See deriveCardanoKeys() — never hardcode or log these values.
import { deriveCardanoKeys } from "@guardian-sdk/cardano";
const { paymentPrivateKey: PAYMENT_KEY, stakingPrivateKey: STAKING_KEY } =
  deriveCardanoKeys(process.env.CARDANO_ROOT_KEY!);

// 1. Browse stake pools
const { data: pools } = await sdk.getValidators(chains.cardanoMainnet);
console.log(`${pools.length} pools found, top pool: ${pools[0].name}`);

// 2. Check current delegation
const { delegations, stakingSummary } = await sdk.getDelegations(chains.cardanoMainnet, STAKE_ADDRESS);
console.log(`Max APY: ${stakingSummary.maxApy.toFixed(2)}%`);

// 3. Check balances
const balances = await sdk.getBalances(chains.cardanoMainnet, STAKE_ADDRESS);
for (const b of balances) {
  console.log(b.type, Number(b.amount) / 1e6, "ADA");
}
// Available  9.95 ADA   ← controlled minus rewards (spendable UTXOs)
// Staked     9.95 ADA   ← equals Available while delegating
// Pending    0 ADA      ← never used on Cardano
// Rewards    2.1 ADA    ← withdrawable rewards

// 4. Delegate to the top pool
import type { CardanoSigningWithPrivateKey } from "@guardian-sdk/cardano";

const transaction = {
  type: "Delegate" as const,
  chain: chains.cardanoMainnet,
  amount: 0n,
  account: PAYMENT_ADDRESS,
  isMaxAmount: false,
  validator: pools[0],
};

const fee = await sdk.estimateFee(transaction);
console.log(`Fee: ${Number(fee.total) / 1e6} ADA`);

const signingArgs: CardanoSigningWithPrivateKey = {
  transaction,
  fee,
  nonce: 0,
  paymentPrivateKey: PAYMENT_KEY,
  stakingPrivateKey: STAKING_KEY,
};
const rawTx = await sdk.sign(signingArgs);
const txHash = await sdk.broadcast(chains.cardanoMainnet, rawTx);
console.log(`Delegated! https://cardanoscan.io/transaction/${txHash}`);

API Reference

cardano()

Factory function. Returns a GuardianServiceContract for the Cardano mainnet.

function cardano(config: CardanoConfig): GuardianServiceContract

interface CardanoConfig {
  apiKey: string;   // Blockfrost project_id for mainnet
  logger?: Logger;  // optional; defaults to NoopLogger
}

getValidators

Returns a page of stake pools registered on Cardano. Pagination is passed directly to Blockfrost — only the requested page is fetched. Pool metadata (name, ticker, description) is batch-fetched in parallel for that page.

// First page, default 20 pools
const { data, pagination } = await sdk.getValidators(chains.cardanoMainnet);

// Paginate
const page2 = await sdk.getValidators(chains.cardanoMainnet, { page: 2, pageSize: 50 });
console.log(page2.pagination.hasNextPage); // true/false — use this to drive pagination

Returns: Promise<ValidatorsPage>

interface ValidatorsPage {
  data: Validator[];
  pagination: {
    page: number;
    pageSize: number;
    total: undefined;      // Not available — Blockfrost does not return total count
    totalPages: undefined; // Not available
    hasNextPage: boolean;  // true when the page returned a full pageSize of results
  };
}

interface Validator {
  id: string;                      // bech32 pool ID — pool1...
  status: ValidatorStatus;         // "Active" | "Inactive"
  name: string;                    // Pool name from metadata, or ticker, or truncated pool ID
  description: string;             // Pool description from metadata
  image: undefined;                // Cardano pools have no standard logo URL format
  apy: number;                     // Estimated annual yield (%)
  delegators: number | undefined;  // Live delegator count
  operatorAddress: string;         // bech32 pool ID — use in transaction.validator
  creditAddress: string;           // Same as operatorAddress (no separate credit contract)
}

type ValidatorStatus = "Active" | "Inactive";
// Active   — pool is registered and not in retirement
// Inactive — pool has submitted a retirement certificate (retiring next epoch)

APY estimation: Uses the Cardano ledger reward formula (Shelley spec §11.8). APY is derived from live on-chain parameters — monetary expansion rate (ρ), treasury rate (τ), pledge influence (a0), and pool saturation — fetched alongside pools on each call. Actual earned rewards depend on pool luck and real-time block production. For precise historical ROA, call Blockfrost's /pools/{id}/history directly.


getDelegations

Returns the current delegation state and a summary of the staking protocol. Accepts either a stake address (stake1...) or a base payment address (addr1q...) — the stake credential is extracted automatically from a payment address.

const { delegations, stakingSummary } = await sdk.getDelegations(
  chains.cardanoMainnet,
  "stake1ux3g2c9dx2nhhehyrezy4uvtyvgmndp3v4kplasjan2fcgfv7jyfa"
);

Returns: Promise<Delegations>

interface Delegations {
  delegations: Delegation[];    // At most one entry on Cardano
  stakingSummary: StakingSummary;
}

interface Delegation {
  id: string;
  validator: Validator;         // Full pool details
  amount: bigint;               // Total ADA controlled by the stake key, in lovelaces
  status: "Active";             // Cardano: always "Active" when delegating
  delegationIndex: 0n;          // Not used in Cardano
  pendingUntil: 0;              // No unbonding period
}

interface StakingSummary {
  totalProtocolStake: number;   // Total ADA staked network-wide (in ADA, not lovelaces)
  maxApy: number;               // Highest estimated APY across the first page of pools
  minAmountToStake: 2_000_000n; // 2 ADA stake key registration deposit (in lovelaces)
  unboundPeriodInMillis: 0;     // No unbonding period
  redelegateFeeRate: 0;         // No fee to switch pools
  activeValidators: undefined;  // Not available from getDelegations — use getValidators()
  totalValidators: undefined;   // Not available from getDelegations — use getValidators()
}

Cardano on-chain supports only one active delegation per stake key. Multi-pool delegation (CIP-17) is a metadata-layer convention implemented by wallets across multiple stake keys — there is no native multi-pool delegation. delegations therefore contains at most one entry.

If the stake key is not registered or not currently delegating, delegations is an empty array.


getBalances

Returns balance information for an address (Cardano surfaces Available, Staked, and Rewards). Accepts either a stake address (stake1...) or a base payment address (addr1q...) — the stake credential is extracted automatically from a payment address. Balances reflect the total ADA controlled by the stake key across all associated payment addresses.

const balances = await sdk.getBalances(
  chains.cardanoMainnet,
  "stake1ux3g2c9dx2nhhehyrezy4uvtyvgmndp3v4kplasjan2fcgfv7jyfa"
);

Returns: Promise<Balance[]>

All amounts are in lovelaces (1 ADA = 1,000,000 lovelaces).

| Type | What it represents on Cardano | |---|---| | Available | Spendable UTXO balance (controlled_amount minus withdrawable_amount). Fully spendable at all times regardless of delegation. | | Staked | Equals Available when the stake key is actively delegated to a pool; 0 otherwise. Only delegated ADA earns staking rewards. | | Rewards | withdrawable_amount from Blockfrost — rewards that have been distributed and are sitting in the reward account, ready to withdraw via a ClaimRewards transaction |

type BalanceType = "Available" | "Staked" | "Pending" | "Rewards";

Cardano does not use the Pending type (it is present for BSC compatibility). getBalances() returns Available, Staked, and Rewards.


```typescript
const balances = await sdk.getBalances(chains.cardanoMainnet, STAKE_ADDRESS);
for (const b of balances) {
  console.log(b.type, (Number(b.amount) / 1e6).toFixed(6), "ADA");
}
// Available  9.950000 ADA   ← controlled_amount minus withdrawable_amount
// Staked     9.950000 ADA   ← equals Available while delegating
// Pending    0.000000 ADA   ← never used on Cardano
// Rewards    2.100000 ADA   ← withdrawable_amount

Rewards reflects rewards that have already been distributed and are available to withdraw. Rewards currently being earned in the active epoch are not yet included.


getNonce

Cardano uses a UTXO model — transactions reference specific UTXOs as inputs, not account nonces. This always returns 0. The double-spend protection is handled internally by UTXO input selection during signing.

const nonce = await sdk.getNonce(chains.cardanoMainnet, address); // always 0

estimateFee

Estimates the transaction fee by fetching protocol parameters, UTXOs, and the stake account (registration status + reward balance) from Blockfrost, building a draft transaction that mirrors the one sign() submits — distinct payment/staking witnesses, a TTL, the real certificate set, and any full-balance reward withdrawal — then applying the fee formula plus a 10% safety buffer.

transaction.account must be set to a base payment address (addr1q...) — it carries the stake credential used to fetch UTXOs and the reward account. Enterprise/pointer addresses are rejected with ValidationError("INVALID_ADDRESS").

const fee = await sdk.estimateFee(transaction);

Returns: Promise<UtxoFee>

interface UtxoFee {
  type: "UtxoFee";
  txSizeBytes: number;   // Estimated serialised transaction size in bytes
  total: bigint;         // Total fee in lovelaces
}

Accepts all four transaction types:

// Delegate — register stake key and set pool
const fee = await sdk.estimateFee({
  type: "Delegate",
  chain: chains.cardanoMainnet,
  amount: 0n,                  // amount is unused for delegation
  account: PAYMENT_ADDRESS,   // required for UTXO fetch
  isMaxAmount: false,
  validator: pools[0],
});

// Redelegate — change pool
const fee = await sdk.estimateFee({
  type: "Redelegate",
  chain: chains.cardanoMainnet,
  amount: 0n,
  account: PAYMENT_ADDRESS,
  isMaxAmount: false,
  fromValidator: currentDelegation.validator,
  toValidator: newPool,
});

// Undelegate — deregister stake key
const fee = await sdk.estimateFee({
  type: "Undelegate",
  chain: chains.cardanoMainnet,
  amount: 0n,
  account: PAYMENT_ADDRESS,
  isMaxAmount: false,
  validator: currentDelegation.validator,
});

// ClaimRewards — withdraw rewards
const fee = await sdk.estimateFee({
  type: "ClaimRewards",
  chain: chains.cardanoMainnet,
  amount: claimableAmount,     // validated (> 0 and <= on-chain `Rewards`); the full reward balance is always withdrawn
  account: PAYMENT_ADDRESS,
  validator: currentDelegation.validator,
  index: 0n,                   // not used in Cardano; required by interface
});

sign

Signs a Cardano transaction and returns the CBOR hex string ready to broadcast.

Cardano staking requires two Ed25519 keys — pass them as paymentPrivateKey and stakingPrivateKey in the signing args. Use deriveCardanoKeys() to obtain them from a BIP32 root key:

import { deriveCardanoKeys } from "@guardian-sdk/cardano";
import type { CardanoSigningWithPrivateKey } from "@guardian-sdk/cardano";

const { paymentPrivateKey, stakingPrivateKey } = deriveCardanoKeys(rootKeyHex);

const signingArgs: CardanoSigningWithPrivateKey = {
  transaction,
  fee,
  nonce: 0, // always 0 for Cardano
  paymentPrivateKey,
  stakingPrivateKey,
};

const rawTx = await sdk.sign(signingArgs);

CardanoSigningWithPrivateKey extends BaseSignArgs and is accepted wherever the SDK expects signing arguments.

The SDK:

  1. Derives the verification key from each private key
  2. Fetches UTXOs and protocol parameters from Blockfrost
  3. Builds and CBOR-encodes the transaction body
  4. Hashes the body with Blake2b-256
  5. Signs the hash with both keys
  6. Returns the fully assembled transaction as CBOR hex

preHash and compile

For MPC wallets, hardware signers, or custody setups where private keys are not available in the application process. Splits signing into two steps:

  Your App                  SDK                  External Signer (MPC/HSM)
     │                       │                           │
     │  preHash(tx,fee,0)    │                           │
     │ ─────────────────────►│                           │
     │                       │                           │
     │  { serializedTx,      │                           │
     │    signArgs }         │                           │
     │ ◄─────────────────────│                           │
     │                       │                           │
     │  serializedTx (32-byte body hash/digest)          │
     │ ──────────────────────────────────────────────────►
     │                       │                           │
     │  paymentSig, stakingVKey, stakingSig, paymentVKey │
     │ ◄──────────────────────────────────────────────────
     │                       │                           │
     │  compile({signArgs,   │                           │
     │    signature: "a:b:c:d"})                         │
     │ ─────────────────────►│                           │
     │                       │                           │
     │  rawTx (CBOR hex)     │                           │
     │ ◄─────────────────────│                           │

Step 1 — serialise the transaction body:

const { serializedTransaction, signArgs } = await sdk.preHash({
  transaction,
  fee,
  nonce: 0,
});

// For Cardano, `serializedTransaction` is the Blake2b-256 **hash** of the tx body
// (32 bytes / 64 hex chars) — this is the exact value the Ed25519 signer must sign.
// The full CBOR body is carried inside `signArgs._txBodyCbor` for use in compile().
// Send `serializedTransaction` to your external signer. The signer must:
//   1. Treat it as the 32-byte digest to sign
//   2. Sign with payment Ed25519 key  → paymentSig (64 bytes → 128 hex)
//   3. Sign with staking Ed25519 key  → stakingSig (64 bytes → 128 hex)
//   4. Return both signatures and both verification keys (vkeys)

Step 2 — compile the signed transaction:

// Encode all witness components into the single signature field using ":" delimiter
const rawTx = await sdk.compile({
  signArgs,
  signature: `${paymentSig}:${stakingVKey}:${stakingSig}:${paymentVKey}`,
  // Format: paymentSigHex:stakingVKeyHex:stakingSigHex:paymentVKeyHex
  // Each component is a hex string. : is the separator.
});

Step 3 — broadcast:

const txHash = await sdk.broadcast(chains.cardanoMainnet, rawTx);

deriveCardanoKeys

Derives the payment and staking private keys from a BIP32 root key. Both keys are required for every signing operation.

import { deriveCardanoKeys } from "@guardian-sdk/cardano";

const { paymentPrivateKey, stakingPrivateKey } = deriveCardanoKeys(rootKeyHex);

Parameters

| Parameter | Type | Description | |---|---|---| | rootKeyHex | string | BIP32-Ed25519 root private key — 96 bytes as a 192-character hex string |

Returns: { paymentPrivateKey: string, stakingPrivateKey: string } — both are 32-byte Ed25519 scalars (64 hex chars), ready to pass to sign().

Derivation paths (CIP-1852):

| Key | Path | |---|---| | Payment | m / 1852' / 1815' / 0' / 0 / 0 | | Staking | m / 1852' / 1815' / 0' / 2 / 0 |

Obtaining a root key from a mnemonic:

deriveCardanoKeys accepts a root key hex, not a mnemonic directly. To convert a mnemonic, use a BIP39 library alongside @cardano-sdk/crypto (already a bundled dependency):

import { Bip32PrivateKey, ready } from "@cardano-sdk/crypto";
import { mnemonicToEntropy } from "@scure/bip39";          // npm install @scure/bip39
import { wordlist } from "@scure/bip39/wordlists/english.js";

await ready(); // initialise WASM module

const entropy = mnemonicToEntropy("your twenty four word mnemonic ...", wordlist);
const rootKeyHex = Bip32PrivateKey.fromBip39Entropy(Buffer.from(entropy), "").hex();

const { paymentPrivateKey, stakingPrivateKey } = deriveCardanoKeys(rootKeyHex);

Store rootKeyHex the same way you would store a mnemonic — it is equivalent in sensitivity. Never log or commit it.


Transaction Flows

Full working examples for each staking operation. All examples share this setup:

import { GuardianSDK } from "@guardian-sdk/sdk";
import { cardano, chains } from "@guardian-sdk/cardano";
import type { CardanoSigningWithPrivateKey } from "@guardian-sdk/cardano";

const sdk = new GuardianSDK([
  cardano({ apiKey: "mainnetXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }),
]);

const PAYMENT_ADDRESS = "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae";
const STAKE_ADDRESS   = "stake1ux3g2c9dx2nhhehyrezy4uvtyvgmndp3v4kplasjan2fcgfv7jyfa";

const { paymentPrivateKey: PAYMENT_KEY, stakingPrivateKey: STAKING_KEY } =
  deriveCardanoKeys(process.env.CARDANO_ROOT_KEY!);

Delegate — register and start staking

Registers the stake key on-chain (2 ADA deposit) and sets the pool to delegate to. The registration and delegation certificates are bundled in a single transaction.

const { data: pools } = await sdk.getValidators(chains.cardanoMainnet);
const selectedPool = pools[0];

const transaction = {
  type: "Delegate" as const,
  chain: chains.cardanoMainnet,
  amount: 0n,                       // delegation carries no token amount
  account: PAYMENT_ADDRESS,         // required: used to fetch UTXOs for fee payment
  isMaxAmount: false,
  validator: selectedPool,
};

const fee = await sdk.estimateFee(transaction);
console.log(`Fee: ~${(Number(fee.total) / 1e6).toFixed(4)} ADA`);
console.log(`Deposit: 2 ADA (returned when you deregister)`);
// Wallet needs at least: fee.total + 2_000_000n lovelaces

const signingArgs: CardanoSigningWithPrivateKey = {
  transaction, fee, nonce: 0,
  paymentPrivateKey: PAYMENT_KEY,
  stakingPrivateKey: STAKING_KEY,
};
const rawTx = await sdk.sign(signingArgs);
const txHash = await sdk.broadcast(chains.cardanoMainnet, rawTx);
console.log(`Delegated! https://cardanoscan.io/transaction/${txHash}`);
// First rewards arrive in ~15–20 days (N+4)

If the stake key is already registered (you have delegated before), the StakeRegistration certificate is still included but is harmless on-chain. No second deposit is charged for an already-registered key.


Redelegate — switch pool

Changes the delegation to a different pool. Takes effect at the next epoch boundary. No waiting period, no fee beyond the standard transaction fee.

const { delegations } = await sdk.getDelegations(chains.cardanoMainnet, STAKE_ADDRESS);
const currentDelegation = delegations[0];

const { data: pools } = await sdk.getValidators(chains.cardanoMainnet);
const newPool = pools.find((p) => p.operatorAddress !== currentDelegation.validator.operatorAddress)!;

const transaction = {
  type: "Redelegate" as const,
  chain: chains.cardanoMainnet,
  amount: 0n,
  account: PAYMENT_ADDRESS,
  isMaxAmount: false,
  fromValidator: currentDelegation.validator,
  toValidator: newPool,
};

const fee = await sdk.estimateFee(transaction);
const signingArgs: CardanoSigningWithPrivateKey = {
  transaction, fee, nonce: 0,
  paymentPrivateKey: PAYMENT_KEY,
  stakingPrivateKey: STAKING_KEY,
};
const rawTx = await sdk.sign(signingArgs);
const txHash = await sdk.broadcast(chains.cardanoMainnet, rawTx);
console.log(`Pool switched! https://cardanoscan.io/transaction/${txHash}`);
// Old pool earns for remainder of current epoch.
// New pool earns from next epoch.

Undelegate — stop staking and recover deposit

Deregisters the stake key. The 2 ADA registration deposit is returned in the same transaction. Stops earning rewards from the next epoch.

Claim rewards before undelegating. Any unclaimed rewards in the reward account are lost permanently when the stake key is deregistered.

// Step 1: claim any pending rewards first (see Claim flow below)
const balances = await sdk.getBalances(chains.cardanoMainnet, STAKE_ADDRESS);
const rewards = balances.find((b) => b.type === "Rewards")!;
if (rewards.amount > 0n) {
  // claim first — rewards are lost on deregistration
}

// Step 2: deregister
const { delegations } = await sdk.getDelegations(chains.cardanoMainnet, STAKE_ADDRESS);

const transaction = {
  type: "Undelegate" as const,
  chain: chains.cardanoMainnet,
  amount: 0n,
  account: PAYMENT_ADDRESS,
  isMaxAmount: false,
  validator: delegations[0].validator,
};

const fee = await sdk.estimateFee(transaction);
// Net cost = fee.total − 2_000_000n (deposit returned)
console.log(`Net effect: +${((2_000_000n - fee.total) / 1_000_000n).toString()} ADA returned to wallet`);

const signingArgs: CardanoSigningWithPrivateKey = {
  transaction, fee, nonce: 0,
  paymentPrivateKey: PAYMENT_KEY,
  stakingPrivateKey: STAKING_KEY,
};
const rawTx = await sdk.sign(signingArgs);
const txHash = await sdk.broadcast(chains.cardanoMainnet, rawTx);
console.log(`Deregistered! https://cardanoscan.io/transaction/${txHash}`);

ClaimRewards — withdraw accumulated rewards

Withdraws accumulated rewards from the reward account to your payment address. Delegation continues uninterrupted.

const balances = await sdk.getBalances(chains.cardanoMainnet, STAKE_ADDRESS);
const rewards = balances.find((b) => b.type === "Rewards")!;

if (rewards.amount === 0n) {
  console.log("No rewards available yet. Check back after the next epoch.");
} else {
  const { delegations } = await sdk.getDelegations(chains.cardanoMainnet, STAKE_ADDRESS);

  const transaction = {
    type: "ClaimRewards" as const,
    chain: chains.cardanoMainnet,
    amount: rewards.amount,          // exact lovelace amount to withdraw
    account: PAYMENT_ADDRESS,
    validator: delegations[0].validator,
    index: 0n,                         // unused in Cardano; required by interface
  };

  const fee = await sdk.estimateFee(transaction);
  console.log(`Claiming ${(Number(rewards.amount) / 1e6).toFixed(6)} ADA, fee: ~${(Number(fee.total) / 1e6).toFixed(4)} ADA`);

  const signingArgs: CardanoSigningWithPrivateKey = {
    transaction, fee, nonce: 0,
    paymentPrivateKey: PAYMENT_KEY,
    stakingPrivateKey: STAKING_KEY,
  };
  const rawTx = await sdk.sign(signingArgs);
  const txHash = await sdk.broadcast(chains.cardanoMainnet, rawTx);
  console.log(`Rewards claimed! https://cardanoscan.io/transaction/${txHash}`);
}

Signing Flows

See the main README signing flows diagram for a visual reference of the direct and MPC signing paths.

Cardano signing always involves two keys and produces two witness entries in the transaction:

| Key | Signs | Why | |---|---|---| | Payment key | Transaction body hash | Authorises UTXO consumption (fee payment) | | Staking key | Transaction body hash | Authorises delegation certificates and reward withdrawals |

Both verification keys are included in the transaction's witness set so validators can verify the signatures on-chain.


Logging

Logging is opt-in — pass a logger to cardano() to enable it:

import { ConsoleLogger } from "@guardian-sdk/sdk";
import { cardano } from "@guardian-sdk/cardano";

const sdk = new GuardianSDK([
  cardano({
    apiKey: "mainnetXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    logger: new ConsoleLogger("debug"), // "debug" | "info" | "warn" | "error"
  }),
]);

Plug in any logger that implements the Logger interface (debug, info, warn, error methods). See the main README Logging section for full details including bring-your-own-logger examples.

Private keys and signatures are never logged at any level.


Error Handling

Every error thrown by the SDK extends GuardianError. See the main README Error Handling section for the catch pattern and base class reference.

ValidationError

Thrown when the caller provides invalid input, before any network call is made.

import { ValidationError } from "@guardian-sdk/sdk";

| Code | Thrown when | |---|---| | INVALID_ADDRESS | An address string is not valid bech32 — e.g. a pool ID passed to getDelegations, or an enterprise/pointer address passed where a base address or stake address is required | | INVALID_AMOUNT | Insufficient UTXOs to cover the required fee and deposit | | INVALID_PRIVATE_KEY | A key is not 32 bytes (64 hex characters) | | UNSUPPORTED_OPERATION | The wallet contains UTXOs with native tokens and ADA-only UTXOs are insufficient — move tokens to a separate address before staking |

SigningError

Thrown when signing arguments are malformed.

import { SigningError } from "@guardian-sdk/sdk";

| Code | Thrown when | |---|---| | INVALID_SIGNING_ARGS | paymentPrivateKey or stakingPrivateKey missing from signing args | | INVALID_FEE_TYPE | fee.type is not "UtxoFee" — use estimateFee() to get a Cardano fee | | INVALID_SIGNING_ARGS | The signature string passed to compile() does not contain exactly four : delimited components |

ConfigError

import { ConfigError } from "@guardian-sdk/sdk";

| Code | Thrown when | |---|---| | UNSUPPORTED_CHAIN | The chain passed to any method has no registered service |

Catching by code

import { ValidationError } from "@guardian-sdk/sdk";

try {
  await sdk.getBalances(chains.cardanoMainnet, rawInput);
} catch (err) {
  if (err instanceof ValidationError && err.code === "INVALID_ADDRESS") {
    showError("Please enter a valid stake address (stake1...) or base payment address (addr1q...).");
  }
}

Supported Chains

import { chains } from "@guardian-sdk/cardano";

| Chain | Symbol | Explorer | |---|---|---| | Cardano Mainnet | ADA | https://cardanoscan.io |

import { SUPPORTED_CHAINS } from "@guardian-sdk/cardano";
// [{ id: "cardano-mainnet", symbol: "ADA", decimals: 6, ... }]

Roadmap

| Feature | Status | Issue | |---|---|---| | Native-token UTXOs — staking currently spends pure-ADA UTXOs only; wallets holding native tokens in the same address are not yet supported. | Planned (v1.1.0) | — |


← Back to Guardian SDK