Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Interact with Dogecoin from a smart contract on the Internet Computer

Introduction

The Build on Dogecoin book is intended for developers to explain how smart contracts on the Internet Computer, often referred as canisters, can interact with the Dogecoin blockchain.

Background

Through a protocol-level integration with the Dogecoin network, canisters deployed on ICP can interact with the Dogecoin network directly without using a bridge or oracle.

To interact with the Dogecoin blockchain, your canister will make use of the following:

  • Dogecoin canister: Think of it as your decentralized gateway to reach the Dogecoin blockchain. This canister provides an API that can be used by others to query information about the Dogecoin network state, for example, unspent transaction outputs (UTXOs), block headers, or the balance of any Dogecoin address; and to send transactions to the network.

  • Threshold ECDSA: Your canister can have a secret key that is stored in a secure and decentralized manner using chain-key cryptography (several such keys can be computed by key derivation). Messages sent by the canister can be signed using this key, enabling your canister to send signed transactions to the Dogecoin network through the Dogecoin canister.

To submit a Dogecoin transaction from a canister, the following steps are typically performed:

  • Request a public key from the threshold ECDSA API
  • Derive a Dogecoin address from the public key
  • Read UTXOs from the Dogecoin API
  • Build the transaction payload
  • Sign the transaction using the threshold ECDSA API
  • Submit the transaction to the Dogecoin API

Getting Started

First, set up your development environment. Then, to build canisters interacting with the Dogecoin network, you will need to know how to:

Additional resources

Building Dogecoin applications is not trivial. It’s beneficial to understand core Bitcoin concepts which underpin Dogecoin, including transactions, UTXOs, the Script language, and hash formats.

Developer Environment

To develop Dogecoin applications to be deployed on ICP, your local developer environment will need to include:

  • A local Dogecoin regtest node.

  • The Rust toolchain for installing Rust crates and compiling Rust code.

  • The IC SDK for creating, deploying, and managing canisters. You can install it natively on macOS and Linux; however, Windows users will need to set up WSL 2 before installing the IC SDK.

The IC SDK includes the dfx command-line tool, which is used to manage canisters and interact with the Internet Computer network.

Interacting with Dogecoin requires dfx version 0.30.1-beta.0 or higher. You can check your installed version by running:

dfx --version

To install and switch to a specific dfx version, use:

dfxvm install <version>
dfxvm default <version>

Create a local Dogecoin network (regtest) with dogecoind

It is recommended to set up a local Dogecoin regtest network to mine blocks quickly and at will, which facilitates testing various cases without having to rely on the Dogecoin mainnet where blocks are produced every minute on average.

Example for a Linux machine:

# Download the binary
curl -L -O https://github.com/dogecoin/dogecoin/releases/download/v1.14.9/dogecoin-1.14.9-x86_64-linux-gnu.tar.gz

# Unpack
tar -xvf dogecoin-1.14.9-x86_64-linux-gnu.tar.gz

# Add binaries to the PATH environment variable
export PATH="$(pwd)/dogecoin-1.14.9/bin:$PATH"

Mac OS X users

There are currently no released binaries for Mac OS X. You will need to build Dogecoin Core from source. Follow the instructions in the Dogecoin Core repository.

This should be created in the project folder root. This allows you to run different local Dogecoin regtest networks for different projects.

mkdir dogecoin_data
cat > dogecoin_data/dogecoin.conf <<EOF
regtest=1
txindex=1
rpcuser=ic-doge-integration
rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E=
rpcauth=ic-doge-integration:cdf2741387f3a12438f69092f0fdad8e\$62081498c98bee09a0dce2b30671123fa561932992ce377585e8e08bb0c11dfa
EOF

Explanation of settings:

  • regtest=1: Enables Dogecoin’s regression test mode for local testing.

  • txindex=1: Maintains a full transaction index to support lookups by transaction ID.

  • rpcuser=ic-doge-integration: Sets a default username for JSON-RPC authentication.

  • rpcpassword=QPQ…b-E=: Sets the password for JSON-RPC authentication.

  • rpcauth=ic-doge-integration:cdf…dfa: Uses an alternative authentication method for RPC, combining the username and a salted hash.

Find more details about the dogecoin.conf settings in the Dogecoin Core Daemon documentation.

dogecoind -datadir=$(pwd)/dogecoin_data -printtoconsole --port=18444 

This command assumes that port 18444 on your machine is available. If it isn't, change the specified port accordingly.

Starting dfx with Dogecoin support

To deploy and test projects locally, first start dfx in your local development environment with Dogecoin support enabled.

dfx start --enable-dogecoin

dfx will run a local instance of the Dogecoin API deployed as a canister for your application to interact with. The --enable-dogecoin flag uses the default Dogecoin node configuration, 127.0.0.1:18444. This address and port can be manually configured with the --dogecoin-node flag:

