From 7d8eaef76347870e6d87e1bf2227840f9dce983f Mon Sep 17 00:00:00 2001 From: Marc Nijdam Date: Mon, 10 Jun 2024 11:14:45 -0500 Subject: [PATCH] Add hotspots transfer command also generalizes setting compute budget and price as commands on the anchor RequestBuilder --- Cargo.lock | 56 ++++++--- helium-lib/Cargo.toml | 1 + helium-lib/src/asset.rs | 42 ++++++- helium-lib/src/hotspot.rs | 127 ++++++++++++--------- helium-lib/src/priority_fee.rs | 33 ++++++ helium-lib/src/reward.rs | 4 +- helium-wallet/src/cmd/hotspots/mod.rs | 5 +- helium-wallet/src/cmd/hotspots/transfer.rs | 30 +++++ 8 files changed, 221 insertions(+), 77 deletions(-) create mode 100644 helium-wallet/src/cmd/hotspots/transfer.rs diff --git a/Cargo.lock b/Cargo.lock index a58b22a3..de90b86e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,7 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive 0.10.3", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -1219,7 +1219,7 @@ dependencies = [ [[package]] name = "circuit-breaker" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "data-credits" version = "0.2.1" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -2017,7 +2017,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fanout" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -2368,7 +2368,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helium-anchor-gen" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -2414,7 +2414,7 @@ dependencies = [ [[package]] name = "helium-entity-manager" version = "0.3.1" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -2442,6 +2442,7 @@ dependencies = [ "itertools 0.10.5", "jsonrpc_client", "lazy_static", + "mpl-bubblegum", "pyth-sdk-solana", "reqwest", "rust_decimal", @@ -2484,7 +2485,7 @@ dependencies = [ [[package]] name = "helium-sub-daos" version = "0.1.5" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -2543,7 +2544,7 @@ dependencies = [ [[package]] name = "hexboosting" version = "0.0.5" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -2886,6 +2887,15 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "kaigan" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dd100976df9dd59d0c3fecf6f9ad3f161a087374d1b2a77ebb4ad8920f11bb" +dependencies = [ + "borsh 0.10.3", +] + [[package]] name = "keccak" version = "0.1.5" @@ -2898,7 +2908,7 @@ dependencies = [ [[package]] name = "lazy-distributor" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -2907,7 +2917,7 @@ dependencies = [ [[package]] name = "lazy-transactions" version = "0.2.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -3115,12 +3125,26 @@ dependencies = [ [[package]] name = "mobile-entity-manager" version = "0.1.2" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", ] +[[package]] +name = "mpl-bubblegum" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9eff5ae5cafd1acdf7e7c93359da1eec91dcaede318470d9f68b78e8b7469f4" +dependencies = [ + "borsh 0.10.3", + "kaigan", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + [[package]] name = "multihash" version = "0.18.1" @@ -3359,7 +3383,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn 2.0.66", @@ -3627,7 +3651,7 @@ dependencies = [ [[package]] name = "price-oracle" version = "0.2.1" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -4118,7 +4142,7 @@ dependencies = [ [[package]] name = "rewards-oracle" version = "0.2.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -6323,7 +6347,7 @@ dependencies = [ [[package]] name = "treasury-management" version = "0.2.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", @@ -6509,7 +6533,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "voter-stake-registry" version = "0.3.1" -source = "git+https://github.com/helium/helium-anchor-gen.git#9352356832c68a737897aae075211692d32e8bf7" +source = "git+https://github.com/helium/helium-anchor-gen.git#fe60ed1d49e9255bd779a99bdd7928f278c07256" dependencies = [ "anchor-gen", "anchor-lang", diff --git a/helium-lib/Cargo.toml b/helium-lib/Cargo.toml index be01bfc3..d97320f0 100644 --- a/helium-lib/Cargo.toml +++ b/helium-lib/Cargo.toml @@ -31,6 +31,7 @@ reqwest = { version = "0", default-features = false, features = [ ] } helium-anchor-gen = {git = "https://github.com/helium/helium-anchor-gen.git"} spl-associated-token-account = { version = "*", features = ["no-entrypoint"] } +mpl-bubblegum = "1" solana-program = "*" solana-transaction-status = "*" serde = {workspace = true} diff --git a/helium-lib/src/asset.rs b/helium-lib/src/asset.rs index b2a6d4e7..9ed1ecfb 100644 --- a/helium-lib/src/asset.rs +++ b/helium-lib/src/asset.rs @@ -6,9 +6,10 @@ use crate::{ settings::{DasClient, DasSearchAssetsParams, Settings}, }; use helium_anchor_gen::helium_entity_manager; +use itertools::Itertools; use serde::{Deserialize, Serialize}; use solana_sdk::{bs58, signer::Signer}; -use std::{ops::Deref, result::Result as StdResult, str::FromStr}; +use std::{collections::HashMap, ops::Deref, result::Result as StdResult, str::FromStr}; pub async fn account_for_entity_key, E>( client: &anchor_client::Client, @@ -54,6 +55,26 @@ pub async fn get_with_proof( Ok((asset, asset_proof)) } +pub async fn get_canopy_heights() -> Result> { + const KNOWN_CANOPY_HEIGHT_URL: &str = "https://shdw-drive.genesysgo.net/6tcnBSybPG7piEDShBcrVtYJDPSvGrDbVvXmXKpzBvWP/merkles.json"; + let client = reqwest::Client::new(); + let map: HashMap = client + .get(KNOWN_CANOPY_HEIGHT_URL) + .send() + .await? + .error_for_status()? + .json() + .await?; + + map.into_iter() + .map(|(str, value)| { + Pubkey::from_str(str.as_str()) + .map_err(|err| DecodeError::from(err).into()) + .map(|key| (key, value)) + }) + .try_collect() +} + pub mod proof { use super::*; @@ -151,12 +172,18 @@ pub struct AssetProof { pub proof: Vec, #[serde(with = "serde_pubkey")] pub root: Pubkey, + #[serde(with = "serde_pubkey")] + pub tree_id: Pubkey, } impl AssetProof { - pub fn proof(&self) -> Result> { + pub fn proof( + &self, + len: Option, + ) -> Result> { self.proof .iter() + .take(len.unwrap_or(self.proof.len())) .map(|s| { Pubkey::from_str(s) .map_err(DecodeError::from) @@ -169,6 +196,17 @@ impl AssetProof { }) .collect() } + + pub async fn proof_for_tree( + &self, + tree: &Pubkey, + ) -> Result> { + let canopy_heights = get_canopy_heights().await?; + let height = canopy_heights + .get(tree) + .ok_or_else(|| anchor_client::ClientError::AccountNotFound)?; + self.proof(Some(*height)) + } } #[derive(Debug, Deserialize, Serialize, Clone)] diff --git a/helium-lib/src/hotspot.rs b/helium-lib/src/hotspot.rs index af00325d..23a16718 100644 --- a/helium-lib/src/hotspot.rs +++ b/helium-lib/src/hotspot.rs @@ -4,13 +4,13 @@ use crate::{ entity_key::AsEntityKey, is_zero, keypair::{pubkey, serde_pubkey, GetPubkey, Keypair, Pubkey}, - onboarding, priority_fee, - programs::{MPL_BUBBLEGUM_PROGRAM_ID, SPL_ACCOUNT_COMPRESSION_PROGRAM_ID}, + onboarding, + priority_fee::{self, SetPriorityFees}, + programs::{MPL_BUBBLEGUM_PROGRAM_ID, SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID}, result::{DecodeError, EncodeError, Error, Result}, settings::{DasClient, DasSearchAssetsParams, Settings}, token::Token, }; -use anchor_client::{self, solana_sdk::signer::Signer}; use angry_purple_tiger::AnimalName; use chrono::Utc; use futures::{ @@ -25,6 +25,7 @@ use rust_decimal::prelude::*; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use solana_program::instruction::AccountMeta; +use solana_sdk::{commitment_config::CommitmentConfig, signature::Signature, signer::Signer}; use std::{collections::HashMap, ops::Deref, result::Result as StdResult, str::FromStr}; pub const HOTSPOT_CREATOR: Pubkey = pubkey!("Fv5hf1Fg58htfC7YEXKNEfkpuogUUQDDTLgjGWxxv48H"); @@ -79,7 +80,6 @@ pub mod info { OnboardDataOnlyIotHotspotArgsV0, OnboardIotHotspotArgsV0, OnboardMobileHotspotArgsV0, UpdateIotInfoArgsV0, UpdateMobileInfoArgsV0, }; - use solana_sdk::{commitment_config::CommitmentConfig, signature::Signature}; use solana_transaction_status::{ EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, UiInstruction, UiMessage, UiParsedInstruction, UiTransactionEncoding, @@ -328,24 +328,12 @@ pub async fn direct_update + GetPubkey>( let asset_account = asset::account_for_entity_key(&anchor_client, hotspot).await?; let (asset, asset_proof) = asset::get_with_proof(settings, &asset_account).await?; + let accounts = mk_update_accounts(update.subdao(), &asset_account, &asset, &program.payer()); - let update_accounts = - mk_update_accounts(update.subdao(), &asset_account, &asset, &program.payer()); - let priority_fee = priority_fee::get_estimate( - &solana_client, - &update_accounts, - priority_fee::MIN_PRIORITY_FEE, - ) - .await?; - - let compute_ix = - solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(200_000); - let compute_price_ix = - solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price(priority_fee); let mut ixs = program .request() - .instruction(compute_ix) - .instruction(compute_price_ix) + .compute_budget(200_000) + .compute_price(priority_fee::get_estimate(&solana_client, &accounts).await?) .args(helium_entity_manager::instruction::UpdateIotInfoV0 { _args: helium_entity_manager::UpdateIotInfoArgsV0 { root: asset_proof.root.to_bytes(), @@ -357,11 +345,11 @@ pub async fn direct_update + GetPubkey>( location: update.location_u64(), }, }) - .accounts(update_accounts) + .accounts(accounts) .instructions()?; ixs[2] .accounts - .extend_from_slice(&asset_proof.proof()?[0..3]); + .extend_from_slice(&asset_proof.proof(Some(3))?); let mut tx = solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&program.payer())); let blockhash = program.rpc().get_latest_blockhash()?; @@ -390,12 +378,66 @@ pub async fn update + GetPubkey>( Ok(tx) } +pub async fn transfer + GetPubkey>( + settings: &Settings, + hotspot_key: &helium_crypto::PublicKey, + target: &Pubkey, + keypair: C, +) -> Result { + let anchor_client = settings.mk_anchor_client(keypair.clone())?; + let solana_client = settings.mk_solana_client()?; + let program = anchor_client.program(mpl_bubblegum::ID)?; + let asset_account = asset::account_for_entity_key(&anchor_client, hotspot_key).await?; + let (asset, asset_proof) = asset::get_with_proof(settings, &asset_account).await?; + + let leaf_delegate = asset.ownership.delegate.unwrap_or(asset.ownership.owner); + let merkle_tree = asset_proof.tree_id; + let remaining_accounts = asset_proof.proof_for_tree(&merkle_tree).await?; + + let transfer = mpl_bubblegum::instructions::Transfer { + leaf_owner: (asset.ownership.owner, false), + leaf_delegate: (leaf_delegate, false), + new_leaf_owner: *target, + tree_config: mpl_bubblegum::accounts::TreeConfig::find_pda(&merkle_tree).0, + merkle_tree, + log_wrapper: SPL_NOOP_PROGRAM_ID, + compression_program: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + system_program: solana_sdk::system_program::id(), + }; + let args = mpl_bubblegum::instructions::TransferInstructionArgs { + creator_hash: asset.compression.creator_hash, + root: asset_proof.root.to_bytes(), + data_hash: asset.compression.data_hash, + index: asset.compression.leaf_id()?, + nonce: asset.compression.leaf_id, + }; + + let transfer_ix = transfer.instruction_with_remaining_accounts(args, &remaining_accounts); + let mut priority_fee_accounts = transfer_ix.accounts.clone(); + priority_fee_accounts.extend_from_slice(&remaining_accounts); + + let ixs = program + .request() + .compute_budget(200_000) + .compute_price(priority_fee::get_estimate(&solana_client, &priority_fee_accounts).await?) + .instruction(transfer_ix) + .instructions()?; + let mut tx = + solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&keypair.pubkey())); + let blockhash = program.rpc().get_latest_blockhash()?; + + tx.try_sign(&[&*keypair], blockhash)?; + + Ok(tx) +} + pub mod dataonly { use super::*; use crate::programs::{ SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, TOKEN_METADATA_PROGRAM_ID, }; use helium_proto::{BlockchainTxnAddGatewayV1, Message}; + use priority_fee::SetPriorityFees; pub async fn onboard + GetPubkey>( settings: &Settings, @@ -404,7 +446,7 @@ pub mod dataonly { keypair: C, ) -> Result { use helium_entity_manager::accounts::OnboardDataOnlyIotHotspotV0; - fn mk_onboard_accounts( + fn mk_accounts( config_account: helium_entity_manager::DataOnlyConfigV0, owner: Pubkey, hotspot_key: &helium_crypto::PublicKey, @@ -445,22 +487,12 @@ pub mod dataonly { let asset_account = asset::account_for_entity_key(&anchor_client, hotspot_key).await?; let (asset, asset_proof) = asset::get_with_proof(settings, &asset_account).await?; + let onboard_accounts = mk_accounts(config_account, program.payer(), hotspot_key); - let onboard_accounts = mk_onboard_accounts(config_account, program.payer(), hotspot_key); - let compute_ix = - solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(300_000); - let priority_fee = priority_fee::get_estimate( - &solana_client, - &onboard_accounts, - priority_fee::MIN_PRIORITY_FEE, - ) - .await?; - let compute_price_ix = - solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price( - priority_fee, - ); let mut ixs = program .request() + .compute_budget(300_000) + .compute_price(priority_fee::get_estimate(&solana_client, &onboard_accounts).await?) .args( helium_entity_manager::instruction::OnboardDataOnlyIotHotspotV0 { _args: helium_entity_manager::OnboardDataOnlyIotHotspotArgsV0 { @@ -475,12 +507,10 @@ pub mod dataonly { }, ) .accounts(onboard_accounts) - .instruction(compute_ix) - .instruction(compute_price_ix) .instructions()?; ixs[2] .accounts - .extend_from_slice(&asset_proof.proof()?[0..3]); + .extend_from_slice(&asset_proof.proof(Some(3))?); let mut tx = solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&keypair.pubkey())); @@ -537,29 +567,16 @@ pub mod dataonly { .await?; let hotspot_key = helium_crypto::PublicKey::from_bytes(&add_tx.gateway)?; let entity_key = hotspot_key.as_entity_key(); - - let issue_accounts = mk_issue_accounts(config_account, program.payer(), &entity_key); - let compute_ix = - solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(300_000); - let priority_fee = priority_fee::get_estimate( - &solana_client, - &issue_accounts, - priority_fee::MIN_PRIORITY_FEE, - ) - .await?; - let compute_price_ix = - solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price( - priority_fee, - ); + let accounts = mk_issue_accounts(config_account, program.payer(), &entity_key); let ix = program .request() + .compute_budget(300_000) + .compute_price(priority_fee::get_estimate(&solana_client, &accounts).await?) .args(helium_entity_manager::instruction::IssueDataOnlyEntityV0 { _args: helium_entity_manager::IssueDataOnlyEntityArgsV0 { entity_key }, }) - .accounts(issue_accounts) - .instruction(compute_ix) - .instruction(compute_price_ix) + .accounts(accounts) .instructions()?; let mut tx = diff --git a/helium-lib/src/priority_fee.rs b/helium-lib/src/priority_fee.rs index 47dd6cc2..e7f0877b 100644 --- a/helium-lib/src/priority_fee.rs +++ b/helium-lib/src/priority_fee.rs @@ -1,6 +1,9 @@ use crate::{result::Error, solana_client::nonblocking::rpc_client::RpcClient as SolanaRpcClient}; +use anchor_client::RequestBuilder; use helium_anchor_gen::anchor_lang::ToAccountMetas; use itertools::Itertools; +use solana_sdk::signer::Signer; +use std::ops::Deref; pub const MAX_RECENT_PRIORITY_FEE_ACCOUNTS: usize = 128; pub const MIN_PRIORITY_FEE: u64 = 1; @@ -8,6 +11,13 @@ pub const MIN_PRIORITY_FEE: u64 = 1; pub async fn get_estimate( client: &SolanaRpcClient, accounts: &impl ToAccountMetas, +) -> Result { + get_estimate_with_min(client, accounts, MIN_PRIORITY_FEE).await +} + +pub async fn get_estimate_with_min( + client: &SolanaRpcClient, + accounts: &impl ToAccountMetas, min_priority_fee: u64, ) -> Result { let account_keys: Vec<_> = accounts @@ -43,3 +53,26 @@ pub async fn get_estimate( .max(min_priority_fee); Ok(estimate) } + +pub trait SetPriorityFees { + fn compute_budget(self, limit: u32) -> Self; + fn compute_price(self, priority_fee: u64) -> Self; +} + +impl<'a, C: Deref + Clone> SetPriorityFees for RequestBuilder<'a, C> { + fn compute_budget(self, compute_limit: u32) -> Self { + let compute_ix = + solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit( + compute_limit, + ); + self.instruction(compute_ix) + } + + fn compute_price(self, priority_fee: u64) -> Self { + let compute_price_ix = + solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price( + priority_fee, + ); + self.instruction(compute_price_ix) + } +} diff --git a/helium-lib/src/reward.rs b/helium-lib/src/reward.rs index bd782c1d..0fe12f28 100644 --- a/helium-lib/src/reward.rs +++ b/helium-lib/src/reward.rs @@ -151,7 +151,7 @@ where distribute_ixs[0] .accounts - .extend_from_slice(&asset_proof.proof()?[0..3]); + .extend_from_slice(&asset_proof.proof(Some(3))?); ixs.push(distribute_ixs[0].clone()); let mut tx = solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&program.payer())); @@ -387,7 +387,7 @@ pub mod recipient { ixs[0] .accounts - .extend_from_slice(&asset_proof.proof()?[0..3]); + .extend_from_slice(&asset_proof.proof(Some(3))?); let mut tx = solana_sdk::transaction::Transaction::new_with_payer(&ixs, Some(&keypair.pubkey())); diff --git a/helium-wallet/src/cmd/hotspots/mod.rs b/helium-wallet/src/cmd/hotspots/mod.rs index 16dc1853..40ceecb6 100644 --- a/helium-wallet/src/cmd/hotspots/mod.rs +++ b/helium-wallet/src/cmd/hotspots/mod.rs @@ -4,6 +4,7 @@ mod add; mod info; mod list; mod rewards; +mod transfer; mod update; mod updates; @@ -28,7 +29,7 @@ pub enum HotspotCommand { Info(info::Cmd), Updates(updates::Cmd), Rewards(rewards::Cmd), - // Transfer(Box), + Transfer(transfer::Cmd), } impl HotspotCommand { @@ -40,7 +41,7 @@ impl HotspotCommand { Self::Info(cmd) => cmd.run(opts).await, Self::Updates(cmd) => cmd.run(opts).await, Self::Rewards(cmd) => cmd.run(opts).await, - // Self::Transfer(cmd) => cmd.run(opts).await, + Self::Transfer(cmd) => cmd.run(opts).await, } } } diff --git a/helium-wallet/src/cmd/hotspots/transfer.rs b/helium-wallet/src/cmd/hotspots/transfer.rs new file mode 100644 index 00000000..cd1c469d --- /dev/null +++ b/helium-wallet/src/cmd/hotspots/transfer.rs @@ -0,0 +1,30 @@ +use crate::cmd::*; +use helium_lib::{ + hotspot, + keypair::{GetPubkey, Pubkey}, +}; + +#[derive(Clone, Debug, clap::Args)] +/// Transfer a hotspot to another owner +pub struct Cmd { + /// Key of hotspot + address: helium_crypto::PublicKey, + /// Solana address of Recipient of hotspot + recipient: Pubkey, + /// Commit the transfer + #[command(flatten)] + commit: CommitOpts, +} + +impl Cmd { + pub async fn run(&self, opts: Opts) -> Result { + let password = get_wallet_password(false)?; + let keypair = opts.load_keypair(password.as_bytes())?; + if keypair.pubkey() == self.recipient { + bail!("recipient already owner of hotspot"); + } + let settings = opts.clone().try_into()?; + let tx = hotspot::transfer(&settings, &self.address, &self.recipient, keypair).await?; + print_json(&self.commit.maybe_commit(&tx, &settings).await?.to_json()) + } +}