snap-coin-pay 1.0.0

A simple way to handle deposits and withdrawals on the Snap Coin Network
Documentation

Snap Coin Pay

A payment management library to make deposits and withdrawals easy on the Snap Coin Network!

General

You probably also want to add the snap-coin, async-trait, and tokio libraries, since these are quite essential.

Reliability

Deposits

Deposits are scanned atomically - even if the deposit processor is restarted or stopped for any amount of time, it will re-sync to the chain head and confirm all deposit addresses that were previously defined. Make sure you store all deposit addresses and re-add them on restart.

Withdrawals

Withdrawals are also handled atomically. Each withdrawal is assigned a stable WithdrawalId (UUID) that persists across resubmissions. If a transaction expires from the mempool or is dropped, it is automatically resubmitted. The processor resumes correctly after a restart, scanning from where it left off.


Usage

We must have some connection to the Snap Coin Network. There are two standardized ways to create a ChainInteraction struct:

Snap Coin API Client

Great when you need more than one instance of this program running.

use snap_coin_pay::chain_interaction::ApiChainInteraction;

let chain_interaction = ApiChainInteraction::new("127.0.0.1:3003".parse().unwrap());

Snap Coin Embedded Node

Best for maximum performance and uptime.

use snap_coin::full_node::{SharedBlockchain, create_full_node, node_state::SharedNodeState};
use snap_coin_pay::chain_interaction::NodeChainInteraction;

let (blockchain, node_state): (SharedBlockchain, SharedNodeState) =
    create_full_node("./node-snap-coin-pay", false);

let chain_interaction = NodeChainInteraction::new(node_state.clone(), blockchain.clone());

Deposits

Handling incoming deposit transactions can be tricky - re-orgs, expiration, and confirmations are all handled for you by the DepositPaymentProcessor.

Define your confirmation logic:

use snap_coin_pay::deposit_payment_processor::{DepositPaymentProcessor, OnConfirmation};

#[derive(Clone)]
struct ConfirmationHandler {}

#[async_trait]
impl OnConfirmation for ConfirmationHandler {
    async fn on_confirmation(&self, deposit_address: Public, transaction: Transaction) {
        println!("Confirmed deposit! Transaction:\n{:#?}", transaction);
        // Add your business logic here
    }
}

Create and start the processor:

let on_confirmation = ConfirmationHandler {};

// Pass an optional path for the scanned-height save file (defaults to ./snap-pay-deposit-scanned-height)
let deposit_processor = DepositPaymentProcessor::create(chain_interaction, None, None);

// 10 is the number of confirmations required. 10–20 is typical depending on your application.
deposit_processor.start(10, on_confirmation).await?;

Add deposit addresses and poll for status:

// Create a deposit address - in a real app, store this somewhere persistent
let deposit_address = Private::new_random().to_public();

println!("Deposit address: {}", deposit_address.dump_base36());

// Tell the processor to watch this address.
// Note: this does NOT persist across restarts - re-add all addresses on every startup.
deposit_processor.add_deposit_address(deposit_address).await;

println!("Waiting for deposit...");
loop {
    println!(
        "Deposit status: {:?}",
        deposit_processor.get_deposit_status(deposit_address).await
    );
    tokio::time::sleep(Duration::from_secs(3)).await;
}

Additional DepositPaymentProcessor methods:

  • processor.stop() - Stop the processor and all underlying tasks.
  • processor.remove_deposit_address(deposit_address: Public) - Stop watching an address.

Withdrawals

Snap Coin Pay supports fully atomic withdrawals. Transactions that expire from the mempool are automatically resubmitted. Each withdrawal has a stable WithdrawalId you can use to query status at any time. The WithdrawalPaymentProcessor has persistent state of all the ongoing withdrawals, so you do not need to store these manually. The processor is atomic, meaning it can be stopped mid withdrawals, and it will still handle everything correctly. The withdrawal processor also automatically logs all withdrawals to the withdrawal log, with the sender's private key, for emergency recovery and monitoring.

Define your confirmation logic:

use snap_coin_pay::withdrawal_payment_processor::{
    WithdrawalPaymentProcessor, OnWithdrawalConfirmation, WithdrawalId,
};

#[derive(Clone)]
struct WithdrawalConfirmationHandler {}

#[async_trait]
impl OnWithdrawalConfirmation for WithdrawalConfirmationHandler {
    async fn on_confirmation(&self, withdrawal_id: WithdrawalId, transaction: Transaction) {
        println!("Confirmed withdrawal ({})! {:#?}", withdrawal_id, transaction);
        // Add your business logic here
    }
}

Create and start the processor:

let on_confirmation = WithdrawalConfirmationHandler {};

// Pass optional paths for the DB, scanned-height save files and the withdrawal log
let withdrawal_processor = WithdrawalPaymentProcessor::create(chain_interaction, None, None, None);

// 10 is the number of confirmations required. 10–20 is typical.
withdrawal_processor.start(10, on_confirmation).await?;

Submit a withdrawal and poll for status:

let wallet_private =
    Private::new_from_base36("jw4iipqhquxz60pw3b5vt531fy5z58w9zu56qfm631adq71pz").unwrap();
let receiver =
    Public::new_from_base36("2j25rf3xur2ytvb8yg31l6h61t6944gfshufypdlzyqp6jdm08").unwrap();

println!("Starting withdrawal...");

// submit_withdrawal returns a WithdrawalId (UUID) you can use to track status
let withdrawal_id = withdrawal_processor
    .submit_withdrawal(vec![(receiver, 1)], wallet_private) // amount is in NANO (smallest unit)
    .await?;

loop {
    println!(
        "Withdrawal status: ({withdrawal_id}) {:?}",
        withdrawal_processor.get_withdrawal_status(withdrawal_id).await
    );
    tokio::time::sleep(Duration::from_secs(3)).await;
}

Additional WithdrawalPaymentProcessor methods:

  • processor.stop() - Stop the processor and all underlying tasks.
  • processor.untrack_withdrawal(withdrawal_id: WithdrawalId) - Remove a withdrawal from tracking (e.g. after confirmed and fully processed).
  • processor.get_all_withdrawals() - Returns a HashMap<WithdrawalId, WithdrawalStatus> of all tracked withdrawals.

Withdrawal statuses

  • Pending { transaction } - Submitted and waiting in the mempool.
  • Confirming { transaction } - Included in a block, accumulating confirmations.
  • Expired - Dropped from the mempool; being automatically resubmitted.
  • Confirmed { transaction } - Finalized with sufficient confirmations.