rust-sdk-4mica

Crates.iorust-sdk-4mica
lib.rsrust-sdk-4mica
version0.4.0
created_at2025-10-14 12:45:36.026345+00
updated_at2026-01-14 14:24:35.685523+00
descriptionOfficial Rust SDK for interacting with the 4Mica payment network.
homepagehttps://4mica.xyz
repositoryhttps://github.com/4mica-Network/4mica-core
max_upload_size
id1882213
size297,490
Mairon (mahzoun)

documentation

README

Crates.io

4Mica Rust SDK

The official Rust SDK for interacting with the 4Mica payment network

Overview

4Mica is a payment network that enables cryptographically-enforced line of credit of autonomos payments. The SDK provides:

  • User Client: Deposit collateral, sign payments, and manage withdrawals in ETH or ERC20 tokens
  • Recipient Client: Create payment tabs, verify payment guarantees, and claim from user collateral when payments aren't fulfilled
  • X402 Flow Helper: Generate X-PAYMENT headers for 402-protected HTTP resources via an X402-compatible service

Installation

Add the SDK to your Cargo.toml:

[dependencies]
rust-sdk-4mica = "0.3.3"

Initialization and Configuration

The SDK requires a signing key and can use sensible defaults for the rest:

  • wallet_private_key (required): Private key for signing transactions (hex string with or without 0x prefix)
  • rpc_url (optional): URL of the 4Mica RPC server. Defaults to https://api.4mica.xyz/; override for local development.

The following parameters are optional and will be automatically fetched from the server if not provided.

  • ethereum_http_rpc_url: URL of the Ethereum JSON-RPC endpoint (optional)
  • contract_address: Address of the deployed Core4Mica smart contract (optional)

Note: You normally don't need to provide ethereum_http_rpc_url and contract_address as the SDK will fetch these from the server automatically. Only override these if you need to use different values than the server's defaults.

The Ethereum chain_id is fetched from the core service and validated against the connected Ethereum provider automatically.

Configuration Methods

1. Using ConfigBuilder

use rust_sdk_4mica::{Config, ConfigBuilder, Client};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ConfigBuilder::default()
        .wallet_private_key("your_private_key".to_string())
        .build()?;

    let client = Client::new(config).await?;
    Ok(())
}

2. Using Environment Variables

Set the following environment variables:

export 4MICA_WALLET_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
# Optional (defaults to https://api.4mica.xyz/ if not set; override for local dev)
export 4MICA_RPC_URL="http://localhost:3000"
export 4MICA_ETHEREUM_HTTP_RPC_URL="http://localhost:8545"
export 4MICA_CONTRACT_ADDRESS="0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0"

Then in your code:

use rust_sdk_4mica::{ConfigBuilder, Client};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ConfigBuilder::default()
        .from_env()  // Loads environment variables
        .build()?;

    let client = Client::new(config).await?;
    Ok(())
}

Usage

The SDK provides client interfaces for both sides of the payment flow plus a helper to bridge HTTP 402 resources:

  • UserClient: payer controls collateral and signs payments
  • RecipientClient: payment recipient creates tabs, verify guarantees, and claims collateral
  • X402Flow: builds X-PAYMENT headers for X402-protected HTTP endpoints

X402 flow (HTTP 402)

The X402 helper turns the paymentRequirements emitted by a 402 Payment Required response into an X-PAYMENT header (and optional /settle call) that the facilitator will accept. The examples in https://github.com/4mica-Network/x402-4mica/examples model the expected flow: the client speaks to the resource server, and the resource server talks to the facilitator for /tabs, /verify, and /settle.

What the SDK expects from paymentRequirements

rust-sdk-4mica accepts the canonical X402 JSON (camelCase). At minimum you need:

  • scheme and network: scheme must contain 4mica (e.g. 4mica-credit) X402Flow will refresh the tab by calling extra.tabEndpoint before signing.

End-to-end client flow

