dynamic-waas-sdk 0.0.3

Rust SDK for Dynamic Wallet-as-a-Service — manage wallets from your backend.
Documentation
//! Ed25519 (`ExportableEd25519`) sign orchestration. SVM equivalent of
//! `sign.rs::run_sign_ecdsa`.

use dynamic_waas_sdk_core::{
    api::{KeygenCompleteEvent, SignMessageReq},
    sse::{stream_sse_with_callback, SseEventData},
    Error, Result, ServerKeyShare,
};
use dynamic_waas_sdk_mpc::{Ed25519Signer, RoomUuid, SecretShare};
use tracing::{debug, instrument};

use crate::client::DynamicWalletClient;

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SignOptsEd25519 {
    pub wallet_id: String,
    /// Raw message bytes to sign. For SVM message-signing this is the
    /// UTF-8 bytes of the message; the SDK hex-encodes them for the API
    /// + relay.
    pub message: Vec<u8>,
    pub secret_share: ServerKeyShare,
}

impl SignOptsEd25519 {
    pub fn new(
        wallet_id: impl Into<String>,
        message: Vec<u8>,
        secret_share: ServerKeyShare,
    ) -> Self {
        Self {
            wallet_id: wallet_id.into(),
            message,
            secret_share,
        }
    }
}

/// Returns the raw 64-byte Ed25519 signature.
#[instrument(skip(client, opts), fields(wallet_id = %opts.wallet_id))]
pub async fn run_sign_ed25519(
    client: &DynamicWalletClient,
    opts: SignOptsEd25519,
) -> Result<[u8; 64]> {
    if !client.is_authenticated() {
        return Err(Error::Authentication(crate::AUTH_REQUIRED_MSG.into()));
    }

    // For SVM the API body carries hex(message_bytes) and is_formatted=false.
    // The relay receives the same hex via the MPC ceremony.
    let msg_hex = hex::encode(&opts.message);
    let body = SignMessageReq {
        message: msg_hex,
        is_formatted: false,
        server_is_formatted: None,
        context: None,
    };
    let response = client
        .api()
        .sign_message_with_callback(&opts.wallet_id, &body)
        .await?;

    let host_url = client.base_mpc_relay_url().to_string();
    let secret_share = SecretShare::from_string(opts.secret_share.secret_share);
    let message = opts.message.clone();

    let (signature, _ceremony_data) =
        stream_sse_with_callback(response, "room_created", move |trigger| async move {
            let event: KeygenCompleteEvent = match trigger {
                SseEventData::Json(v) => serde_json::from_value(v).map_err(Error::from)?,
                SseEventData::Raw(s) => {
                    return Err(Error::Sse(format!(
                        "room_created payload was not JSON: {s}"
                    )))
                }
            };
            debug!(room_id = %event.room_id, "running MPC ed25519 sign");

            let signer = Ed25519Signer::new(host_url);
            let room = RoomUuid::new(event.room_id);
            let sig = signer.sign(&room, &secret_share, &message).await?;
            Ok::<_, Error>(sig)
        })
        .await?;

    Ok(signature)
}