-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtransfer.rs
215 lines (184 loc) · 6.26 KB
/
transfer.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
use crate::{
backend::{
sube::{initialize_client, SubeClient},
wallet::{get_account, get_address, get_wallet, sign},
},
model::{
error::{Cause, Error},
result::Result,
},
};
use parity_scale_codec::{Compact, Encode};
use sp_core::{crypto::Ss58Codec, hexdisplay::AsBytesRef};
use sube::{
meta::{Meta, Pallet},
Backend,
};
/// Builds a [transfer extrinsic](https://github.com/paritytech/subxt/blob/8484c18624783af36476fc5bf6a0f08d5363a3db/subxt/src/tx/tx_client.rs#L124-L207), then
/// sends it using sube
pub async fn transfer(uname: String, dest: String, value: u128) -> Result<()> {
let sube = initialize_client("wss://westend-rpc.polkadot.io")
.await
.map_err(|e| Error::Sube(Box::new(e)))?;
let call_data = construct_transfer_call(&sube, &dest, value).await?;
let wallet = get_wallet(&uname).await?;
let account = get_account(&wallet).await?;
let from_address = format!("0x{}", &account);
let (extra_params, signature_payload) =
construct_extrinsic_data(&sube, &from_address, &call_data).await?;
let signature = sign(&wallet, signature_payload).await?;
let extrinsic_call = {
let encoded_inner = [
// header: "is signed" (1 byte) + transaction protocol version (7 bytes)
vec![0b10000000 + 4u8],
// signer
vec![0x00],
account.public().as_ref().to_vec(),
// signature
signature,
// extra
extra_params,
// call data
call_data,
]
.concat();
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
)
.encode();
[len, encoded_inner].concat()
};
println!("Ready to go: 0x{}", hex::encode(&extrinsic_call));
sube.submit(extrinsic_call).await.map_err(move |e| {
dbg!(&e);
Error::Sube(Box::new(e))
})?;
Ok(())
}
async fn construct_transfer_call(sube: &SubeClient, dest: &str, value: u128) -> Result<Vec<u8>> {
let meta = sube
.metadata()
.await
.map_err(move |e| Error::Sube(Box::new(e)))?;
let (call_ty, pallet_index) = {
let pallet = meta
.pallet_by_name("Balances")
.expect("pallet does not exist");
(
pallet
.get_calls()
.expect("pallet does not have calls")
.ty
.id(),
pallet.index,
)
};
let dest = get_address(&dest).await?;
let mut encoded_call = vec![pallet_index];
let call_payload = serde_json::json!({
"transfer": {
"dest": {
"Id": dest.as_bytes_ref(),
},
"value": value as u64,
}
});
let call_data = sube.encode(call_payload, call_ty).await.map_err(|e| {
dbg!(&e);
Error::Sube(Box::new(e))
})?;
encoded_call.extend(call_data);
println!(
"
Transferring {} to {} (0x{})
Hex-encoded call: {}\n",
value,
&dest.to_ss58check(),
hex::encode(&dest),
format!("0x{}", hex::encode(&encoded_call)),
);
Ok(encoded_call)
}
async fn construct_extrinsic_data(
sube: &SubeClient,
from_address: &str,
call_data: &Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>)> {
let extra_params = {
// ImmortalEra
let era = 0u8;
// Impl. Note: in a real-world use case, you should store your account's nonce somewhere else
let account_info = sube
.query(&format!("system/account/{}", &from_address))
.await
.map_err(move |e| Error::Sube(Box::new(e)))?;
let account_info =
<serde_json::Value as std::str::FromStr>::from_str(&account_info.to_string())
.map_err(move |e| Error::Codec(Box::new(e)))?;
let nonce = account_info
.get("nonce")
.unwrap_or(&serde_json::json!(0))
.as_u64()
.expect("nonce be a number");
let tip: u128 = 0;
[vec![era], Compact(nonce).encode(), Compact(tip).encode()].concat()
};
let additional_params = {
// Error: Still failing to deserialize the const
let metadata = sube
.metadata()
.await
.map_err(|e| Error::Sube(Box::new(e)))?
.clone();
let mut constants = metadata
.pallet_by_name("System")
.ok_or(Error::Codec(Box::new(Cause::from(
"System pallet not found on metadata",
))))?
.constants
.clone()
.into_iter();
let data = constants
.find(|c| c.name == "Version")
.ok_or(Error::Codec(Box::new(Cause::from(
"System_Version constant not found",
))))?
.clone()
.to_owned();
let chain_version = sube
.decode(data.value.to_vec(), data.ty.id())
.await
.map_err(|e| Error::Codec(Box::new(e)))?;
let chain_version =
serde_json::to_value(chain_version).map_err(|e| Error::Codec(Box::new(e)))?;
let spec_version = chain_version
.get("spec_version")
.ok_or(Error::Codec(Box::new(Cause::from("spec_version not found"))))?
.as_u64()
.ok_or(Error::Codec(Box::new(Cause::from("spec_version not a Number"))))? as u32;
let transaction_version = chain_version
.get("transaction_version")
.ok_or(Error::Codec(Box::new(Cause::from("transaction_version not found"))))?
.as_u64()
.ok_or(Error::Codec(Box::new(Cause::from("transaction_version not a Number"))))? as u32;
let genesis_block: Vec<u8> = sube
.block_info(Some(0u32))
.await
.map_err(move |e| Error::Sube(Box::new(e)))?
.into();
[
spec_version.to_le_bytes().to_vec(),
transaction_version.to_le_bytes().to_vec(),
genesis_block.clone(),
genesis_block.clone(),
]
.concat()
};
let signature_payload = [
call_data.clone(),
extra_params.clone(),
additional_params.clone(),
]
.concat();
Ok((extra_params, signature_payload))
}