Skip to content
Snippets Groups Projects
Commit 9e7c3544 authored by Éloïs's avatar Éloïs
Browse files

Initial commit

parents
Branches
No related tags found
No related merge requests found
/target
This diff is collapsed.
[package]
name = "gva-rust-client"
version = "0.1.0"
authors = ["librelois <c@elo.tf>"]
edition = "2018"
license = "AGPL-3.0"
keywords = ["duniter", "gva", "client"]
description = "Duniter GVA client"
[dependencies]
anyhow = "1.0.32"
graphql_client = "0.9.0"
reqwest = { version = "0.10.9", features = ["blocking", "json"] }
serde = { version = "1.0.105", features = ["derive"] }
structopt = "0.3.18"
# GVA Rust Client
A simple command line client written in Rust that use Duniter GVA API.
This example project is more there to show the way for those who would like to develop Rust projects using GVA :)
If you have any questions about the code don't hesitate to ask @elois on the duniter forum: [https://forum.duniter.org](https://forum.duniter.org)
query BalanceQuery($script: String!) {
balance(script: $script) {
amount
base
}
}
query CurrentUdQuery {
currentUd {
amount
base
}
}
schema {
query: QueryRoot
mutation: MutationRoot
subscription: SubscriptionRoot
}
# Directs the executor to query only when the field exists.
directive @ifdef on FIELD
type AmountWithBase {
amount: Int!
base: Int!
}
type CurrentUdGva {
# Ud amount
amount: Int!
# Ud base
base: Int!
}
type MutationRoot {
# Process a transaction
# Return the transaction if it successfully inserted
tx(rawTx: String!): TxGva!
# Process several transactions
# Return an array of successfully inserted transactions
txs(rawTxs: [String!]!): [TxGva!]!
}
type Node {
# Peer card
peer: PeerCard
# Software
software: String!
# Software version
version: String!
}
enum Order {
# Ascending order
ASC
# Decreasing order
DESC
}
# Information about pagination in a connection
type PageInfo {
# When paginating backwards, are there more items?
hasPreviousPage: Boolean!
# When paginating forwards, are there more items?
hasNextPage: Boolean!
# When paginating backwards, the cursor to continue.
startCursor: String
# When paginating forwards, the cursor to continue.
endCursor: String
}
input Pagination {
# Identifier of the 1st desired element (of the last one in descending order)
cursor: String
ord: Order!
pageSize: Int
}
type PeerCard {
version: Int!
currency: String!
pubkey: String!
blockstamp: String!
endpoints: [String!]!
status: String!
signature: String!
}
type QueryRoot {
# Transactions history
utxosOfScript(
# DUBP wallet script
script: String!
# pagination
pagination: Pagination! = { cursor: null, ord: ASC, pageSize: null }
# Amount needed
amount: Int
): UtxoGvaConnection!
# Current universal dividends amount
currentUd: CurrentUdGva
# Universal dividends issued by a public key
udsOfPubkey(
# Ed25519 public key on base 58 representation
pubkey: String!
filter: UdsFilter! = ALL
# pagination
pagination: Pagination! = { cursor: null, ord: ASC, pageSize: null }
# Amount needed
amount: Int
): UdGvaConnection!
# Universal dividends revaluations
udsReval: [RevalUdGva!]!
# Transactions history
transactionsHistory(
# Ed25519 public key on base 58 representation
pubkey: String!
): TxsHistoryGva!
# Generate simple transaction document
genTx(
# Transaction amount
amount: Int!
# Transaction comment
comment: String
# Ed25519 public key on base 58 representation
issuer: String!
# Ed25519 public key on base 58 representation
recipient: String!
# Use mempool sources
useMempoolSources: Boolean! = false
): [String!]!
# Generate complex transaction document
genComplexTx(
# Transaction issuers
issuers: [TxIssuer!]!
# Transaction issuers
recipients: [TxRecipient!]!
# Transaction comment
comment: String
# Use mempool sources
useMempoolSources: Boolean! = false
): RawTxOrChanges!
# Account balance
balance(
# Account script or public key
script: String!
): AmountWithBase!
node: Node!
}
type RawTxOrChanges {
# Intermediate transactions documents for compacting sources (`null` if not needed)
changes: [String!]
# Transaction document that carries out the requested transfer (`null` if the amount to be sent requires too many sources)
tx: String
}
type RevalUdGva {
# Ud amount
amount: Int!
# Ud base
base: Int!
# Number of the block that revaluate ud amount
blockNumber: Int!
}
type SubscriptionRoot {
receivePendingTxs: [TxGva!]!
}
type TxGva {
# Version.
version: Int!
# Currency.
currency: String!
# Blockstamp
blockstamp: String!
# Locktime
locktime: Int!
# Document issuers.
issuers: [String!]!
# Transaction inputs.
inputs: [String!]!
# Inputs unlocks.
unlocks: [String!]!
# Transaction outputs.
outputs: [String!]!
# Transaction comment
comment: String!
# Document signatures
signatures: [String!]!
# Transaction hash
hash: String!
# Written block
writtenBlock: String
# Written Time
writtenTime: Int
}
input TxIssuer {
# Account script (default is a script needed all provided signers)
script: String
# Signers
signers: [String!]!
# XHX codes needed to unlock funds
codes: [String!]
# Amount
amount: Int!
}
input TxRecipient {
# Amount
amount: Int!
# Account script
script: String!
}
type TxsHistoryGva {
# Transactions sent
sent: [TxGva!]!
# Transactions sending
sending: [TxGva!]!
# Transactions received
received: [TxGva!]!
# Transactions receiving
receiving: [TxGva!]!
}
type UdGva {
# Ud amount
amount: Int!
# Ud base
base: Int!
# Issuer of this universal dividend
issuer: String!
# Number of the block that created this UD
blockNumber: Int!
# Blockchain time of the block that created this UD
blockchainTime: Int!
}
type UdGvaConnection {
# Information to aid in pagination.
pageInfo: PageInfo!
# A list of edges.
edges: [UdGvaEdge]
sum: AmountWithBase!
}
# An edge in a connection.
type UdGvaEdge {
# The item at the end of the edge
node: UdGva!
# A cursor for use in pagination
cursor: String!
}
enum UdsFilter {
ALL
UNSPENT
}
type UtxoGva {
# Source amount
amount: Int!
# Source base
base: Int!
# Hash of origin transaction
txHash: String!
# Index of output in origin transaction
outputIndex: Int!
# Written block
writtenBlock: Int!
# Written time
writtenTime: Int!
}
type UtxoGvaConnection {
# Information to aid in pagination.
pageInfo: PageInfo!
# A list of edges.
edges: [UtxoGvaEdge]
sum: AmountWithBase!
}
# An edge in a connection.
type UtxoGvaEdge {
# The item at the end of the edge
node: UtxoGva!
# A cursor for use in pagination
cursor: String!
}
use crate::*;
pub(crate) struct Client(reqwest::blocking::Client);
impl Client {
pub(crate) fn new() -> Self {
Client(reqwest::blocking::Client::new())
}
pub(crate) fn send_gql_query<Req: serde::Serialize, ResData: serde::de::DeserializeOwned>(
&self,
request_body: &Req,
server_url: &str,
) -> anyhow::Result<ResData> {
let request = self.0.post(server_url).json(request_body);
let start_time = Instant::now();
let response = request.send()?;
let req_duration = Instant::now().duration_since(start_time);
println!("The server responded in {} ms.", req_duration.as_millis());
let mut gql_response: Response<ResData> = response.json()?;
if let Some(errors) = gql_response.errors.take() {
print_server_errors(errors);
Err(anyhow::Error::msg(""))
} else if let Some(data) = gql_response.data {
Ok(data)
} else {
Err(anyhow::Error::msg("server response contains no data"))
}
}
}
fn print_server_errors(errors: Vec<graphql_client::Error>) {
println!("Server errors:");
for error in errors {
println!("{}", error);
}
}
// Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#![deny(
clippy::unwrap_used,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces
)]
mod client;
use crate::client::Client;
use graphql_client::GraphQLQuery;
use graphql_client::Response;
use std::time::Instant;
use structopt::StructOpt;
const DEFAULT_GVA_SERVER: &str = "https://g1.librelois.fr/gva";
#[derive(Debug, Clone, Copy, GraphQLQuery)]
#[graphql(schema_path = "gql/gva_schema.gql", query_path = "gql/gva_queries.gql")]
pub struct BalanceQuery;
#[derive(Debug, Clone, Copy, GraphQLQuery)]
#[graphql(schema_path = "gql/gva_schema.gql", query_path = "gql/gva_queries.gql")]
pub struct CurrentUdQuery;
#[derive(StructOpt)]
#[structopt(name = "rust-gva-client", about = "Client use GVA API of Duniter.")]
struct CliArgs {
/// GVA server url (default `https://g1.librelois.fr/gva`)
#[structopt(short, long, default_value = DEFAULT_GVA_SERVER)]
server: String,
#[structopt(subcommand)]
command: SubCommand,
}
#[derive(StructOpt)]
enum SubCommand {
// Get account balance
Balance {
pubkey_or_script: String,
},
/// Get current UD value
CurrentUd,
}
fn main() -> anyhow::Result<()> {
let cli_args = CliArgs::from_args();
let client = Client::new();
match cli_args.command {
SubCommand::Balance { pubkey_or_script } => {
let request_body = BalanceQuery::build_query(balance_query::Variables {
script: pubkey_or_script.clone(),
});
let balance_query::ResponseData {
balance: balance_query::BalanceQueryBalance { amount, .. },
} = client.send_gql_query(&request_body, &cli_args.server)?;
let int_part = amount / 100;
let dec_part = amount % 100;
println!(
"The balance of account '{}' is {}.{} Ğ1 !",
pubkey_or_script, int_part, dec_part
);
}
SubCommand::CurrentUd => {
let request_body = CurrentUdQuery::build_query(current_ud_query::Variables);
if let current_ud_query::ResponseData {
current_ud: Some(current_ud_query::CurrentUdQueryCurrentUd { amount, .. }),
} = client.send_gql_query(&request_body, &cli_args.server)?
{
let int_part = amount / 100;
let dec_part = amount % 100;
println!("The current UD value is {}.{} Ğ1 !", int_part, dec_part);
} else {
println!("server with empty blockchain");
}
}
}
Ok(())
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment