1use crate::{Function, Pallet, Param, errors::Error, find_callable_by_name};
4use pop_common::{
5 call::{DefaultEnvironment, DisplayEvents, TokenMetadata, Verbosity},
6 create_signer,
7};
8use sp_core::bytes::{from_hex, to_hex};
9use subxt::{
10 OnlineClient, SubstrateConfig,
11 blocks::ExtrinsicEvents,
12 dynamic::Value,
13 tx::{DynamicPayload, Payload, SubmittableTransaction, TxStatus},
14};
15pub mod metadata;
16
17pub async fn set_up_client(url: &str) -> Result<OnlineClient<SubstrateConfig>, Error> {
22 OnlineClient::<SubstrateConfig>::from_url(url)
23 .await
24 .map_err(|e| Error::ConnectionFailure(e.to_string()))
25}
26
27pub fn construct_extrinsic(
33 function: &Function,
34 args: Vec<String>,
35) -> Result<DynamicPayload, Error> {
36 let parsed_args: Vec<Value> = metadata::parse_dispatchable_arguments(&function.params, args)?;
37 Ok(subxt::dynamic::tx(function.pallet.clone(), function.name.clone(), parsed_args))
38}
39
40pub fn construct_sudo_extrinsic(xt: DynamicPayload) -> DynamicPayload {
46 subxt::dynamic::tx("Sudo", "sudo", [xt.into_value()].to_vec())
47}
48
49pub fn construct_proxy_extrinsic(
57 pallets: &[Pallet],
58 proxied_account: String,
59 xt: DynamicPayload,
60) -> Result<DynamicPayload, Error> {
61 let proxy_function = find_callable_by_name(pallets, "Proxy", "proxy")?;
62 let required_params: Vec<Param> = match proxy_function {
67 metadata::CallItem::Function(ref function) =>
68 function.params.iter().take(2).cloned().collect(),
69 _ => return Err(Error::CallableNotSupported),
70 };
71 let mut parsed_args: Vec<Value> = metadata::parse_dispatchable_arguments(
72 &required_params,
73 vec![proxied_account, "None()".to_string()],
74 )?;
75 let real = parsed_args.remove(0);
76 let proxy_type = parsed_args.remove(0);
77 Ok(subxt::dynamic::tx("Proxy", "proxy", [real, proxy_type, xt.into_value()].to_vec()))
78}
79
80pub async fn sign_and_submit_extrinsic<Xt: Payload>(
88 client: &OnlineClient<SubstrateConfig>,
89 url: &url::Url,
90 xt: Xt,
91 suri: &str,
92) -> Result<String, Error> {
93 let signer = create_signer(suri)?;
94 let mut tx = client
95 .tx()
96 .sign_and_submit_then_watch_default(&xt, &signer)
97 .await
98 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?;
99
100 let tx_hash = tx.extrinsic_hash();
101
102 while let Some(status) = tx.next().await {
103 match status.map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))? {
104 TxStatus::InFinalizedBlock(tx_in_block) => {
105 let events = tx_in_block
106 .wait_for_success()
107 .await
108 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?;
109
110 let parsed_events = parse_and_format_events(client, url, &events).await?;
111
112 return Ok(format!(
113 "Extrinsic Submitted with hash: {:?}\n\n{}",
114 tx_hash, parsed_events
115 ));
116 },
117 TxStatus::InBestBlock(tx_in_block) => {
118 let events = tx_in_block
119 .wait_for_success()
120 .await
121 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?;
122
123 let parsed_events = parse_and_format_events(client, url, &events).await?;
124
125 return Ok(format!(
126 "Extrinsic Submitted with hash: {:?}\n\n{}",
127 tx_hash, parsed_events
128 ));
129 },
130 TxStatus::Error { message } => {
131 return Err(Error::ExtrinsicSubmissionError(format!("{:?}", message)));
132 },
133 TxStatus::Invalid { message } => {
134 return Err(Error::ExtrinsicSubmissionError(format!("{:?}", message)));
135 },
136 TxStatus::Dropped { message } => {
137 return Err(Error::ExtrinsicSubmissionError(format!("{:?}", message)));
138 },
139 _ => continue,
140 }
141 }
142
143 Err(Error::ExtrinsicSubmissionError(
144 "Transaction stream ended without finalization".to_string(),
145 ))
146}
147
148pub async fn parse_and_format_events(
155 client: &OnlineClient<SubstrateConfig>,
156 url: &url::Url,
157 result: &ExtrinsicEvents<SubstrateConfig>,
158) -> Result<String, Error> {
159 let metadata = client.metadata();
163 let token_metadata = TokenMetadata::query::<SubstrateConfig>(url).await?;
164 let events =
165 DisplayEvents::from_events::<SubstrateConfig, DefaultEnvironment>(result, None, &metadata)?;
166 let events =
167 events.display_events::<DefaultEnvironment>(Verbosity::Default, &token_metadata)?;
168
169 Ok(events)
170}
171
172pub async fn submit_signed_extrinsic(
178 client: OnlineClient<SubstrateConfig>,
179 payload: String,
180) -> Result<ExtrinsicEvents<SubstrateConfig>, Error> {
181 let hex_encoded =
182 from_hex(&payload).map_err(|e| Error::CallDataDecodingError(e.to_string()))?;
183 let extrinsic = SubmittableTransaction::from_bytes(client, hex_encoded);
184 extrinsic
185 .submit_and_watch()
186 .await
187 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))?
188 .wait_for_finalized_success()
189 .await
190 .map_err(|e| Error::ExtrinsicSubmissionError(format!("{:?}", e)))
191}
192
193pub fn encode_call_data(
199 client: &OnlineClient<SubstrateConfig>,
200 xt: &DynamicPayload,
201) -> Result<String, Error> {
202 let call_data = xt
203 .encode_call_data(&client.metadata())
204 .map_err(|e| Error::CallDataEncodingError(e.to_string()))?;
205 Ok(to_hex(&call_data, false))
206}
207
208pub fn decode_call_data(call_data: &str) -> Result<Vec<u8>, Error> {
213 from_hex(call_data).map_err(|e| Error::CallDataDecodingError(e.to_string()))
214}
215
216pub struct CallData(Vec<u8>);
219
220impl CallData {
221 pub fn new(data: Vec<u8>) -> CallData {
223 CallData(data)
224 }
225}
226
227impl Payload for CallData {
228 fn encode_call_data_to(
229 &self,
230 _: &subxt::Metadata,
231 out: &mut Vec<u8>,
232 ) -> Result<(), subxt::ext::subxt_core::Error> {
233 out.extend_from_slice(&self.0);
234 Ok(())
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::set_up_client;
242 use anyhow::Result;
243
244 const ALICE_SURI: &str = "//Alice";
245
246 #[tokio::test]
247 async fn set_up_client_fails_wrong_url() -> Result<()> {
248 assert!(matches!(
249 set_up_client("wss://wronguri.xyz").await,
250 Err(Error::ConnectionFailure(_))
251 ));
252 Ok(())
253 }
254
255 #[tokio::test]
256 async fn construct_extrinsic_works() -> Result<()> {
257 let transfer_allow_death = Function {
258 pallet: "Balances".into(),
259 name: "transfer_allow_death".into(),
260 index: 0,
261 docs: ".".into(),
262 params: vec![
263 Param {
264 name: "dest".into(),
265 type_name: "MultiAddress<AccountId32 ([u8;32]),()>: Id(AccountId32 ([u8;32])), Index(Compact<()>), Raw([u8]), Address32([u8;32]), Address20([u8;20])".into(),
266 sub_params: vec![
267 Param {
268 name: "Id".into(),
269 type_name: "".into(),
270 sub_params: vec![
271 Param {
272 name: "Id".into(),
273 type_name: "AccountId32 ([u8;32])".into(),
274 sub_params: vec![
275 Param {
276 name: "Id".into(),
277 type_name: "[u8;32]".into(),
278 sub_params: vec![],
279 ..Default::default()
280 }
281 ],
282 ..Default::default()
283 }
284 ],
285 ..Default::default()
286 }],
287 ..Default::default()
288 },
289 Param {
290 name: "value".into(),
291 type_name: "Compact<u128>".into(),
292 sub_params: vec![],
293 ..Default::default()
294 }
295 ],
296 is_supported: true,
297 };
298 assert!(matches!(
300 construct_extrinsic(
301 &transfer_allow_death,
302 vec![ALICE_SURI.to_string(), "100".to_string()],
303 ),
304 Err(Error::ParamProcessingError)
305 ));
306 let xt = construct_extrinsic(
308 &transfer_allow_death,
309 vec![
310 "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(),
311 "100".to_string(),
312 ],
313 )?;
314 assert_eq!(xt.call_name(), "transfer_allow_death");
315 assert_eq!(xt.pallet_name(), "Balances");
316 Ok(())
317 }
318
319 #[tokio::test]
320 async fn construct_sudo_extrinsic_works() -> Result<()> {
321 let xt = construct_extrinsic(
322 &Function::default(),
323 vec![
324 "Id(5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)".to_string(),
325 "Id(5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy)".to_string(),
326 "100".to_string(),
327 ],
328 )?;
329 let xt = construct_sudo_extrinsic(xt);
330 assert_eq!(xt.call_name(), "sudo");
331 assert_eq!(xt.pallet_name(), "Sudo");
332 Ok(())
333 }
334}