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
+}