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:
-
icp-cli, the command-line tool used to create, deploy, and manage canisters and to run a local development network. Install it with:
npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm -
The Rust toolchain with the
wasm32-unknown-unknowntarget for compiling canisters:rustup target add wasm32-unknown-unknown -
A local Dogecoin regtest node (
dogecoind). You can either let icp-cli run one for you inside a Docker container (recommended, see below) or run your own.
On macOS, a clang with WebAssembly support is required to compile the secp256k1-sys C library for the wasm32-unknown-unknown target. Xcode's bundled clang does not include the WebAssembly backend. Install the Homebrew LLVM and add it to your PATH:
brew install llvm
export PATH="$(brew --prefix llvm)/bin:$PATH"
Add the export line to your shell profile (~/.zshrc or ~/.bashrc) to make it permanent.
Running a local Dogecoin regtest network
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.
icp-cli runs the ICP Dogecoin integration (a local instance of the Dogecoin API, deployed as a canister for your application to interact with) as part of its managed local network. There are two ways to provide the underlying dogecoind regtest node.
Option 1: Docker image that bundles dogecoind (recommended)
This is the approach used by the basic_dogecoin example. A custom network-launcher Docker image bundles dogecoind together with the network launcher, so a single icp network start command brings up both the local IC network and a regtest Dogecoin node — you do not need to install or run dogecoind yourself. This is especially convenient on macOS, where there are no official dogecoind binaries.
The example wires this up in icp.yaml by pointing its local network at the custom image:
networks:
- name: local
mode: managed
image: icp-cli-network-launcher-dogecoin
See Deploy your first app locally for the full workflow (building the image, starting the network, and deploying).
Option 2: Run your own dogecoind and point icp-cli at it
If you prefer to manage the node yourself, run a local dogecoind regtest network and connect icp-cli's default (non-Docker) network launcher to it with the dogecoind-addr field:
networks:
- name: local
mode: managed
dogecoind-addr:
- "127.0.0.1:18444"
dogecoind-addr takes the node's P2P address (not its RPC endpoint). For the full set of options, see the icp-cli configuration reference.
The steps below set up such a node.
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 macOS. Either use the Docker image described in Option 1, or build Dogecoin Core from source by following 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 (and update dogecoind-addr to match).
Deploy your first app 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.
This walkthrough uses the recommended Docker-based setup, where icp-cli runs both the local IC network and a bundled dogecoind regtest node in a single container. (If you would rather run your own dogecoind, see Option 2 in the developer environment.)
Deploying your canister locally
First, navigate into the examples/basic_dogecoin subdirectory of the Dogecoin canister repo:
cd examples/basic_dogecoin
Build the custom network-launcher image. This bundles dogecoind with the network launcher and only needs to be done once (re-run it to pick up new releases):
./build-image.sh
Start the local network in the background. This launches the local IC network together with the regtest Dogecoin node:
icp network start -d
Deploy the basic_dogecoin canister. The local environment in icp.yaml already initializes it with the regtest network argument:
icp deploy --cycles 30t
Congratulations! You have successfully deployed your first canister that can interact with Dogecoin.
If a call later fails with an out-of-cycles error, top up the canister and retry:
icp canister top-up --amount 30t backend
Interacting with your canister
You can interact with your deployed canister using the Candid interface link printed when you deployed the canister. You can also use the icp canister call command to call the canister methods from the command line, as explained below.
The mining and inspection commands below talk to the bundled dogecoind through the container. Capture the container ID once:
CONTAINER=$(docker ps --filter "ancestor=icp-cli-network-launcher-dogecoin" --format "{{.ID}}" | head -1)
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:
icp canister call backend 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:
docker exec $CONTAINER dogecoin-cli -regtest \
-rpcuser=ic-doge-integration -rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E= \
generatetoaddress 61 <doge-address>
The IC Dogecoin integration ingests the newly mined blocks continuously. Then, check your dogecoin balance:
icp canister call backend 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:
icp canister call backend 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:
docker exec $CONTAINER dogecoin-cli -regtest \
-rpcuser=ic-doge-integration -rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E= \
generatetoaddress 1 <doge-address>
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:
docker exec $CONTAINER dogecoin-cli -regtest \
-rpcuser=ic-doge-integration -rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E= getbestblockhash
docker exec $CONTAINER dogecoin-cli -regtest \
-rpcuser=ic-doge-integration -rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E= 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:
icp canister call backend get_block_headers '(0:nat32, opt (10:nat32))'
Troubleshooting
It's often useful to delete the entire local state and start from scratch. To do this, stop the network (this also stops the bundled dogecoind):
icp network stop
Then start it again to get a fresh regtest chain and a clean IC state:
icp network start -d
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::update;
use ic_cdk_bitcoin_canister::{GetUtxosRequest, GetUtxosResponse};
/// 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_utxos.rs
pub async fn dogecoin_get_utxos(arg: &GetUtxosRequest) -> CallResult<GetUtxosResponse> {
let canister_id = get_dogecoin_canister_id(&into_dogecoin_network(arg.network.into()));
// 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::update;
use ic_cdk_bitcoin_canister::GetCurrentFeePercentilesRequest;
/// 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.into()));
// 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::{trap, update};
use ic_cdk_bitcoin_canister::{GetUtxosRequest, SendTransactionRequest};
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.into()));
// 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::update;
use ic_cdk_bitcoin_canister::{GetUtxosRequest, GetUtxosResponse};
/// 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_utxos.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.into()));
// 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::update;
use ic_cdk_bitcoin_canister::GetBalanceRequest;
/// 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.into()));
// 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::update;
use ic_cdk_bitcoin_canister::GetCurrentFeePercentilesRequest;
/// 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.into()));
// 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::update;
use ic_cdk_bitcoin_canister::{GetBlockHeadersRequest, GetBlockHeadersResponse};
/// 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.into()));
// 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