Typical sequence (as in https://github.com/4mica-Network/x402-4mica/examples/server/mock_paid_api.py):

  • GET the protected endpoint; capture paymentRequirementsTemplate.
  • POST tabEndpoint with { userAddress, paymentRequirements } to mint requirements bound to your wallet.
  • Call X402Flow::sign_payment to get the base64 X-PAYMENT header.
  • Retry the protected endpoint with X-PAYMENT; the resource server will call the facilitator /verify and /settle.
use rust_sdk_4mica::{Client, ConfigBuilder, X402Flow};
use rust_sdk_4mica::x402::PaymentRequirements;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1) Core client (only the payer key is required by default)
    let payer = Client::new(
        ConfigBuilder::default()
            .wallet_private_key(std::env::var("PAYER_KEY")?)
            .build()?,
    )
    .await?;

    let payment_requirements: PaymentRequirements =
        serde_json::from_value(tab_response["paymentRequirements"].clone())?;

    // 3) Build the X-PAYMENT header with the SDK
    let flow = X402Flow::new(payer)?;
    let signed = flow
        .sign_payment(payment_requirements.clone(), user_address.clone())
        .await?;
    let x_payment_header = signed.header; // send as `X-PAYMENT: {header}`

    // 4) Call the protected resource with the header
    let _paid = reqwest::Client::new()
        .get("https://resource-url/resource")
        .header("X-PAYMENT", &x_payment_header)
        .send()
        .await?
        .error_for_status()?;

    Ok(())
}

Resource server / facilitator side

If your resource server proxies to the facilitator (the pattern used in examples/server/mock_paid_api.py), you can reuse the SDK to settle after verifying:

use rust_sdk_4mica::{Client, ConfigBuilder, X402Flow, X402SignedPayment};
use rust_sdk_4mica::x402::PaymentRequirements;

async fn settle(
    facilitator_url: &str,
    payment_requirements: PaymentRequirements,
    payment: X402SignedPayment,
) -> Result<(), Box<dyn std::error::Error>> {
    let core = Client::new(
        ConfigBuilder::default()
            .wallet_private_key(std::env::var("RESOURCE_SIGNER_KEY")?)
            .build()?,
    )
    .await?;
    let flow = X402Flow::new(core)?;

    // POST /settle to the facilitator; returns the facilitator JSON body on success
    let settled = flow
        .settle_payment(payment, payment_requirements, facilitator_url)
        .await?;
    println!("settlement result: {}", settled.settlement);
    Ok(())
}

Notes:

  • sign_payment always uses EIP-712 signing and will error if the scheme is not 4mica.
  • settle_payment only hits /settle; resource servers should still call the facilitator /verify first when enforcing access (see the Python example for the end-to-end pattern).

API Methods Summary

UserClient Methods

  • approve_erc20(token: String, amount: U256) -> Result<TransactionReceipt, ApproveErc20Error>: Approve the 4Mica contract to spend ERC20 tokens on behalf of the user
  • deposit(amount: U256, erc20_token: Option<String>) -> Result<TransactionReceipt, DepositError>: Deposit collateral in ETH or ERC20 token
  • get_user() -> Result<Vec<UserInfo>, GetUserError>: Get current user information for all assets
  • get_tab_payment_status(tab_id: U256) -> Result<TabPaymentStatus, TabPaymentStatusError>: Get payment status for a tab
  • sign_payment(claims: PaymentGuaranteeRequestClaims, scheme: SigningScheme) -> Result<PaymentSignature, SignPaymentError>: Sign a payment
  • pay_tab(tab_id: U256, req_id: U256, amount: U256, recipient_address: String, erc20_token: Option<String>) -> Result<TransactionReceipt, PayTabError>: Pay a tab directly on-chain in ETH or ERC20 token
  • request_withdrawal(amount: U256, erc20_token: Option<String>) -> Result<TransactionReceipt, RequestWithdrawalError>: Request withdrawal of collateral in ETH or ERC20 token
  • cancel_withdrawal(erc20_token: Option<String>) -> Result<TransactionReceipt, CancelWithdrawalError>: Cancel pending withdrawal
  • finalize_withdrawal(erc20_token: Option<String>) -> Result<TransactionReceipt, FinalizeWithdrawalError>: Finalize withdrawal after waiting period

