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