dfx start --enable-dogecoin --dogecoin-node <host_address>:<port>

Configuring the local Dogecoin API

The Dogecoin API can be configured either using the command line option --dogecoin-node provided to dfx or by modifying the project’s dfx.json file.

If both command line options and dfx.json configurations are provided, the command line option takes precedence.

The Dogecoin API configuration is specified under the defaults section of the dfx.json file. Below is an example configuration:

{
  "defaults": {
    "dogecoin": {
      "enabled": true,
      "nodes": ["127.0.0.1:18444"]
    }
  }
}

The Dogecoin configuration in dfx.json adds fields under defaults as shown above. This configuration won't actually have any effect unless dfx.json also defines the local IC network, and dfx start is run within the project directory. An example of a full configuration, including the local IC network definition, can be found in the dfx.json file of the basic_dogecoin example project.

Configuration options

  • enabled (boolean): Determines whether the Dogecoin adapter is enabled. Default: false.

  • nodes (array of strings or null): Lists node addresses to connect to. Most likely, you may want to set this to the default IP and port of dogecoind, 127.0.0.1:18444. Default: null.

  • canister_init_arg (string): Optional initialization arguments for the Dogecoin canister. It sets up initial configuration parameters like stability threshold, network type, block sources, fees, syncing state, API access, and more. Details about these parameters can be found in the Dogecoin canister interface specification. By default, the local Dogecoin API mirrors the settings used in the production environment. You can view these settings by searching for the Dogecoin canister on the ICP Dashboard and calling its get_config endpoint.

Deploy your first dapp locally

Before using the local Dogecoin regtest instance, you will need to:

This page demonstrates how to use the local Dogecoin regtest instance using the basic_dogecoin example canister written in Rust. This example will serve as your first example canister to interact with the Dogecoin API. It already implements methods for sending and receiving dogecoin.

Mac OS X users

If you are using macOS, an llvm version that supports the wasm32-unknown-unknown target is required. This is because the Rust bitcoin-dogecoin library relies on secp256k1-sys, which requires llvm to build. The default llvm version provided by XCode does not meet this requirement. Instead, install the Homebrew version, using brew install llvm.

Deploying your canister locally

First, navigate into the examples/basic_dogecoin subdirectory of the Dogecoin canister repo:

cd examples/basic_dogecoin

If you created the subdirectory for your dogecoin_data files in another project's directory when setting up your developer environment, you either need to create them again or copy them into this project's folder.

Start the local Dogecoin regtest network:

dogecoind -datadir=$(pwd)/dogecoin_data -printtoconsole --port=18444

In another terminal, start dfx in your local development environment with the Dogecoin API enabled.

dfx start --clean --enable-dogecoin

In a third terminal, deploy the basic_dogecoin canister to your local development environment with the dfx deploy command and specify the regtest network as an init argument for the canister:

dfx deploy basic_dogecoin --argument '(variant { regtest })'

Congratulations! You have successfully deployed your first canister that can interact with Dogecoin.

Interacting with your canister

You can interact with your deployed canister using the Candid interface link provided when you deployed the canister. You can also use the dfx canister call command to call the canister methods from the command line, as explained below.

Generating a Dogecoin address

The basic_dogecoin example implements a function for generating a Dogecoin P2PKH address using the ecdsa_public_key API endpoint.

You can call this function from the command line:

dfx canister call basic_dogecoin get_p2pkh_address

Receiving dogecoins

In order to generate and receive dogecoins on your local Dogecoin regtest, you need to manually mine blocks. Dogecoin is issued as a reward for each new block mined.

Mining blocks

Block rewards are subject to the coinbase maturity rule: newly mined dogecoins can only be spent after 60 more blocks have been mined.

Use the following command to mine 61 blocks and distribute the block rewards to the Dogecoin address generated previously:

dogecoin-cli -datadir=$(pwd)/dogecoin_data generatetoaddress 61 <doge-address>

After mining blocks, their hash will be returned. In the dfx logs, you will see log entries confirming that the Dogecoin canister has ingested the newly mined blocks.

Then, check your dogecoin balance:

dfx canister call basic_dogecoin get_balance '("<doge-address>")'

Sending dogecoins

You can send dogecoins using the send_from_p2pkh_address function of the basic_dogecoin canister. For example, to send 1 DOGE (100,000,000 koinus) to the address mhXcJVuNA48bZsrKq4t21jx1neSqyceqTM, run the following command:

dfx canister call basic_dogecoin send_from_p2pkh_address '(record { destination_address = "mhXcJVuNA48bZsrKq4t21jx1neSqyceqTM"; amount_in_koinu = 100000000; })'