RecipientClient Methods

  • create_tab(user_address: String, recipient_address: String, erc20_token: Option<String>, ttl: Option<u64>) -> Result<U256, CreateTabError>: Create a new payment tab in ETH or ERC20 token
  • get_tab_payment_status(tab_id: U256) -> Result<TabPaymentStatus, TabPaymentStatusError>: Get payment status for a tab
  • issue_payment_guarantee(claims: PaymentGuaranteeRequestClaims, signature: String, scheme: SigningScheme) -> Result<BLSCert, IssuePaymentGuaranteeError>: Issue a payment guarantee
  • verify_payment_guarantee(cert: &BLSCert) -> Result<PaymentGuaranteeClaims, VerifyGuaranteeError>: Verify a BLS certificate and extract claims
  • remunerate(cert: BLSCert) -> Result<TransactionReceipt, RemunerateError>: Claim from user collateral using BLS certificate
  • list_settled_tabs() -> Result<Vec<TabInfo>, RecipientQueryError>: List all settled tabs for the recipient
  • list_pending_remunerations() -> Result<Vec<PendingRemunerationInfo>, RecipientQueryError>: List pending remunerations for the recipient
  • get_tab(tab_id: U256) -> Result<Option<TabInfo>, RecipientQueryError>: Get tab information by ID
  • list_recipient_tabs(settlement_statuses: Option<Vec<String>>) -> Result<Vec<TabInfo>, RecipientQueryError>: List tabs for the recipient with optional status filter
  • get_tab_guarantees(tab_id: U256) -> Result<Vec<GuaranteeInfo>, RecipientQueryError>: Get all guarantees for a tab
  • get_latest_guarantee(tab_id: U256) -> Result<Option<GuaranteeInfo>, RecipientQueryError>: Get the latest guarantee for a tab
  • get_guarantee(tab_id: U256, req_id: U256) -> Result<Option<GuaranteeInfo>, RecipientQueryError>: Get a specific guarantee by tab ID and request ID
  • list_recipient_payments() -> Result<Vec<RecipientPaymentInfo>, RecipientQueryError>: List all payments for the recipient
  • get_collateral_events_for_tab(tab_id: U256) -> Result<Vec<CollateralEventInfo>, RecipientQueryError>: Get collateral events for a specific tab
  • get_user_asset_balance(user_address: String, asset_address: String) -> Result<Option<AssetBalanceInfo>, RecipientQueryError>: Get user's asset balance

Note: Each method returns a specific error type that provides detailed information about what went wrong. See the Error Handling section for comprehensive documentation and examples.

User Client (Payer)

The user client allows you to manage your collateral and sign payments in ETH or ERC20 tokens.

Approve ERC20 Token (Required before depositing or paying with ERC20)

use rust_sdk_4mica::U256;

// Approve the 4Mica contract to spend 1000 USDC on your behalf
let token_address = "0x1234567890123456789012345678901234567890".to_string();
let amount = U256::from(1000_000_000u128); // 1000 USDC (6 decimals)

match client.user.approve_erc20(token_address, amount).await {
    Ok(receipt) => {
        println!("ERC20 approval successful: {:?}", receipt.transaction_hash);
    }
    Err(e) => {
        eprintln!("ERC20 approval failed: {}", e);
    }
}

Deposit Collateral

use rust_sdk_4mica::U256;

// Deposit 1 ETH as collateral
let amount = U256::from(1_000_000_000_000_000_000u128); // 1 ETH in wei
match client.user.deposit(amount, None).await {
    Ok(receipt) => {
        println!("Deposit successful: {:?}", receipt.transaction_hash);
    }
    Err(e) => {
        eprintln!("Deposit failed: {}", e);
    }
}

// Or deposit 1000 USDC (make sure to approve first!)
let token_address = "0x1234567890123456789012345678901234567890".to_string();
let amount = U256::from(1000_000_000u128);
let receipt = client.user.deposit(amount, Some(token_address)).await?;
println!("USDC deposit successful: {:?}", receipt.transaction_hash);

Get User Info

// Get information about the current user for all assets
let user_assets = client.user.get_user().await?;
for user_info in user_assets {
    println!("Asset: {}", user_info.asset);
    println!("Collateral: {}", user_info.collateral);
    println!("Withdrawal request amount: {}", user_info.withdrawal_request_amount);
    println!("Withdrawal request timestamp: {}", user_info.withdrawal_request_timestamp);
    println!("---");
}

Get Tab Payment Status

use rust_sdk_4mica::U256;

let tab_id = U256::from(1);
let status = client.user.get_tab_payment_status(tab_id).await?;
println!("Paid: {}", status.paid);
println!("Remunerated: {}", status.remunerated);
println!("Asset: {}", status.asset);

Sign a Payment

use rust_sdk_4mica::{PaymentGuaranteeRequestClaims, SigningScheme, U256};

// Create payment claims for ETH payment
let claims = PaymentGuaranteeRequestClaims::new(
    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_string(), // user_address
    "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC".to_string(), // recipient_address
    U256::from(1),                                              // tab_id
    U256::ZERO,                                                 // req_id (first request)
    U256::from(1_000_000_000_000_000_000u128),                  // amount (1 ETH)
    1704067200,                                                 // timestamp
    None,                                                       // erc20_token (None for ETH)
);

// Sign using EIP-712 (recommended)
match client.user.sign_payment(claims.clone(), SigningScheme::Eip712).await {
    Ok(payment_sig) => {
        println!("Signature: {}", payment_sig.signature);
        println!("Scheme: {:?}", payment_sig.scheme);
    }
    Err(e) => {
        eprintln!("Signing failed: {}", e);
    }
}

// Or use EIP-191 (personal_sign)
let payment_sig = client.user.sign_payment(claims, SigningScheme::Eip191).await?;

// For ERC20 token payment, pass the token address
let usdc_token = "0x1234567890123456789012345678901234567890".to_string();
let claims_usdc = PaymentGuaranteeRequestClaims::new(
    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_string(),
    "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC".to_string(),
    U256::from(1),
    U256::ZERO,
    U256::from(1000_000_000u128), // 1000 USDC
    1704067200,
    Some(usdc_token),
);
let payment_sig_usdc = client.user.sign_payment(claims_usdc, SigningScheme::Eip712).await?;

Pay a Tab

use rust_sdk_4mica::U256;

// Pay 1 ETH to a tab
let tab_id = U256::from(1);
// Use the req_id that came back with the guarantee certificate (placeholder value shown)
let req_id = U256::from(1);
let amount = U256::from(1_000_000_000_000_000_000u128);
let recipient_address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC".to_string();

let receipt = client.user.pay_tab(tab_id, req_id, amount, recipient_address.clone(), None).await?;
println!("Payment successful: {:?}", receipt.transaction_hash);

// Or pay 1000 USDC to a tab (make sure to approve the 4Mica contract first!)
let token_address = "0x1234567890123456789012345678901234567890".to_string();
let amount_usdc = U256::from(1000_000_000u128);
let receipt = client.user.pay_tab(
    tab_id,
    req_id,
    amount_usdc,
    recipient_address,
    Some(token_address)
).await?;
println!("USDC payment successful: {:?}", receipt.transaction_hash);

Request Withdrawal

use rust_sdk_4mica::U256;

// Request to withdraw 0.5 ETH
let amount = U256::from(500_000_000_000_000_000u128);
let receipt = client.user.request_withdrawal(amount, None).await?;
println!("Withdrawal requested: {:?}", receipt.transaction_hash);

// Or request to withdraw 500 USDC
let token_address = "0x1234567890123456789012345678901234567890".to_string();
let amount_usdc = U256::from(500_000_000u128);
let receipt = client.user.request_withdrawal(amount_usdc, Some(token_address)).await?;
println!("USDC withdrawal requested: {:?}", receipt.transaction_hash);

Cancel Withdrawal

// Cancel a pending ETH withdrawal request
let receipt = client.user.cancel_withdrawal(None).await?;
println!("Withdrawal cancelled: {:?}", receipt.transaction_hash);

// Cancel a pending USDC withdrawal request
let token_address = "0x1234567890123456789012345678901234567890".to_string();
let receipt = client.user.cancel_withdrawal(Some(token_address)).await?;
println!("USDC withdrawal cancelled: {:?}", receipt.transaction_hash);

Finalize Withdrawal

// Finalize ETH withdrawal (after the waiting period)
let receipt = client.user.finalize_withdrawal(None).await?;
println!("Withdrawal finalized: {:?}", receipt.transaction_hash);

// Finalize USDC withdrawal (after the waiting period)
let token_address = "0x1234567890123456789012345678901234567890".to_string();
let receipt = client.user.finalize_withdrawal(Some(token_address)).await?;
println!("USDC withdrawal finalized: {:?}", receipt.transaction_hash);

Recipient Client

The recipient client allows you to create payment tabs, issue payment guarantees, and claim from user collateral when payments aren't fulfilled.

Create Payment Tab

use rust_sdk_4mica::U256;

// Create a new payment tab for ETH
let user_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_string();
let recipient_address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC".to_string();
let ttl = Some(3600); // Tab expires in 1 hour (optional)

let tab_id = client.recipient.create_tab(
    user_address.clone(),
    recipient_address.clone(),
    None,  // None for ETH
    ttl
).await?;
println!("Created ETH tab with ID: {}", tab_id);

