Skip to content

Add additional method to parse raw call data bytes into an unsigned payload so it can be signed and submitted later on #2194

@paulormart

Description

@paulormart

Hi @jsdw,

Opening this issue to share what I'm currently doing since I haven't found a default option on the latest version of subxt and I think it might be good to have something similar available.

I often find it useful to keep the raw bytes exported from here tx().call_data(&proxy_call)? around, and only sign and submit on a second stage. I couldn't find a method on the latest version that could easily decode these bytes, the option tx().from_bytes(encoded) requires the encoded data to be already signed. I have looked to the other methods on how to create partial transaction, but they don't seem to apply here.

Do you have any suggestions?

Sharing the steps and code blocks to demonstrate my case:

  1. Export call data:
pub fn wrap_call_into_proxy(
    api: &ClientAtBlock<SubstrateConfig, OnlineClientAtBlockImpl<SubstrateConfig>>,
    call: RuntimeCall,
    proxied_account: &AccountId32,
) -> Result<Vec<u8>, Error> {
    let proxy_call = node_runtime::tx().proxy().proxy(
        (*proxied_account).into(),
        Some(ProxyType::NonTransfer),
        call,
    );

    let payload = api.tx().call_data(&proxy_call).boxed()?;

    Ok(payload)
}
  1. With the chain Metadata available, decode the original call data from previous method wrap_call_into_proxy, sign it with a valid Keypair and submit:
    async fn sign_and_submit_call_data(
        &self,
        api: &OnlineClient<SubstrateConfig>,
        proxy_signer: &Keypair,
        call_data: &[u8],
    ) -> Result<Response, Error> {
        let at_block = api.at_current_block().await.boxed()?;
        let metadata = at_block.metadata();
        let payload = RawPayload::from_bytes(&metadata, call_data).boxed()?;
        let response = api
            .tx()
            .await
            .boxed()?
            .sign_and_submit_then_watch_default(&payload, proxy_signer)
            .await
            .boxed()?;

        Ok(Response::transaction_submitted(response))
    }
  1. RawPayload implementation:
use scale_encode::{EncodeAsFields, Error as EncodeError, FieldIter, TypeResolver};
use subxt::{
    transactions::{Payload, ValidationDetails},
    Metadata,
};

pub type Bytes = Vec<u8>;

pub struct RawPayload {
    pallet_name: String,
    call_name: String,
    field_bytes: RawFields,
}

pub struct RawFields(Bytes);

impl EncodeAsFields for RawFields {
    fn encode_as_fields_to<R: TypeResolver>(
        &self,
        _fields: &mut dyn FieldIter<'_, R::TypeId>,
        _types: &R,
        out: &mut Bytes,
    ) -> Result<(), EncodeError> {
        out.extend_from_slice(&self.0);
        Ok(())
    }
}

impl Payload for RawPayload {
    type CallData = RawFields;

    fn pallet_name(&self) -> &str {
        &self.pallet_name
    }
    fn call_name(&self) -> &str {
        &self.call_name
    }
    fn call_data(&self) -> &RawFields {
        &self.field_bytes
    }
    fn validation_details(&self) -> Option<ValidationDetails<'_>> {
        None
    }
}

impl RawPayload {
    pub fn from_bytes(metadata: &Metadata, bytes: &[u8]) -> Result<Self, Error> {
        let pallet = metadata
            .pallet_by_call_index(bytes[0])
            .ok_or(Error::PalletNotFound)?;
        let call_variant = pallet
            .call_variant_by_index(bytes[1])
            .ok_or(Error::CallNotFound)?;

        Ok(Self {
            pallet_name: pallet.name().to_string(),
            call_name: call_variant.name.clone(),
            field_bytes: RawFields(bytes[2..].to_vec()),
        })
    }
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("Pallet not found")]
    PalletNotFound,
    #[error("Call not found")]
    CallNotFound,
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions