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:
-
Generate a Dogecoin address. Dogecoin addresses are necessary for your canister to sign transactions and hold DOGE. A canister can have multiple addresses.
-
Create a Dogecoin transaction. Dogecoin transactions spend UTXOs and create new UTXOs. A UTXO is the output of a Dogecoin transaction. It exists until it is used as the input of another transaction.
-
Sign the transaction using threshold ECDSA API. All inputs of a transaction must be signed before the transaction can be submitted to the Dogecoin network.
-
Submit the transaction by sending a request to the Dogecoin API that specifies the blob of the transaction and the target Dogecoin network (mainnet or regtest).
-
Read information from the Dogecoin network, such as UTXOs, address balances, or block headers.
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"
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 ofdogecoind,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 itsget_configendpoint.
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.
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.
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.dfxfolder in your project directory which contains the local state ofdfx.
rm -rf .dfx
- In the terminal running
dogecoind, stop the daemon using Ctrl+C, then delete theregtestdata folder located insidedogecoin_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 principal ID: gordg-fyaaa-aaaan-aaadq-cai
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).
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 call | Description | Price (Cycles) | Price (USD) | Minimum cycles to send with call |
|---|---|---|---|---|
dogecoin_get_utxos | Retrieve the UTXO set for a Dogecoin address | 50_000_000 + 1 cycle per Wasm instruction | $0.00007058 + Wasm instruction cost | 10_000_000_000 |
dogecoin_get_current_fee_percentiles | Obtain the fee percentiles of the most recent transactions | 10_000_000 | $0.00001412 | 100_000_000 |
dogecoin_get_balance | Retrieve the balance of a given Dogecoin address | 10_000_000 | $0.00001412 | 100_000_000 |
dogecoin_send_transaction | Submit a Dogecoin transaction to the Dogecoin network, per transaction | 5_000_000_000 | $0.00706 | N/A |
dogecoin_send_transaction | Submit a Dogecoin transaction to the Dogecoin network, per byte of payload | 20_000_000 | $0.00002823 | N/A |
dogecoin_get_block_headers | Retrieve the block headers in specified range | 50_000_000 + 1 cycle per Wasm instruction | $0.00007058 + Wasm instruction cost | 10_000_000_000 |
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:
-
Get the available UTXOs corresponding to a Dogecoin address controlled by your canister using the
dogecoin_get_utxosAPI endpoint. -
Calculate an appropriate transaction fee using the
dogecoin_get_current_fee_percentilesAPI endpoint. -
Select a subset of the available UTXOs to spend that covers the transaction amount and fee.
-
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