diff --git a/packages/shared/lib/Cargo.lock b/packages/shared/lib/Cargo.lock index 679360e0c..49d1152e1 100644 --- a/packages/shared/lib/Cargo.lock +++ b/packages/shared/lib/Cargo.lock @@ -1925,8 +1925,8 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.18.0" -source = "git+https://github.com/anoma/namada#c2779df63b226036c345f4ad645664933cd412d0" +version = "0.18.1" +source = "git+https://github.com/anoma/namada#ecd4c5ed936bb8c9f88be6cf456092b323d957b0" dependencies = [ "async-std", "async-trait", @@ -1965,8 +1965,8 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.18.0" -source = "git+https://github.com/anoma/namada#c2779df63b226036c345f4ad645664933cd412d0" +version = "0.18.1" +source = "git+https://github.com/anoma/namada#ecd4c5ed936bb8c9f88be6cf456092b323d957b0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -2010,8 +2010,8 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.18.0" -source = "git+https://github.com/anoma/namada#c2779df63b226036c345f4ad645664933cd412d0" +version = "0.18.1" +source = "git+https://github.com/anoma/namada#ecd4c5ed936bb8c9f88be6cf456092b323d957b0" dependencies = [ "proc-macro2", "quote", @@ -2020,8 +2020,8 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.18.0" -source = "git+https://github.com/anoma/namada#c2779df63b226036c345f4ad645664933cd412d0" +version = "0.18.1" +source = "git+https://github.com/anoma/namada#ecd4c5ed936bb8c9f88be6cf456092b323d957b0" dependencies = [ "borsh", "data-encoding", diff --git a/packages/shared/lib/Cargo.toml b/packages/shared/lib/Cargo.toml index 8a44fe67c..052bb1d18 100644 --- a/packages/shared/lib/Cargo.toml +++ b/packages/shared/lib/Cargo.toml @@ -23,7 +23,7 @@ gloo-utils = { version = "0.1.5", features = ["serde"] } js-sys = "0.3.60" masp_primitives = { git = "https://github.com/anoma/masp", rev = "9320c6b69b5d2e97134866871e960f0a31703813" } masp_proofs = { git = "https://github.com/anoma/masp", rev = "9320c6b69b5d2e97134866871e960f0a31703813", default-features = false, features = ["local-prover"] } -namada = { git = "https://github.com/anoma/namada", version = "0.18.0", default-features = false, features = ["abciplus", "namada-sdk"] } +namada = { git = "https://github.com/anoma/namada", version = "0.18.1", default-features = false, features = ["abciplus", "namada-sdk"] } prost = "0.9.0" prost-types = "0.9.0" rand = "0.8.5" diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 460cc6fd6..e1ee89367 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -1,14 +1,22 @@ -use namada::ledger::{ - masp::ShieldedContext, - wallet::{Store, Wallet}, -}; -use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; - +use crate::utils::to_js_result; use crate::{ rpc_client::HttpClient, sdk::masp::WebShieldedUtils, utils::{set_panic_hook, to_bytes}, }; +use borsh::BorshSerialize; +use namada::{ + ledger::{ + args, + masp::ShieldedContext, + signing, + wallet::{Store, Wallet}, + }, + proto::{Section, Signature, Tx}, + types::{key::common::PublicKey, key::common::SecretKey as SK, key::ed25519::SecretKey}, +}; +use std::str::FromStr; +use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; pub mod masp; mod tx; @@ -87,28 +95,126 @@ impl Sdk { wallet::add_spending_key(&mut self.wallet, xsk, password, alias) } - pub async fn submit_bond( + async fn submit_reveal_pk( &mut self, - tx_msg: &[u8], - password: Option, + args: &args::Tx, + mut tx: Tx, + pk: &PublicKey, ) -> Result<(), JsError> { - let args = tx::bond_tx_args(tx_msg, password)?; + // Build a transaction to reveal the signer of this transaction + let reveal_pk = namada::ledger::tx::build_reveal_pk( + &self.client, + &mut self.wallet, + args::RevealPk { + tx: args.clone(), + public_key: pk.clone(), + }, + ) + .await?; + + // Sign and submit reveal pk + if let Some((mut rtx, _, pk)) = reveal_pk { + // Sign the reveal public key transaction with the fee payer + signing::sign_tx(&mut self.wallet, &mut rtx, &args, &pk).await?; + // Submit the reveal public key transaction first + namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args, rtx).await?; + // Update the stateful PoW challenge of the outer transaction + #[cfg(not(feature = "mainnet"))] + signing::update_pow_challenge(&self.client, &args, &mut tx, &pk, false).await; + } + + Ok(()) + } + + /// Sign and submit transactions + async fn sign_and_process_tx( + &mut self, + args: args::Tx, + mut tx: Tx, + pk: PublicKey, + ) -> Result<(), JsError> { + // Submit a reveal pk tx if necessary + self.submit_reveal_pk(&args, tx.clone(), &pk).await?; - namada::ledger::tx::submit_bond(&mut self.client, &mut self.wallet, args) + // Sign tx + signing::sign_tx(&mut self.wallet, &mut tx, &args, &pk) .await - .map_err(|e| JsError::from(e)) + .map_err(JsError::from)?; + + // Submit tx + namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args, tx) + .await + .map_err(JsError::from)?; + + Ok(()) } - pub async fn submit_unbond( + /// Contruct transfer data for external signers, returns byte array + pub async fn build_transfer(&mut self, tx_msg: &[u8]) -> Result { + let args = tx::transfer_tx_args(tx_msg, None, None)?; + + let transfer = namada::ledger::tx::build_transfer( + &self.client, + &mut self.wallet, + &mut self.shielded_ctx, + args.clone(), + ) + .await + .map_err(JsError::from)?; + + let bytes = transfer.0.try_to_vec().map_err(JsError::from)?; + + to_js_result(bytes) + } + + // Append signatures and return tx bytes + pub fn sign_tx( + &self, + tx_bytes: &[u8], + data_key: String, + header_key: String, + ) -> Result { + let mut tx: Tx = Tx::try_from(tx_bytes).map_err(JsError::from)?; + let data_secret = SecretKey::from_str(&data_key).map_err(JsError::from)?; + let header_secret = SecretKey::from_str(&header_key).map_err(JsError::from)?; + + // Sign over the transaction data + tx.add_section(Section::Signature(Signature::new( + vec![*tx.data_sechash(), *tx.code_sechash()], + &SK::Ed25519(data_secret), + ))); + + tx.protocol_filter(); + + // Then sign over the bound wrapper + tx.add_section(Section::Signature(Signature::new( + tx.sechashes(), + &SK::Ed25519(header_secret), + ))); + + let bytes = tx.try_to_vec().map_err(|e| JsError::from(e))?; + to_js_result(Vec::from(bytes)) + } + + /// Submit signed transfer tx + pub async fn submit_signed_transfer( &mut self, + pk: String, tx_msg: &[u8], - password: Option, + tx_bytes: &[u8], ) -> Result<(), JsError> { - let args = tx::unbond_tx_args(tx_msg, password)?; + let transfer_tx = Tx::try_from(tx_bytes).map_err(|e| JsError::from(e))?; + let args = tx::transfer_tx_args(tx_msg, None, None).map_err(|e| JsError::from(e))?; + let pk = PublicKey::from_str(&pk).map_err(JsError::from)?; + + self.submit_reveal_pk(&args.tx, transfer_tx.clone(), &pk) + .await?; - namada::ledger::tx::submit_unbond(&mut self.client, &mut self.wallet, args) + namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args.tx, transfer_tx) .await - .map_err(|e| JsError::from(e)) + .map_err(JsError::from)?; + + Ok(()) } pub async fn submit_transfer( @@ -118,14 +224,18 @@ impl Sdk { xsk: Option, ) -> Result<(), JsError> { let args = tx::transfer_tx_args(tx_msg, password, xsk)?; - namada::ledger::tx::submit_transfer( + let (tx, _, pk, _, _) = namada::ledger::tx::build_transfer( &self.client, &mut self.wallet, &mut self.shielded_ctx, - args, + args.clone(), ) .await - .map_err(|e| JsError::from(e)) + .map_err(JsError::from)?; + + self.sign_and_process_tx(args.tx, tx, pk).await?; + + Ok(()) } pub async fn submit_ibc_transfer( @@ -135,9 +245,48 @@ impl Sdk { ) -> Result<(), JsError> { let args = tx::ibc_transfer_tx_args(tx_msg, password)?; - namada::ledger::tx::submit_ibc_transfer(&self.client, &mut self.wallet, args) - .await - .map_err(|e| JsError::from(e)) + let (tx, _, pk) = + namada::ledger::tx::build_ibc_transfer(&self.client, &mut self.wallet, args.clone()) + .await + .map_err(JsError::from)?; + + self.sign_and_process_tx(args.tx, tx, pk).await?; + + Ok(()) + } + + pub async fn submit_bond( + &mut self, + tx_msg: &[u8], + password: Option, + ) -> Result<(), JsError> { + let args = tx::bond_tx_args(tx_msg, password)?; + + let (tx, _, pk) = + namada::ledger::tx::build_bond(&mut self.client, &mut self.wallet, args.clone()) + .await + .map_err(JsError::from)?; + + self.sign_and_process_tx(args.tx, tx, pk).await?; + + Ok(()) + } + + pub async fn submit_unbond( + &mut self, + tx_msg: &[u8], + password: Option, + ) -> Result<(), JsError> { + let args = tx::unbond_tx_args(tx_msg, password)?; + + let (tx, _, pk, _) = + namada::ledger::tx::build_unbond(&mut self.client, &mut self.wallet, args.clone()) + .await + .map_err(JsError::from)?; + + self.sign_and_process_tx(args.tx, tx, pk).await?; + + Ok(()) } } diff --git a/packages/shared/lib/src/sdk/tx.rs b/packages/shared/lib/src/sdk/tx.rs index d428f373d..9a44c29db 100644 --- a/packages/shared/lib/src/sdk/tx.rs +++ b/packages/shared/lib/src/sdk/tx.rs @@ -7,6 +7,8 @@ use namada::{ types::{ address::Address, chain::ChainId, + key::common::PublicKey as PK, + key::ed25519::PublicKey, masp::{ExtendedSpendingKey, PaymentAddress, TransferSource, TransferTarget}, token::{Amount, DenominatedAmount, Denomination}, transaction::GasLimit, @@ -20,6 +22,7 @@ pub struct TxMsg { fee_amount: u64, gas_limit: u64, chain_id: String, + public_key: Option, } #[derive(BorshSerialize, BorshDeserialize)] @@ -175,6 +178,7 @@ pub fn transfer_tx_args( ))), }, }?; + let native_token = Address::from_str(&native_token)?; let token = Address::from_str(&token)?; let amount_str = amount.to_string(); @@ -275,8 +279,8 @@ fn tx_msg_into_args(tx_msg: TxMsg, password: Option) -> Result) -> Result { + let pk = PublicKey::from_str(&v).map_err(JsError::from)?; + Some(PK::Ed25519(pk)) + } + _ => None, + }; let args = args::Tx { dry_run: false, @@ -306,6 +317,7 @@ fn tx_msg_into_args(tx_msg: TxMsg, password: Option) -> Result) { this.token = properties.token; - this.fee_amount = 'feeAmount' in properties ? - new BN(properties.feeAmount.toString()) : - properties.fee_amount; - this.gas_limit = 'gasLimit' in properties ? - new BN(properties.gasLimit.toString()) : - properties.gas_limit; - this.chain_id = 'chainId' in properties ? - properties.chainId : - properties.chain_id; + this.fee_amount = + "feeAmount" in properties + ? new BN(properties.feeAmount.toString()) + : properties.fee_amount; + this.gas_limit = + "gasLimit" in properties + ? new BN(properties.gasLimit.toString()) + : properties.gas_limit; + this.chain_id = + "chainId" in properties ? properties.chainId : properties.chain_id; + this.public_key = + "publicKey" in properties ? properties.publicKey : undefined; } } @@ -31,6 +35,7 @@ export const TxMsgSchema = [ ["fee_amount", "u64"], ["gas_limit", "u64"], ["chain_id", "string"], + ["public_key", { kind: "option", type: "string" }], ], }, ] as const; // needed for SchemaObject to deduce types correctly diff --git a/packages/types/src/tx/types.ts b/packages/types/src/tx/types.ts index 45e32a4bf..4eb07f906 100644 --- a/packages/types/src/tx/types.ts +++ b/packages/types/src/tx/types.ts @@ -20,6 +20,7 @@ export type TxProps = { feeAmount: BigNumber; gasLimit: BigNumber; chainId: string; + publicKey?: string; }; export type TransferProps = {