use std::error;
use flow::access_api_client::AccessApiClient;
use flow::*;
pub mod flow {
tonic::include_proto!("flow.access");
}
use bytes::Bytes;
pub use p256_flow::ecdsa::SigningKey;
use p256_flow::ecdsa::{signature_flow::Signature, signature_flow::Signer};
use p256_flow::elliptic_curve_flow::SecretKey;
pub use rand_core::OsRng;
pub extern crate hex;
pub extern crate rlp;
use rlp::*;
pub struct FlowConnection<T> {
pub client: AccessApiClient<T>,
}
impl FlowConnection<tonic::transport::Channel> {
pub async fn new(
network_address: &str,
) -> Result<FlowConnection<tonic::transport::Channel>, Box<dyn error::Error>> {
let mut client = AccessApiClient::connect(network_address.to_owned()).await?;
let request = tonic::Request::new(PingRequest {});
client.ping(request).await?;
Ok(FlowConnection::<tonic::transport::Channel> { client })
}
pub async fn get_account(
&mut self,
account_address: &str,
) -> Result<AccountResponse, Box<dyn error::Error>> {
let request = tonic::Request::new(GetAccountAtLatestBlockRequest {
address: hex::decode(account_address).unwrap(),
});
let response = self.client.get_account_at_latest_block(request).await?;
Ok(response.into_inner())
}
pub async fn execute_script(
&mut self,
script: Vec<u8>,
arguments: Vec<Vec<u8>>,
block_height: Option<u64>,
block_id: Option<Vec<u8>>,
) -> Result<ExecuteScriptResponse, Box<dyn error::Error>> {
if block_id.is_some() {
let request = tonic::Request::new(ExecuteScriptAtBlockIdRequest {
script,
arguments,
block_id: block_id.unwrap(),
});
let response = self.client.execute_script_at_block_id(request).await?;
Ok(response.into_inner())
} else if block_height.is_some() {
let request = tonic::Request::new(ExecuteScriptAtBlockHeightRequest {
script,
arguments,
block_height: block_height.unwrap(),
});
let response = self.client.execute_script_at_block_height(request).await?;
Ok(response.into_inner())
} else {
let request =
tonic::Request::new(ExecuteScriptAtLatestBlockRequest { script, arguments });
let response = self.client.execute_script_at_latest_block(request).await?;
Ok(response.into_inner())
}
}
pub async fn send_transaction(
&mut self,
transaction: Option<Transaction>,
) -> Result<SendTransactionResponse, Box<dyn error::Error>> {
let request = tonic::Request::new(SendTransactionRequest { transaction });
let response = self.client.send_transaction(request).await?;
Ok(response.into_inner())
}
pub async fn get_transaction_result(
&mut self,
id: Vec<u8>,
) -> Result<TransactionResultResponse, Box<dyn error::Error>> {
let request = tonic::Request::new(GetTransactionRequest { id });
let response = self.client.get_transaction_result(request).await?;
Ok(response.into_inner())
}
pub async fn get_block(
&mut self,
block_id: Option<String>,
block_height: Option<u64>,
is_sealed: Option<bool>,
) -> Result<BlockResponse, Box<dyn error::Error>> {
if block_id.is_some() {
let request = tonic::Request::new(GetBlockByIdRequest {
id: hex::decode(block_id.unwrap())?,
});
let response = self.client.get_block_by_id(request).await?;
Ok(response.into_inner())
} else if block_height.is_some() {
let request = tonic::Request::new(GetBlockByHeightRequest {
height: block_height.unwrap(),
});
let response = self.client.get_block_by_height(request).await?;
Ok(response.into_inner())
} else {
if is_sealed.is_some() {
let request = tonic::Request::new(GetLatestBlockRequest {
is_sealed: is_sealed.unwrap(),
});
let response = self.client.get_latest_block(request).await?;
Ok(response.into_inner())
} else {
let request = tonic::Request::new(GetLatestBlockRequest { is_sealed: false });
let response = self.client.get_latest_block(request).await?;
Ok(response.into_inner())
}
}
}
pub async fn get_events_for_height_range(
&mut self,
event_type: &str,
start_height: u64,
end_height: u64,
) -> Result<EventsResponse, Box<dyn error::Error>> {
let request = tonic::Request::new(GetEventsForHeightRangeRequest {
r#type: event_type.to_owned(),
start_height,
end_height,
});
let response = self.client.get_events_for_height_range(request).await?;
Ok(response.into_inner())
}
pub async fn get_events_for_block_ids(
&mut self,
event_type: &str,
ids: Vec<Vec<u8>>,
) -> Result<EventsResponse, Box<dyn error::Error>> {
let request = tonic::Request::new(GetEventsForBlockIdsRequest {
r#type: event_type.to_owned(),
block_ids: ids,
});
let response = self.client.get_events_for_block_i_ds(request).await?;
Ok(response.into_inner())
}
pub async fn get_collection(
&mut self,
collection_id: Vec<u8>,
) -> Result<CollectionResponse, Box<dyn error::Error>> {
let request = tonic::Request::new(GetCollectionByIdRequest { id: collection_id });
let response = self.client.get_collection_by_id(request).await?;
Ok(response.into_inner())
}
pub async fn create_account(
&mut self,
account_keys: Vec<String>,
payer: &str,
payer_private_key: &str,
key_id: u32,
) -> Result<flow::Account, Box<dyn error::Error>> {
let create_account_template = b"
import Crypto
transaction(publicKeys: [String], contracts: {String: String}) {
prepare(signer: AuthAccount) {
let acct = AuthAccount(payer: signer)
for pkey in publicKeys {
let key = PublicKey(
publicKey: pkey.decodeHex(),
signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
)
acct.keys.add(publicKey: key, hashAlgorithm: HashAlgorithm.SHA3_256, weight: 1000.0)
}
for contract in contracts.keys {
acct.contracts.add(name: contract, code: contracts[contract]!.decodeHex())
}
}
}";
let latest_block: BlockResponse = self.get_block(None, None, Some(false)).await?;
let account: flow::Account = self.get_account(payer).await?.account.unwrap();
let proposer = TransactionProposalKey {
address: hex::decode(payer).unwrap(),
key_id,
sequence_number: account.keys[key_id as usize].sequence_number as u64,
};
let keys_arg = process_keys_args(account_keys);
let contracts_arg = Argument::dictionary(vec![]);
let keys_arg = json!(keys_arg);
let contracts_arg = json!(contracts_arg);
let transaction: Transaction = build_transaction(
create_account_template.to_vec(),
vec![to_vec(&keys_arg)?, to_vec(&contracts_arg)?],
latest_block.block.unwrap().id,
1000,
proposer,
vec![payer.to_owned()],
payer.to_owned(),
)
.await?;
let signature = Sign {
address: payer.to_owned(),
key_id,
private_key: payer_private_key.to_owned(),
};
let transaction: Option<Transaction> =
sign_transaction(transaction, vec![], vec![&signature]).await?;
let transaction: SendTransactionResponse = self.send_transaction(transaction).await?;
let mut time: u64 = 50;
let mut i = 0;
println!("{}", hex::encode(transaction.id.to_vec()));
while i < 50 {
i += 1;
sleep(Duration::from_millis(time)).await;
let res = self.get_transaction_result(transaction.id.to_vec()).await?;
match res.status {
0 | 1 | 2 | 3 => {
time += 200;
}
4 => {
if res.status_code == 1 {
return Err("Error during execution".into());
}
let new_account_address: flow::Event = res
.events
.into_iter()
.filter(|x| x.r#type == "flow.AccountCreated")
.collect::<Vec<flow::Event>>()
.pop()
.unwrap();
let payload: Value = from_slice(&new_account_address.payload)?;
let address: String = payload["value"]["fields"][0]["value"]["value"]
.to_string()
.split_at(3)
.1
.to_string()
.split_at(16)
.0
.to_string();
let acct: flow::Account = self
.get_account(&address)
.await?
.account
.expect("could not get newly created account");
return Ok(acct);
}
_ => return Err("Cadence Runtime Error".into()),
}
}
Err("Could not produce result".into())
}
pub async fn add_key(
&mut self,
public_key_to_add: &str,
payer: &str,
payer_private_key: &str,
key_id: u32,
) -> Result<flow::SendTransactionResponse, Box<dyn error::Error>> {
let update_contract_template = b"
import Crypto
transaction(publicKey: String) {
prepare(signer: AuthAccount) {
let key = PublicKey(
publicKey: pkey.decodeHex(),
signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
)
signer.keys.add(publicKey: key, hashAlgorithm: HashAlgorithm.SHA3_256, weight: 1000.0)
}
}
";
let latest_block: BlockResponse = self.get_block(None, None, Some(false)).await?;
let account: flow::Account = self.get_account(payer).await?.account.unwrap();
let proposer = TransactionProposalKey {
address: hex::decode(payer).unwrap(),
key_id,
sequence_number: account.keys[key_id as usize].sequence_number as u64,
};
let public_key_to_add_arg = Argument::str(public_key_to_add);
let transaction: Transaction = build_transaction(
update_contract_template.to_vec(),
vec![public_key_to_add_arg.encode_str()],
latest_block.block.unwrap().id,
1000,
proposer,
vec![payer.to_owned()],
payer.to_owned(),
)
.await?;
let signature = Sign {
address: payer.to_owned(),
key_id,
private_key: payer_private_key.to_owned(),
};
let transaction: Option<Transaction> =
sign_transaction(transaction, vec![], vec![&signature]).await?;
let transaction: SendTransactionResponse = self.send_transaction(transaction).await?;
Ok(transaction)
}
pub async fn remove_key(
&mut self,
key_to_remove: u64,
payer: &str,
payer_private_key: &str,
key_id: u32,
) -> Result<flow::SendTransactionResponse, Box<dyn error::Error>> {
let update_contract_template = b"
transaction(keyIndex: Int) {
prepare(signer: AuthAccount) {
signer.keys.revoke(keyIndex)
}
}
";
let latest_block: BlockResponse = self.get_block(None, None, Some(false)).await?;
let account: flow::Account = self.get_account(payer).await?.account.unwrap();
let proposer = TransactionProposalKey {
address: hex::decode(payer).unwrap(),
key_id,
sequence_number: account.keys[key_id as usize].sequence_number as u64,
};
let key_to_remove_arg = Argument::uint64(key_to_remove);
let transaction: Transaction = build_transaction(
update_contract_template.to_vec(),
vec![key_to_remove_arg.encode()],
latest_block.block.unwrap().id,
1000,
proposer,
vec![payer.to_owned()],
payer.to_owned(),
)
.await?;
let signature = Sign {
address: payer.to_owned(),
key_id,
private_key: payer_private_key.to_owned(),
};
let transaction: Option<Transaction> =
sign_transaction(transaction, vec![], vec![&signature]).await?;
let transaction: SendTransactionResponse = self.send_transaction(transaction).await?;
Ok(transaction)
}
pub async fn add_contract(
&mut self,
contract_name: &str,
contract_code: &str,
payer: &str,
payer_private_key: &str,
key_id: u32,
) -> Result<flow::SendTransactionResponse, Box<dyn error::Error>> {
let update_contract_template = b"
transaction(name: String, code: String) {
prepare(signer: AuthAccount) {
signer.contracts.add(name: name, code: code.decodeHex())
}
}
";
let latest_block: BlockResponse = self.get_block(None, None, Some(false)).await?;
let account: flow::Account = self.get_account(payer).await?.account.unwrap();
let proposer = TransactionProposalKey {
address: hex::decode(payer).unwrap(),
key_id,
sequence_number: account.keys[key_id as usize].sequence_number as u64,
};
let contract_name_arg = Argument::str(contract_name);
let contract_code_arg = Argument::str(contract_code);
let transaction: Transaction = build_transaction(
update_contract_template.to_vec(),
vec![
contract_name_arg.encode_str(),
contract_code_arg.encode_str(),
],
latest_block.block.unwrap().id,
1000,
proposer,
vec![payer.to_owned()],
payer.to_owned(),
)
.await?;
let signature = Sign {
address: payer.to_owned(),
key_id,
private_key: payer_private_key.to_owned(),
};
let transaction: Option<Transaction> =
sign_transaction(transaction, vec![], vec![&signature]).await?;
let transaction: SendTransactionResponse = self.send_transaction(transaction).await?;
Ok(transaction)
}
pub async fn update_contract(
&mut self,
contract_name: &str,
contract_code: &str,
payer: &str,
payer_private_key: &str,
key_id: u32,
) -> Result<flow::SendTransactionResponse, Box<dyn error::Error>> {
let update_contract_template = b"
transaction(name: String, code: String) {
prepare(signer: AuthAccount) {
signer.contracts.update__experimental(name: name, code: code.decodeHex())
}
}
";
let latest_block: BlockResponse = self.get_block(None, None, Some(false)).await?;
let account: flow::Account = self.get_account(payer).await?.account.unwrap();
let proposer = TransactionProposalKey {
address: hex::decode(payer).unwrap(),
key_id,
sequence_number: account.keys[key_id as usize].sequence_number as u64,
};
let contract_name_arg = Argument::str(contract_name);
let contract_code_arg = Argument::str(contract_code);
let transaction: Transaction = build_transaction(
update_contract_template.to_vec(),
vec![
contract_name_arg.encode_str(),
contract_code_arg.encode_str(),
],
latest_block.block.unwrap().id,
1000,
proposer,
vec![payer.to_owned()],
payer.to_owned(),
)
.await?;
let signature = Sign {
address: payer.to_owned(),
key_id,
private_key: payer_private_key.to_owned(),
};
let transaction: Option<Transaction> =
sign_transaction(transaction, vec![], vec![&signature]).await?;
let transaction: SendTransactionResponse = self.send_transaction(transaction).await?;
Ok(transaction)
}
pub async fn remove_contract(
&mut self,
contract_name: &str,
payer: &str,
payer_private_key: &str,
key_id: u32,
) -> Result<flow::SendTransactionResponse, Box<dyn error::Error>> {
let update_contract_template = b"
transaction(name: String) {
prepare(signer: AuthAccount) {
signer.contracts.remove(name: name)
}
}
";
let latest_block: BlockResponse = self.get_block(None, None, Some(false)).await?;
let account: flow::Account = self.get_account(payer).await?.account.unwrap();
let proposer = TransactionProposalKey {
address: hex::decode(payer).unwrap(),
key_id,
sequence_number: account.keys[key_id as usize].sequence_number as u64,
};
let contract_name_arg = Argument::str(contract_name);
let transaction: Transaction = build_transaction(
update_contract_template.to_vec(),
vec![contract_name_arg.encode_str()],
latest_block.block.unwrap().id,
1000,
proposer,
vec![payer.to_owned()],
payer.to_owned(),
)
.await?;
let signature = Sign {
address: payer.to_owned(),
key_id,
private_key: payer_private_key.to_owned(),
};
let transaction: Option<Transaction> =
sign_transaction(transaction, vec![], vec![&signature]).await?;
let transaction: SendTransactionResponse = self.send_transaction(transaction).await?;
Ok(transaction)
}
}
use serde::Serialize;
pub use serde_json::{from_slice, json, to_vec, Value};
use tokio::time::{sleep, Duration};
#[derive(Serialize)]
pub struct Argument<T> {
r#type: &'static str,
value: T,
}
impl Argument<Vec<Value>> {
pub fn array(values: Vec<Value>) -> Argument<Vec<Value>> {
Argument {
r#type: "Array",
value: values,
}
}
pub fn dictionary(values: Vec<(String, String)>) -> Argument<Vec<Value>> {
Argument {
r#type: "Dictionary",
value: values
.into_iter()
.map(|(x, y)| json!({"Key":x, "Value":y}))
.collect(),
}
}
pub fn encode_arr(&self) -> Vec<u8> {
to_vec(&json!(self)).unwrap()
}
}
impl Argument<bool> {
pub fn boolean(value: bool) -> Argument<bool> {
Argument {
r#type: "Bool",
value,
}
}
}
impl Argument<&str> {
pub fn str(value: &str) -> Argument<&str> {
Argument {
r#type: "String",
value,
}
}
pub fn encode_str(&self) -> Vec<u8> {
to_vec(&json!(self)).unwrap()
}
}
impl Argument<String> {
pub fn string(value: String) -> Argument<String> {
Argument {
r#type: "String",
value,
}
}
pub fn ufix64(value: f64) -> Argument<String> {
assert!(value >= 0.0, "{}", true); Argument {
r#type: "UFix64",
value: value.to_string(),
}
}
pub fn fix64(value: f64) -> Argument<String> {
Argument {
r#type: "Fix64",
value: value.to_string(),
}
}
pub fn uint64(value: u64) -> Argument<String> {
Argument {
r#type: "UInt64",
value: value.to_string(),
}
}
pub fn int64(value: i64) -> Argument<String> {
Argument {
r#type: "Int64",
value: value.to_string(),
}
}
pub fn address(value: String) -> Argument<String> {
Argument {
r#type: "Address",
value,
}
}
pub fn encode(&self) -> Vec<u8> {
to_vec(&json!(self)).unwrap()
}
}
fn padding(vec: &mut Vec<u8>, count: usize) {
let mut i: usize = count;
i -= vec.len();
while i > 0 {
vec.push(0);
i -= 1;
}
}
pub struct Sign {
pub address: String,
pub key_id: u32,
pub private_key: String,
}
pub async fn build_transaction(
script: Vec<u8>,
arguments: Vec<Vec<u8>>,
reference_block_id: Vec<u8>,
gas_limit: u64,
proposer: TransactionProposalKey,
authorizers: Vec<String>,
payer: String,
) -> Result<Transaction, Box<dyn error::Error>> {
Ok(Transaction {
script,
arguments,
reference_block_id,
gas_limit,
proposal_key: Some(proposer),
authorizers: authorizers
.iter()
.map(|x| hex::decode(x).unwrap())
.collect(),
payload_signatures: vec![],
envelope_signatures: vec![],
payer: hex::decode(payer).unwrap(),
})
}
fn envelope_from_transaction(
transaction: Transaction,
payload_signatures: &[TransactionSignature],
) -> Vec<u8> {
let proposal_key = transaction.proposal_key.unwrap();
let mut proposal_address = proposal_key.address;
padding(&mut proposal_address, 8);
let mut ref_block = transaction.reference_block_id;
padding(&mut ref_block, 32);
let mut stream = RlpStream::new_list(2);
stream.begin_list(9);
stream.append(&Bytes::from(transaction.script).to_vec());
stream.begin_list(transaction.arguments.len());
for (_i, arg) in transaction.arguments.into_iter().enumerate() {
stream.append(&Bytes::from(arg).to_vec());
}
stream.append(&Bytes::from(ref_block).to_vec());
stream.append(&transaction.gas_limit);
stream.append(&Bytes::from(proposal_address).to_vec());
stream.append(&proposal_key.key_id);
stream.append(&proposal_key.sequence_number);
stream.append(&Bytes::from(transaction.payer).to_vec());
stream.begin_list(transaction.authorizers.len());
for (_i, auth) in transaction.authorizers.into_iter().enumerate() {
stream.append(&Bytes::from(auth).to_vec());
}
stream.begin_list(payload_signatures.len());
for (i, sig) in payload_signatures.iter().enumerate() {
let signature = sig.signature.to_vec();
stream.begin_list(3);
stream.append(&(i as u32));
stream.append(&sig.key_id);
stream.append(&signature);
}
stream.out().to_vec()
}
fn payload_from_transaction(transaction: Transaction) -> Vec<u8> {
let proposal_key = transaction.proposal_key.unwrap();
let mut proposal_address = proposal_key.address;
padding(&mut proposal_address, 8);
let mut ref_block = transaction.reference_block_id;
padding(&mut ref_block, 32);
let mut stream = RlpStream::new_list(9);
stream.append(&Bytes::from(transaction.script).to_vec());
stream.begin_list(transaction.arguments.len());
for (_i, arg) in transaction.arguments.into_iter().enumerate() {
stream.append(&Bytes::from(arg).to_vec());
}
stream.append(&Bytes::from(ref_block).to_vec());
stream.append(&transaction.gas_limit);
stream.append(&Bytes::from(proposal_address).to_vec());
stream.append(&proposal_key.key_id);
stream.append(&proposal_key.sequence_number);
stream.append(&Bytes::from(transaction.payer).to_vec());
stream.begin_list(transaction.authorizers.len());
for (_i, auth) in transaction.authorizers.into_iter().enumerate() {
stream.append(&Bytes::from(auth).to_vec());
}
stream.out().to_vec()
}
fn sign(message: Vec<u8>, private_key: String) -> Result<Vec<u8>, Box<dyn error::Error>> {
let secret_key = SecretKey::from_be_bytes(&hex::decode(private_key)?)?;
let sig_key = SigningKey::from(secret_key);
let signature = sig_key.sign(&message);
Ok(signature.as_bytes().to_vec())
}
pub fn process_keys_args(account_keys: Vec<String>) -> Argument<Vec<Value>> {
Argument::array(
account_keys
.into_iter()
.map(|x| json!(Argument::string(format!("f847b840{}02038203e8", x))))
.collect::<Vec<Value>>(),
)
}
pub async fn sign_transaction(
built_transaction: Transaction,
payload_signatures: Vec<&Sign>,
envelope_signatures: Vec<&Sign>,
) -> Result<Option<Transaction>, Box<dyn error::Error>> {
let mut payload: Vec<TransactionSignature> = vec![];
let mut envelope: Vec<TransactionSignature> = vec![];
for signer in payload_signatures {
let encoded_payload: &[u8] = &payload_from_transaction(built_transaction.clone());
let mut domain_tag: Vec<u8> = b"FLOW-V0.0-transaction".to_vec();
padding(&mut domain_tag, 32);
let fully_encoded: Vec<u8> = [&domain_tag, encoded_payload].concat();
let mut addr = hex::decode(signer.address.clone()).unwrap();
padding(&mut addr, 8);
payload.push(TransactionSignature {
address: addr,
key_id: signer.key_id,
signature: sign(fully_encoded, signer.private_key.clone())?,
});
}
for signer in envelope_signatures {
let encoded_payload: &[u8] =
&envelope_from_transaction(built_transaction.clone(), &payload);
let mut domain_tag: Vec<u8> = b"FLOW-V0.0-transaction".to_vec();
padding(&mut domain_tag, 32);
let fully_encoded: Vec<u8> = [&domain_tag, encoded_payload].concat();
let mut addr = hex::decode(signer.address.clone()).unwrap();
padding(&mut addr, 8);
envelope.push(TransactionSignature {
address: addr,
key_id: signer.key_id,
signature: sign(fully_encoded, signer.private_key.clone())?,
});
}
let signed_transaction = Some(Transaction {
script: built_transaction.script,
arguments: built_transaction.arguments,
reference_block_id: built_transaction.reference_block_id,
gas_limit: built_transaction.gas_limit,
proposal_key: built_transaction.proposal_key,
authorizers: built_transaction.authorizers,
payload_signatures: payload,
envelope_signatures: envelope,
payer: built_transaction.payer,
});
Ok(signed_transaction)
}
#[cfg(test)]
mod tests {
#[tokio::test]
async fn meaningful_test() {
println!("does not exist yet. :)")
}
}