This command creates a transaction and sends it to your local Dogecoin regtest. The value returned is the hash of your transaction. Now, you need to mine a block so that your transaction is included in the blockchain:

dogecoin-cli -datadir=$(pwd)/dogecoin_data generate 1

To verify that the transaction was successfully mined, you can use the getblock command of dogecoin-cli, which requires knowing the block hash. You can get the latest block hash using the getbestblockhash command:

dogecoin-cli -datadir=$(pwd)/dogecoin_data getbestblockhash
dogecoin-cli -datadir=$(pwd)/dogecoin_data getblock <hash_obtained_from_getbestblockhash>

After executing these commands, you should see your transaction hash in the list of transactions included in the block. The first transaction in the list is the coinbase transaction which contains the block reward.

Getting block headers

You can retrieve block headers from the Dogecoin API using the get_block_headers function of the basic_dogecoin canister. For example, to get block headers from height 0 to height 10:

dfx canister call basic_dogecoin get_block_headers '(0:nat32, opt (10:nat32))'

Troubleshooting

It's often useful to delete the entire local Dogecoin state and start from scratch. To do this:

  • In the terminal running dfx, stop the process using Ctrl+C, then delete the .dfx folder in your project directory which contains the local state of dfx.
rm -rf .dfx
  • In the terminal running dogecoind, stop the daemon using Ctrl+C, then delete the regtest data folder located inside dogecoin_data.
rm -r dogecoin_data/regtest

Dogecoin API Endpoints

To be able to reach the Dogecoin network, your canister needs to target one of the available endpoints on the Dogecoin canister.

Dogecoin Canister

Dogecoin canister principal ID: gordg-fyaaa-aaaan-aaadq-cai

Testnet?

Dogecoin testnet is not supported.

Available Endpoints

dogecoin_get_utxos

Returns the UTXOs associated with a Dogecoin address. UTXOs can be filtered by minimum confirmations (only UTXOs with at least the provided number of confirmations are returned, with some upper bound which varies with the current difficulty target) or via a page reference when pagination is used for addresses with many UTXOs.

dogecoin_get_utxos_query

Queries dogecoin_get_utxos using a query call. Since this is a query call, it returns quickly but results are not trustworthy.

dogecoin_get_balance

Returns the balance of a Dogecoin address in koinus (1 DOGE = 100,000,000 koinus). Takes an optional argument min_confirmations which can be used to limit the set of considered UTXOs for the calculation of the balance to those with at least the provided number of confirmations.

dogecoin_get_balance_query

Queries dogecoin_get_balance using a query call. Since this is a query call, it returns quickly but results are not trustworthy.

dogecoin_get_current_fee_percentiles

Returns fee percentiles (in millikoinus/byte) from the most recent 1,000 Dogecoin transactions.

dogecoin_get_block_headers

Returns raw block headers for a given range of heights. At most 100 block headers are returned per request.

dogecoin_send_transaction

Sends a raw Dogecoin transaction to the specified network (mainnet or regtest).

Further references

See the Dogecoin canister interface specification for more details.

Cycles Cost

The costs of API calls in cycles and USD for the Dogecoin Mainnet APIs are presented in the following table. As a general principle for the Dogecoin API, some API calls must have a minimum number of cycles attached to them, as indicated in the column Minimum cycles to send with call. Requiring a relatively large minimum number of cycles makes it possible to change the pricing of API calls without breaking existing canisters when the Dogecoin subnet grows in terms of its replication factor in the future. Cycles not consumed by the call are returned to the caller.

The call for submitting a Dogecoin transaction to the Dogecoin network does not require a minimum number of cycles to send with the call as the charged cost is independent of the replication factor of the subnet.

The cost per API call in USD uses the XDR/USD exchange rate of November 25, 2025 (1 XDR = 1.411492 USD).

API callDescriptionPrice (Cycles)Price (USD)Minimum cycles to send with call
dogecoin_get_utxosRetrieve the UTXO set for a Dogecoin address50_000_000 + 1 cycle per Wasm instruction$0.00007058 + Wasm instruction cost10_000_000_000
dogecoin_get_current_fee_percentilesObtain the fee percentiles of the most recent transactions10_000_000$0.00001412100_000_000
dogecoin_get_balanceRetrieve the balance of a given Dogecoin address10_000_000$0.00001412100_000_000
dogecoin_send_transactionSubmit a Dogecoin transaction to the Dogecoin network, per transaction5_000_000_000$0.00706N/A
dogecoin_send_transactionSubmit a Dogecoin transaction to the Dogecoin network, per byte of payload20_000_000$0.00002823N/A
dogecoin_get_block_headersRetrieve the block headers in specified range50_000_000 + 1 cycle per Wasm instruction$0.00007058 + Wasm instruction cost10_000_000_000

Note

Fees for calling the dogecoin_get_utxos and dogecoin_get_block_headers endpoints depend on the number of Wasm instructions that the Dogecoin canister consumes when processing the requests to ensure fair charging.

Dogecoin Transactions

Generating a Dogecoin address

Dogecoin, like Bitcoin, doesn't use accounts; instead, it uses a UTXO model. A UTXO is a Dogecoin transaction output that is unspent.

Each UTXO is associated with a Dogecoin address that is derived from either a public key or a script that defines the conditions under which the UTXO can be spent. A Dogecoin address is often used as a single-use invoice instead of a persistent address to increase privacy.

Dogecoin P2PKH addresses

Pay-to-public-key-hash (P2PKH) addresses are the most common types of addresses in Dogecoin. On mainnet, they start with the prefix D. They encode the hash of an ECDSA public key.

Dogecoin P2SH addresses

Another type of address is pay-to-script-hash (P2SH) address. It encodes the hash of a Dogecoin script and starts with a A or 9 on mainnet. The script can define complex locking conditions such as multisig or timelocks.

Generating addresses with threshold ECDSA

To generate a Dogecoin address, you need to generate an ECDSA public key. An ECDSA public key can be retrieved using the ecdsa_public_key system API endpoint. The basic Dogecoin example demonstrates how to generate a P2PKH address from a public key:

#[update]
pub async fn get_p2pkh_address() -> String {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    // Unique derivation paths are used for every address type generated, to ensure
    // each address has its own unique key pair.
    let derivation_path = DerivationPath::p2pkh(0, 0);

    // Get the ECDSA public key of this smart contract at the given derivation path
    let public_key = get_ecdsa_public_key(&ctx, derivation_path.to_vec_u8_path()).await;

    // Convert the public key to the format used by the Dogecoin library
    let public_key = PublicKey::from_slice(&public_key).unwrap();

    // Generate a P2PKH address from the public key.
    // The address encoding (Base58) depends on the network type.
    Address::p2pkh(public_key, ctx.dogecoin_network).to_string()
}

View the source on GitHub: get_p2pkh_address.rs

/// Retrieves the ECDSA public key for the given derivation path from the ECDSA API.
///
/// This function checks the local in-memory cache first. If no cached key exists,
/// it queries the ECDSA API for the public key at the given derivation path
/// and stores the result in the cache.
pub async fn get_ecdsa_public_key(ctx: &DogecoinContext, derivation_path: Vec<Vec<u8>>) -> Vec<u8> {
    // Check in-memory cache first.
    if let Some(key) = ECDSA_KEY_CACHE.with_borrow(|map| map.get(&derivation_path).cloned()) {
        return key;
    }

    // Request the ECDSA public key from the ECDSA API.
    let public_key = management_canister::ecdsa_public_key(&EcdsaPublicKeyArgs {
        canister_id: None,
        derivation_path: derivation_path.clone(),
        key_id: EcdsaKeyId {
            curve: EcdsaCurve::Secp256k1,
            name: ctx.key_name.to_string(),
        },
    })
    .await
    .unwrap()
    .public_key;

    // Store it in the in-memory cache for future reuse.
    ECDSA_KEY_CACHE.with_borrow_mut(|map| {
        map.insert(derivation_path, public_key.clone());
    });

    public_key
}

View the source on GitHub: ecdsa.rs

Resources

Learn more about Dogecoin P2PKH addresses.

Learn more about the ecdsa_public_key API.

Creating Dogecoin Transactions

Unspent transaction outputs (UTXOs) are used as inputs to build Dogecoin transactions. Every Dogecoin transaction spends one or more UTXOs and in return creates new UTXOs. A UTXO exists until it is used as input in a transaction. In order to create a Dogecoin transaction, you need to:

  1. Get the available UTXOs corresponding to a Dogecoin address controlled by your canister using the dogecoin_get_utxos API endpoint.

  2. Calculate an appropriate transaction fee using the dogecoin_get_current_fee_percentiles API endpoint.

  3. Select a subset of the available UTXOs to spend that covers the transaction amount and fee.

  4. Create a transaction that spends the selected UTXOs and creates new UTXOs. You will need at least one for the recipient and, in most cases, one to collect the change.

A UTXO has the following structure:

// Unspent transaction output (UTXO).
pub struct Utxo {
    /// See [Outpoint].
    pub outpoint: Outpoint,
    /// Value in the units of koinu.
    pub value: Koinu,
    /// Height in the blockchain.
    pub height: u32,
}

/// Identifier of [Utxo].
pub struct Outpoint {
    /// Transaction Identifier.
    pub txid: Vec<u8>,
    /// The output index in the transaction.
    pub vout: u32,
}

Get available UTXOs

To get the available UTXOs for a Dogecoin address, use the dogecoin_get_utxos API endpoint. The following example demonstrates how to retrieve UTXOs for a given Dogecoin P2PKH address.

use crate::{dogecoin_get_utxos, DOGE_CONTEXT};
use ic_cdk::{
    bitcoin_canister::{GetUtxosRequest, GetUtxosResponse},
    update,
};

/// Returns the UTXOs of the given Dogecoin address.
#[update]
pub async fn get_utxos(address: String) -> GetUtxosResponse {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    dogecoin_get_utxos(&GetUtxosRequest {
        address,
        network: ctx.network.into(),
        filter: None,
    })
    .await
    .unwrap()
}

View the source on GitHub: get_utxo.rs

pub async fn dogecoin_get_utxos(arg: &GetUtxosRequest) -> CallResult<GetUtxosResponse> {
    let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network));
    // same cycles cost as for the Bitcoin canister
    let cycles = bitcoin_canister::cost_get_utxos(arg);
    Ok(Call::bounded_wait(canister_id, "dogecoin_get_utxos")
        .with_arg(arg)
        .with_cycles(cycles)
        .await?
        .candid()?)
}

View the source on GitHub: lib.rs

Calculate transaction fee per byte

The transaction fee of a Dogecoin transaction is calculated based on the size of the transaction in bytes. An appropriate fee per byte can be determined by looking at the fees of recent transactions on the Dogecoin mainnet. The following snippet shows how to estimate the fee per byte for a transaction using the dogecoin_get_current_fee_percentiles API endpoint and choosing the 50th percentile.

use crate::{dogecoin_get_fee_percentiles, MillikoinuPerByte, DOGE_CONTEXT};
use ic_cdk::{bitcoin_canister::GetCurrentFeePercentilesRequest, update};

/// Returns the 100 fee percentiles measured in millikoinu/byte.
/// Percentiles are computed from the last 10,000 transactions (if available).
#[update]
pub async fn get_current_fee_percentiles() -> Vec<MillikoinuPerByte> {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    dogecoin_get_fee_percentiles(&GetCurrentFeePercentilesRequest {
        network: ctx.network.into(),
    })
    .await
    .unwrap()
}

View the source on GitHub: get_current_fee_percentiles.rs

pub async fn dogecoin_get_fee_percentiles(
    arg: &GetCurrentFeePercentilesRequest,
) -> CallResult<Vec<MillikoinuPerByte>> {
    let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network));
    // same cycles cost as for the Bitcoin canister
    let cycles = bitcoin_canister::cost_get_current_fee_percentiles(arg);
    Ok(
        Call::bounded_wait(canister_id, "dogecoin_get_current_fee_percentiles")
            .with_arg(arg)
            .with_cycles(cycles)
            .await?
            .candid()?,
    )
}

View the source on GitHub: lib.rs

Build the transaction

Now the transaction can be built. Since the fee of a transaction is based on its size, the transaction has to be built iteratively and signed with a mock signer that adds the respective size of the signature. Each selected UTXO is used as an input for the transaction and requires a signature.

The following snippet shows a simplified version of how to build a transaction that will be signed by a P2PKH address:

// Builds a transaction to send the given `amount` of koinu to the
// destination address.
pub async fn build_transaction(
    ctx: &DogecoinContext,
    own_public_key: &PublicKey,
    own_address: &Address,
    own_utxos: &[Utxo],
    primary_output: &PrimaryOutput,
    fee_per_byte: MillisatoshiPerByte,
) -> Transaction {
    // We have a chicken-and-egg problem where we need to know the length
    // of the transaction in order to compute its proper fee, but we need
    // to know the proper fee in order to figure out the inputs needed for
    // the transaction.
    //
    // We solve this problem iteratively. We start with a fee of zero, build
    // and sign a transaction, see what its size is, and then update the fee,
    // rebuild the transaction, until the fee is set to the correct amount.

    let amount = match primary_output {
        PrimaryOutput::Address(_, amt) => *amt, // grab the amount
        PrimaryOutput::OpReturn(_) => trap("expected an address output, got OP_RETURN"),
    };

    let mut fee = 0;
    loop {
        let utxos_to_spend = select_utxos_greedy(own_utxos, amount, fee).unwrap();
        let transaction =
            build_transaction_with_fee(utxos_to_spend, own_address, primary_output, fee).unwrap();

        // Sign the transaction. In this case, we only care about the size
        // of the signed transaction, so we use a mock signer here for efficiency.
        let signed_transaction = sign_transaction(
            ctx,
            own_public_key,
            own_address,
            transaction.clone(),
            vec![], // mock derivation path
            mock_sign_with_ecdsa,
        )
        .await;

        let tx_vsize = signed_transaction.vsize() as u64;

        if (tx_vsize * fee_per_byte) / 1000 == fee {
            return transaction;
        } else {
            fee = (tx_vsize * fee_per_byte) / 1000;
        }
    }
}