// Create a new payment tab for USDC
let token_address = "0x1234567890123456789012345678901234567890".to_string();
let tab_id_usdc = client.recipient.create_tab(
    user_address,
    recipient_address,
    Some(token_address),
    ttl
).await?;
println!("Created USDC tab with ID: {}", tab_id_usdc);

Get Tab Payment Status

use rust_sdk_4mica::U256;

let tab_id = U256::from(1);
let status = client.recipient.get_tab_payment_status(tab_id).await?;
println!("Paid: {}", status.paid);
println!("Remunerated: {}", status.remunerated);
println!("Asset: {}", status.asset);

Issue Payment Guarantee

use rust_sdk_4mica::{PaymentGuaranteeRequestClaims, SigningScheme, U256};

// First, the user signs the payment (see User Client example above)
let claims = PaymentGuaranteeRequestClaims::new(
    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_string(),
    "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC".to_string(),
    U256::from(1),
    U256::ZERO,
    U256::from(1_000_000_000_000_000_000u128), // 1 ETH
    1704067200,
    None, // None for ETH
);

let payment_sig = client.user.sign_payment(claims.clone(), SigningScheme::Eip712).await?;

let bls_cert = client.recipient.issue_payment_guarantee(
    claims,
    payment_sig.signature,
    payment_sig.scheme,
).await?;
println!("BLS Certificate: {:?}", bls_cert);

Remunerate (Claim from Collateral)

// If the user doesn't fulfill the payment guarantee,
// the recipient can claim from the user's collateral on-chain
let receipt = client.recipient.remunerate(bls_cert).await?;
println!("Claimed from user collateral successfully!");
println!("Transaction hash: {:?}", receipt.transaction_hash);

Complete Example

Here's a complete example showing a payment flow with ETH:

use rust_sdk_4mica::{
    Client, ConfigBuilder, PaymentGuaranteeRequestClaims, SigningScheme, U256,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Setup clients (user and recipient each have their own)
    let user_config = ConfigBuilder::default()
        .wallet_private_key("user_private_key".to_string())
        .build()?;
    let user_client = Client::new(user_config).await?;

    let recipient_config = ConfigBuilder::default()
        .wallet_private_key("recipient_private_key".to_string())
        .build()?;
    let recipient_client = Client::new(recipient_config).await?;

    // 2. User deposits collateral
    let deposit_amount = U256::from(2_000_000_000_000_000_000u128); // 2 ETH
    let receipt = user_client.user.deposit(deposit_amount, None).await?;
    println!("Deposited collateral: {:?}", receipt.transaction_hash);

    // 3. Recipient creates a payment tab
    let user_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_string();
    let recipient_address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC".to_string();
    let tab_id = recipient_client
        .recipient
        .create_tab(user_address.clone(), recipient_address.clone(), None, Some(3600))
        .await?;
    println!("Created tab: {}", tab_id);

    // 4. User signs a payment
    let claims = PaymentGuaranteeRequestClaims::new(
        user_address.clone(),
        recipient_address.clone(),
        tab_id,
        U256::ZERO,
        U256::from(1_000_000_000_000_000_000u128), // 1 ETH
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_secs(),
        None, // None for ETH
    );
    let payment_sig = user_client.user.sign_payment(claims.clone(), SigningScheme::Eip712).await?;
    println!("Payment signed");

    // 5. Recipient issues guarantee
    let bls_cert = recipient_client
        .recipient
        .issue_payment_guarantee(claims, payment_sig.signature, payment_sig.scheme)
        .await?;
    println!("Guarantee issued");

    // 6. If user doesn't pay, recipient can claim from user's collateral
    let receipt = recipient_client.recipient.remunerate(bls_cert).await?;
    println!("Claimed from user collateral!");
    println!("Transaction hash: {:?}", receipt.transaction_hash);

    Ok(())
}

Complete Example with ERC20 Token (USDC)

Here's a complete example showing a payment flow with an ERC20 token:

