1use std::error::Error;
2
3use anyhow::anyhow;
4use cosmos_sdk_proto::{
5 cosmos::{
6 auth::v1beta1::{
7 query_client::QueryClient as AuthQueryClient, BaseAccount as RawBaseAccount,
8 QueryAccountRequest,
9 },
10 tx::v1beta1::{
11 service_client::ServiceClient, BroadcastMode, BroadcastTxRequest, BroadcastTxResponse,
12 },
13 },
14 cosmwasm::wasm::v1::{
15 query_client::QueryClient as WasmdQueryClient, QueryRawContractStateRequest,
16 QuerySmartContractStateRequest,
17 },
18 traits::Message,
19 Any,
20};
21use cosmrs::{
22 auth::BaseAccount,
23 cosmwasm::MsgExecuteContract,
24 crypto::{secp256k1::SigningKey, PublicKey},
25 tendermint::chain::Id as TmChainId,
26 tx,
27 tx::{Fee, Msg, SignDoc, SignerInfo},
28 AccountId, Coin,
29};
30use reqwest::Url;
31use serde::de::DeserializeOwned;
32
33use crate::CwClient;
34
35pub struct GrpcClient {
36 sk: SigningKey,
37 url: Url,
38}
39
40impl GrpcClient {
41 pub fn new(sk: SigningKey, url: Url) -> Self {
42 Self { sk, url }
43 }
44}
45
46#[async_trait::async_trait]
47impl CwClient for GrpcClient {
48 type Address = AccountId;
49 type Query = serde_json::Value;
50 type RawQuery = String;
51 type ChainId = TmChainId;
52 type Error = anyhow::Error;
53
54 async fn query_smart<R: DeserializeOwned + Send>(
55 &self,
56 contract: &Self::Address,
57 query: Self::Query,
58 ) -> Result<R, Self::Error> {
59 let mut client = WasmdQueryClient::connect(self.url.to_string()).await?;
60
61 let raw_query_request = QuerySmartContractStateRequest {
62 address: contract.to_string(),
63 query_data: query.to_string().into_bytes(),
64 };
65
66 let raw_query_response = client.smart_contract_state(raw_query_request).await?;
67
68 let raw_value = raw_query_response.into_inner().data;
69 serde_json::from_slice(&raw_value)
70 .map_err(|e| anyhow!("failed to deserialize JSON reponse: {}", e))
71 }
72
73 async fn query_raw<R: DeserializeOwned + Default>(
74 &self,
75 contract: &Self::Address,
76 query: Self::RawQuery,
77 ) -> Result<R, Self::Error> {
78 let mut client = WasmdQueryClient::connect(self.url.to_string()).await?;
79
80 let raw_query_request = QueryRawContractStateRequest {
81 address: contract.to_string(),
82 query_data: query.to_string().into_bytes(),
83 };
84
85 let raw_query_response = client.raw_contract_state(raw_query_request).await?;
86
87 let raw_value = raw_query_response.into_inner().data;
88 serde_json::from_slice(&raw_value)
89 .map_err(|e| anyhow!("failed to deserialize JSON reponse: {}", e))
90 }
91
92 fn query_tx<R: DeserializeOwned + Default>(&self, _txhash: &str) -> Result<R, Self::Error> {
93 unimplemented!()
94 }
95
96 async fn tx_execute<M: ToString + Send>(
97 &self,
98 contract: &Self::Address,
99 chain_id: &TmChainId,
100 gas: u64,
101 _sender: &str,
102 msg: M,
103 _pay_amount: &str,
104 ) -> Result<String, Self::Error> {
105 let tm_pubkey = self.sk.public_key();
106 let sender = tm_pubkey
107 .account_id("neutron")
108 .map_err(|e| anyhow!("failed to create AccountId from pubkey: {}", e))?;
109
110 let msgs = vec![MsgExecuteContract {
111 sender: sender.clone(),
112 contract: contract.clone(),
113 msg: msg.to_string().into_bytes(),
114 funds: vec![],
115 }
116 .to_any()
117 .unwrap()];
118
119 let account = account_info(self.url.to_string(), sender.to_string())
120 .await
121 .map_err(|e| anyhow!("error querying account info: {}", e))?;
122 let amount = Coin {
123 amount: 11000u128,
124 denom: "untrn".parse().expect("hardcoded denom"),
125 };
126 let tx_bytes = tx_bytes(
127 &self.sk,
128 amount,
129 gas,
130 tm_pubkey,
131 msgs,
132 account.sequence,
133 account.account_number,
134 chain_id,
135 )
136 .map_err(|e| anyhow!("failed to create msg/tx: {}", e))?;
137
138 let response = send_tx(self.url.to_string(), tx_bytes)
139 .await
140 .map_err(|e| anyhow!("failed to send tx: {}", e))?;
141 println!("{response:?}");
142 Ok(response
143 .tx_response
144 .map(|tx_response| tx_response.txhash)
145 .unwrap_or_default())
146 }
147
148 fn deploy<M: ToString>(
149 &self,
150 _chain_id: &TmChainId,
151 _sender: &str,
152 _wasm_path: M,
153 ) -> Result<String, Self::Error> {
154 unimplemented!()
155 }
156
157 fn init<M: ToString>(
158 &self,
159 _chain_id: &TmChainId,
160 _sender: &str,
161 _code_id: u64,
162 _init_msg: M,
163 _label: &str,
164 ) -> Result<String, Self::Error> {
165 unimplemented!()
166 }
167
168 fn trusted_height_hash(&self) -> Result<(u64, String), Self::Error> {
169 unimplemented!()
170 }
171}
172
173pub async fn account_info(
174 node: impl ToString,
175 address: impl ToString,
176) -> Result<BaseAccount, Box<dyn Error>> {
177 let mut client = AuthQueryClient::connect(node.to_string()).await?;
178 let request = tonic::Request::new(QueryAccountRequest {
179 address: address.to_string(),
180 });
181 let response = client.account(request).await?;
182 let response = RawBaseAccount::decode(response.into_inner().account.unwrap().value.as_slice())?;
183 let account = BaseAccount::try_from(response)?;
184 Ok(account)
185}
186
187#[allow(clippy::too_many_arguments)]
188pub fn tx_bytes(
189 secret: &SigningKey,
190 amount: Coin,
191 gas: u64,
192 tm_pubkey: PublicKey,
193 msgs: Vec<Any>,
194 sequence_number: u64,
195 account_number: u64,
196 chain_id: &TmChainId,
197) -> Result<Vec<u8>, Box<dyn Error>> {
198 let tx_body = tx::Body::new(msgs, "", 0u16);
199 let signer_info = SignerInfo::single_direct(Some(tm_pubkey), sequence_number);
200 let auth_info = signer_info.auth_info(Fee::from_amount_and_gas(amount, gas));
201 let sign_doc = SignDoc::new(&tx_body, &auth_info, chain_id, account_number)?;
202 let tx_signed = sign_doc.sign(secret)?;
203 Ok(tx_signed.to_bytes()?)
204}
205
206pub async fn send_tx(
207 node: impl ToString,
208 tx_bytes: Vec<u8>,
209) -> Result<BroadcastTxResponse, Box<dyn Error>> {
210 let mut client = ServiceClient::connect(node.to_string()).await?;
211 let request = tonic::Request::new(BroadcastTxRequest {
212 tx_bytes,
213 mode: BroadcastMode::Sync.into(),
214 });
215 let tx_response = client.broadcast_tx(request).await?;
216 Ok(tx_response.into_inner())
217}