View the source on GitHub: p2pkh.rs

Signing Transactions

Before a transaction can be sent to the Dogecoin network, each input must be signed. Canisters can sign transactions with threshold ECDSA through the sign_with_ecdsa system API endpoint.

Threshold ECDSA

The following snippet shows a simplified example of how to sign a Dogecoin transaction where all UTXOs are owned by own_address and own_address is a P2PKH address.

pub async fn sign_transaction<SignFun, Fut>(
    ctx: &DogecoinContext,
    own_public_key: &PublicKey,
    own_address: &Address,
    mut transaction: Transaction,
    derivation_path: Vec<Vec<u8>>,
    signer: SignFun,
) -> Transaction
where
    SignFun: Fn(String, Vec<Vec<u8>>, Vec<u8>) -> Fut,
    Fut: std::future::Future<Output = SecpSignature>,
{
    assert_eq!(
        own_address.address_type(),
        Some(AddressType::P2pkh),
        "Only P2PKH addresses are supported"
    );

    let transaction_clone = transaction.clone();
    let sighash_cache = SighashCache::new(&transaction_clone);

    for (index, input) in transaction.input.iter_mut().enumerate() {
        let sighash = sighash_cache
            .legacy_signature_hash(
                index,
                &own_address.script_pubkey(),
                EcdsaSighashType::All.to_u32(),
            )
            .unwrap();

        let signature = signer(
            ctx.key_name.to_string(),
            derivation_path.clone(),
            sighash.as_byte_array().to_vec(),
        )
        .await;

        let mut signature = signature.serialize_der().to_vec();
        signature.push(EcdsaSighashType::All.to_u32() as u8);

        let sig_bytes = PushBytesBuf::try_from(signature).unwrap();
        let pubkey_bytes = PushBytesBuf::try_from(own_public_key.to_bytes()).unwrap();

        input.script_sig = Builder::new()
            .push_slice(sig_bytes)
            .push_slice(pubkey_bytes)
            .into_script();
    }

    transaction
}

View the source on GitHub: p2pkh.rs

The signature function signer: SignFun passed as argument is shown below. This function makes a call to the sign_with_ecdsa system API endpoint to sign the provided transaction sighash.

/// Signs a 32-byte message hash using the ECDSA key derived from the given path.
///
/// This function uses the ICP ECDSA signing API to produce a compact, 64-byte signature.
pub async fn sign_with_ecdsa(
    key_name: String,
    derivation_path: Vec<Vec<u8>>,
    message_hash: Vec<u8>,
) -> Signature {
    let signature = management_canister::sign_with_ecdsa(&SignWithEcdsaArgs {
        message_hash,
        derivation_path,
        key_id: EcdsaKeyId {
            curve: EcdsaCurve::Secp256k1,
            name: key_name,
        },
    })
    .await
    .unwrap()
    .signature;

    Signature::from_compact(&signature).unwrap()
}

View the source on GitHub: ecdsa.rs

Resources

Complete flow

The following snippet shows the full process, from generating a transaction to submitting it to the Dogecoin network:

use crate::{
    common::{get_fee_per_byte, DerivationPath, PrimaryOutput},
    dogecoin_get_utxos, dogecoin_send_transaction,
    ecdsa::{get_ecdsa_public_key, sign_with_ecdsa},
    p2pkh::{self},
    SendRequest, DOGE_CONTEXT,
};
use bitcoin::{consensus::serialize, dogecoin::Address, PublicKey};
use ic_cdk::{
    bitcoin_canister::{GetUtxosRequest, SendTransactionRequest},
    trap, update,
};
use std::str::FromStr;

