From 1e383b470c08bd78f7e2e645bd6f55f9daa450da Mon Sep 17 00:00:00 2001 From: Patrice Tisserand Date: Fri, 19 Apr 2024 20:18:50 +0200 Subject: [PATCH] feat(indexer): add support to send starknet multicall transaction --- apps/indexer/Cargo.lock | 100 +++++++++++-- apps/indexer/refund/Cargo.toml | 3 +- apps/indexer/refund/src/bin/extract_refund.rs | 18 +-- apps/indexer/refund/src/bin/send_refund.rs | 141 ++++++++++++++++++ apps/indexer/refund/src/lib.rs | 15 ++ 5 files changed, 251 insertions(+), 26 deletions(-) create mode 100644 apps/indexer/refund/src/bin/send_refund.rs create mode 100644 apps/indexer/refund/src/lib.rs diff --git a/apps/indexer/Cargo.lock b/apps/indexer/Cargo.lock index 136fb5d1..38bbdec2 100644 --- a/apps/indexer/Cargo.lock +++ b/apps/indexer/Cargo.lock @@ -3112,6 +3112,7 @@ dependencies = [ "serde", "serde_json", "starklane_indexer", + "starknet 0.10.0", "tokio", ] @@ -3890,7 +3891,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "starknet", + "starknet 0.5.0", "tokio", "url", ] @@ -3901,14 +3902,30 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcb61961b91757a9bc2d11549067445b2f921bd957f53710db35449767a1ba3" dependencies = [ - "starknet-accounts", - "starknet-contract", + "starknet-accounts 0.4.0", + "starknet-contract 0.4.0", "starknet-core 0.5.1", "starknet-crypto", "starknet-ff", "starknet-macros", - "starknet-providers", - "starknet-signers", + "starknet-providers 0.5.0", + "starknet-signers 0.3.0", +] + +[[package]] +name = "starknet" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b9a7b7bfd87287af85854f7458b8170ba6aa59c39113436532b7ff3d2fcbd8" +dependencies = [ + "starknet-accounts 0.9.0", + "starknet-contract 0.9.0", + "starknet-core 0.10.0", + "starknet-crypto", + "starknet-ff", + "starknet-macros", + "starknet-providers 0.10.0", + "starknet-signers 0.8.0", ] [[package]] @@ -3919,8 +3936,22 @@ checksum = "111ed887e4db14f0df1f909905e7737e4730770c8ed70997b58a71d5d940daac" dependencies = [ "async-trait", "starknet-core 0.5.1", - "starknet-providers", - "starknet-signers", + "starknet-providers 0.5.0", + "starknet-signers 0.3.0", + "thiserror", +] + +[[package]] +name = "starknet-accounts" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2095d7584608ae1707bd1cf2889368ab3734d9f54e4fcef4765cba1f3b3f7618" +dependencies = [ + "async-trait", + "auto_impl", + "starknet-core 0.10.0", + "starknet-providers 0.10.0", + "starknet-signers 0.8.0", "thiserror", ] @@ -3933,9 +3964,24 @@ dependencies = [ "serde", "serde_json", "serde_with 2.3.3", - "starknet-accounts", + "starknet-accounts 0.4.0", "starknet-core 0.5.1", - "starknet-providers", + "starknet-providers 0.5.0", + "thiserror", +] + +[[package]] +name = "starknet-contract" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3b73d437b4d62241612d13fce612602de6684c149cccf696e76a20757e2156" +dependencies = [ + "serde", + "serde_json", + "serde_with 2.3.3", + "starknet-accounts 0.9.0", + "starknet-core 0.10.0", + "starknet-providers 0.10.0", "thiserror", ] @@ -4060,6 +4106,26 @@ dependencies = [ "url", ] +[[package]] +name = "starknet-providers" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6abf40ffcbe3b887b4d5cfc8ab73170c816b4aa78d1d4ad59abd3fb3b0f53cd" +dependencies = [ + "async-trait", + "auto_impl", + "ethereum-types", + "flate2", + "log", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_with 2.3.3", + "starknet-core 0.10.0", + "thiserror", + "url", +] + [[package]] name = "starknet-signers" version = "0.3.0" @@ -4076,6 +4142,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "starknet-signers" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a2bd4fd66090003c3b7f0d76476e5b63cd44f6a49ede2442673f4427d5a40" +dependencies = [ + "async-trait", + "auto_impl", + "crypto-bigint", + "eth-keystore", + "rand", + "starknet-core 0.10.0", + "starknet-crypto", + "thiserror", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/apps/indexer/refund/Cargo.toml b/apps/indexer/refund/Cargo.toml index 8c54b2fe..9fde5038 100644 --- a/apps/indexer/refund/Cargo.toml +++ b/apps/indexer/refund/Cargo.toml @@ -15,4 +15,5 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1", features = ["full"] } -starklane_indexer = { version = "0.1.0", path= ".."} \ No newline at end of file +starklane_indexer = { version = "0.1.0", path= ".."} +starknet = "0.10.0" diff --git a/apps/indexer/refund/src/bin/extract_refund.rs b/apps/indexer/refund/src/bin/extract_refund.rs index 086d796c..0d448b45 100644 --- a/apps/indexer/refund/src/bin/extract_refund.rs +++ b/apps/indexer/refund/src/bin/extract_refund.rs @@ -2,30 +2,16 @@ use anyhow::{anyhow, Result}; use clap::Parser; -use serde::Serialize; - use starklane_indexer::{price::moralis::MoralisPrice, storage::{extract_database_name, mongo::MongoStore, store::{EventStore, RequestStore}}}; +use refund::Refund; const ENV_PREFIX: &'static str = "INDEXER"; const ENV_SEPARATOR: &'static str = "__"; // "_" can't be used since we have key with '_' in json -#[derive(Serialize, Debug, Clone)] -struct Refund { - #[serde(rename = "Token Address")] - token_address: String, - #[serde(rename = "Recipient")] - dest: String, - #[serde(rename = "Amount")] - amount: f64, - #[serde(rename = "USD")] - amount_usd: f64, - #[serde(rename = "Transaction Hash")] - tx_hash: String -} #[derive(Parser, Debug)] -#[clap(about = "Get refund")] +#[clap(about = "Extract refund")] struct Args { #[clap(long, help = "Mongo db connection string", env = format!("{}{}MONGODB_URI", ENV_PREFIX, ENV_SEPARATOR))] mongodb: String, diff --git a/apps/indexer/refund/src/bin/send_refund.rs b/apps/indexer/refund/src/bin/send_refund.rs new file mode 100644 index 00000000..3c4d65b1 --- /dev/null +++ b/apps/indexer/refund/src/bin/send_refund.rs @@ -0,0 +1,141 @@ +use std::time::Duration; + +use anyhow::{anyhow, Result}; +use clap::Parser; + +use refund::Refund; +use starknet::{ + accounts::{Account, Call, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount}, + core::{ + types::{ExecutionResult, FieldElement, StarknetError}, + utils::get_selector_from_name, + }, + providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider, ProviderError, Url}, + signers::{LocalWallet, SigningKey}, +}; + +#[derive(Parser, Debug)] +#[clap(about = "Send refund")] +struct Args { + #[clap(long, help = "Mongo db connection string", env = "REFUND_MONGODB_URI")] + mongodb: String, + + #[clap(long, help = "CSV input file")] + input: String, + + #[clap(long, help = "Starknet RPC", env = "STARKNET_RPC")] + rpc: String, + + #[clap( + long, + help = "Starknet account address", + env = "STARKNET_ACCOUNT_ADDRESS" + )] + address: String, + + #[clap( + long, + help = "Starknet account private key", + env = "STARKNET_PRIVATE_KEY" + )] + private_key: String, +} + +fn refund_to_call(refund: &Refund) -> Call { + let selector = get_selector_from_name("transfer").unwrap(); + let token_address = FieldElement::from_hex_be(&refund.token_address).unwrap(); + let dest = FieldElement::from_hex_be(&refund.dest).unwrap(); + let token_decimals = 18; + let decimals = 8; + let amount = refund.amount * (10_u64.pow(decimals) as f64); + let amount = (amount as u128) * (10_u64.pow(token_decimals - decimals) as u128); + // FIXME: for testing purpose!! + let amount = amount / 100; + // FIXME + let amount = FieldElement::from(amount); + Call { + to: token_address, + selector, + calldata: vec![dest, amount, FieldElement::ZERO], + } +} + +// From starkli +pub async fn watch_tx