use rust_sdk_4mica::{
    Client, ConfigBuilder, PaymentGuaranteeRequestClaims, SigningScheme, U256,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Setup
    let user_config = ConfigBuilder::default()
        .wallet_private_key("user_private_key".to_string())
        .build()?;
    let user_client = Client::new(user_config).await?;

    let recipient_config = ConfigBuilder::default()
        .wallet_private_key("recipient_private_key".to_string())
        .build()?;
    let recipient_client = Client::new(recipient_config).await?;

    let usdc_token = "0x1234567890123456789012345678901234567890".to_string();

    // 1. User approves the 4Mica contract to spend USDC
    let approval_amount = U256::from(10000_000_000u128); // 10,000 USDC
    user_client.user.approve_erc20(usdc_token.clone(), approval_amount).await?;
    println!("Approved USDC spending");

    // 2. User deposits USDC collateral
    let deposit_amount = U256::from(5000_000_000u128); // 5,000 USDC
    user_client.user.deposit(deposit_amount, Some(usdc_token.clone())).await?;
    println!("Deposited USDC collateral");

    // 3. Recipient creates a USDC payment tab
    let user_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_string();
    let recipient_address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC".to_string();
    let tab_id = recipient_client
        .recipient
        .create_tab(
            user_address.clone(),
            recipient_address.clone(),
            Some(usdc_token.clone()),
            Some(3600)
        )
        .await?;
    println!("Created USDC tab: {}", tab_id);

    // 4. User signs a USDC payment
    let claims = PaymentGuaranteeRequestClaims::new(
        user_address.clone(),
        recipient_address.clone(),
        tab_id,
        U256::ZERO,
        U256::from(1000_000_000u128), // 1,000 USDC
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_secs(),
        Some(usdc_token.clone()),
    );
    let payment_sig = user_client.user.sign_payment(claims.clone(), SigningScheme::Eip712).await?;
    println!("Payment signed");

    // 5. Recipient issues guarantee
    let bls_cert = recipient_client
        .recipient
        .issue_payment_guarantee(claims, payment_sig.signature, payment_sig.scheme)
        .await?;
    println!("Guarantee issued");

    // 6. If user doesn't pay, recipient can claim from user's USDC collateral
    let receipt = recipient_client.recipient.remunerate(bls_cert).await?;
    println!("Claimed USDC from user collateral!");
    println!("Transaction hash: {:?}", receipt.transaction_hash);

    Ok(())
}

Error Handling

The SDK provides comprehensive, type-safe error handling with specific error types for each operation. All errors are strongly typed and provide detailed context about what went wrong.

Importing

// Import specific error types when needed
use rust_sdk_4mica::error::{
    ApproveErc20Error, DepositError, RemunerateError, RequestWithdrawalError,
    SignPaymentError, FinalizeWithdrawalError, CreateTabError, PayTabError,
    IssuePaymentGuaranteeError, VerifyGuaranteeError, RecipientQueryError,
    // ... other error types as needed
};

Error Types

Configuration Errors

ConfigError

  • InvalidValue(String): Invalid configuration value
  • Missing(String): Required configuration parameter is missing

Client Errors

ClientError

  • Rpc(String): RPC connection error
  • Provider(String): Provider initialization error
  • Initialization(String): Client initialization error

Payment Signing Errors

SignPaymentError

  • AddressMismatch { signer: Address, claims: String }: Signer address doesn't match user address in claims
  • InvalidUserAddress: User address in claims is invalid
  • InvalidRecipientAddress: Recipient address in claims is invalid
  • Failed(String): Failed to sign the payment (includes digest computation and signing errors)
  • Rpc(rpc::ApiClientError): RPC communication error

Deposit Errors

ApproveErc20Error

  • InvalidParams(String): Invalid parameters provided (e.g., invalid token address)
  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

DepositError

  • InvalidParams(String): Invalid parameters provided (e.g., invalid token address)
  • AmountZero: Cannot deposit zero amount
  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

Withdrawal Errors

RequestWithdrawalError

  • InvalidParams(String): Invalid parameters provided (e.g., invalid token address)
  • AmountZero: Cannot withdraw zero amount
  • InsufficientAvailable: Not enough available balance to withdraw
  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

CancelWithdrawalError

  • InvalidParams(String): Invalid parameters provided (e.g., invalid token address)
  • NoWithdrawalRequested: No withdrawal request exists to cancel
  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

FinalizeWithdrawalError

  • InvalidParams(String): Invalid parameters provided (e.g., invalid token address)
  • NoWithdrawalRequested: No withdrawal request exists to finalize
  • GracePeriodNotElapsed: Grace period has not elapsed yet
  • TransferFailed: Transfer of funds failed
  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

Tab Payment Errors

CreateTabError

  • InvalidParams(String): Invalid parameters (e.g., signer address mismatch)
  • Rpc(rpc::ApiClientError): RPC communication error