/// Sends the given amount of dogecoin from this smart contract's P2PKH address to the given address.
/// Returns the transaction ID.
#[update]
pub async fn send_from_p2pkh_address(request: SendRequest) -> String {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    if request.amount_in_koinu == 0 {
        trap("Amount must be greater than 0");
    }

    // Parse and validate the destination address. The address type needs to be
    // valid for the Dogecoin network we are on.
    let dst_address = Address::from_str(&request.destination_address)
        .unwrap()
        .require_network(ctx.dogecoin_network)
        .unwrap();

    // Unique derivation paths are used for every address type generated, to ensure
    // each address has its own unique key pair. To generate a user-specific address,
    // you would typically use a derivation path based on the user's identity or some other unique identifier.
    let derivation_path = DerivationPath::p2pkh(0, 0);

    // Get the ECDSA public key of this smart contract at the given derivation path.
    let own_public_key = get_ecdsa_public_key(&ctx, derivation_path.to_vec_u8_path()).await;

    // Convert the public key to the format used by the rust-dogecoin library.
    let own_public_key = PublicKey::from_slice(&own_public_key).unwrap();

    // Generate a P2PKH address from the public key.
    let own_address = Address::p2pkh(own_public_key, ctx.dogecoin_network);

    // Note that pagination may have to be used to get all UTXOs for the given address.
    // For the sake of simplicity, it is assumed here that the `utxo` field in the response
    // contains all UTXOs.
    let own_utxos = dogecoin_get_utxos(&GetUtxosRequest {
        address: own_address.to_string(),
        network: ctx.network.into(),
        filter: None,
    })
    .await
    .unwrap()
    .utxos;

    // Build the transaction.
    let fee_per_byte = get_fee_per_byte(&ctx).await;
    let transaction = p2pkh::build_transaction(
        &ctx,
        &own_public_key,
        &own_address,
        &own_utxos,
        &PrimaryOutput::Address(dst_address, request.amount_in_koinu),
        fee_per_byte,
    )
    .await;

    // Sign the transaction.
    let signed_transaction = p2pkh::sign_transaction(
        &ctx,
        &own_public_key,
        &own_address,
        transaction,
        derivation_path.to_vec_u8_path(),
        sign_with_ecdsa,
    )
    .await;

    // Send the transaction to the Dogecoin API.
    dogecoin_send_transaction(&SendTransactionRequest {
        network: ctx.network.into(),
        transaction: serialize(&signed_transaction),
    })
    .await
    .unwrap();

    // Return the transaction ID.
    signed_transaction.compute_txid().to_string()
}

View the source on GitHub: send_from_p2pkh_address.rs

/// Input structure for sending Dogecoin.
/// Used in P2PKH transfer endpoint.
#[derive(candid::CandidType, candid::Deserialize)]
pub struct SendRequest {
    pub destination_address: String,
    pub amount_in_koinu: u64,
}

View the source on GitHub: lib.rs

To submit transactions to the Dogecoin network, the Dogecoin API exposes the dogecoin_send_transaction method.

pub async fn dogecoin_send_transaction(arg: &SendTransactionRequest) -> CallResult<()> {
    let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network));
    // same cycles cost as for the Bitcoin canister
    let cycles = bitcoin_canister::cost_send_transaction(arg);

    Ok(
        Call::unbounded_wait(canister_id, "dogecoin_send_transaction")
            .with_arg(arg)
            .with_cycles(cycles)
            .await?
            .candid()?,
    )
}

View the source on GitHub: lib.rs

Reading the Dogecoin State

Canisters can query information about the Dogecoin mainnet programmatically.

Reading unspent transaction outputs (UTXOs)

To read unspent transaction outputs (UTXOs) associated with an address from the Dogecoin network, make a call to the dogecoin_get_utxos Dogecoin API method.

use crate::{dogecoin_get_utxos, DOGE_CONTEXT};
use ic_cdk::{
    bitcoin_canister::{GetUtxosRequest, GetUtxosResponse},
    update,
};

/// Returns the UTXOs of the given Dogecoin address.
#[update]
pub async fn get_utxos(address: String) -> GetUtxosResponse {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    dogecoin_get_utxos(&GetUtxosRequest {
        address,
        network: ctx.network.into(),
        filter: None,
    })
    .await
    .unwrap()
}

View the source on GitHub: get_utxo.rs

/// Gets the UTXOs of a specified address.
///
/// **Bounded-wait call**
///
/// Check the [Dogecoin Canisters Interface Specification](https://github.com/dfinity/dogecoin-canister/blob/master/INTERFACE_SPECIFICATION.md#dogecoin_get_utxos) for more details.
pub async fn dogecoin_get_utxos(arg: &GetUtxosRequest) -> CallResult<GetUtxosResponse> {
    let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network));
    // same cycles cost as for the Bitcoin canister
    let cycles = bitcoin_canister::cost_get_utxos(arg);
    Ok(Call::bounded_wait(canister_id, "dogecoin_get_utxos")
        .with_arg(arg)
        .with_cycles(cycles)
        .await?
        .candid()?)
}

View the source on GitHub: lib.rs

Reading current balance

To read the current balance of a Dogecoin address, make a call to the dogecoin_get_balance Dogecoin API method.

use crate::{dogecoin_get_balance, Amount, DOGE_CONTEXT};
use ic_cdk::{bitcoin_canister::GetBalanceRequest, update};

/// Returns the balance of the given Dogecoin address.
#[update]
pub async fn get_balance(address: String) -> Amount {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    dogecoin_get_balance(&GetBalanceRequest {
        address,
        network: ctx.network.into(),
        min_confirmations: None,
    })
    .await
    .unwrap()
}

