feat: trezor support (#663)
* added trezor signer * linting * TrezorHDPath instead of HDPath * update trezor_client rev. added compatible hidapi backend * remove unused variables * keep track of the client session_id * add to Other derivation paths to trezor * remove commented macro * remove unnecessary drops * no ens * added TrezorTransaction that loads from TypedTransaction * enforce minimum firmware version * add big data test to trezor app * clippy * replace trezor-client git with published crate * change one char string to char * bump trezor-client, with ethereum feature only
This commit is contained in:
parent
4c677933ce
commit
6bf325dcab
|
@ -1333,6 +1333,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber",
|
||||
"trezor-client",
|
||||
"yubihsm",
|
||||
]
|
||||
|
||||
|
@ -2452,6 +2453,12 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "2.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47c327e191621a2158159df97cdbc2e7074bb4e940275e35abf38eb3d2595754"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
|
@ -3648,6 +3655,21 @@ dependencies = [
|
|||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trezor-client"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff94fab279e0d429d762c289f9727f37a0f1b8207ea4795f09c11caad009046f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"hex",
|
||||
"hidapi",
|
||||
"log",
|
||||
"primitive-types",
|
||||
"protobuf",
|
||||
"rusb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
|
|
|
@ -67,6 +67,7 @@ openssl = ["ethers-providers/openssl"]
|
|||
dev-rpc = ["ethers-providers/dev-rpc"]
|
||||
## signers
|
||||
ledger = ["ethers-signers/ledger"]
|
||||
trezor = ["ethers-signers/trezor"]
|
||||
yubi = ["ethers-signers/yubi"]
|
||||
## contracts
|
||||
abigen = ["ethers-contract/abigen"]
|
||||
|
|
|
@ -28,6 +28,7 @@ yubihsm = { version = "0.39.0", features = ["secp256k1", "http", "usb"], optiona
|
|||
futures-util = "0.3.18"
|
||||
futures-executor = "0.3.18"
|
||||
semver = "1.0.4"
|
||||
trezor-client = { version = "0.0.3", optional = true, default-features = false, features = ["f_ethereum"] }
|
||||
|
||||
# aws
|
||||
rusoto_core = { version = "0.47.0", optional = true }
|
||||
|
@ -56,3 +57,4 @@ celo = ["ethers-core/celo"]
|
|||
ledger = ["coins-ledger"]
|
||||
yubi = ["yubihsm"]
|
||||
aws = ["rusoto_core", "rusoto_kms", "tracing", "tracing-futures", "spki"]
|
||||
trezor = ["trezor-client"]
|
||||
|
|
|
@ -20,6 +20,14 @@ pub use ledger::{
|
|||
types::{DerivationType as HDPath, LedgerError},
|
||||
};
|
||||
|
||||
#[cfg(feature = "trezor")]
|
||||
mod trezor;
|
||||
#[cfg(feature = "trezor")]
|
||||
pub use trezor::{
|
||||
app::TrezorEthereum as Trezor,
|
||||
types::{DerivationType as TrezorHDPath, TrezorError},
|
||||
};
|
||||
|
||||
#[cfg(feature = "yubi")]
|
||||
pub use yubihsm;
|
||||
|
||||
|
|
|
@ -0,0 +1,339 @@
|
|||
#![allow(unused)]
|
||||
use trezor_client::client::{AccessListItem as Trezor_AccessListItem, Trezor};
|
||||
|
||||
use futures_executor::block_on;
|
||||
use futures_util::lock::Mutex;
|
||||
|
||||
use ethers_core::{
|
||||
types::{
|
||||
transaction::{eip2718::TypedTransaction, eip712::Eip712},
|
||||
Address, NameOrAddress, Signature, Transaction, TransactionRequest, TxHash, H256, U256,
|
||||
},
|
||||
utils::keccak256,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::types::*;
|
||||
|
||||
/// A Trezor Ethereum App.
|
||||
///
|
||||
/// This is a simple wrapper around the [Trezor transport](Trezor)
|
||||
#[derive(Debug)]
|
||||
pub struct TrezorEthereum {
|
||||
derivation: DerivationType,
|
||||
session_id: Vec<u8>,
|
||||
pub(crate) chain_id: u64,
|
||||
pub(crate) address: Address,
|
||||
}
|
||||
|
||||
const FIRMWARE_MIN_VERSION: &str = ">=2.4.2";
|
||||
|
||||
impl TrezorEthereum {
|
||||
pub async fn new(derivation: DerivationType, chain_id: u64) -> Result<Self, TrezorError> {
|
||||
let mut blank = Self {
|
||||
derivation: derivation.clone(),
|
||||
chain_id,
|
||||
address: Address::from([0_u8; 20]),
|
||||
session_id: vec![],
|
||||
};
|
||||
|
||||
// Check if reachable
|
||||
blank.initate_session()?;
|
||||
blank.address = blank.get_address_with_path(&derivation).await?;
|
||||
Ok(blank)
|
||||
}
|
||||
|
||||
fn check_version(version: String) -> Result<(), TrezorError> {
|
||||
let req = semver::VersionReq::parse(FIRMWARE_MIN_VERSION)?;
|
||||
let version = semver::Version::parse(&version)?;
|
||||
|
||||
// Enforce firmware version is greater than FIRMWARE_MIN_VERSION
|
||||
if !req.matches(&version) {
|
||||
return Err(TrezorError::UnsupportedFirmwareVersion(FIRMWARE_MIN_VERSION.to_string()))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initate_session(&mut self) -> Result<(), TrezorError> {
|
||||
let mut client = trezor_client::unique(false)?;
|
||||
client.init_device(None)?;
|
||||
|
||||
let features = client.features().ok_or(TrezorError::FeaturesError)?;
|
||||
|
||||
Self::check_version(format!(
|
||||
"{}.{}.{}",
|
||||
features.get_major_version(),
|
||||
features.get_minor_version(),
|
||||
features.get_patch_version()
|
||||
))?;
|
||||
|
||||
self.session_id = features.get_session_id().to_vec();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// You need to drop(client) once you're done with it
|
||||
fn get_client(&self, session_id: Vec<u8>) -> Result<Trezor, TrezorError> {
|
||||
let mut client = trezor_client::unique(false)?;
|
||||
client.init_device(Some(session_id))?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Get the account which corresponds to our derivation path
|
||||
pub async fn get_address(&self) -> Result<Address, TrezorError> {
|
||||
Ok(self.get_address_with_path(&self.derivation).await?)
|
||||
}
|
||||
|
||||
/// Gets the account which corresponds to the provided derivation path
|
||||
pub async fn get_address_with_path(
|
||||
&self,
|
||||
derivation: &DerivationType,
|
||||
) -> Result<Address, TrezorError> {
|
||||
let mut client = self.get_client(self.session_id.clone())?;
|
||||
|
||||
let address_str = client.ethereum_get_address(Self::convert_path(derivation))?;
|
||||
|
||||
let mut address = [0; 20];
|
||||
address.copy_from_slice(&hex::decode(&address_str[2..])?);
|
||||
|
||||
Ok(Address::from(address))
|
||||
}
|
||||
|
||||
/// Signs an Ethereum transaction (requires confirmation on the Trezor)
|
||||
pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result<Signature, TrezorError> {
|
||||
let mut client = self.get_client(self.session_id.clone())?;
|
||||
|
||||
let arr_path = Self::convert_path(&self.derivation);
|
||||
|
||||
let transaction = TrezorTransaction::load(tx)?;
|
||||
|
||||
let signature = match tx {
|
||||
TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => client.ethereum_sign_tx(
|
||||
arr_path,
|
||||
transaction.nonce,
|
||||
transaction.gas_price,
|
||||
transaction.gas,
|
||||
transaction.to,
|
||||
transaction.value,
|
||||
transaction.data,
|
||||
self.chain_id,
|
||||
)?,
|
||||
TypedTransaction::Eip1559(eip1559_tx) => client.ethereum_sign_eip1559_tx(
|
||||
arr_path,
|
||||
transaction.nonce,
|
||||
transaction.gas,
|
||||
transaction.to,
|
||||
transaction.value,
|
||||
transaction.data,
|
||||
self.chain_id,
|
||||
transaction.max_fee_per_gas,
|
||||
transaction.max_priority_fee_per_gas,
|
||||
transaction.access_list,
|
||||
)?,
|
||||
};
|
||||
|
||||
Ok(Signature { r: signature.r, s: signature.s, v: signature.v })
|
||||
}
|
||||
|
||||
/// Signs an ethereum personal message
|
||||
pub async fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Result<Signature, TrezorError> {
|
||||
let message = message.as_ref();
|
||||
let mut client = self.get_client(self.session_id.clone())?;
|
||||
let apath = Self::convert_path(&self.derivation);
|
||||
|
||||
let signature = client.ethereum_sign_message(message.into(), apath)?;
|
||||
|
||||
Ok(Signature { r: signature.r, s: signature.s, v: signature.v })
|
||||
}
|
||||
|
||||
/// Signs an EIP712 encoded domain separator and message
|
||||
pub async fn sign_typed_struct<T>(&self, payload: &T) -> Result<Signature, TrezorError>
|
||||
where
|
||||
T: Eip712,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// helper which converts a derivation path to [u32]
|
||||
fn convert_path(derivation: &DerivationType) -> Vec<u32> {
|
||||
let derivation = derivation.to_string();
|
||||
let elements = derivation.split('/').skip(1).collect::<Vec<_>>();
|
||||
let depth = elements.len();
|
||||
|
||||
let mut path = vec![];
|
||||
for derivation_index in elements {
|
||||
let hardened = derivation_index.contains('\'');
|
||||
let mut index = derivation_index.replace('\'', "").parse::<u32>().unwrap();
|
||||
if hardened {
|
||||
index |= 0x80000000;
|
||||
}
|
||||
path.push(index);
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "trezor"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Signer;
|
||||
use ethers_contract::EthAbiType;
|
||||
use ethers_core::types::{
|
||||
transaction::{
|
||||
eip2930::{AccessList, AccessListItem},
|
||||
eip712::Eip712,
|
||||
},
|
||||
Address, Eip1559TransactionRequest, TransactionRequest, I256, U256,
|
||||
};
|
||||
use ethers_derive_eip712::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
||||
#[eip712(
|
||||
name = "Eip712Test",
|
||||
version = "1",
|
||||
chain_id = 1,
|
||||
verifying_contract = "0x0000000000000000000000000000000000000001",
|
||||
salt = "eip712-test-75F0CCte"
|
||||
)]
|
||||
struct FooBar {
|
||||
foo: I256,
|
||||
bar: U256,
|
||||
fizz: Vec<u8>,
|
||||
buzz: [u8; 32],
|
||||
far: String,
|
||||
out: Address,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
// Replace this with your ETH addresses.
|
||||
async fn test_get_address() {
|
||||
// Instantiate it with the default trezor derivation path
|
||||
let trezor = TrezorEthereum::new(DerivationType::TrezorLive(1), 1).await.unwrap();
|
||||
assert_eq!(
|
||||
trezor.get_address().await.unwrap(),
|
||||
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
trezor.get_address_with_path(&DerivationType::TrezorLive(0)).await.unwrap(),
|
||||
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_sign_tx() {
|
||||
let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap();
|
||||
|
||||
// approve uni v2 router 0xff
|
||||
let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
|
||||
|
||||
let tx_req = TransactionRequest::new()
|
||||
.to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
|
||||
.gas(1000000)
|
||||
.gas_price(400e9 as u64)
|
||||
.nonce(5)
|
||||
.data(data)
|
||||
.value(ethers_core::utils::parse_ether(100).unwrap())
|
||||
.into();
|
||||
let tx = trezor.sign_transaction(&tx_req).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_sign_big_data_tx() {
|
||||
let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap();
|
||||
|
||||
// invalid data
|
||||
let big_data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string()+ &"ff".repeat(1032*2) + &"aa".to_string()).unwrap();
|
||||
let tx_req = TransactionRequest::new()
|
||||
.to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
|
||||
.gas(1000000)
|
||||
.gas_price(400e9 as u64)
|
||||
.nonce(5)
|
||||
.data(big_data)
|
||||
.value(ethers_core::utils::parse_ether(100).unwrap())
|
||||
.into();
|
||||
let tx = trezor.sign_transaction(&tx_req).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_sign_eip1559_tx() {
|
||||
let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap();
|
||||
|
||||
// approve uni v2 router 0xff
|
||||
let data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
|
||||
|
||||
let lst = AccessList(vec![
|
||||
AccessListItem {
|
||||
address: "0x8ba1f109551bd432803012645ac136ddd64dba72".parse().unwrap(),
|
||||
storage_keys: vec![
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000042"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
],
|
||||
},
|
||||
AccessListItem {
|
||||
address: "0x2ed7afa17473e17ac59908f088b4371d28585476".parse().unwrap(),
|
||||
storage_keys: vec![
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000042"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
let tx_req = Eip1559TransactionRequest::new()
|
||||
.to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
|
||||
.gas(1000000)
|
||||
.max_fee_per_gas(400e9 as u64)
|
||||
.max_priority_fee_per_gas(400e9 as u64)
|
||||
.nonce(5)
|
||||
.data(data)
|
||||
.access_list(lst)
|
||||
.value(ethers_core::utils::parse_ether(100).unwrap())
|
||||
.into();
|
||||
|
||||
let tx = trezor.sign_transaction(&tx_req).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_sign_message() {
|
||||
let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap();
|
||||
let message = "hello world";
|
||||
let sig = trezor.sign_message(message).await.unwrap();
|
||||
let addr = trezor.get_address().await.unwrap();
|
||||
sig.verify(message, addr).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_sign_eip712_struct() {
|
||||
let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1u64).await.unwrap();
|
||||
|
||||
let foo_bar = FooBar {
|
||||
foo: I256::from(10),
|
||||
bar: U256::from(20),
|
||||
fizz: b"fizz".to_vec(),
|
||||
buzz: keccak256("buzz"),
|
||||
far: String::from("space"),
|
||||
out: Address::from([0; 20]),
|
||||
};
|
||||
|
||||
let sig = trezor.sign_typed_struct(&foo_bar).await.expect("failed to sign typed data");
|
||||
let foo_bar_hash = foo_bar.encode_eip712().unwrap();
|
||||
sig.verify(foo_bar_hash, trezor.address).unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
pub mod app;
|
||||
pub mod types;
|
||||
|
||||
use crate::Signer;
|
||||
use app::TrezorEthereum;
|
||||
use async_trait::async_trait;
|
||||
use ethers_core::types::{
|
||||
transaction::{eip2718::TypedTransaction, eip712::Eip712},
|
||||
Address, Signature,
|
||||
};
|
||||
use types::TrezorError;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl Signer for TrezorEthereum {
|
||||
type Error = TrezorError;
|
||||
|
||||
/// Signs the hash of the provided message after prefixing it
|
||||
async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
|
||||
&self,
|
||||
message: S,
|
||||
) -> Result<Signature, Self::Error> {
|
||||
self.sign_message(message).await
|
||||
}
|
||||
|
||||
/// Signs the transaction
|
||||
async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
|
||||
self.sign_tx(message).await
|
||||
}
|
||||
|
||||
/// Signs a EIP712 derived struct
|
||||
async fn sign_typed_data<T: Eip712 + Send + Sync>(
|
||||
&self,
|
||||
payload: &T,
|
||||
) -> Result<Signature, Self::Error> {
|
||||
self.sign_typed_struct(payload).await
|
||||
}
|
||||
|
||||
/// Returns the signer's Ethereum Address
|
||||
fn address(&self) -> Address {
|
||||
self.address
|
||||
}
|
||||
|
||||
fn with_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
|
||||
self.chain_id = chain_id.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn chain_id(&self) -> u64 {
|
||||
self.chain_id
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
#![allow(clippy::upper_case_acronyms)]
|
||||
//! Helpers for interacting with the Ethereum Trezor App
|
||||
//! [Official Docs](https://github.com/TrezorHQ/app-ethereum/blob/master/doc/ethapp.asc)
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
use ethers_core::types::{transaction::eip2718::TypedTransaction, NameOrAddress, U256};
|
||||
use trezor_client::client::AccessListItem as Trezor_AccessListItem;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Trezor wallet type
|
||||
pub enum DerivationType {
|
||||
/// Trezor Live-generated HD path
|
||||
TrezorLive(usize),
|
||||
/// Any other path. Attention! Trezor by default forbids custom derivation paths
|
||||
/// Run trezorctl set safety-checks prompt, to allow it
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for DerivationType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
DerivationType::TrezorLive(index) => format!("m/44'/60'/{}'/0/0", index),
|
||||
DerivationType::Other(inner) => inner.to_owned(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Error when using the Trezor transport
|
||||
pub enum TrezorError {
|
||||
/// Underlying Trezor transport error
|
||||
#[error(transparent)]
|
||||
TrezorError(#[from] trezor_client::error::Error),
|
||||
#[error("Trezor was not able to retrieve device features")]
|
||||
FeaturesError,
|
||||
#[error("Not able to unpack value for TrezorTransaction.")]
|
||||
DataError,
|
||||
#[error(transparent)]
|
||||
/// Error when converting from a hex string
|
||||
HexError(#[from] hex::FromHexError),
|
||||
#[error(transparent)]
|
||||
/// Error when converting a semver requirement
|
||||
SemVerError(#[from] semver::Error),
|
||||
/// Error when signing EIP712 struct with not compatible Trezor ETH app
|
||||
#[error("Trezor ethereum app requires at least version: {0:?}")]
|
||||
UnsupportedFirmwareVersion(String),
|
||||
}
|
||||
|
||||
/// Trezor Transaction Struct
|
||||
pub struct TrezorTransaction {
|
||||
pub nonce: Vec<u8>,
|
||||
pub gas: Vec<u8>,
|
||||
pub gas_price: Vec<u8>,
|
||||
pub value: Vec<u8>,
|
||||
pub to: String,
|
||||
pub data: Vec<u8>,
|
||||
pub max_fee_per_gas: Vec<u8>,
|
||||
pub max_priority_fee_per_gas: Vec<u8>,
|
||||
pub access_list: Vec<Trezor_AccessListItem>,
|
||||
}
|
||||
|
||||
impl TrezorTransaction {
|
||||
fn to_trimmed_big_endian(_value: &U256) -> Vec<u8> {
|
||||
let mut trimmed_value = [0_u8; 32];
|
||||
_value.to_big_endian(&mut trimmed_value);
|
||||
trimmed_value[_value.leading_zeros() as usize / 8..].to_vec()
|
||||
}
|
||||
|
||||
pub fn load(tx: &TypedTransaction) -> Result<Self, TrezorError> {
|
||||
let to: String = match tx.to().ok_or(TrezorError::DataError)? {
|
||||
NameOrAddress::Name(_) => unimplemented!(),
|
||||
NameOrAddress::Address(value) => format!("0x{}", hex::encode(value)),
|
||||
};
|
||||
|
||||
let nonce = Self::to_trimmed_big_endian(tx.nonce().ok_or(TrezorError::DataError)?);
|
||||
let gas = Self::to_trimmed_big_endian(tx.gas().ok_or(TrezorError::DataError)?);
|
||||
let gas_price = Self::to_trimmed_big_endian(&tx.gas_price().ok_or(TrezorError::DataError)?);
|
||||
let value = Self::to_trimmed_big_endian(tx.value().ok_or(TrezorError::DataError)?);
|
||||
let data = tx.data().ok_or(TrezorError::DataError)?.to_vec();
|
||||
|
||||
match tx {
|
||||
TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => Ok(Self {
|
||||
nonce,
|
||||
gas,
|
||||
gas_price,
|
||||
value,
|
||||
to,
|
||||
data,
|
||||
max_fee_per_gas: vec![],
|
||||
max_priority_fee_per_gas: vec![],
|
||||
access_list: vec![],
|
||||
}),
|
||||
TypedTransaction::Eip1559(eip1559_tx) => {
|
||||
let max_fee_per_gas = Self::to_trimmed_big_endian(
|
||||
&eip1559_tx.max_fee_per_gas.ok_or(TrezorError::DataError)?,
|
||||
);
|
||||
let max_priority_fee_per_gas = Self::to_trimmed_big_endian(
|
||||
&eip1559_tx.max_priority_fee_per_gas.ok_or(TrezorError::DataError)?,
|
||||
);
|
||||
|
||||
let mut access_list: Vec<Trezor_AccessListItem> = Vec::new();
|
||||
for item in &eip1559_tx.access_list.0 {
|
||||
let address: String = format!("0x{}", hex::encode(item.address));
|
||||
let mut storage_keys: Vec<Vec<u8>> = Vec::new();
|
||||
|
||||
for key in &item.storage_keys {
|
||||
storage_keys.push(key.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
access_list.push(Trezor_AccessListItem { address, storage_keys })
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
nonce,
|
||||
gas,
|
||||
gas_price,
|
||||
value,
|
||||
to,
|
||||
data,
|
||||
max_fee_per_gas,
|
||||
max_priority_fee_per_gas,
|
||||
access_list,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#[tokio::main]
|
||||
#[cfg(feature = "trezor")]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use ethers::{prelude::*, utils::parse_ether};
|
||||
|
||||
// Connect over websockets
|
||||
let provider = Provider::new(Ws::connect("ws://localhost:8545").await?);
|
||||
// Instantiate the connection to trezor with Trezor Live derivation path and
|
||||
// the wallet's index. You may also provide the chain_id.
|
||||
// (here: mainnet) for EIP155 support.
|
||||
// EIP1559 support
|
||||
// No EIP712 support yet.
|
||||
let trezor = Trezor::new(TrezorHDPath::TrezorLive(0), 1).await?;
|
||||
let client = SignerMiddleware::new(provider, trezor);
|
||||
|
||||
// Create and broadcast a transaction (ENS disabled!)
|
||||
// (this will require confirming the tx on the device)
|
||||
let tx = TransactionRequest::new()
|
||||
.to("0x99E2B13A8Ea8b00C68FA017ee250E98e870D8241")
|
||||
.value(parse_ether(10)?);
|
||||
let pending_tx = client.send_transaction(tx, None).await?;
|
||||
|
||||
// Get the receipt
|
||||
let _receipt = pending_tx.confirmations(3).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "trezor"))]
|
||||
fn main() {}
|
Loading…
Reference in New Issue