PayTabError

  • InvalidParams(String): Invalid parameters provided
  • InvalidAsset: Asset does not match the tab asset
  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

TabPaymentStatusError

  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

Payment Guarantee Errors

IssuePaymentGuaranteeError

  • InvalidParams(String): Invalid parameters (e.g., signer address mismatch)
  • Rpc(rpc::ApiClientError): RPC communication error

VerifyGuaranteeError

  • InvalidCertificate(anyhow::Error): Invalid BLS certificate
  • CertificateMismatch: Certificate signature mismatch
  • GuaranteeDomainMismatch: Guarantee domain mismatch
  • UnsupportedGuaranteeVersion(u64): Unsupported guarantee version

X402Error

  • InvalidScheme(String): paymentRequirements.scheme must include 4mica
  • InvalidFacilitatorUrl(String): Invalid facilitator /settle base URL
  • TabResolutionFailed(String) / InvalidExtra(String): Issues resolving or parsing paymentRequirements.extra
  • InvalidNumber { field, source } / UserMismatch { found, expected }: Invalid numeric fields or wrong user in requirements
  • EncodeEnvelope(String): Failed to encode the X-PAYMENT envelope
  • SettlementFailed { status, body }: Facilitator /settle returned a non-success status
  • Signing(SignPaymentError) / Http(reqwest::Error): Errors while signing or making HTTP requests

RecipientQueryError

  • Rpc(rpc::ApiClientError): RPC communication error

RemunerateError

  • InvalidParams(String): Invalid parameters provided
  • ClaimsHex(anyhow::Error): Failed to decode the hex-encoded guarantee claims blob
  • ClaimsDecode(anyhow::Error): Failed to deserialize guarantee claims after decoding
  • GuaranteeConversion(anyhow::Error): Failed to convert decoded claims into the contract call type
  • SignatureHex(FromHexError): Failed to decode the hex-encoded BLS signature
  • SignatureDecode(anyhow::Error): Failed to parse the decoded BLS signature bytes
  • TabNotYetOverdue: Tab has not reached its due date yet
  • TabExpired: Tab has expired and can no longer be remunerated
  • TabPreviouslyRemunerated: Tab has already been remunerated
  • TabAlreadyPaid: Tab has already been paid by user
  • InvalidSignature: BLS signature verification failed
  • DoubleSpendingDetected: Attempt to spend same guarantee twice
  • InvalidRecipient: Caller is not the recipient of this tab
  • AmountZero: Guarantee amount is zero
  • TransferFailed: Transfer of funds failed
  • CertificateInvalid(anyhow::Error): Certificate verification failed
  • CertificateMismatch: Certificate signature mismatch before submission
  • GuaranteeDomainMismatch: Guarantee domain mismatch
  • UnsupportedGuaranteeVersion(u64): Unsupported guarantee version
  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

GetUserError

  • UnknownRevert { selector: u32, data: Vec<u8> }: Unknown contract revert
  • Transport(String): Provider or transport error

Development

Running Tests

cargo test

Building

cargo build --release

Security Considerations

  • Never commit private keys: Always use environment variables or secure key management systems
  • Validate addresses: The SDK validates addresses automatically and returns SignPaymentError::AddressMismatch if the signer doesn't match the claims
  • Signature verification: The SDK ensures the signer address matches the claims user address before signing
  • Use EIP-712: Prefer EIP-712 signing over EIP-191 for better security and structured data hashing
  • Handle errors properly: Always handle errors explicitly. The SDK provides specific error types for each failure scenario to help you build robust applications
  • Check signer addresses: For RecipientClient operations, ensure your signer address matches the recipient address. The SDK will return InvalidParams errors for mismatches
  • Validate amounts: The SDK prevents zero-amount transactions at the contract level, but you should validate amounts in your application for better UX
  • ERC20 Approvals: Always approve the 4Mica contract before depositing or paying with ERC20 tokens. Approve only the amount you need to minimize risk
  • Asset Matching: When paying a tab or creating payment claims, ensure the asset (ETH or ERC20 token) matches the tab's asset. The contract will reject mismatched assets
  • Multi-Asset Management: Each asset (ETH and each ERC20 token) has its own collateral balance and withdrawal request. Use get_user() to view all your asset balances

License

This project is licensed under the Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0).

Support


Made with ❤️ by the 4Mica Network

Commit count: 419

cargo fmt