View the source on GitHub: get_balance.rs

/// Gets the current balance of a Dogecoin address in Koinu.
///
/// **Bounded-wait call**
///
/// Check the [Dogecoin Canisters Interface Specification](https://github.com/dfinity/dogecoin-canister/blob/master/INTERFACE_SPECIFICATION.md#dogecoin_get_balance) for more details.
pub async fn dogecoin_get_balance(arg: &GetBalanceRequest) -> CallResult<Amount> {
    let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network));
    // same cycles cost as for the Bitcoin canister
    let cycles = bitcoin_canister::cost_get_balance(arg);
    Ok(Call::bounded_wait(canister_id, "dogecoin_get_balance")
        .with_arg(arg)
        .with_cycles(cycles)
        .await?
        .candid()?)
}

View the source on GitHub: lib.rs

Reading the fee percentiles

The transaction fees on the Dogecoin network change dynamically based on the number of pending transactions. In order to get fee percentiles of the last 1,000 transactions, call the dogecoin_get_current_fee_percentiles Dogecoin API method.

This endpoint returns 101 numbers that are fees measured in millikoinus (1,000 millikoinus = 1 koinu; 100,000,000 koinus = 1 DOGE) per byte. The ith element of the result corresponds to the ith percentile fee. For example, to get the median fee over the last few blocks, look at the 50th element of the result.

use crate::{dogecoin_get_fee_percentiles, MillikoinuPerByte, DOGE_CONTEXT};
use ic_cdk::{bitcoin_canister::GetCurrentFeePercentilesRequest, update};

/// Returns the 100 fee percentiles measured in millikoinu/byte.
/// Percentiles are computed from the last 10,000 transactions (if available).
#[update]
pub async fn get_current_fee_percentiles() -> Vec<MillikoinuPerByte> {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    dogecoin_get_fee_percentiles(&GetCurrentFeePercentilesRequest {
        network: ctx.network.into(),
    })
    .await
    .unwrap()
}

View the source on GitHub: get_current_fee_percentiles.rs

/// Gets the current transaction fee percentiles on the Dogecoin network.
///
/// **Bounded-wait call**
///
/// Check the [Dogecoin Canisters Interface Specification](https://github.com/dfinity/dogecoin-canister/blob/master/INTERFACE_SPECIFICATION.md#dogecoin_get_current_fee_percentiles) for more details.
pub async fn dogecoin_get_fee_percentiles(
    arg: &GetCurrentFeePercentilesRequest,
) -> CallResult<Vec<MillikoinuPerByte>> {
    let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network));
    // same cycles cost as for the Bitcoin canister
    let cycles = bitcoin_canister::cost_get_current_fee_percentiles(arg);
    Ok(
        Call::bounded_wait(canister_id, "dogecoin_get_current_fee_percentiles")
            .with_arg(arg)
            .with_cycles(cycles)
            .await?
            .candid()?,
    )
}

View the source on GitHub: lib.rs

Reading the block headers

To read the block headers within a provided range of start and end heights, make a call to the dogecoin_get_block_headers Dogecoin API method. Note that at most 100 block headers are returned per request.

use crate::{dogecoin_get_block_headers, DOGE_CONTEXT};
use ic_cdk::{
    bitcoin_canister::{GetBlockHeadersRequest, GetBlockHeadersResponse},
    update,
};

/// Returns the block headers in the given height range.
#[update]
pub async fn get_block_headers(
    start_height: u32,
    end_height: Option<u32>,
) -> GetBlockHeadersResponse {
    let ctx = DOGE_CONTEXT.with(|ctx| ctx.get());

    dogecoin_get_block_headers(&GetBlockHeadersRequest {
        start_height,
        end_height,
        network: ctx.network.into(),
    })
    .await
    .unwrap()
}

View the source on GitHub: get_block_headers.rs

/// Gets the block headers in the provided range of block heights.
///
/// **Bounded-wait call**
///
/// Check the [Dogecoin Canisters Interface Specification](https://github.com/dfinity/dogecoin-canister/blob/master/INTERFACE_SPECIFICATION.md#dogecoin_get_block_headers) for more details.
pub async fn dogecoin_get_block_headers(
    arg: &GetBlockHeadersRequest,
) -> CallResult<GetBlockHeadersResponse> {
    let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network));
    // same cycles cost as for the Bitcoin canister
    let cycles = bitcoin_canister::cost_get_block_headers(arg);
    Ok(
        Call::bounded_wait(canister_id, "dogecoin_get_block_headers")
            .with_arg(arg)
            .with_cycles(cycles)
            .await?
            .candid()?,
    )
}

View the source on GitHub: lib.rs