diff --git a/packages/docs/pages/integrating-with-namada/sdk.mdx b/packages/docs/pages/integrating-with-namada/sdk.mdx index 0411f303..ce481f35 100644 --- a/packages/docs/pages/integrating-with-namada/sdk.mdx +++ b/packages/docs/pages/integrating-with-namada/sdk.mdx @@ -1,56 +1,13 @@ import Expandable from '../../components/Expandable'; +import { Callout } from 'nextra-theme-docs' # The Namada SDK -The Namada software development kit (SDK) can be found in the `namada` repo under the path [`namada/crates](https://github.com/anoma/namada/tree/main/crates/sdk). The SDK is written in Rust and can be used to interact with the Namada blockchain, by constructing transactions, signing them, and submitting them to the network. +The Namada software development kit (SDK) can be found in the `namada` repo under the path [`namada/crates/sdk`](https://github.com/anoma/namada/tree/main/crates/sdk). +The SDK is written in Rust and can be used to interact with the Namada blockchain by constructing transactions, signing them, and submitting them to the network. -## Quick Start +This section explains some basic operations using the SDK with example code. -A good starting point to see the SDK in use is to check-out the [namada interface](https://github.com/anoma/namada/tree/main/crates/sdk) repo. This repo contains a simple web application that uses the SDK to interact with the Namada blockchain. However, it is important to note the added complexity arising from the application integrating javascript using [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/), which is not necessary. - -## Installation - -The Namada SDK can be installed by creating a new Rust project and adding the following to the `Cargo.toml` file: - - - - -```toml -[package] -name = "namada-sdk-example" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clap = { version = "4.4.2", features = ["derive", "env"] } -rand = {version = "0.8", default-features = false} -rand_core = {version = "0.6", default-features = false} -serde = { version = "1.0.188", features = ["derive"] } -serde_json = "1.0.107" -namada_sdk = { git = "https://github.com/anoma/namada", rev = "v0.31.8", default-features = false, features = ["tendermint-rpc", "std", "async-send", "download-params", "rand"] } -tendermint-config = "0.34.0" -tendermint-rpc = { version = "0.34.0", features = ["http-client"]} -tokio = {version = "1.8.2", default-features = false} -tempfile = "3.8.0" -async-trait = "0.1.74" -markdown-gen = "1.2.1" -reqwest = "0.11.22" -minio = "0.1.0" -itertools = "0.12.0" - -[build-dependencies] -vergen = { version = "8.0.0", features = ["build", "git", "gitcl"] } -``` - - - - -Once the sdk is installed, you can use it to interact with the Namada blockchain. - -## Table of contents - -- [Setting up a client](./sdk/setting-up-a-client.mdx) -- [Setting up a wallet](./sdk/setting-up-a-wallet.mdx) -- [Constructing transfers](./sdk/constructing-transfers.mdx) + +Read the following sections in order, as they may refer to variables, imports or actions taken in previous sections. + diff --git a/packages/docs/pages/integrating-with-namada/sdk/_meta.json b/packages/docs/pages/integrating-with-namada/sdk/_meta.json index c0091273..4225342c 100644 --- a/packages/docs/pages/integrating-with-namada/sdk/_meta.json +++ b/packages/docs/pages/integrating-with-namada/sdk/_meta.json @@ -1,9 +1,12 @@ { - "setting-up-a-client" : "Setting up a client", + "setup": "Project setup", + "context": "Chain context", + "query": "Querying", "setting-up-a-wallet" : "Setting up a wallet", "generating-accounts": "Generating accounts", "constructing-transfers" : "Constructing transfers", + "shielded-sync": "Shielded sync", + "shielded-transfers": "Shielded transfers", "proof-of-stake" : "Proof of stake transactions", - "governance" : "Governance", - "interface-integration" : "Integrating with the interface" + "governance" : "Governance" } \ No newline at end of file diff --git a/packages/docs/pages/integrating-with-namada/sdk/constructing-transfers.mdx b/packages/docs/pages/integrating-with-namada/sdk/constructing-transfers.mdx index e8fc34d7..0f50d978 100644 --- a/packages/docs/pages/integrating-with-namada/sdk/constructing-transfers.mdx +++ b/packages/docs/pages/integrating-with-namada/sdk/constructing-transfers.mdx @@ -1,81 +1,46 @@ # Constructing transfers -Now that we have the wallet and client set up, we can create the environment necessary for constructing transfers. -This can be a bit tricky, but the below boilerplate code should do the trick: -The following imports will be needed in order to generate transfers: +The SDK provides methods for constructing, signing, and submitting transactions to the chain. These are accessed through the [Chain Context](./context.mdx) object. -```rust -use namada_sdk::args::InputAmount; -``` - -After the user has [generated an account](./generating-accounts.mdx) and created the appropriate struct, it is not too difficult to construct and submit transfer transactions. +### Preparation -```rust -let mut namada = NamadaImpl::new(&http_client, &mut wallet, &mut shielded_ctx, &NullIo) - .await - .expect("unable to construct Namada object") - .chain_id(ChainId::from_str("public-testnet-14.5d79b6958580").unwrap()); -// Transfer the given amount of native tokens from the source account to the -// destination account. -async fn gen_transfer( - namada: &impl Namada, - source: &Account, - destination: &Account, - amount: InputAmount, -) -> std::result::Result { - let mut transfer_tx_builder = namada - .new_transfer( - TransferSource::Address(Address::from(&source.public_key)), - TransferTarget::Address(Address::from(&destination.public_key)), - namada.native_token(), - amount, - ) - .signing_keys(vec![source.public_key.clone()]); - let (mut transfer_tx, signing_data, _epoch) = transfer_tx_builder - .build(namada) - .await - .expect("unable to build transfer"); - namada - .sign(&mut transfer_tx, args: &transfer_tx_builder.tx, signing_data, with: default_sign, user_data: ()) - .await - .expect("unable to sign reveal pk tx"); - namada.submit(transfer_tx, &transfer_tx_builder.tx).await -} -``` +Let's assume we have two accounts—Alice and Bob—[added to the wallet](./setting-up-a-wallet.mdx) as in the previous section +and that Alice's account already contains sufficient funds. -Other transactions can be constructed in a similar fashion. - -## Shielded transfers - -In order to make the transfer shielded, we need to the only difference is to use shielded addresses and keys instead of transparent ones. - -It is important to use the shielded extended `SpendingKey` as the source. +### Transparent Transfers +The following code will build a `transparent-transfer` tx that sends 1 NAM from Alice to Bob, then sign it and submit +it to the chain. ```rust -use std::str::FromStr; -use namada_sdk::{ - masp::{ - PaymentAddress, - TransferSource, - TransferTarget, - }, - masp_primitives::zip32::ExtendedSpendingKey, - key::common::PublicKey, +use namada_sdk::args::TxTransparentTransferData; +use namada_sdk::args::InputAmount; +use namada_sdk::signing::default_sign; + +// Prepare the tx arguments +let data = TxTransparentTransferData { + source: sdk.wallet().await.find_address("alice".to_string()).expect("Invalid alias").into_owned(), + target: sdk.wallet().await.find_address("bob".to_string()).expect("Invalid alias").into_owned(), + token: sdk.native_token(), + amount: InputAmount::from_str("1").expect("Invalid amount"), }; -// Make sure to replace "secret-ex" with an actual Namada extended spending key -let source = ExtendedSpendingKey::from_str("secret-ex").unwrap(); -// Make sure to replace "payment-addr-ex" with an actual Namada payment address -let destination = PaymentAddress::from_str("payment-addr-ex").unwrap(); -// Make sure to replace "public-key" with an actual Namada public key -let fee_payer = PublicKey::from_str("public-key"); -let amount = /* Needed amount here */; -let mut transfer_tx_builder = namada - .new_transfer( - TransferSource::ExtendedSpendingKey(source.into()), - TransferTarget::PaymentAddress(destination), - namada.native_token(), - amount, - ) - .signing_keys(vec![fee_payer]); -``` \ No newline at end of file +// Build the tx +let mut transfer_tx_builder = sdk + .new_transparent_transfer(vec![data]) + .signing_keys(vec![alice_acct.public_key.clone()]); +let (mut transfer_tx, signing_data) = transfer_tx_builder + .build(&sdk) + .await + .expect("unable to build transfer"); + +// Sign the tx +sdk.sign(&mut transfer_tx, &transfer_tx_builder.tx, signing_data, default_sign, ()) + .await + .expect("unable to sign transparent-transfer tx"); + +// Submit the signed tx to the ledger for execution +match sdk.submit(transfer_tx, &transfer_tx_builder.tx).await { + Ok(res) => println!("Tx result: {:?}", res), + Err(e) => println!("Tx error: {:?}", e) +} +``` diff --git a/packages/docs/pages/integrating-with-namada/sdk/context.mdx b/packages/docs/pages/integrating-with-namada/sdk/context.mdx new file mode 100644 index 00000000..2a87604c --- /dev/null +++ b/packages/docs/pages/integrating-with-namada/sdk/context.mdx @@ -0,0 +1,85 @@ +import { Callout } from 'nextra-theme-docs'; + +# Initializing the Chain Context + +To begin working with the SDK, the first step is to initialize a 'Chain Context'. This is an instance of the +[`NamadaImpl struct`](https://github.com/anoma/namada/blob/main/crates/sdk/src/lib.rs#L659), which allows us to access +wallet storage, shielded context storage, and the RPC endpoint of our full node. + +The Chain Context object also provides methods for common operations like querying the chain, constructing, signing, +and submitting transactions. + +To create the Chain Context, we'll need the RPC url of a synced full node. + +### Code + +First, import our dependencies: +```rust +use tokio; +use std::str::FromStr; +use namada_sdk::{args::TxBuilder, io::NullIo, masp::fs::FsShieldedUtils, wallet::fs::FsWalletUtils, NamadaImpl, chain::ChainId}; +use tendermint_rpc::{HttpClient, Url}; +``` + +Next, we'll create the required helper objects. + +Instantiate an http client for interacting with the full node: +```rust +let url = Url::from_str("http://127.0.0.1:26657").expect("Invalid RPC address"); +let http_client = HttpClient::new(url).unwrap(); +``` + +Set up wallet storage for our addresses and keys: +```rust +let wallet = FsWalletUtils::new("./sdk-wallet".into()); +``` + +Set up shielded context storage for the current state of the shielded pool: +```rust +let shielded_ctx = FsShieldedUtils::new("./masp".into()); +``` + +Create an IO object to capture the input/output streams. Since we don't need to +capture anything, we'll use `NullIo` +```rust +let null_io = NullIo; +``` + +Now we're ready to instantiate our Chain Context: +```rust +let sdk = NamadaImpl::new(http_client, wallet, shielded_ctx, null_io) + .await + .expect("unable to initialize Namada context") + .chain_id(ChainId::from_str("local-net.b8f955720ab").unwrap()); +``` + +### Full Example: + +```rust +use tokio; +use std::str::FromStr; +use namada_sdk::{args::TxBuilder, io::NullIo, masp::fs::FsShieldedUtils, rpc, wallet::fs::FsWalletUtils, Namada, NamadaImpl, chain::ChainId}; +use tendermint_rpc::{HttpClient, Url}; + +#[tokio::main] +async fn main() { + + let url = Url::from_str("http://127.0.0.1:26657").expect("Invalid RPC address"); + let http_client = HttpClient::new(url).unwrap(); + + let wallet = FsWalletUtils::new("./sdk-wallet".into()); + let shielded_ctx = FsShieldedUtils::new("./masp".into()); + let null_io = NullIo; + + let sdk = NamadaImpl::new(http_client, wallet, shielded_ctx, null_io) + .await + .expect("unable to initialize Namada context") + .chain_id(ChainId::from_str("local-net.b8f955720ab").unwrap()); +} +``` +The paths provided when creating `wallet` and `shielded_ctx` are the directories where the shielded context data and `wallet.toml` will be +saved to/read from. + + +All following pages in this section assume you have already created a Chain Context with the name `sdk`. + \ No newline at end of file diff --git a/packages/docs/pages/integrating-with-namada/sdk/generating-accounts.mdx b/packages/docs/pages/integrating-with-namada/sdk/generating-accounts.mdx index 28727445..ae7aca0a 100644 --- a/packages/docs/pages/integrating-with-namada/sdk/generating-accounts.mdx +++ b/packages/docs/pages/integrating-with-namada/sdk/generating-accounts.mdx @@ -1,13 +1,15 @@ # Generating accounts -## Representing accounts -Representing accounts using the Namada SDK is straightforward. An account on Namada is defined by its public key(s) and private key(s) (plural for multisignatures). The public key(s) is/are used to identify the account and the private key is used to sign transactions. In the below snippet, we represent the account using the public key and private key. +### Representing accounts +Representing accounts using the Namada SDK is straightforward. An account on Namada is defined by its public key(s) and private key(s) +(plural for multi-signature accounts). The public key(s) is/are used to identify the account and the private key is used to sign transactions. +In the below snippet, we create a struct to represent the account using the public key and private key. ```rust use namada_sdk::key::common::{PublicKey, SecretKey}; struct SimpleAccount { - public_key: PublicKey, - private_key: SecretKey + public_key: PublicKey, + private_key: SecretKey } ``` @@ -16,8 +18,8 @@ For a multisignature account, we can represent this through a vector of keys. ```rust use namada_sdk::key::common::{PublicKey, SecretKey}; struct MultisigAccount { - public_keys: Vec, - private_keys: Vec + public_keys: Vec, + private_keys: Vec } ``` @@ -25,51 +27,39 @@ Multisignature accounts, because they are initialized by an on-chain transaction ```rust use namada_sdk::key::common::{PublicKey, SecretKey}; -struct Account { - public_key: PublicKey, - private_key: SecretKey, - revealed: bool +struct SimpleAccount { + public_key: PublicKey, + private_key: SecretKey, + revealed: bool, } ``` - -## Revealing the public key of an implicit account +### Revealing the public key of an implicit account In order to reveal the public key of an implicit account, the user must submit a transaction to the ledger. ```rust -use namada_sdk::io::NullIo; -use namada_sdk::NamadaImpl; -use namada_sdk::chain::ChainId; - +use namada_sdk::signing::default_sign; -// Define the namada implementation (assuming we have a wallet, http_client, and shielded_ctx) -let mut namada = NamadaImpl::new(&http_client, &mut wallet, &mut shielded_ctx, &NullIo) - .await - .expect("unable to construct Namada object") - .chain_id(ChainId::from_str("shielded-expedition.88f17d1d14").unwrap()); +// Build the reveal pk transaction using the sdk object +let reveal_tx_builder = sdk + .new_reveal_pk(account.public_key.clone()) + .signing_keys(vec![account.public_key.clone()]); +let (mut reveal_tx, signing_data) = reveal_tx_builder + .build(&sdk) + .await + .expect("unable to build reveal pk tx"); -// Generate an account (assuming sk is a SecretKey) -let account = Account { - public_key: sk.to_public(), - private_key: sk, - revealed: false, -}; - -// Build the reveal pk transaction using the NamadaImpl object -let reveal_tx_builder = namada - .new_reveal_pk(account.public_key.clone()) - .signing_keys(vec![account.private_key.clone()]); -let (mut reveal_tx, signing_data, _) = reveal_tx_builder - .build(namada) - .await - .expect("unable to build reveal pk tx"); // Sign the transaction -namada - .sign(&mut reveal_tx, &reveal_tx_builder.tx, signing_data) - .await - .expect("unable to sign reveal pk tx"); +sdk.sign(&mut reveal_tx, &reveal_tx_builder.tx, signing_data, default_sign, ()) + .await + .expect("unable to sign reveal pk tx"); + // Submit the signed tx to the ledger for execution -namada.submit(reveal_tx.clone(), reveal_tx_builder) +// Assumes account already has funds to cover gas costs +match sdk.submit(reveal_tx.clone(), &reveal_tx_builder.tx).await { + Ok(res) => println!("Tx result: {:?}", res), + Err(e) => println!("Tx error: {:?}", e) +} ``` Once the public key is revealed, the account can be used to sign transactions. diff --git a/packages/docs/pages/integrating-with-namada/sdk/governance.mdx b/packages/docs/pages/integrating-with-namada/sdk/governance.mdx index e4bd9a52..53d38652 100644 --- a/packages/docs/pages/integrating-with-namada/sdk/governance.mdx +++ b/packages/docs/pages/integrating-with-namada/sdk/governance.mdx @@ -1,113 +1,111 @@ -import { Callout } from 'nextra-theme-docs'; - # Interacting with governance - -The `namada_impl` object is assumed to have been constructed as described in the [setting up a client](./setting-up-a-client.mdx#instantiating-a-namada-implementation-object) section. - +Governance related transactions, such as submitting or voting on a proposal, can be constructed using the SDK similar to other types of transactions. + +### Constructing a proposal +For details on proposal structure, refer to the [Governance section](../../users/governance/proposal-structure.mdx) of these docs. +The following code will prepare the proposal json; in the next step we'll construct a transaction to submit it on-chain. + +This example assumes that Alice's account exists in the SDK wallet and that it contains enough funds to cover the proposal deposit. + +Add the `serde_json` crate to your `Cargo.toml`. + +```rust +serde_json = "1.0.128" +``` -## Constructing a proposal +Next, prepare the proposal json: ```rust - let source_address = namada_sdk::Address::from_str("tnam1qp326qxmkncgwavskar9lfwm68x04vu8sg80crm4").unwrap(); - let start_epoch = 420 as u64; - let end_epoch = 424 as u64; - let grace_epoch = 428 as u64; - let signing_key = namada_sdk::SecretKey::from_str(FAUCET_KEY).unwrap(); - - let proposal = json!({ - "proposal": { - "content": { - "title": "TheTitle", - "authors": "test@test.com", - "discussions-to": "forum.namada.net", - "created": "2024-03-10T08:54:37Z", - "license": "MIT", - "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "requires": "2" - }, - "author": source_address.to_string(), - "voting_start_epoch": start_epoch, - "voting_end_epoch": end_epoch, - "grace_epoch": grace_epoch - } - }).to_string() +use serde_json::json; + +let source_address = alice_addr; +let start_epoch = 3 as u64; +let end_epoch = 6 as u64; +let activation_epoch = 9 as u64; + +let proposal = json!({ + "proposal": { + "content": { + "title": "TheTitle", + "authors": "test@test.com", + "discussions-to": "forum.namada.net", + "created": "2024-03-10T08:54:37Z", + "license": "MIT", + "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "requires": "2" + }, + "author": source_address.to_string(), + "voting_start_epoch": start_epoch, + "voting_end_epoch": end_epoch, + "activation_epoch": activation_epoch + } +}).to_string(); ``` -## Submitting the proposal +### Submitting the proposal Once the json is constructed, the proposal can be submitted to the network using the following code: ```rust - let proposal_data = proposal.as_bytes().to_vec(); - - let init_proposal_tx_builder = namada_impl - .new_init_proposal(proposal_data) - .signing_keys(vec![signing_key]); - - let (mut init_proposal_tx, signing_data) = init_proposal_tx_builder - .build(&namada_impl) - .await - .expect("unable to build init_proposal tx"); - - namada_impl - .sign( - &mut init_proposal_tx, - &init_proposal_tx_builder.tx, - signing_data, - default_sign, - (), - ) - .await - .expect("unable to sign redelegate tx"); - let tx = namada_impl - .submit(init_proposal_tx, &init_proposal_tx_builder.tx) - .await; +use namada_sdk::signing::default_sign; + +let proposal_data = proposal.as_bytes().to_vec(); + +let init_proposal_tx_builder = sdk + .new_init_proposal(proposal_data) + .signing_keys(vec![alice_acct.public_key.clone()]); + +let (mut init_proposal_tx, signing_data) = init_proposal_tx_builder + .build(&sdk) + .await + .expect("unable to build init_proposal tx"); + +sdk.sign(&mut init_proposal_tx, &init_proposal_tx_builder.tx, signing_data, default_sign, (),) + .await + .expect("unable to sign init_proposal tx"); + +match sdk.submit(init_proposal_tx, &init_proposal_tx_builder.tx).await { + Ok(res) => println!("Tx result: {:?}", res), + Err(e) => println!("Tx error: {:?}", e) +} ``` -## Voting on a proposal +### Voting on a proposal -In order to vote on a proposal, we need a valid proposal id. We can retrieve the proposal id from the latest proposal submitted to the network using the following code: +In order to vote on a proposal, we need a valid proposal id. If you know the proposal id, we can use it directly. +However, we can retrieve the proposal id from the latest proposal submitted to the network using the following code: ```rust - let storage_key = namada_governance::storage::keys::get_counter_key(); - // This returns the next proposal_id, so always subtract 1 - let proposal_id = namada_sdk::rpc::query_storage_value::<_, u64>(namada_impl.client(), &storage_key) - .await - .unwrap() - - 1; +let storage_key = namada_governance::storage::keys::get_counter_key(); +// This returns the next proposal_id, so always subtract 1 +let proposal_id = rpc::query_storage_value::<_, u64>(sdk.client(), &storage_key) + .await + .unwrap() + - 1; ``` - Once we have the proposal id, we can vote on the proposal using the following code: ```rust - use namada_sdk::signing::default_sign; - - let proposal_id = 1 as u64; // placeholder, replace with actual proposal id - let vote = String::from("Yay"); - let signing_public_key = signing_key.to_public(); - - let vote_proposal_tx_builder = namada_impl - .new_vote_prposal(vote.clone(), voter_address.clone()) - .proposal_id(proposal_id) - .signing_keys(vec![signing_public_key]); - - let (mut vote_proposal_tx, signing_data) = vote_proposal_tx_builder - .build(&namada_impl) - .await - .expect("unable to build vote_proposal tx"); - - namada_impl - .sign( - &mut vote_proposal_tx, - &vote_proposal_tx_builder.tx, - signing_data, - default_sign, - (), - ) - .await - .expect("unable to sign redelegate tx"); - let tx = namada_impl - .submit(vote_proposal_tx, &vote_proposal_tx_builder.tx) - .await; \ No newline at end of file +let vote = String::from("Yay"); + +let vote_proposal_tx_builder = sdk + .new_proposal_vote(proposal_id, vote.clone(), alice_addr.clone()) + .proposal_id(proposal_id) + .signing_keys(vec![alice_acct.public_key.clone()]); + +let (mut vote_proposal_tx, signing_data) = vote_proposal_tx_builder + .build(&sdk) + .await + .expect("unable to build vote_proposal tx"); + +sdk.sign(&mut vote_proposal_tx, &vote_proposal_tx_builder.tx, signing_data, default_sign, ()) + .await + .expect("unable to sign vote_proposal tx"); + +match sdk.submit(vote_proposal_tx, &vote_proposal_tx_builder.tx).await { + Ok(res) => println!("Tx result: {:?}", res), + Err(e) => println!("Tx error: {:?}", e) +} +``` \ No newline at end of file diff --git a/packages/docs/pages/integrating-with-namada/sdk/interface-integration.mdx b/packages/docs/pages/integrating-with-namada/sdk/interface-integration.mdx deleted file mode 100644 index aff3ec0b..00000000 --- a/packages/docs/pages/integrating-with-namada/sdk/interface-integration.mdx +++ /dev/null @@ -1,32 +0,0 @@ -# Using the SDK with the Namada interface - -The [Namada interface](https://github.com/anoma/namada-interface) is a web interface developed by the Anoma Foundation. - -It allows you to easily create and manage your own decentralized identity, including integrating with the ledger application. - -The interface implementation uses a combination of TypeScript and Rust in order to interact with the SDK. - -## Example - -```typescript -// Import Sdk & Query -import { Query, Sdk } from "@namada/shared"; -// Import wasm initialization function -import { init as initShared } from "@namada/shared/src/init"; - -async function init() { - await initShared(); - - const sdk = new Sdk(RPC_URL_OF_NAM_NODE); - const query = new Query(RPC_URL_OF_NAM_NODE); - - // ... -} - -init(); -``` - -## Documentation - -{/* TODO: Make sure this is updated to the main repo, once ready and merged */} -To learn more, please read the documentation maintained [here](https://github.com/anoma/namada-interface/blob/e9b76f54966ec48befe4a12a47808684e0d40eaf/specs/packages/sdk.md#how-can-you-import-and-use-the-sdk) \ No newline at end of file diff --git a/packages/docs/pages/integrating-with-namada/sdk/proof-of-stake.mdx b/packages/docs/pages/integrating-with-namada/sdk/proof-of-stake.mdx index cd28638c..5caabcb4 100644 --- a/packages/docs/pages/integrating-with-namada/sdk/proof-of-stake.mdx +++ b/packages/docs/pages/integrating-with-namada/sdk/proof-of-stake.mdx @@ -1,42 +1,42 @@ -import { Callout } from 'nextra-theme-docs' - ## Proof of stake using the SDK -All proof of stake functionality will be find within the `namada_sdk::proof_of_stake` module. +You can use the SDK to construct proof-of-stake related transactions. - -The `namada_impl` object is assumed to have been constructed as described in the [setting up a client](./setting-up-a-client.mdx#instantiating-a-namada-implementation-object) section. - +### Bond Transactions +This code will construct, sign, and submit a bond transaction to the chain. This code assumes Alice's keys already exist in the wallet +and that you know the valid address of a target validator. ```rust +use namada_sdk::token::Amount; use namada_sdk::signing::default_sign; -// We assume we have a namada object that is already initialized -let bond_tx_builder = namada - .new_bond(validator_address.clone(), amount) - .source(source_address.clone()) - .signing_keys(vec![source_public_key]); - +let alice_addr = sdk.wallet().await.find_address("alice".to_string()) + .expect("Invalid alias").into_owned(); +let validator_addr = Address::from_str("tnam1qykv7yvcx8eyjld4kuumg0whwjq8usulwy7uhlsw") + .expect("Invalid address"); + +// build the bond tx +let bond_tx_builder = sdk + .new_bond(validator_addr, Amount::native_whole(100)) // whole amount, ie. 100 NAM + .source(alice_addr) + .signing_keys(vec![alice_acct.public_key.clone()]); + let (mut bond_tx, signing_data) = bond_tx_builder - .build(&namada_iml) - .await - .expect("unable to build bond"); - -namada - .sign( - &mut bond_tx, - &bond_tx_builder.tx, - signing_data, - default_sign, - (), - ) - .await - .expect("unable to sign reveal bond"); - -let tx = namada_iml.submit(bond_tx, &bond_tx_builder.tx).await; + .build(&sdk) + .await + .expect("Unable to build bond tx"); + +// sign the tx +sdk.sign(&mut bond_tx, &bond_tx_builder.tx, signing_data, default_sign, ()) + .await + .expect("unable to sign bond tx"); + +//submit to the chain +match sdk.submit(bond_tx, &bond_tx_builder.tx).await { + Ok(res) => println!("Tx result: {:?}", res), + Err(e) => println!("Tx error: {:?}", e) +} ``` -That will submit the bond transaction to the network. - Similar proof of stake transactions such as `new_unbond`, `new_redelegation`, `new_claim_rewards` and `new_withdraw` are also available in the SDK. diff --git a/packages/docs/pages/integrating-with-namada/sdk/query.mdx b/packages/docs/pages/integrating-with-namada/sdk/query.mdx new file mode 100644 index 00000000..077159cd --- /dev/null +++ b/packages/docs/pages/integrating-with-namada/sdk/query.mdx @@ -0,0 +1,16 @@ +# Querying the Chain + +Use the methods in the [`rpc`](https://github.com/anoma/namada/blob/main/crates/sdk/src/rpc.rs) SDK module +to directly query your full node's RPC endpoint. + +### Example +This code will attempt to query the chain's current epoch: + +```rust +use namada_sdk::{rpc, Namada}; + + match rpc::query_epoch(sdk.client()).await { + Ok(current_epoch) => println!("Current epoch: {:?}", current_epoch), + Err(e) => println!("Query error: {:?}", e), + } +``` \ No newline at end of file diff --git a/packages/docs/pages/integrating-with-namada/sdk/setting-up-a-client.mdx b/packages/docs/pages/integrating-with-namada/sdk/setting-up-a-client.mdx deleted file mode 100644 index b600ec16..00000000 --- a/packages/docs/pages/integrating-with-namada/sdk/setting-up-a-client.mdx +++ /dev/null @@ -1,120 +0,0 @@ -# Setting up an SDK client - -Once the sdk has been imported into the project, it can be used to interact with the Namada blockchain. -Let's assume we have a node running on ip and port `127.0.0.1:26657` and we want to send a transaction to the network. - -The SDK may be for various purposes, but in this example we will use it to send a transaction to the network. - -First, we will need to implement the `Client` so that we can communicate with a running node. - -```rust -use reqwest::{Client, Response as ClientResponse}; - - -pub struct SdkClient { - url: String, - client: Client, -} - -impl SdkClient { - pub fn new(url: String) -> Self { - Self { - client: Client::new(), - url, - } - } - - pub async fn post(&self, body: String) -> Result { - self.client - .post(format!("http://{}", &self.url)) - .body(body) - .send() - .await - } -} -``` - -This allows us to use `Client` from `reqwest` (an external library) to send a transaction to the network. - -We will need to also define some functions that the client will use to interact with the network. - -```rust -#[async_trait::async_trait] -impl ClientTrait for SdkClient { - type Error = Error; - - async fn request( - &self, - path: String, - data: Option>, - height: Option, - prove: bool, - ) -> Result { - let data = data.unwrap_or_default(); // default to empty vec - let height = height - .map(|height| { - tendermint::block::Height::try_from(height.0) - .map_err(|_err| Error::InvalidHeight(height)) - }) - .transpose()?; // convert to tendermint::block::Height - let response = self - .abci_query( - Some(std::str::FromStr::from_str(&path).unwrap()), - data, - height, - prove, - ) - .await?; - - match response.code { - Code::Ok => Ok(EncodedResponseQuery { - data: response.value, - info: response.info, - proof: response.proof, - }), - Code::Err(code) => Err(Error::Query(response.info, code)), - } - } - - async fn perform(&self, request: R) -> Result - where - R: tm_rpc::SimpleRequest, - { - let request_body = request.into_json(); - let response = self.post(request_body).await; - - match response { - Ok(response) => { - let response_json = response.text().await.unwrap(); - R::Response::from_string(response_json) - } - Err(e) => { - let error_msg = e.to_string(); - Err(tm_rpc::Error::server(error_msg)) - } - } - } -} -``` - -This client will allow us to make asynchronous calls to the network and handle the responses. - -## Instantiating a Namada Implementation object - -When constructing transactions using the sdk, we almost alwasy need a `namada` object. - -```rust -use namada_sdk::NamadaImpl; // This module allows us to access the NamadaImpl struct, which is needed for most transactions - -let source_address = Address::from_str("tnam1qp326qxmkncgwavskar9lfwm68x04vu8sg80crm4").unwrap(); -let http_client = reqwest::Client::new(); -let wallet = Wallet::from_mnemonic("your mnemonic here").unwrap(); -let wallet: namada_sdk::wallet::Wallet = FsWalletUtils::new(PathBuf::from("wallet.toml")); -let shielded_ctx = FsShieldedUtils::new(Path::new("masp/").to_path_buf()); -let namada = NamadaImpl::new(http_client, wallet, shielded_ctx, NullIo) - .await - .expect("unable to construct Namada object") - .chain_id(ChainId::from_str(CHAIN_ID).unwrap()); -``` - -This object will be referenced throughout the documentation as `namada`. diff --git a/packages/docs/pages/integrating-with-namada/sdk/setting-up-a-wallet.mdx b/packages/docs/pages/integrating-with-namada/sdk/setting-up-a-wallet.mdx index 558d637a..32c82a49 100644 --- a/packages/docs/pages/integrating-with-namada/sdk/setting-up-a-wallet.mdx +++ b/packages/docs/pages/integrating-with-namada/sdk/setting-up-a-wallet.mdx @@ -1,113 +1,63 @@ # Setting up an SDK wallet -## Relevant imports +The SDK exposes methods for interacting with a filesystem wallet. +You can insert, read, or remove both transparent and shielded keys/addresses for later use when constructing and signing transactions. -### General imports -```rust -// namada_sdk::Namada is a high level interface in order to interact with the Namada SDK -use namada_sdk::Namada; -// The NamadaImpl provides convenient implementations to frequently used Namada SDK interactons -use namada_sdk::NamadaImpl; -``` - -### Wallet specific imports +### Add a Keypair to the Wallet +If you know an address and secret (private) key, you can add them directly to the wallet: ```rust -// SecretKey, common and SchemeType give access to Namada cryptographic keys and their relevant implementations. Namada supports ED25519 and SECP256K1 keys. +use namada_sdk::address::Address; use namada_sdk::key::common::SecretKey; -use namada_sdk::key::{common, SchemeType}; -// Filesystem wallet utilities (stores the path of the wallet on the filesystem) -use namada_sdk::wallet::fs::FsWalletUtils; -``` -## Creating a wallet from a mnemonic +let alice_sk = SecretKey::from_str("0071b4b45443ead34bef175cac5eaf266f653d122b75e077c87b5146f35f45b6ee").expect("Invalid secret key"); +let alice_address = Address::from_str("tnam1qpm2l32hvlw44fwne8fs0y4qsw2jfy4tvyvyywt8").expect("Invalid address"); +sdk.wallet_mut().await.insert_keypair( + "alice".to_string(), // the alias + true, // overwrite this alias if it exists in wallet + alice_sk, + None, // no password + Some(alice_address), + None // default derivation path +); + +println!("alice: {:?}", sdk.wallet().await.find_address("alice".to_string())); +``` -The SDK can create a wallet from a mnemonic phrase. The mnemonic phrase is a 24 word phrase that can be used to restore a wallet. +### Derive a Keypair from a Mnemonic +You can also derive a keypair from a mnemonic: ```rust use namada_sdk::bip39::Mnemonic; - -let mnemonic = Mnemonic::from_phrase(MNEMONIC_CODE, namada_sdk::bip39::Language::English) -``` - -```rust -// Assuming a cometbft node is running on localhost:26657 -let http_client = HttpClient::new("http://localhost:26657").unwrap(); -// Assuming wallet.toml exists in the current directory -let mut wallet = FsWalletUtils::new(PathBuf::from("wallet.toml")); -// The key can be generated from the wallet by passing in the mnemonic phrase -let (_key_alias, sk) = NamadaImpl::new(&http_client, &mut wallet, &mut shielded_ctx, &NullIo) - .wallet_mut() - .await - let (_key_alias, sk) = namada - .wallet_mut() - .await - .derive_store_key_from_mnemonic_code( - scheme: SchemeType::Ed25519, - alias: Some(alias), - alias_force: false, - derivation_path: derivation_path, - mnemonic_passphrase: Some((mnemonic.clone(), Zeroizing::new("".to_owned()))), - prompt_bip39_passphrase: false, - password: None, - ) - .expect("unable to derive key from mnemonic code"); +use namada_sdk::key::SchemeType; +use namada_sdk::wallet::DerivationPath; +use namada_sdk::zeroize::Zeroizing; + +let phrase = "invest canal odor resource valley property chimney royal puzzle inch earth route diagram letter ceiling clinic post zebra hidden promote list valid define wedding"; +let mnemonic = Mnemonic::from_phrase(phrase, namada_sdk::bip39::Language::English).expect("Invalid mnemonic"); + +let derivation_path = DerivationPath::default_for_transparent_scheme(SchemeType::Ed25519); +let (_key_alias, _sk) = sdk + .wallet_mut() + .await + .derive_store_key_from_mnemonic_code( + SchemeType::Ed25519, // key scheme + Some("bob".to_string()), // alias + true, // overwrite alias if it exists + derivation_path, + Some((mnemonic.clone(), Zeroizing::new("".to_owned()))), + false, // do not prompt for passphrase + None, // no password + ) + .expect("unable to derive key from mnemonic code"); + +println!("bob: {:?}", sdk.wallet().await.find_address("bob".to_string())); ``` -In the second part of the above function, the key is derived from the mnemonic phrase. The `alias` is the name of the key that will be stored in the wallet. The `derivation_path` is the path to the key in the HD wallet. The `mnemonic` is the mnemonic phrase that was generated earlier. The `shielded_ctx` is the context for the shielded transactions. The `NullIo` is the IO context for the wallet. - -## Generating a new wallet and saving it to the filesystem - -It is also possible to create the sdk wallet from scratch. This is more involved because it requires generating a new store for the wallet to exist in. - +### Saving the Wallet +The sdk wallet exists only in memory until you explicitly save it to disk: ```rust -use namada_sdk::{ - wallet::{ - Store, - StoredKeypair, - Wallet, - WalletIo, - }, - address::Address, - key::{common::SecretKey, PublicKeyHash}, - }, -}; -use rand::rngs::OsRng; - -pub struct SdkWallet { - pub wallet: Wallet, -} - -impl SdkWallet { - pub fn new(sk: SecretKey, nam_address: Address) -> Self { - let store = Store::default(); - let mut wallet = Wallet::new(SdkWalletUtils {}, store); - let stored_keypair = StoredKeypair::Raw(sk.clone()); - let pk_hash = PublicKeyHash::from(&sk.to_public()); - let alias = "alice".to_string(); - wallet.insert_keypair(alias, stored_keypair, pk_hash, true); - wallet.add_address("nam", nam_address, true); - Self { wallet } - } -} - -pub struct SdkWalletUtils {} - -impl WalletIo for SdkWalletUtils { - type Rng = OsRng; -} - -impl Clone for SdkWalletUtils { - fn clone(&self) -> Self { - SdkWalletUtils::new() - } -} - -impl SdkWalletUtils { - fn new() -> Self { - Self {} - } -} +sdk.wallet().await.save().expect("Could not save wallet!"); ``` -The above code allows us now to construct any instance of the `SdkWallet` by simply passing in a secret key and the address for the `NAM` token. If we wish to make transfers with other tokens, we would need to add those addresses as well. \ No newline at end of file +This will write the `wallet.toml` file to the directory you provided when [creating the context](./context.mdx). \ No newline at end of file diff --git a/packages/docs/pages/integrating-with-namada/sdk/setup.mdx b/packages/docs/pages/integrating-with-namada/sdk/setup.mdx new file mode 100644 index 00000000..7d6f0eeb --- /dev/null +++ b/packages/docs/pages/integrating-with-namada/sdk/setup.mdx @@ -0,0 +1,21 @@ +# Project Setup + + +### Imports +To begin working with the Namada SDK, add the following to your `Cargo.toml`: + +```rust +namada_core = { git = "https://github.com/anoma/namada", tag = "v0.43.0" } +namada_sdk = { git = "https://github.com/anoma/namada", tag = "v0.43.0", default-features = false, features = ["std", "async-send", "download-params"] } +namada_tx = { git = "https://github.com/anoma/namada", tag = "v0.43.0" } +namada_governance = { git = "https://github.com/anoma/namada", tag = "v0.43.0" } +namada_ibc = { git = "https://github.com/anoma/namada", tag = "v0.43.0" } +namada_token = { git = "https://github.com/anoma/namada", tag = "v0.43.0" } +namada_parameters = { git = "https://github.com/anoma/namada", tag = "v0.43.0" } +namada_proof_of_stake = { git = "https://github.com/anoma/namada", tag = "v0.43.0" } +tendermint = "0.37.0" +tendermint-config = "0.37.0" +tendermint-rpc = { version = "0.37.0", features = ["http-client"] } +tendermint-proto = "0.37.0" +tokio = { version = "1.0", features = ["full"] } +``` diff --git a/packages/docs/pages/integrating-with-namada/sdk/shielded-sync.mdx b/packages/docs/pages/integrating-with-namada/sdk/shielded-sync.mdx new file mode 100644 index 00000000..d20e0332 --- /dev/null +++ b/packages/docs/pages/integrating-with-namada/sdk/shielded-sync.mdx @@ -0,0 +1,108 @@ +# Shielded Sync + +Before performing MASP operations such as querying shielded balances, you will need to sync the local shielded context with the chain (this +is equivalent to running the [`namadac shielded-sync`](../../users/shielded-accounts/shielded-sync.mdx) command when using the CLI). + +Syncing the shielded context directly from the chain is performance-intensive, so it's recommended to sync using a running +instance of the [`namada-masp-indexer`](https://github.com/anoma/namada-masp-indexer). + +### Example code + +This example demonstrates how to perform a shielded-sync from a masp-indexer endpoint, and save the synced context to disk. + +Add the following dependencies to your `Cargo.toml`: +```rust +reqwest = "0.11.4" +kdam = "0.5.2" +``` + +Then, the following code will sync the shielded context and save it to the directory provided when [creating the context](./context.mdx). + +```rust +use std::time::Duration; +use namada_sdk::masp::ExtendedSpendingKey; +use namada_sdk::control_flow::install_shutdown_signal; +use namada_sdk::error::Error; +use namada_sdk::wallet::DatedSpendingKey; +use namada_sdk::masp::{MaspLocalTaskEnv, ShieldedSyncConfig}; + +// the spend key with which to check note ownership +let spend_key = ExtendedSpendingKey::from_str("zsknam1q0medj45qqqqpq9wh90qd9c7d9f7n5xxn89h6dl54k0jfmucwn4yk7nykxwcrjmk4ylkdnlnn3wkkd9f3ul3nyw8hv5wlsfgklzr5ghzk2spzzwm05csvl2s3rn0aq7f9w4z7guul682yrw4hsmren2k2lgdp003uuj00lsd8nlevc8n32sz6j350up209980c04qdqcu97vh5476xv423k6jp58qn7hqjf9nvsvk8p8g5yfmqguu039q34c2euzhcpnca7vpp4pelqu6y87k") + .expect("Invalid spending key"); + +let dated_key = DatedSpendingKey::try_from(spend_key).expect("Error reading spend key"); + +// create a thread pool for the shielded sync +let env = MaspLocalTaskEnv::new(500).expect("could not create masp env"); + +// progress bars for displaying sync progress +let fetched = kdam::tqdm!( + total = 0, + desc = "fetched ", + animation = "fillup", + position = 0, + force_refresh = true, + dynamic_ncols = true, + miniters = 0, + mininterval = 0.05 +); + +let scanned = kdam::tqdm!( + total = 0, + desc = "scanned ", + animation = "fillup", + position = 1, + force_refresh = true, + dynamic_ncols = true, + miniters = 0, + mininterval = 0.05 +); + +let applied = kdam::tqdm!( + total = 0, + desc = "applied ", + animation = "fillup", + position = 2, + force_refresh = true, + dynamic_ncols = true, + miniters = 0, + mininterval = 0.05 +); + +// create a masp client to sync from the masp-indexer +let client = reqwest::Client::builder() +.connect_timeout(Duration::from_secs(60)) +.build() +.map_err(|err| { + Error::Other(format!("Failed to build http client: {err}")) +}).expect("could not create client"); + +let endpoint = "http://localhost:5000/api/v1".to_string(); +let url = endpoint.as_str().try_into().map_err(|err| { + Error::Other(format!( + "Failed to parse API endpoint {endpoint:?}: {err}" + )) +}).expect("failed to parse url"); + +let shielded_client = IndexerMaspClient::new( + client, + url, + true, + 100, +); + +let config = ShieldedSyncConfig::builder() + .client(shielded_client) + .fetched_tracker(fetched) + .scanned_tracker(scanned) + .applied_tracker(applied) + .shutdown_signal(install_shutdown_signal(false)) + .build(); + +// shielded sync and save the results +println!("Syncing shielded context"); +sdk.shielded_mut().await.sync(env, config, None, &[dated_key], &[]).await.expect("Could not sync shielded context"); +println!("Shielded context synced"); +``` + +In the above example, one would replace the URL in the `endpoint` with the proper URL for the desired MASP indexer. \ No newline at end of file diff --git a/packages/docs/pages/integrating-with-namada/sdk/shielded-transfers.mdx b/packages/docs/pages/integrating-with-namada/sdk/shielded-transfers.mdx new file mode 100644 index 00000000..d5e82f3e --- /dev/null +++ b/packages/docs/pages/integrating-with-namada/sdk/shielded-transfers.mdx @@ -0,0 +1,56 @@ +# Shielded Transfers + +You can construct shielded transfers similarly to transparent transfers, except that rather than providing implicit addresses as the `source` and `target`, +you will need to provide a spending key and payment address, respectively. + +Before building your transaction, you will want to make sure the [shielded context is synced](./shielded-sync.mdx). + +### Constructing the Transfer + +This example assumes you have a spending key with sufficient shielded balance, a recipient payment address, and the +public key of an implicit account with gas funds. + +```rust +use namada_sdk::args::TxShieldedTransferData; +use namada_sdk::masp::PaymentAddress; +use namada_sdk::signing::default_sign; + +let spend_key = ExtendedSpendingKey::from_str("zsknam1q0medj45qqqqpq9wh90qd9c7d9f7n5xxn89h6dl54k0jfmucwn4yk7nykxwcrjmk4ylkdnlnn3wkkd9f3ul3nyw8hv5wlsfgklzr5ghzk2spzzwm05csvl2s3rn0aq7f9w4z7guul682yrw4hsmren2k2lgdp003uuj00lsd8nlevc8n32sz6j350up209980c04qdqcu97vh5476xv423k6jp58qn7hqjf9nvsvk8p8g5yfmqguu039q34c2euzhcpnca7vpp4pelqu6y87k") + .expect("Invalid spending key"); +let payment_addr = PaymentAddress::from_str("znam1vsz9wsge4u9c0thh38vhn2z9awzfqn4540ue4haxmcxl43srptrgrtlhn6r9cd9razawuakcclc") + .expect("Invalid payment address"); + +sdk.shielded_mut().await.load().await.expect("Could not load shielded context"); + +// specify the transfer arguments +let data = TxShieldedTransferData { + source: spend_key, + target: payment_addr, + token: sdk.native_token(), + amount: InputAmount::from_str("5").expect("Invalid amount"), +}; + +// build the tx +let mut transfer_tx_builder = sdk + .new_shielded_transfer( + vec![data], + vec![spend_key] + ) + .wrapper_fee_payer(alice_acct.public_key.clone()); + +let (mut transfer_tx, signing_data) = transfer_tx_builder + .build(&sdk) + .await + .expect("Unable to build transfer tx"); + +// sign the tx +sdk.sign(&mut transfer_tx, &transfer_tx_builder.tx, signing_data, default_sign, ()) + .await + .expect("unable to sign transfer tx"); + +// submit tx to the chain +match sdk.submit(transfer_tx, &transfer_tx_builder.tx).await { + Ok(res) => println!("Tx result: {:?}", res), + Err(e) => println!("Tx error: {:?}", e) +} +``` \ No newline at end of file