( + provider: P, + transaction_hash: FieldElement, + poll_interval: Duration, +) -> Result<()> +where + P: Provider, +{ + loop { + match provider.get_transaction_receipt(transaction_hash).await { + Ok(receipt) => match receipt.execution_result() { + ExecutionResult::Succeeded => { + log::info!( + "Transaction {} confirmed", + format!("{:#064x}", transaction_hash) + ); + + return Ok(()); + } + ExecutionResult::Reverted { reason } => { + return Err(anyhow::anyhow!("transaction reverted: {}", reason)); + } + }, + Err(ProviderError::StarknetError(StarknetError::TransactionHashNotFound)) => { + log::debug!("Transaction not confirmed yet..."); + } + Err(err) => return Err(err.into()), + } + + tokio::time::sleep(poll_interval).await; + } +} + + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + let args = Args::parse(); + + let input = args.input; + let rpc = args.rpc; + let address = args.address; + let private_key = args.private_key; + + let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(&rpc).unwrap())); + let signer = LocalWallet::from(SigningKey::from_secret_scalar( + FieldElement::from_hex_be(&private_key).unwrap(), + )); + let address = FieldElement::from_hex_be(&address).unwrap(); + let chain_id = provider.chain_id().await?; + + let account = + SingleOwnerAccount::new(provider, signer, address, chain_id, ExecutionEncoding::New); + log::debug!("Account address: {:?}", account.address()); + let mut calls: Vec = vec![]; + + let mut rdr = csv::Reader::from_path(input)?; + for elem in rdr.deserialize() { + let refund: Refund = elem?; + log::debug!("{:?}", refund); + let call = refund_to_call(&refund); + calls.push(call); + } + // log::debug!("Calls: {:?}", calls); + + let invoke = account + .execute(calls) + .send() + .await + .unwrap(); + let provider = account.provider(); + log::debug!("Wait for transaction: {:?}", invoke.transaction_hash); + let result = watch_tx(provider, invoke.transaction_hash, Duration::from_millis(10000)).await; + match result { + Ok(_) => Ok(()), + Err(e) => Err(anyhow!("Transaction failed! {:?}", e)) + } +} diff --git a/apps/indexer/refund/src/lib.rs b/apps/indexer/refund/src/lib.rs new file mode 100644 index 00000000..73fc5212 --- /dev/null +++ b/apps/indexer/refund/src/lib.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct Refund { + #[serde(rename = "Token Address")] + pub token_address: String, + #[serde(rename = "Recipient")] + pub dest: String, + #[serde(rename = "Amount")] + pub amount: f64, + #[serde(rename = "USD")] + pub amount_usd: f64, + #[serde(rename = "Transaction Hash")] + pub tx_hash: String +}