mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-08 08:58:01 +00:00
Make ExtrinsicParams more flexible, and introduce signed extensions (#1107)
* WIP new SignedExtensions * Integrate new signex extension stuff, update docs, remove Static wrapper * remove impl Into in tx_client to avoid type inference annoyances * clippy and fix example * Fix book links * clippy * book tweaks * fmt: remove spaces Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com> * re-expose Era in utils, and tweak wasm-example * clippy; remove useless conversion --------- Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
@@ -8,7 +8,7 @@ use subxt::tx::SubmittableExtrinsic;
|
||||
use subxt::tx::TxPayload;
|
||||
use subxt::utils::{AccountId32, MultiSignature};
|
||||
|
||||
use crate::services::{extension_signature_for_partial_extrinsic, get_accounts, polkadot, Account};
|
||||
use crate::services::{extension_signature_for_extrinsic, get_accounts, polkadot, Account};
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::prelude::*;
|
||||
|
||||
@@ -139,15 +139,15 @@ impl Component for SigningExamplesComponent {
|
||||
ctx.link()
|
||||
.send_future(
|
||||
async move {
|
||||
let partial_extrinsic =
|
||||
match api.tx().create_partial_signed(&remark_call, &account_id, Default::default()).await {
|
||||
Ok(partial_extrinsic) => partial_extrinsic,
|
||||
Err(err) => {
|
||||
return Message::Error(anyhow!("could not create partial extrinsic:\n{:?}", err));
|
||||
}
|
||||
};
|
||||
let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else {
|
||||
return Message::Error(anyhow!("Fetching account nonce failed"));
|
||||
};
|
||||
|
||||
let Ok(signature) = extension_signature_for_partial_extrinsic(&partial_extrinsic, &api, &account_id, account_source, account_address).await else {
|
||||
let Ok(call_data) = api.tx().call_data(&remark_call) else {
|
||||
return Message::Error(anyhow!("could not encode call data"));
|
||||
};
|
||||
|
||||
let Ok(signature) = extension_signature_for_extrinsic(&call_data, &api, account_nonce, account_source, account_address).await else {
|
||||
return Message::Error(anyhow!("Signing via extension failed"));
|
||||
};
|
||||
|
||||
@@ -155,7 +155,12 @@ impl Component for SigningExamplesComponent {
|
||||
return Message::Error(anyhow!("MultiSignature Decoding"));
|
||||
};
|
||||
|
||||
let signed_extrinsic = partial_extrinsic.sign_with_address_and_signature(&account_id.into(), &multi_signature);
|
||||
let Ok(partial_signed) = api.tx().create_partial_signed_with_nonce(&remark_call, account_nonce, Default::default()) else {
|
||||
return Message::Error(anyhow!("PartialExtrinsic creation failed"));
|
||||
};
|
||||
|
||||
// Apply the signature
|
||||
let signed_extrinsic = partial_signed.sign_with_address_and_signature(&account_id.into(), &multi_signature);
|
||||
|
||||
// do a dry run (to debug in the js console if the extrinsic would work)
|
||||
let dry_res = signed_extrinsic.dry_run(None).await;
|
||||
@@ -193,7 +198,7 @@ impl Component for SigningExamplesComponent {
|
||||
match submit_wait_finalized_and_get_extrinsic_success_event(
|
||||
signed_extrinsic,
|
||||
)
|
||||
.await
|
||||
.await
|
||||
{
|
||||
Ok(remark_event) => Message::ExtrinsicFinalized { remark_event },
|
||||
Err(err) => Message::ExtrinsicFailed(err),
|
||||
|
||||
@@ -4,10 +4,8 @@ use js_sys::Promise;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::fmt::Write;
|
||||
use subxt::ext::codec::Encode;
|
||||
use subxt::tx::PartialExtrinsic;
|
||||
use subxt::ext::codec::{Compact, Encode};
|
||||
use subxt::{self, OnlineClient, PolkadotConfig};
|
||||
use subxt::utils::AccountId32;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use yew::{AttrValue, Callback};
|
||||
@@ -117,37 +115,30 @@ fn to_hex(bytes: impl AsRef<[u8]>) -> String {
|
||||
format!("0x{}", hex::encode(bytes.as_ref()))
|
||||
}
|
||||
|
||||
fn encode_to_hex<E: Encode>(input: &E) -> String {
|
||||
fn encode_then_hex<E: Encode>(input: &E) -> String {
|
||||
format!("0x{}", hex::encode(input.encode()))
|
||||
}
|
||||
|
||||
/// this is used because numeric types (e.g. u32) are encoded as little-endian via scale (e.g. 9430 -> d6240000)
|
||||
/// while we need a big-endian representation for the json (e.g. 9430 -> 000024d6).
|
||||
fn encode_to_hex_reverse<E: Encode>(input: &E) -> String {
|
||||
let mut bytes = input.encode();
|
||||
bytes.reverse();
|
||||
format!("0x{}", hex::encode(bytes))
|
||||
}
|
||||
|
||||
|
||||
/// communicates with JavaScript to obtain a signature for the `partial_extrinsic` via a browser extension (e.g. polkadot-js or Talisman)
|
||||
///
|
||||
/// Some parameters are hard-coded here and not taken from the partial_extrinsic itself (mortality_checkpoint, era, tip).
|
||||
pub async fn extension_signature_for_partial_extrinsic(
|
||||
partial_extrinsic: &PartialExtrinsic<PolkadotConfig, OnlineClient<PolkadotConfig>>,
|
||||
pub async fn extension_signature_for_extrinsic(
|
||||
call_data: &[u8],
|
||||
api: &OnlineClient<PolkadotConfig>,
|
||||
account_id: &AccountId32,
|
||||
account_nonce: u64,
|
||||
account_source: String,
|
||||
account_address: String,
|
||||
) -> Result<Vec<u8>, anyhow::Error> {
|
||||
let spec_version = encode_to_hex_reverse(&api.runtime_version().spec_version);
|
||||
let transaction_version = encode_to_hex_reverse(&api.runtime_version().transaction_version);
|
||||
let mortality_checkpoint = encode_to_hex(&api.genesis_hash());
|
||||
let era = encode_to_hex(&subxt::config::extrinsic_params::Era::Immortal);
|
||||
let genesis_hash = encode_to_hex(&api.genesis_hash());
|
||||
let method = to_hex(partial_extrinsic.call_data());
|
||||
let nonce = api.tx().account_nonce(account_id).await?;
|
||||
let nonce = encode_to_hex_reverse(&nonce);
|
||||
let genesis_hash = encode_then_hex(&api.genesis_hash());
|
||||
// These numbers aren't SCALE encoded; their bytes are just converted to hex:
|
||||
let spec_version = to_hex(&api.runtime_version().spec_version.to_be_bytes());
|
||||
let transaction_version = to_hex(&api.runtime_version().transaction_version.to_be_bytes());
|
||||
let nonce = to_hex(&account_nonce.to_be_bytes());
|
||||
// If you construct a mortal transaction, then this block hash needs to correspond
|
||||
// to the block number passed to `Era::mortal()`.
|
||||
let mortality_checkpoint = encode_then_hex(&api.genesis_hash());
|
||||
let era = encode_then_hex(&subxt::utils::Era::Immortal);
|
||||
let method = to_hex(call_data);
|
||||
let signed_extensions: Vec<String> = api
|
||||
.metadata()
|
||||
.extrinsic()
|
||||
@@ -155,7 +146,7 @@ pub async fn extension_signature_for_partial_extrinsic(
|
||||
.iter()
|
||||
.map(|e| e.identifier().to_string())
|
||||
.collect();
|
||||
let tip = encode_to_hex(&subxt::config::polkadot::PlainTip::new(0));
|
||||
let tip = encode_then_hex(&Compact(0u128));
|
||||
|
||||
let payload = json!({
|
||||
"specVersion": spec_version,
|
||||
|
||||
Reference in New Issue
Block a user