1
- use std:: error:: Error ;
1
+ use std:: { error:: Error , str :: FromStr } ;
2
2
3
3
use anyhow:: anyhow;
4
4
use cosmos_sdk_proto:: {
@@ -25,7 +25,7 @@ use cosmrs::{
25
25
tendermint:: chain:: Id as TmChainId ,
26
26
tx,
27
27
tx:: { Fee , Msg , SignDoc , SignerInfo } ,
28
- AccountId , Coin ,
28
+ AccountId , Amount , Coin , Denom ,
29
29
} ;
30
30
use reqwest:: Url ;
31
31
use serde:: de:: DeserializeOwned ;
@@ -100,7 +100,7 @@ impl CwClient for GrpcClient {
100
100
gas : u64 ,
101
101
_sender : & str ,
102
102
msg : M ,
103
- _pay_amount : & str ,
103
+ pay_amount : & str ,
104
104
) -> Result < String , Self :: Error > {
105
105
let tm_pubkey = self . sk . public_key ( ) ;
106
106
let sender = tm_pubkey
@@ -119,10 +119,7 @@ impl CwClient for GrpcClient {
119
119
let account = account_info ( self . url . to_string ( ) , sender. to_string ( ) )
120
120
. await
121
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
- } ;
122
+ let amount = parse_coin ( pay_amount) ?;
126
123
let tx_bytes = tx_bytes (
127
124
& self . sk ,
128
125
amount,
@@ -215,3 +212,82 @@ pub async fn send_tx(
215
212
let tx_response = client. broadcast_tx ( request) . await ?;
216
213
Ok ( tx_response. into_inner ( ) )
217
214
}
215
+
216
+ pub fn parse_coin ( input : & str ) -> anyhow:: Result < Coin > {
217
+ let split_at = input
218
+ . find ( |c : char | !c. is_ascii_digit ( ) )
219
+ . ok_or ( anyhow ! ( "invalid coin format: missing denomination" ) ) ?;
220
+ let ( amt_str, denom_str) = input. split_at ( split_at) ;
221
+
222
+ let amount: Amount = amt_str. parse ( ) ?;
223
+ let denom: Denom = Denom :: from_str ( denom_str) . map_err ( |e| anyhow ! ( "invalid denom: {e}" ) ) ?;
224
+
225
+ Ok ( Coin { denom, amount } )
226
+ }
227
+
228
+ #[ cfg( test) ]
229
+ mod tests {
230
+ use std:: str:: FromStr ;
231
+
232
+ use super :: * ;
233
+
234
+ #[ test]
235
+ fn parse_valid_basic ( ) {
236
+ let coin = parse_coin ( "11000untrn" ) . unwrap ( ) ;
237
+ assert_eq ! ( coin. amount, 11_000 ) ;
238
+ assert_eq ! ( coin. denom, Denom :: from_str( "untrn" ) . unwrap( ) ) ;
239
+ }
240
+
241
+ #[ test]
242
+ fn parse_leading_zeros ( ) {
243
+ let coin = parse_coin ( "000123abc" ) . unwrap ( ) ;
244
+ assert_eq ! ( coin. amount, 123 ) ;
245
+ assert_eq ! ( coin. denom, Denom :: from_str( "abc" ) . unwrap( ) ) ;
246
+ }
247
+
248
+ #[ test]
249
+ fn parse_zero_amount ( ) {
250
+ let coin = parse_coin ( "0xyz" ) . unwrap ( ) ;
251
+ assert_eq ! ( coin. amount, 0 ) ;
252
+ assert_eq ! ( coin. denom, Denom :: from_str( "xyz" ) . unwrap( ) ) ;
253
+ }
254
+
255
+ #[ test]
256
+ fn parse_denom_with_digits ( ) {
257
+ let coin = parse_coin ( "10token123" ) . unwrap ( ) ;
258
+ assert_eq ! ( coin. amount, 10 ) ;
259
+ assert_eq ! ( coin. denom, Denom :: from_str( "token123" ) . unwrap( ) ) ;
260
+ }
261
+
262
+ #[ test]
263
+ fn parse_max_u128_amount ( ) {
264
+ // u128::MAX = 340282366920938463463374607431768211455
265
+ let s = "340282366920938463463374607431768211455max" ;
266
+ let coin = parse_coin ( s) . unwrap ( ) ;
267
+ assert_eq ! ( coin. amount, u128 :: MAX ) ;
268
+ assert_eq ! ( coin. denom, Denom :: from_str( "max" ) . unwrap( ) ) ;
269
+ }
270
+
271
+ #[ test]
272
+ fn error_missing_denom ( ) {
273
+ assert ! ( parse_coin( "123" ) . is_err( ) ) ;
274
+ }
275
+
276
+ #[ test]
277
+ fn error_missing_amount ( ) {
278
+ assert ! ( parse_coin( "abc" ) . is_err( ) ) ;
279
+ }
280
+
281
+ #[ test]
282
+ fn error_overflow_amount ( ) {
283
+ // one more than u128::MAX
284
+ let s = "340282366920938463463374607431768211456overflow" ;
285
+ assert ! ( parse_coin( s) . is_err( ) ) ;
286
+ }
287
+
288
+ #[ test]
289
+ fn error_negative_amount ( ) {
290
+ // '-' is non-digit at pos 0 → empty amount → parse error
291
+ assert ! ( parse_coin( "-100untrn" ) . is_err( ) ) ;
292
+ }
293
+ }
0 commit comments