mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 13:23:10 +00:00
Merge pull request #241 from logos-blockchain/schouhy/add-program-deployment-example-pda
Add example and instructions for pda and privacy tail calls
This commit is contained in:
commit
a280551ee3
@ -340,7 +340,7 @@ Luckily all that complexity is hidden behind the `wallet_core.send_privacy_prese
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
&Program::serialize_instruction(greeting).unwrap(),
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -568,4 +568,94 @@ Output:
|
||||
```
|
||||
Hola mundo!Hello from tail call
|
||||
```
|
||||
## Private tail-calls
|
||||
There's support for tail calls in privacy preserving executions too. The `run_hello_world_through_tail_call_private.rs` runner walks you through the process of invoking such an execution.
|
||||
The only difference is that, since the execution is local, the runner will need both programs: the `simple_tail_call` and it's dependency `hello_world`.
|
||||
|
||||
Let's use our existing private account with id `8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU`. This one is already owned by the `hello_world` program.
|
||||
|
||||
You can test the privacy tail calls with
|
||||
```bash
|
||||
cargo run --bin run_hello_world_through_tail_call_private \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/simple_tail_call.bin \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin \
|
||||
8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU
|
||||
```
|
||||
|
||||
>[!NOTE]
|
||||
> The above command may take longer than the previous privacy executions because needs to generate proofs of execution of both the `simple_tail_call` and the `hello_world` programs.
|
||||
|
||||
Once finished run the following to see the changes
|
||||
```bash
|
||||
wallet account sync-private
|
||||
wallet account get --account-id Private/8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU
|
||||
```
|
||||
|
||||
# 13. Program derived accounts: authorizing accounts through tail calls
|
||||
|
||||
## Digression: account authority vs account program ownership
|
||||
|
||||
In NSSA there are two distinct concepts that control who can modify an account:
|
||||
**Program Ownership:** Each account has a field: `program_owner: ProgramId`.
|
||||
This indicates which program is allowed to update the account’s state during execution.
|
||||
- If a program is the program_owner of an account, it can freely mutate its fields.
|
||||
- If the account is uninitialized (`program_owner = DEFAULT_PROGRAM_ID`), a program may claim it and become its owner.
|
||||
- If a program is not the owner and the account is not claimable, any attempt to modify it will cause the transition to fail.
|
||||
Program ownership is about mutation rights during program execution.
|
||||
|
||||
**Account authority**: Independent from program ownership, each account also has an authority. The entity that is allowed to set: `is_authorized = true`. This flag indicates that the account has been authorized for use in a transaction.
|
||||
Who can act as authority?
|
||||
- User-defined accounts: The user is the authority. They can mark an account as authorized by:
|
||||
- Signing the transaction (public accounts)
|
||||
- Providing a valid nullifiers secret key ownership proof (private accounts)
|
||||
- Program derived accounts: Programs are automatically the authority of a dedicated namespace of public accounts.
|
||||
|
||||
Each program owns a non-overlapping space of 2^256 **public** account IDs. They do not overlap with:
|
||||
- User accounts (public or private)
|
||||
- Other program’s PDAs
|
||||
|
||||
> [!NOTE]
|
||||
> Currently PDAs are restricted to the public state.
|
||||
|
||||
A program can be the authority of an account owned by another program, which is the most common case.
|
||||
During a chained call, a program can mark its PDA accounts as `is_authorized=true` without requiring any user signatures or nullifier secret keys. This enables programs to safely authorize accounts during program composition. Importantly, these flags can only be set to true for PDA accounts through an execution of the program that is their authority. No user and no other program can execute any transition that requires authorization of PDA accounts belonging to a different program.
|
||||
|
||||
## Running the example
|
||||
This tutorial includes an example of PDA usage in `methods/guest/src/bin/tail_call_with_pda.rs.`. That program’s sole purpose is to forward one of its own PDA accounts, an account for which it is the authority, to the "Hello World with authorization" program via a chained call. The Hello World program will then claim the account and become its program owner, but the `tail_call_with_pda` program remains the authority. This means it is still the only entity capable of marking that account as `is_authorized=true`.
|
||||
|
||||
Deploy the program:
|
||||
```bash
|
||||
wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/tail_call_with_pda.bin
|
||||
```
|
||||
|
||||
There is no need to create a new account for this example, because we simply use one of the PDA accounts belonging to the `tail_call_with_pda` program.
|
||||
|
||||
Execute the program
|
||||
```bash
|
||||
cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda $EXAMPLE_PROGRAMS_BUILD_DIR/tail_call_with_pda.bin
|
||||
```
|
||||
|
||||
You'll see an output like the following:
|
||||
|
||||
```bash
|
||||
The program derived account ID is: 3tfTPPuxj3eSE1cLVuNBEk8eSHzpnYS1oqEdeH3Nfsks
|
||||
```
|
||||
|
||||
Then check the status of that account
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/3tfTPPuxj3eSE1cLVuNBEk8eSHzpnYS1oqEdeH3Nfsks
|
||||
```
|
||||
|
||||
Output:
|
||||
```bash
|
||||
{
|
||||
"balance":0,
|
||||
"program_owner_b64":"HZXHYRaKf6YusVo8x00/B15uyY5sGsJb1bzH4KlCY5g=",
|
||||
"data_b64": "SGVsbG8gZnJvbSB0YWlsIGNhbGwgd2l0aCBQcm9ncmFtIERlcml2ZWQgQWNjb3VudCBJRA==",
|
||||
"nonce":0"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ use nssa_core::program::{
|
||||
/// `cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml`
|
||||
/// This compiles the programs and outputs the IDs in hex that can be used to copy here.
|
||||
const HELLO_WORLD_PROGRAM_ID_HEX: &str =
|
||||
"7e99d6e2d158f4dea59597011da5d1c2eef17beed6667657f515b387035b935a";
|
||||
"e9dfc5a5d03c9afa732adae6e0edfce4bbb44c7a2afb9f148f4309917eb2de6f";
|
||||
|
||||
fn hello_world_program_id() -> ProgramId {
|
||||
let hello_world_program_id_bytes: [u8; 32] = hex::decode(HELLO_WORLD_PROGRAM_ID_HEX)
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs,
|
||||
write_nssa_outputs_with_chained_call,
|
||||
};
|
||||
|
||||
// Tail Call with PDA example program.
|
||||
//
|
||||
// Demonstrates how to chain execution to another program using `ChainedCall`
|
||||
// while authorizing program-derived accounts.
|
||||
//
|
||||
// Expects a single input account whose Account ID is derived from this
|
||||
// program’s ID and the fixed PDA seed below (as defined by the
|
||||
// `<AccountId as From<(&ProgramId, &PdaSeed)>>` implementation).
|
||||
//
|
||||
// Emits this account unchanged, then performs a tail call to the
|
||||
// Hello-World-with-Authorization program with a fixed greeting. The same
|
||||
// account is passed along but marked with `is_authorized = true`.
|
||||
|
||||
const HELLO_WORLD_WITH_AUTHORIZATION_PROGRAM_ID_HEX: &str =
|
||||
"1d95c761168a7fa62eb15a3cc74d3f075e6ec98e6c1ac25bd5bcc7e0a9426398";
|
||||
const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]);
|
||||
|
||||
fn hello_world_program_id() -> ProgramId {
|
||||
let hello_world_program_id_bytes: [u8; 32] =
|
||||
hex::decode(HELLO_WORLD_WITH_AUTHORIZATION_PROGRAM_ID_HEX)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
bytemuck::cast(hello_world_program_id_bytes)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: _,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<()>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.clone()
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// Create the (unchanged) post state
|
||||
let post_state = AccountPostState::new(pre_state.account.clone());
|
||||
|
||||
// Create the chained call
|
||||
let chained_call_greeting: Vec<u8> =
|
||||
b"Hello from tail call with Program Derived Account ID".to_vec();
|
||||
let chained_call_instruction_data = risc0_zkvm::serde::to_vec(&chained_call_greeting).unwrap();
|
||||
|
||||
// Flip the `is_authorized` flag to true
|
||||
let pre_state_for_chained_call = {
|
||||
let mut this = pre_state.clone();
|
||||
this.is_authorized = true;
|
||||
this
|
||||
};
|
||||
let chained_call = ChainedCall {
|
||||
program_id: hello_world_program_id(),
|
||||
instruction_data: chained_call_instruction_data,
|
||||
pre_states: vec![pre_state_for_chained_call],
|
||||
pda_seeds: vec![PDA_SEED],
|
||||
};
|
||||
|
||||
// Write the outputs
|
||||
write_nssa_outputs_with_chained_call(
|
||||
instruction_data,
|
||||
vec![pre_state],
|
||||
vec![post_state],
|
||||
vec![chained_call],
|
||||
);
|
||||
}
|
||||
@ -54,7 +54,7 @@ async fn main() {
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
&Program::serialize_instruction(greeting).unwrap(),
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nssa::{
|
||||
AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies,
|
||||
program::Program,
|
||||
};
|
||||
use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config};
|
||||
|
||||
// Before running this example, compile the `simple_tail_call.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world_through_tail_call_private /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_through_tail_call \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin \
|
||||
// Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Load wallet config and storage
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the simple_tail_call program binary
|
||||
let simple_tail_call_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
// Second argument is the path to the hello_world program binary
|
||||
let hello_world_path = std::env::args_os().nth(2).unwrap().into_string().unwrap();
|
||||
// Third argument is the account_id
|
||||
let account_id: AccountId = std::env::args_os()
|
||||
.nth(3)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// Load the program and its dependencies (the hellow world program)
|
||||
let simple_tail_call_bytecode: Vec<u8> = std::fs::read(simple_tail_call_path).unwrap();
|
||||
let simple_tail_call = Program::new(simple_tail_call_bytecode).unwrap();
|
||||
let hello_world_bytecode: Vec<u8> = std::fs::read(hello_world_path).unwrap();
|
||||
let hello_world = Program::new(hello_world_bytecode).unwrap();
|
||||
let dependencies: HashMap<ProgramId, Program> =
|
||||
[(hello_world.id(), hello_world)].into_iter().collect();
|
||||
let program_with_dependencies = ProgramWithDependencies::new(simple_tail_call, dependencies);
|
||||
|
||||
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
|
||||
|
||||
// Construct and submit the privacy-preserving transaction
|
||||
let instruction = ();
|
||||
wallet_core
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&program_with_dependencies,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use nssa_core::program::PdaSeed;
|
||||
use wallet::{WalletCore, helperfunctions::fetch_config};
|
||||
|
||||
// Before running this example, compile the `simple_tail_call.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda
|
||||
// /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/tail_call_with_pda.bin
|
||||
|
||||
const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]);
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Load wallet config and storage
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the program binary
|
||||
let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
// Compute the PDA to pass it as input account to the public execution
|
||||
let pda = AccountId::from((&program.id(), &PDA_SEED));
|
||||
let account_ids = vec![pda];
|
||||
let instruction_data = ();
|
||||
let nonces = vec![];
|
||||
let signing_keys = [];
|
||||
let message = Message::try_new(program.id(), account_ids, nonces, instruction_data).unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("The program derived account id is: {pda}");
|
||||
}
|
||||
@ -105,7 +105,7 @@ async fn main() {
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -146,7 +146,7 @@ async fn main() {
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -27,7 +27,7 @@ pub struct ProgramInput<T> {
|
||||
pub struct PdaSeed([u8; 32]);
|
||||
|
||||
impl PdaSeed {
|
||||
pub fn new(value: [u8; 32]) -> Self {
|
||||
pub const fn new(value: [u8; 32]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,9 @@ use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::Ke
|
||||
use log::info;
|
||||
use nssa::{
|
||||
Account, AccountId, PrivacyPreservingTransaction,
|
||||
privacy_preserving_transaction::message::EncryptedAccountData, program::Program,
|
||||
privacy_preserving_transaction::{
|
||||
circuit::ProgramWithDependencies, message::EncryptedAccountData,
|
||||
},
|
||||
};
|
||||
use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData};
|
||||
pub use privacy_preserving_tx::PrivacyPreservingAccount;
|
||||
@ -247,7 +249,7 @@ impl WalletCore {
|
||||
&self,
|
||||
accounts: Vec<PrivacyPreservingAccount>,
|
||||
instruction_data: &InstructionData,
|
||||
program: &Program,
|
||||
program: &ProgramWithDependencies,
|
||||
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
|
||||
Ok(())
|
||||
@ -259,7 +261,7 @@ impl WalletCore {
|
||||
&self,
|
||||
accounts: Vec<PrivacyPreservingAccount>,
|
||||
instruction_data: &InstructionData,
|
||||
program: &Program,
|
||||
program: &ProgramWithDependencies,
|
||||
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
|
||||
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||
let acc_manager = privacy_preserving_tx::AccountManager::new(self, accounts).await?;
|
||||
@ -284,7 +286,7 @@ impl WalletCore {
|
||||
.collect::<Vec<_>>(),
|
||||
&acc_manager.private_account_auth(),
|
||||
&acc_manager.private_account_membership_proofs(),
|
||||
&program.to_owned().into(),
|
||||
&program.to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ impl NativeTokenTransfer<'_> {
|
||||
PrivacyPreservingAccount::Public(to),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
|
||||
@ -18,7 +18,7 @@ impl NativeTokenTransfer<'_> {
|
||||
.send_privacy_preserving_tx_with_pre_check(
|
||||
vec![PrivacyPreservingAccount::PrivateOwned(from)],
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&Program::authenticated_transfer_program(),
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
|_| Ok(()),
|
||||
)
|
||||
.await
|
||||
@ -48,7 +48,7 @@ impl NativeTokenTransfer<'_> {
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
@ -75,7 +75,7 @@ impl NativeTokenTransfer<'_> {
|
||||
PrivacyPreservingAccount::PrivateOwned(to),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
|
||||
@ -21,7 +21,7 @@ impl NativeTokenTransfer<'_> {
|
||||
PrivacyPreservingAccount::PrivateOwned(to),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
@ -53,7 +53,7 @@ impl NativeTokenTransfer<'_> {
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
|
||||
@ -38,7 +38,7 @@ impl Pinata<'_> {
|
||||
PrivacyPreservingAccount::PrivateOwned(winner_account_id),
|
||||
],
|
||||
&nssa::program::Program::serialize_instruction(solution).unwrap(),
|
||||
&nssa::program::Program::pinata(),
|
||||
&nssa::program::Program::pinata().into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
|
||||
@ -54,7 +54,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateOwned(supply_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
@ -82,7 +82,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::Public(supply_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
@ -110,7 +110,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateOwned(supply_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
@ -176,7 +176,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
@ -206,7 +206,7 @@ impl Token<'_> {
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
@ -232,7 +232,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::Public(recipient_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
@ -259,7 +259,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
@ -290,7 +290,7 @@ impl Token<'_> {
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user