Skip to main content

avalanche_types/jsonrpc/
avm.rs

1//! AVM JSON-RPC API.
2use std::io::{self, Error, ErrorKind};
3
4use crate::{choices, codec::serde::hex_0x_utxo::Hex0xUtxo, ids, jsonrpc, txs};
5use serde::{Deserialize, Serialize};
6use serde_with::{serde_as, DisplayFromStr};
7
8/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain#avmissuetx>
9#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
10pub struct IssueTxRequest {
11    pub jsonrpc: String,
12    pub id: u32,
13
14    pub method: String,
15
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub params: Option<IssueTxParams>,
18}
19
20impl Default for IssueTxRequest {
21    fn default() -> Self {
22        Self {
23            jsonrpc: String::from(super::DEFAULT_VERSION),
24            id: super::DEFAULT_ID,
25            method: String::new(),
26            params: None,
27        }
28    }
29}
30
31impl IssueTxRequest {
32    pub fn encode_json(&self) -> io::Result<String> {
33        serde_json::to_string(&self)
34            .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
35    }
36}
37
38#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
39#[serde(rename_all = "camelCase")]
40pub struct IssueTxParams {
41    pub tx: String,
42    pub encoding: String,
43}
44
45/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain#avmissuetx>
46#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
47pub struct IssueTxResponse {
48    pub jsonrpc: String,
49    pub id: u32,
50
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub result: Option<IssueTxResult>,
53
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub error: Option<super::ResponseError>,
56}
57
58impl Default for IssueTxResponse {
59    fn default() -> Self {
60        Self {
61            jsonrpc: "2.0".to_string(),
62            id: 1,
63            result: None,
64            error: None,
65        }
66    }
67}
68
69/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain#avmissuetx>
70#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
71pub struct IssueTxResult {
72    #[serde(rename = "txID")]
73    pub tx_id: ids::Id,
74}
75
76impl Default for IssueTxResult {
77    fn default() -> Self {
78        Self {
79            tx_id: ids::Id::empty(),
80        }
81    }
82}
83
84/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::avm::test_issue_tx --exact --show-output
85#[test]
86fn test_issue_tx() {
87    use std::str::FromStr;
88
89    let resp: IssueTxResponse = serde_json::from_str(
90        "
91
92{
93    \"jsonrpc\": \"2.0\",
94    \"result\": {
95        \"txID\": \"G3BuH6ytQ2averrLxJJugjWZHTRubzCrUZEXoheG5JMqL5ccY\"
96    },
97    \"id\": 1
98}
99
100",
101    )
102    .unwrap();
103
104    let expected = IssueTxResponse {
105        jsonrpc: "2.0".to_string(),
106        id: 1,
107        result: Some(IssueTxResult {
108            tx_id: ids::Id::from_str("G3BuH6ytQ2averrLxJJugjWZHTRubzCrUZEXoheG5JMqL5ccY").unwrap(),
109        }),
110        error: None,
111    };
112    assert_eq!(resp, expected);
113}
114
115/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain/#avmgettxstatus>
116#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
117pub struct GetTxStatusResponse {
118    pub jsonrpc: String,
119    pub id: u32,
120
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub result: Option<GetTxStatusResult>,
123
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub error: Option<jsonrpc::ResponseError>,
126}
127
128impl Default for GetTxStatusResponse {
129    fn default() -> Self {
130        Self {
131            jsonrpc: "2.0".to_string(),
132            id: 1,
133            result: Some(GetTxStatusResult::default()),
134            error: None,
135        }
136    }
137}
138
139/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain/#avmgettxstatus>
140#[serde_as]
141#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
142pub struct GetTxStatusResult {
143    #[serde_as(as = "DisplayFromStr")]
144    pub status: choices::status::Status,
145}
146
147impl Default for GetTxStatusResult {
148    fn default() -> Self {
149        Self {
150            status: choices::status::Status::Unknown(String::new()),
151        }
152    }
153}
154
155/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::avm::test_get_tx_status --exact --show-output
156#[test]
157fn test_get_tx_status() {
158    // ref. https://docs.avax.network/apis/avalanchego/apis/x-chain/#avmgettxstatus
159    let resp: GetTxStatusResponse = serde_json::from_str(
160        "
161
162{
163    \"jsonrpc\": \"2.0\",
164    \"result\": {
165        \"status\": \"Accepted\"
166    },
167    \"id\": 1
168}
169
170",
171    )
172    .unwrap();
173
174    let expected = GetTxStatusResponse {
175        jsonrpc: "2.0".to_string(),
176        id: 1,
177        result: Some(GetTxStatusResult {
178            status: choices::status::Status::Accepted,
179        }),
180        error: None,
181    };
182    assert_eq!(resp, expected);
183}
184
185/// ref. <https://docs.avax.network/build/avalanchego-apis/issuing-api-calls>
186#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
187pub struct GetUtxosRequest {
188    pub jsonrpc: String,
189    pub id: u32,
190
191    pub method: String,
192
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub params: Option<GetUtxosParams>,
195}
196
197impl Default for GetUtxosRequest {
198    fn default() -> Self {
199        Self {
200            jsonrpc: String::from(super::DEFAULT_VERSION),
201            id: super::DEFAULT_ID,
202            method: String::new(),
203            params: None,
204        }
205    }
206}
207
208impl GetUtxosRequest {
209    pub fn encode_json(&self) -> io::Result<String> {
210        serde_json::to_string(&self)
211            .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
212    }
213}
214
215/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain/#avmgetutxos>
216#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
217#[serde(rename_all = "camelCase")]
218pub struct GetUtxosParams {
219    pub addresses: Vec<String>,
220    pub limit: u32,
221    pub encoding: String,
222}
223
224/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain/#avmgetutxos>
225#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
226pub struct GetUtxosResponse {
227    pub jsonrpc: String,
228    pub id: u32,
229
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub result: Option<GetUtxosResult>,
232
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub error: Option<super::ResponseError>,
235}
236
237/// ref. <https://docs.avax.network/apis/avalanchego/apis/x-chain/#avmgetutxos>
238#[serde_as]
239#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
240#[serde(rename_all = "camelCase")]
241#[derive(Default)]
242pub struct GetUtxosResult {
243    #[serde_as(as = "DisplayFromStr")]
244    pub num_fetched: u32,
245
246    #[serde_as(as = "Option<Vec<Hex0xUtxo>>")]
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub utxos: Option<Vec<txs::utxo::Utxo>>,
249
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub end_index: Option<super::EndIndex>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub encoding: Option<String>,
254}
255
256/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::avm::test_get_utxos_empty --exact --show-output
257#[test]
258fn test_get_utxos_empty() {
259    // ref. https://docs.avax.network/apis/avalanchego/apis/x-chain/#avmgetutxos
260    let resp: GetUtxosResponse = serde_json::from_str(
261        "
262
263{
264    \"jsonrpc\": \"2.0\",
265    \"result\": {
266        \"numFetched\": \"0\",
267        \"utxos\": [],
268        \"endIndex\": {
269            \"address\": \"P-custom152qlr6zunz7nw2kc4lfej3cn3wk46u3002k4w5\",
270            \"utxo\": \"11111111111111111111111111111111LpoYY\"
271        },
272        \"encoding\":\"hex\"
273    },
274    \"id\": 1
275}
276
277",
278    )
279    .unwrap();
280
281    let expected = GetUtxosResponse {
282        jsonrpc: "2.0".to_string(),
283        id: 1,
284        result: Some(GetUtxosResult {
285            num_fetched: 0,
286            utxos: Some(Vec::new()),
287            end_index: Some(super::EndIndex {
288                address: String::from("P-custom152qlr6zunz7nw2kc4lfej3cn3wk46u3002k4w5"),
289                utxo: String::from("11111111111111111111111111111111LpoYY"),
290            }),
291            encoding: Some(String::from("hex")),
292        }),
293        error: None,
294    };
295    assert_eq!(resp, expected);
296}
297
298/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::avm::test_get_utxos_non_empty --exact --show-output
299#[test]
300fn test_get_utxos_non_empty() {
301    // ref. https://docs.avax.network/build/avalanchego-apis/p-chain/#platformgetbalance
302    let resp: GetUtxosResponse = serde_json::from_str(
303        "
304
305{
306    \"jsonrpc\": \"2.0\",
307    \"result\": {
308        \"numFetched\": \"1\",
309        \"utxos\": [
310            \"0x000000000000000000000000000000000000000000000000000000000000000000000000000088eec2e099c6a528e689618e8721e04ae85ea574c7a15a7968644d14d54780140000000702c68af0bb1400000000000000000000000000010000000165844a05405f3662c1928142c6c2a783ef871de939b564db\"
311        ],
312        \"endIndex\": {
313            \"address\": \"X-avax1x459sj0ssujguq723cljfty4jlae28evjzt7xz\",
314            \"utxo\": \"LUC1cmcxnfNR9LdkACS2ccGKLEK7SYqB4gLLTycQfg1koyfSq\"
315        },
316        \"encoding\": \"hex\"
317    },
318    \"id\": 1
319}
320
321",
322    )
323    .unwrap();
324
325    let raw_utxo =  String::from("0x000000000000000000000000000000000000000000000000000000000000000000000000000088eec2e099c6a528e689618e8721e04ae85ea574c7a15a7968644d14d54780140000000702c68af0bb1400000000000000000000000000010000000165844a05405f3662c1928142c6c2a783ef871de939b564db");
326    let utxo = txs::utxo::Utxo::from_hex(&raw_utxo).unwrap();
327
328    let expected = GetUtxosResponse {
329        jsonrpc: "2.0".to_string(),
330        id: 1,
331        result: Some(GetUtxosResult {
332            num_fetched: 1,
333            utxos: Some(vec![utxo]),
334            end_index: Some(super::EndIndex {
335                address: String::from("X-avax1x459sj0ssujguq723cljfty4jlae28evjzt7xz"),
336                utxo: String::from("LUC1cmcxnfNR9LdkACS2ccGKLEK7SYqB4gLLTycQfg1koyfSq"),
337            }),
338            encoding: Some(String::from("hex")),
339        }),
340        error: None,
341    };
342    assert_eq!(resp, expected);
343}
344
345/// ref. <https://docs.avax.network/build/avalanchego-apis/x-chain#avmgetbalance>
346#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
347pub struct GetBalanceResponse {
348    pub jsonrpc: String,
349    pub id: u32,
350
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub result: Option<GetBalanceResult>,
353
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub error: Option<jsonrpc::ResponseError>,
356}
357
358/// ref. <https://docs.avax.network/build/avalanchego-apis/x-chain#avmgetbalance>
359#[serde_as]
360#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
361pub struct GetBalanceResult {
362    #[serde_as(as = "DisplayFromStr")]
363    pub balance: u64,
364
365    #[serde(rename = "utxoIDs")]
366    pub utxo_ids: Option<Vec<txs::utxo::Id>>,
367}
368
369/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::avm::test_get_balance --exact --show-output
370#[test]
371fn test_get_balance() {
372    use std::str::FromStr;
373
374    // ref. https://docs.avax.network/build/avalanchego-apis/x-chain#avmgetbalance
375    let resp: GetBalanceResponse = serde_json::from_str(
376        "
377
378{
379    \"jsonrpc\": \"2.0\",
380    \"result\": {
381        \"balance\": \"299999999999900\",
382        \"utxoIDs\": [
383            {
384                \"txID\": \"WPQdyLNqHfiEKp4zcCpayRHYDVYuh1hqs9c1RqgZXS4VPgdvo\",
385                \"outputIndex\": 1
386            }
387        ]
388    },
389    \"id\": 1
390}
391
392",
393    )
394    .unwrap();
395
396    let expected = GetBalanceResponse {
397        jsonrpc: "2.0".to_string(),
398        id: 1,
399        result: Some(GetBalanceResult {
400            balance: 299999999999900,
401            utxo_ids: Some(vec![txs::utxo::Id {
402                tx_id: ids::Id::from_str("WPQdyLNqHfiEKp4zcCpayRHYDVYuh1hqs9c1RqgZXS4VPgdvo")
403                    .unwrap(),
404                output_index: 1,
405                ..txs::utxo::Id::default()
406            }]),
407        }),
408        error: None,
409    };
410    assert_eq!(resp, expected);
411}
412
413/// ref. <https://docs.avax.network/build/avalanchego-apis/x-chain/#avmgetassetdescription>
414#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
415pub struct GetAssetDescriptionResponse {
416    pub jsonrpc: String,
417    pub id: u32,
418
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub result: Option<GetAssetDescriptionResult>,
421
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub error: Option<jsonrpc::ResponseError>,
424}
425
426/// ref. <https://docs.avax.network/build/avalanchego-apis/x-chain/#avmgetassetdescription>
427#[serde_as]
428#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
429pub struct GetAssetDescriptionResult {
430    #[serde(rename = "assetID")]
431    pub asset_id: ids::Id,
432
433    pub name: String,
434    pub symbol: String,
435
436    #[serde_as(as = "DisplayFromStr")]
437    pub denomination: usize,
438}
439
440/// RUST_LOG=debug cargo test --package avalanche-types --lib -- jsonrpc::avm::test_get_asset_description --exact --show-output
441#[test]
442fn test_get_asset_description() {
443    use std::str::FromStr;
444
445    // ref. https://docs.avax.network/build/avalanchego-apis/x-chain/#avmgetassetdescription
446    let resp: GetAssetDescriptionResponse = serde_json::from_str(
447        "
448
449{
450    \"jsonrpc\": \"2.0\",
451    \"result\": {
452        \"assetID\": \"2fombhL7aGPwj3KH4bfrmJwW6PVnMobf9Y2fn9GwxiAAJyFDbe\",
453        \"name\": \"Avalanche\",
454        \"symbol\": \"AVAX\",
455        \"denomination\": \"9\"
456    },
457    \"id\": 1
458}
459
460",
461    )
462    .unwrap();
463
464    let expected = GetAssetDescriptionResponse {
465        jsonrpc: "2.0".to_string(),
466        id: 1,
467        result: Some(GetAssetDescriptionResult {
468            asset_id: ids::Id::from_str("2fombhL7aGPwj3KH4bfrmJwW6PVnMobf9Y2fn9GwxiAAJyFDbe")
469                .unwrap(),
470            name: String::from("Avalanche"),
471            symbol: String::from("AVAX"),
472            denomination: 9,
473        }),
474        error: None,
475    };
476    assert_eq!(resp, expected);
477}
478
479/// ref. <https://docs.avax.network/build/avalanchego-apis/issuing-api-calls>
480#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
481pub struct IssueStopVertexRequest {
482    pub jsonrpc: String,
483    pub id: u32,
484
485    pub method: String,
486
487    #[serde(skip_serializing_if = "Option::is_none")]
488    pub params: Option<IssueStopVertexParams>,
489}
490
491impl Default for IssueStopVertexRequest {
492    fn default() -> Self {
493        Self {
494            jsonrpc: String::from(super::DEFAULT_VERSION),
495            id: super::DEFAULT_ID,
496            method: String::new(),
497            params: None,
498        }
499    }
500}
501
502impl IssueStopVertexRequest {
503    pub fn encode_json(&self) -> io::Result<String> {
504        serde_json::to_string(&self)
505            .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
506    }
507}
508
509#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
510#[serde(rename_all = "camelCase")]
511pub struct IssueStopVertexParams;