Merge branch 'main' into Pravdyvy/amm-wallet-integration

This commit is contained in:
Daniil Polyakov 2025-12-26 23:01:40 +03:00
commit 2d1ee4b279
32 changed files with 5697 additions and 3316 deletions

1
Cargo.lock generated
View File

@ -2658,6 +2658,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"borsh", "borsh",
"bytemuck", "bytemuck",
"env_logger",
"hex", "hex",
"hex-literal 1.1.0", "hex-literal 1.1.0",
"log", "log",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -347,11 +347,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
assert_eq!(definition_acc.program_owner, Program::token().id()); assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32
// bytes)]
assert_eq!( assert_eq!(
definition_acc.data.as_ref(), definition_acc.data.as_ref(),
&[ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
] ]
); );
@ -434,6 +437,94 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
7 7
); );
// Burn 3 tokens from `recipient_acc`
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: make_public_account_input_from_str(&definition_account_id.to_string()),
holder: make_public_account_input_from_str(&recipient_account_id.to_string()),
amount: 3,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account is the expected after the execution
let definition_acc = seq_client
.get_account(definition_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
// Check the status of the account at `recipient_account_id` is the expected after the
// execution
let recipient_acc = seq_client
.get_account(recipient_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
4
);
// Mint 10 tokens at `recipient_acc`
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: make_public_account_input_from_str(&definition_account_id.to_string()),
holder: Some(make_public_account_input_from_str(
&recipient_account_id.to_string(),
)),
holder_npk: None,
holder_ipk: None,
amount: 10,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account is the expected after the execution
let definition_acc = seq_client
.get_account(definition_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
// Check the status of the account at `recipient_account_id` is the expected after the
// execution
let recipient_acc = seq_client
.get_account(recipient_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
14
);
} }
/// This test creates a new private token using the token program. After creating the token, the /// This test creates a new private token using the token program. After creating the token, the
@ -506,11 +597,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
assert_eq!(definition_acc.program_owner, Program::token().id()); assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32
// bytes)]
assert_eq!( assert_eq!(
definition_acc.data.as_ref(), definition_acc.data.as_ref(),
&[ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
] ]
); );
@ -590,6 +684,194 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.get_private_account_commitment(&recipient_account_id) .get_private_account_commitment(&recipient_account_id)
.unwrap(); .unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
// Burn 3 tokens from `recipient_acc`
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: make_public_account_input_from_str(&definition_account_id.to_string()),
holder: make_private_account_input_from_str(&recipient_account_id.to_string()),
amount: 3,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account is the expected after the execution
let definition_acc = seq_client
.get_account(definition_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
let new_commitment2 = wallet_storage
.get_private_account_commitment(&recipient_account_id)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
// Check the status of the account at `recipient_account_id` is the expected after the
// execution
let recipient_acc = wallet_storage
.get_account_private(&recipient_account_id)
.unwrap();
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
11
);
// Mint 10 tokens at `recipient_acc`
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: make_public_account_input_from_str(&definition_account_id.to_string()),
holder: Some(make_private_account_input_from_str(
&recipient_account_id.to_string(),
)),
holder_npk: None,
holder_ipk: None,
amount: 10,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account is the expected after the execution
let definition_acc = seq_client
.get_account(definition_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
let new_commitment2 = wallet_storage
.get_private_account_commitment(&recipient_account_id)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
// Check the status of the account at `recipient_account_id` is the expected after the
// execution
let recipient_acc = wallet_storage
.get_account_private(&recipient_account_id)
.unwrap();
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
21
);
// Now the same mint, but in foreign way
// Create new account for receiving a mint transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id2,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private { cci: None },
)))
.await
.unwrap()
else {
panic!("invalid subcommand return value");
};
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
let (holder_keys, _) = wallet_storage
.storage
.user_data
.get_private_account(&recipient_account_id2)
.unwrap();
// Mint 9 tokens at `recipient_acc2`
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: make_public_account_input_from_str(&definition_account_id.to_string()),
holder: None,
holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)),
holder_ipk: Some(hex::encode(
holder_keys.incoming_viewing_public_key.0.clone(),
)),
amount: 9,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Sync to claim holder
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(command).await.unwrap();
// Check the status of the token definition account is the expected after the execution
let definition_acc = seq_client
.get_account(definition_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
let new_commitment2 = wallet_storage
.get_private_account_commitment(&recipient_account_id2)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
// Check the status of the account at `recipient_account_id2` is the expected after the
// execution
let recipient_acc = wallet_storage
.get_account_private(&recipient_account_id2)
.unwrap();
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
9
);
} }
/// This test creates a new private token using the token program. All accounts are private /// This test creates a new private token using the token program. All accounts are private
@ -663,8 +945,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.account; .account;
assert_eq!(supply_acc.program_owner, Program::token().id()); assert_eq!(supply_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token holding account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ]
assert_eq!( assert_eq!(
supply_acc.data.as_ref(), supply_acc.data.as_ref(),
&[ &[
@ -673,6 +955,287 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
); );
// Create new account for receiving a mint transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id_pr,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private { cci: None },
)))
.await
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new account for receiving a mint transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id_pub,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public { cci: None },
)))
.await
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Mint 10 tokens at `recipient_acc_pub`
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: make_private_account_input_from_str(&definition_account_id.to_string()),
holder: Some(make_public_account_input_from_str(
&recipient_account_id_pub.to_string(),
)),
holder_npk: None,
holder_ipk: None,
amount: 10,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Check the status of the token definition account is the expected after the execution
let definition_acc = wallet_storage
.get_account_private(&definition_account_id)
.unwrap();
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
// Check the status of the account at `recipient_account_id_pub` is the expected after the
// execution
let recipient_acc_pub = seq_client
.get_account(recipient_account_id_pub.to_string())
.await
.unwrap()
.account;
assert_eq!(
u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()),
10
);
let (holder_keys, _) = wallet_storage
.storage
.user_data
.get_private_account(&recipient_account_id_pr)
.unwrap();
// Mint 5 tokens at `recipient_acc_pr`
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: make_private_account_input_from_str(&definition_account_id.to_string()),
holder: None,
holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)),
holder_ipk: Some(hex::encode(
holder_keys.incoming_viewing_public_key.0.clone(),
)),
amount: 5,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Sync to claim holder
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(command).await.unwrap();
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Check the status of the token definition account is the expected after the execution
let definition_acc = wallet_storage
.get_account_private(&definition_account_id)
.unwrap();
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
let new_commitment2 = wallet_storage
.get_private_account_commitment(&recipient_account_id_pr)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
// Check the status of the account at `recipient_account_id_pr` is the expected after the
// execution
let recipient_acc_pr = wallet_storage
.get_account_private(&recipient_account_id_pr)
.unwrap();
assert_eq!(
u128::from_le_bytes(recipient_acc_pr.data[33..].try_into().unwrap()),
5
);
// Mint 5 tokens at `recipient_acc_pr`
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: make_private_account_input_from_str(&definition_account_id.to_string()),
holder: Some(make_private_account_input_from_str(
&recipient_account_id_pr.to_string(),
)),
holder_npk: None,
holder_ipk: None,
amount: 5,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Check the status of the token definition account is the expected after the execution
let definition_acc = wallet_storage
.get_account_private(&definition_account_id)
.unwrap();
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
let new_commitment2 = wallet_storage
.get_private_account_commitment(&recipient_account_id_pr)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
// Check the status of the account at `recipient_account_id_pr` is the expected after the
// execution
let recipient_acc = wallet_storage
.get_account_private(&recipient_account_id_pr)
.unwrap();
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
10
);
// Burn 5 tokens at `recipient_acc_pub`
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: make_private_account_input_from_str(&definition_account_id.to_string()),
holder: make_public_account_input_from_str(&recipient_account_id_pub.to_string()),
amount: 5,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Check the status of the token definition account is the expected after the execution
let definition_acc = wallet_storage
.get_account_private(&definition_account_id)
.unwrap();
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
// Check the status of the account at `recipient_account_id_pub` is the expected after the
// execution
let recipient_acc_pub = seq_client
.get_account(recipient_account_id_pub.to_string())
.await
.unwrap()
.account;
assert_eq!(
u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()),
5
);
// Burn 5 tokens at `recipient_acc_pr`
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: make_private_account_input_from_str(&definition_account_id.to_string()),
holder: make_private_account_input_from_str(&recipient_account_id_pr.to_string()),
amount: 5,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Check the status of the token definition account is the expected after the execution
let definition_acc = wallet_storage
.get_account_private(&definition_account_id)
.unwrap();
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
let new_commitment2 = wallet_storage
.get_private_account_commitment(&recipient_account_id_pr)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
// Check the status of the account at `recipient_account_id_pr` is the expected after the
// execution
let recipient_acc = wallet_storage
.get_account_private(&recipient_account_id_pr)
.unwrap();
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
5
);
} }
/// This test creates a new private token using the token program. All accounts are private /// This test creates a new private token using the token program. All accounts are private
@ -754,17 +1317,20 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
assert_eq!(definition_acc.program_owner, Program::token().id()); assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32
// bytes)]
assert_eq!( assert_eq!(
definition_acc.data.as_ref(), definition_acc.data.as_ref(),
&[ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
] ]
); );
assert_eq!(supply_acc.program_owner, Program::token().id()); assert_eq!(supply_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token holding account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ]
assert_eq!( assert_eq!(
supply_acc.data.as_ref(), supply_acc.data.as_ref(),
&[ &[
@ -844,11 +1410,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
assert_eq!(definition_acc.program_owner, Program::token().id()); assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32
// bytes)]
assert_eq!( assert_eq!(
definition_acc.data.as_ref(), definition_acc.data.as_ref(),
&[ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
] ]
); );
@ -980,11 +1549,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
assert_eq!(definition_acc.program_owner, Program::token().id()); assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32
// bytes)]
assert_eq!( assert_eq!(
definition_acc.data.as_ref(), definition_acc.data.as_ref(),
&[ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
] ]
); );
@ -1116,11 +1688,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
assert_eq!(definition_acc.program_owner, Program::token().id()); assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32
// bytes)]
assert_eq!( assert_eq!(
definition_acc.data.as_ref(), definition_acc.data.as_ref(),
&[ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
] ]
); );

View File

@ -25,6 +25,7 @@ risc0-binfmt = "3.0.2"
[dev-dependencies] [dev-dependencies]
test_program_methods.workspace = true test_program_methods.workspace = true
hex-literal = "1.0.0" hex-literal = "1.0.0"
env_logger.workspace = true
[features] [features]
default = [] default = []

View File

@ -6,14 +6,15 @@ edition = "2024"
[dependencies] [dependencies]
risc0-zkvm.workspace = true risc0-zkvm.workspace = true
borsh.workspace = true borsh.workspace = true
serde = { workspace = true } serde.workspace = true
thiserror.workspace = true thiserror.workspace = true
chacha20 = { version = "0.9", default-features = false } bytemuck.workspace = true
bytemuck = { workspace = true }
k256 = { workspace = true, optional = true } k256 = { workspace = true, optional = true }
base58 = { workspace = true, optional = true } base58 = { workspace = true, optional = true }
anyhow = { workspace = true, optional = true } anyhow = { workspace = true, optional = true }
chacha20 = { version = "0.9", default-features = false }
[dev-dependencies] [dev-dependencies]
serde_json.workspace = true serde_json.workspace = true

View File

@ -15,7 +15,7 @@ pub type Nonce = u128;
/// Account to be used both in public and private contexts /// Account to be used both in public and private contexts
#[derive( #[derive(
Serialize, Deserialize, Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, Clone, Default, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)] )]
#[cfg_attr(any(feature = "host", test), derive(Debug))] #[cfg_attr(any(feature = "host", test), derive(Debug))]
pub struct Account { pub struct Account {
@ -25,7 +25,7 @@ pub struct Account {
pub nonce: Nonce, pub nonce: Nonce,
} }
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(feature = "host", test), derive(Debug))] #[cfg_attr(any(feature = "host", test), derive(Debug))]
pub struct AccountWithMetadata { pub struct AccountWithMetadata {
pub account: Account, pub account: Account,
@ -45,6 +45,7 @@ impl AccountWithMetadata {
} }
#[derive( #[derive(
Default,
Copy, Copy,
Clone, Clone,
Serialize, Serialize,
@ -54,7 +55,6 @@ impl AccountWithMetadata {
Hash, Hash,
BorshSerialize, BorshSerialize,
BorshDeserialize, BorshDeserialize,
Default,
)] )]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialOrd, Ord))] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialOrd, Ord))]
pub struct AccountId { pub struct AccountId {

View File

@ -97,8 +97,6 @@ impl Program {
} }
pub fn amm() -> Self { pub fn amm() -> Self {
// This unwrap wont panic since the `AMM_ELF` comes from risc0 build of
// `program_methods`
Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program") Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program")
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -82,7 +82,7 @@ fn main() {
let winner_token_holding_post = winner_token_holding.account.clone(); let winner_token_holding_post = winner_token_holding.account.clone();
pinata_definition_post.data = data.next_data(); pinata_definition_post.data = data.next_data();
let mut instruction_data: [u8; 23] = [0; 23]; let mut instruction_data = vec![0; 23];
instruction_data[0] = 1; instruction_data[0] = 1;
instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes()); instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes());

File diff suppressed because it is too large Load Diff

View File

@ -289,6 +289,8 @@ impl WalletSubcommand for AccountSubcommand {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use nssa::AccountId;
use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition}; use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition};
#[test] #[test]
@ -297,6 +299,7 @@ mod tests {
account_type: 1, account_type: 1,
name: [137, 12, 14, 3, 5, 4], name: [137, 12, 14, 3, 5, 4],
total_supply: 100, total_supply: 100,
metadata_id: AccountId::new([0; 32]),
}; };
let token_def_view: TokedDefinitionAccountView = token_def.into(); let token_def_view: TokedDefinitionAccountView = token_def.into();
@ -310,6 +313,7 @@ mod tests {
account_type: 1, account_type: 1,
name: [240, 159, 146, 150, 66, 66], name: [240, 159, 146, 150, 66, 66],
total_supply: 100, total_supply: 100,
metadata_id: AccountId::new([0; 32]),
}; };
let token_def_view: TokedDefinitionAccountView = token_def.into(); let token_def_view: TokedDefinitionAccountView = token_def.into();
@ -323,6 +327,7 @@ mod tests {
account_type: 1, account_type: 1,
name: [78, 65, 77, 69, 0, 0], name: [78, 65, 77, 69, 0, 0],
total_supply: 100, total_supply: 100,
metadata_id: AccountId::new([0; 32]),
}; };
let token_def_view: TokedDefinitionAccountView = token_def.into(); let token_def_view: TokedDefinitionAccountView = token_def.into();

View File

@ -50,6 +50,48 @@ pub enum TokenProgramAgnosticSubcommand {
#[arg(long)] #[arg(long)]
amount: u128, amount: u128,
}, },
/// Burn tokens on `holder`, modify `definition`.
///
/// `holder` is owned
///
/// Also if `definition` is private then it is owned, because
/// we can not modify foreign accounts.
Burn {
/// definition - valid 32 byte base58 string with privacy prefix
#[arg(long)]
definition: String,
/// holder - valid 32 byte base58 string with privacy prefix
#[arg(long)]
holder: String,
/// amount - amount of balance to burn
#[arg(long)]
amount: u128,
},
/// Mint tokens on `holder`, modify `definition`.
///
/// `definition` is owned
///
/// If `holder` is private, then `holder` and (`holder_npk` , `holder_ipk`) is a mutually
/// exclusive patterns.
///
/// First is used for owned accounts, second otherwise.
Mint {
/// definition - valid 32 byte base58 string with privacy prefix
#[arg(long)]
definition: String,
/// holder - valid 32 byte base58 string with privacy prefix
#[arg(long)]
holder: Option<String>,
/// holder_npk - valid 32 byte hex string
#[arg(long)]
holder_npk: Option<String>,
/// to_ipk - valid 33 byte hex string
#[arg(long)]
holder_ipk: Option<String>,
/// amount - amount of balance to mint
#[arg(long)]
amount: u128,
},
} }
impl WalletSubcommand for TokenProgramAgnosticSubcommand { impl WalletSubcommand for TokenProgramAgnosticSubcommand {
@ -202,6 +244,150 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
} }
}; };
underlying_subcommand.handle_subcommand(wallet_core).await
}
TokenProgramAgnosticSubcommand::Burn {
definition,
holder,
amount,
} => {
let underlying_subcommand = {
let (definition, definition_privacy) =
parse_addr_with_privacy_prefix(&definition)?;
let (holder, holder_privacy) = parse_addr_with_privacy_prefix(&holder)?;
match (definition_privacy, holder_privacy) {
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::BurnToken {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Private) => {
TokenProgramSubcommand::Private(
TokenProgramSubcommandPrivate::BurnTokenPrivateOwned {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Public) => {
TokenProgramSubcommand::Deshielded(
TokenProgramSubcommandDeshielded::BurnTokenDeshieldedOwned {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
(AccountPrivacyKind::Public, AccountPrivacyKind::Private) => {
TokenProgramSubcommand::Shielded(
TokenProgramSubcommandShielded::BurnTokenShielded {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
}
};
underlying_subcommand.handle_subcommand(wallet_core).await
}
TokenProgramAgnosticSubcommand::Mint {
definition,
holder,
holder_npk,
holder_ipk,
amount,
} => {
let underlying_subcommand = match (holder, holder_npk, holder_ipk) {
(None, None, None) => {
anyhow::bail!(
"Provide either account account_id of holder or their public keys"
);
}
(Some(_), Some(_), Some(_)) => {
anyhow::bail!(
"Provide only one variant: either account_id of holder or their public keys"
);
}
(_, Some(_), None) | (_, None, Some(_)) => {
anyhow::bail!("List of public keys is uncomplete");
}
(Some(holder), None, None) => {
let (definition, definition_privacy) =
parse_addr_with_privacy_prefix(&definition)?;
let (holder, holder_privacy) = parse_addr_with_privacy_prefix(&holder)?;
match (definition_privacy, holder_privacy) {
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::MintToken {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Private) => {
TokenProgramSubcommand::Private(
TokenProgramSubcommandPrivate::MintTokenPrivateOwned {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Public) => {
TokenProgramSubcommand::Deshielded(
TokenProgramSubcommandDeshielded::MintTokenDeshielded {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
(AccountPrivacyKind::Public, AccountPrivacyKind::Private) => {
TokenProgramSubcommand::Shielded(
TokenProgramSubcommandShielded::MintTokenShieldedOwned {
definition_account_id: definition,
holder_account_id: holder,
amount,
},
)
}
}
}
(None, Some(holder_npk), Some(holder_ipk)) => {
let (definition, definition_privacy) =
parse_addr_with_privacy_prefix(&definition)?;
match definition_privacy {
AccountPrivacyKind::Private => TokenProgramSubcommand::Private(
TokenProgramSubcommandPrivate::MintTokenPrivateForeign {
definition_account_id: definition,
holder_npk,
holder_ipk,
amount,
},
),
AccountPrivacyKind::Public => TokenProgramSubcommand::Shielded(
TokenProgramSubcommandShielded::MintTokenShieldedForeign {
definition_account_id: definition,
holder_npk,
holder_ipk,
amount,
},
),
}
}
};
underlying_subcommand.handle_subcommand(wallet_core).await underlying_subcommand.handle_subcommand(wallet_core).await
} }
} }
@ -240,6 +426,24 @@ pub enum TokenProgramSubcommandPublic {
#[arg(short, long)] #[arg(short, long)]
balance_to_move: u128, balance_to_move: u128,
}, },
// Burn tokens using the token program
BurnToken {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
// Transfer tokens using the token program
MintToken {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
} }
/// Represents generic private CLI subcommand for a wallet working with token_program /// Represents generic private CLI subcommand for a wallet working with token_program
@ -267,6 +471,35 @@ pub enum TokenProgramSubcommandPrivate {
#[arg(short, long)] #[arg(short, long)]
balance_to_move: u128, balance_to_move: u128,
}, },
// Burn tokens using the token program
BurnTokenPrivateOwned {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
// Transfer tokens using the token program
MintTokenPrivateOwned {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
// Transfer tokens using the token program
MintTokenPrivateForeign {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_npk: String,
#[arg(short, long)]
holder_ipk: String,
#[arg(short, long)]
amount: u128,
},
} }
/// Represents deshielded public CLI subcommand for a wallet working with token_program /// Represents deshielded public CLI subcommand for a wallet working with token_program
@ -281,6 +514,24 @@ pub enum TokenProgramSubcommandDeshielded {
#[arg(short, long)] #[arg(short, long)]
balance_to_move: u128, balance_to_move: u128,
}, },
// Burn tokens using the token program
BurnTokenDeshieldedOwned {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
// Transfer tokens using the token program
MintTokenDeshielded {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
} }
/// Represents generic shielded CLI subcommand for a wallet working with token_program /// Represents generic shielded CLI subcommand for a wallet working with token_program
@ -308,6 +559,35 @@ pub enum TokenProgramSubcommandShielded {
#[arg(short, long)] #[arg(short, long)]
balance_to_move: u128, balance_to_move: u128,
}, },
// Burn tokens using the token program
BurnTokenShielded {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
// Transfer tokens using the token program
MintTokenShieldedOwned {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_account_id: String,
#[arg(short, long)]
amount: u128,
},
// Transfer tokens using the token program
MintTokenShieldedForeign {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
holder_npk: String,
#[arg(short, long)]
holder_ipk: String,
#[arg(short, long)]
amount: u128,
},
} }
/// Represents generic initialization subcommand for a wallet working with token_program /// Represents generic initialization subcommand for a wallet working with token_program
@ -387,6 +667,34 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
.await?; .await?;
Ok(SubcommandReturnValue::Empty) Ok(SubcommandReturnValue::Empty)
} }
TokenProgramSubcommandPublic::BurnToken {
definition_account_id,
holder_account_id,
amount,
} => {
Token(wallet_core)
.send_burn_transaction(
definition_account_id.parse().unwrap(),
holder_account_id.parse().unwrap(),
amount,
)
.await?;
Ok(SubcommandReturnValue::Empty)
}
TokenProgramSubcommandPublic::MintToken {
definition_account_id,
holder_account_id,
amount,
} => {
Token(wallet_core)
.send_mint_transaction(
definition_account_id.parse().unwrap(),
holder_account_id.parse().unwrap(),
amount,
)
.await?;
Ok(SubcommandReturnValue::Empty)
}
} }
} }
} }
@ -486,6 +794,139 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandPrivate::BurnTokenPrivateOwned {
definition_account_id,
holder_account_id,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_account_id: AccountId = holder_account_id.parse().unwrap();
let (res, [secret_definition, secret_holder]) = Token(wallet_core)
.send_burn_transaction_private_owned_account(
definition_account_id,
holder_account_id,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![
Decode(secret_definition, definition_account_id),
Decode(secret_holder, holder_account_id),
];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandPrivate::MintTokenPrivateOwned {
definition_account_id,
holder_account_id,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_account_id: AccountId = holder_account_id.parse().unwrap();
let (res, [secret_definition, secret_holder]) = Token(wallet_core)
.send_mint_transaction_private_owned_account(
definition_account_id,
holder_account_id,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![
Decode(secret_definition, definition_account_id),
Decode(secret_holder, holder_account_id),
];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandPrivate::MintTokenPrivateForeign {
definition_account_id,
holder_npk,
holder_ipk,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_npk_res = hex::decode(holder_npk)?;
let mut holder_npk = [0; 32];
holder_npk.copy_from_slice(&holder_npk_res);
let holder_npk = nssa_core::NullifierPublicKey(holder_npk);
let holder_ipk_res = hex::decode(holder_ipk)?;
let mut holder_ipk = [0u8; 33];
holder_ipk.copy_from_slice(&holder_ipk_res);
let holder_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point(
holder_ipk.to_vec(),
);
let (res, [secret_definition, _]) = Token(wallet_core)
.send_mint_transaction_private_foreign_account(
definition_account_id,
holder_npk,
holder_ipk,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![Decode(secret_definition, definition_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
} }
} }
@ -534,6 +975,82 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded {
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandDeshielded::BurnTokenDeshieldedOwned {
definition_account_id,
holder_account_id,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_account_id: AccountId = holder_account_id.parse().unwrap();
let (res, secret_definition) = Token(wallet_core)
.send_burn_transaction_deshielded_owned_account(
definition_account_id,
holder_account_id,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![Decode(secret_definition, definition_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandDeshielded::MintTokenDeshielded {
definition_account_id,
holder_account_id,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_account_id: AccountId = holder_account_id.parse().unwrap();
let (res, secret_definition) = Token(wallet_core)
.send_mint_transaction_deshielded(
definition_account_id,
holder_account_id,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![Decode(secret_definition, definition_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
} }
} }
@ -627,6 +1144,128 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandShielded::BurnTokenShielded {
definition_account_id,
holder_account_id,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_account_id: AccountId = holder_account_id.parse().unwrap();
let (res, secret_holder) = Token(wallet_core)
.send_burn_transaction_shielded(
definition_account_id,
holder_account_id,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![Decode(secret_holder, holder_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandShielded::MintTokenShieldedOwned {
definition_account_id,
holder_account_id,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_account_id: AccountId = holder_account_id.parse().unwrap();
let (res, secret_holder) = Token(wallet_core)
.send_mint_transaction_shielded_owned_account(
definition_account_id,
holder_account_id,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![Decode(secret_holder, holder_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandShielded::MintTokenShieldedForeign {
definition_account_id,
holder_npk,
holder_ipk,
amount,
} => {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let holder_npk_res = hex::decode(holder_npk)?;
let mut holder_npk = [0; 32];
holder_npk.copy_from_slice(&holder_npk_res);
let holder_npk = nssa_core::NullifierPublicKey(holder_npk);
let holder_ipk_res = hex::decode(holder_ipk)?;
let mut holder_ipk = [0u8; 33];
holder_ipk.copy_from_slice(&holder_ipk_res);
let holder_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point(
holder_ipk.to_vec(),
);
let (res, _) = Token(wallet_core)
.send_mint_transaction_shielded_foreign_account(
definition_account_id,
holder_npk,
holder_ipk,
amount,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
println!("Transaction data is {:?}", tx.message);
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
} }
} }

View File

@ -18,7 +18,9 @@ use nssa::{
circuit::ProgramWithDependencies, message::EncryptedAccountData, circuit::ProgramWithDependencies, message::EncryptedAccountData,
}, },
}; };
use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData}; use nssa_core::{
Commitment, MembershipProof, SharedSecretKey, account::Data, program::InstructionData,
};
pub use privacy_preserving_tx::PrivacyPreservingAccount; pub use privacy_preserving_tx::PrivacyPreservingAccount;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
@ -45,45 +47,67 @@ pub enum AccDecodeData {
Decode(nssa_core::SharedSecretKey, AccountId), Decode(nssa_core::SharedSecretKey, AccountId),
} }
const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_DATA_SIZE: usize = 55;
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_TYPE: u8 = 1;
const TOKEN_HOLDING_DATA_SIZE: usize = 49; const TOKEN_HOLDING_DATA_SIZE: usize = 49;
const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0;
const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2;
struct TokenDefinition { struct TokenDefinition {
#[allow(unused)] #[allow(unused)]
account_type: u8, account_type: u8,
name: [u8; 6], name: [u8; 6],
total_supply: u128, total_supply: u128,
#[allow(unused)]
metadata_id: AccountId,
} }
pub struct TokenHolding { struct TokenHolding {
pub account_type: u8, #[allow(unused)]
pub definition_id: AccountId, account_type: u8,
pub balance: u128, definition_id: AccountId,
balance: u128,
} }
impl TokenDefinition { impl TokenDefinition {
fn parse(data: &[u8]) -> Option<Self> { fn parse(data: &Data) -> Option<Self> {
if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { let data = Vec::<u8>::from(data.clone());
if data.len() != TOKEN_DEFINITION_DATA_SIZE {
None None
} else { } else {
let account_type = data[0]; let account_type = data[0];
let name = data[1..7].try_into().unwrap(); let name = data[1..7].try_into().expect("Name must be a 6 bytes");
let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap()); let total_supply = u128::from_le_bytes(
data[7..23]
.try_into()
.expect("Total supply must be 16 bytes little-endian"),
);
let metadata_id = AccountId::new(
data[23..TOKEN_DEFINITION_DATA_SIZE]
.try_into()
.expect("Token Program expects valid Account Id for Metadata"),
);
Some(Self { let this = Some(Self {
account_type, account_type,
name, name,
total_supply, total_supply,
}) metadata_id,
});
match account_type {
TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 => None,
TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None,
_ => this,
}
} }
} }
} }
impl TokenHolding { impl TokenHolding {
pub fn parse(data: &[u8]) -> Option<Self> { fn parse(data: &[u8]) -> Option<Self> {
if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE {
None None
} else { } else {

View File

@ -1,9 +1,6 @@
use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse}; use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse};
use nssa::{AccountId, program::Program}; use nssa::{AccountId, program::Program};
use nssa_core::{ use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey};
NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
program::InstructionData,
};
use crate::{PrivacyPreservingAccount, WalletCore}; use crate::{PrivacyPreservingAccount, WalletCore};
@ -20,7 +17,7 @@ impl Token<'_> {
let account_ids = vec![definition_account_id, supply_account_id]; let account_ids = vec![definition_account_id, supply_account_id];
let program_id = nssa::program::Program::token().id(); let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction = [0; 23]; let mut instruction = vec![0u8; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(&name); instruction[17..].copy_from_slice(&name);
let message = nssa::public_transaction::Message::try_new( let message = nssa::public_transaction::Message::try_new(
@ -45,7 +42,9 @@ impl Token<'_> {
name: [u8; 6], name: [u8; 6],
total_supply: u128, total_supply: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_definition(name, total_supply); let instruction = token_program_preparation_definition(name, total_supply);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -54,7 +53,7 @@ impl Token<'_> {
PrivacyPreservingAccount::PrivateOwned(supply_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id),
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -73,7 +72,9 @@ impl Token<'_> {
name: [u8; 6], name: [u8; 6],
total_supply: u128, total_supply: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_definition(name, total_supply); let instruction = token_program_preparation_definition(name, total_supply);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -82,7 +83,7 @@ impl Token<'_> {
PrivacyPreservingAccount::Public(supply_account_id), PrivacyPreservingAccount::Public(supply_account_id),
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -101,7 +102,9 @@ impl Token<'_> {
name: [u8; 6], name: [u8; 6],
total_supply: u128, total_supply: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_definition(name, total_supply); let instruction = token_program_preparation_definition(name, total_supply);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -110,7 +113,7 @@ impl Token<'_> {
PrivacyPreservingAccount::PrivateOwned(supply_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id),
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -131,7 +134,7 @@ impl Token<'_> {
let program_id = nssa::program::Program::token().id(); let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00]. // 0x00 || 0x00 || 0x00].
let mut instruction = [0; 23]; let mut instruction = vec![0u8; 23];
instruction[0] = 0x01; instruction[0] = 0x01;
instruction[1..17].copy_from_slice(&amount.to_le_bytes()); instruction[1..17].copy_from_slice(&amount.to_le_bytes());
let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else { let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else {
@ -167,7 +170,9 @@ impl Token<'_> {
recipient_account_id: AccountId, recipient_account_id: AccountId,
amount: u128, amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount); let instruction = token_program_preparation_transfer(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -176,7 +181,7 @@ impl Token<'_> {
PrivacyPreservingAccount::PrivateOwned(recipient_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -194,7 +199,9 @@ impl Token<'_> {
recipient_ipk: IncomingViewingPublicKey, recipient_ipk: IncomingViewingPublicKey,
amount: u128, amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount); let instruction = token_program_preparation_transfer(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -206,7 +213,7 @@ impl Token<'_> {
}, },
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -223,7 +230,9 @@ impl Token<'_> {
recipient_account_id: AccountId, recipient_account_id: AccountId,
amount: u128, amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount); let instruction = token_program_preparation_transfer(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -232,7 +241,7 @@ impl Token<'_> {
PrivacyPreservingAccount::Public(recipient_account_id), PrivacyPreservingAccount::Public(recipient_account_id),
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -250,7 +259,9 @@ impl Token<'_> {
recipient_account_id: AccountId, recipient_account_id: AccountId,
amount: u128, amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount); let instruction = token_program_preparation_transfer(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -259,7 +270,7 @@ impl Token<'_> {
PrivacyPreservingAccount::PrivateOwned(recipient_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -278,7 +289,9 @@ impl Token<'_> {
recipient_ipk: IncomingViewingPublicKey, recipient_ipk: IncomingViewingPublicKey,
amount: u128, amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount); let instruction = token_program_preparation_transfer(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0 self.0
.send_privacy_preserving_tx( .send_privacy_preserving_tx(
@ -290,7 +303,7 @@ impl Token<'_> {
}, },
], ],
&instruction_data, &instruction_data,
&program.into(), &Program::token().into(),
) )
.await .await
.map(|(resp, secrets)| { .map(|(resp, secrets)| {
@ -301,30 +314,354 @@ impl Token<'_> {
(resp, first) (resp, first)
}) })
} }
pub async fn send_burn_transaction(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, holder_account_id];
let instruction = token_program_preparation_burn(amount);
let Ok(nonces) = self.0.get_accounts_nonces(vec![holder_account_id]).await else {
return Err(ExecutionFailureKind::SequencerError);
};
let message = nssa::public_transaction::Message::try_new(
Program::token().id(),
account_ids,
nonces,
instruction,
)
.expect("Instruction should serialize");
let signing_key = self
.0
.storage
.user_data
.get_pub_account_signing_key(&holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
} }
fn token_program_preparation_transfer(amount: u128) -> (InstructionData, Program) { pub async fn send_burn_transaction_private_owned_account(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let instruction = token_program_preparation_burn(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let mut iter = secrets.into_iter();
let first = iter.next().expect("expected definition's secret");
let second = iter.next().expect("expected holder's secret");
(resp, [first, second])
})
}
pub async fn send_burn_transaction_deshielded_owned_account(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let instruction = token_program_preparation_burn(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::Public(holder_account_id),
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected definition's secret");
(resp, first)
})
}
pub async fn send_burn_transaction_shielded(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let instruction = token_program_preparation_burn(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected holder's secret");
(resp, first)
})
}
pub async fn send_mint_transaction(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, holder_account_id];
let instruction = token_program_preparation_mint(amount);
let Ok(nonces) = self
.0
.get_accounts_nonces(vec![definition_account_id])
.await
else {
return Err(ExecutionFailureKind::SequencerError);
};
let message = nssa::public_transaction::Message::try_new(
Program::token().id(),
account_ids,
nonces,
instruction,
)
.unwrap();
let Some(signing_key) = self
.0
.storage
.user_data
.get_pub_account_signing_key(&definition_account_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_mint_transaction_private_owned_account(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let instruction = token_program_preparation_mint(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let mut iter = secrets.into_iter();
let first = iter.next().expect("expected definition's secret");
let second = iter.next().expect("expected holder's secret");
(resp, [first, second])
})
}
pub async fn send_mint_transaction_private_foreign_account(
&self,
definition_account_id: AccountId,
holder_npk: NullifierPublicKey,
holder_ipk: IncomingViewingPublicKey,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let instruction = token_program_preparation_mint(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::PrivateForeign {
npk: holder_npk,
ipk: holder_ipk,
},
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let mut iter = secrets.into_iter();
let first = iter.next().expect("expected definition's secret");
let second = iter.next().expect("expected holder's secret");
(resp, [first, second])
})
}
pub async fn send_mint_transaction_deshielded(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let instruction = token_program_preparation_mint(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::Public(holder_account_id),
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected definition's secret");
(resp, first)
})
}
pub async fn send_mint_transaction_shielded_owned_account(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let instruction = token_program_preparation_mint(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected holder's secret");
(resp, first)
})
}
pub async fn send_mint_transaction_shielded_foreign_account(
&self,
definition_account_id: AccountId,
holder_npk: NullifierPublicKey,
holder_ipk: IncomingViewingPublicKey,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let instruction = token_program_preparation_mint(amount);
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(definition_account_id),
PrivacyPreservingAccount::PrivateForeign {
npk: holder_npk,
ipk: holder_ipk,
},
],
&instruction_data,
&Program::token().into(),
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected holder's secret");
(resp, first)
})
}
}
fn token_program_preparation_transfer(amount: u128) -> Vec<u8> {
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00]. // 0x00 || 0x00 || 0x00].
let mut instruction = [0; 23]; let mut instruction = vec![0u8; 23];
instruction[0] = 0x01; instruction[0] = 0x01;
instruction[1..17].copy_from_slice(&amount.to_le_bytes()); instruction[1..17].copy_from_slice(&amount.to_le_bytes());
let instruction_data = Program::serialize_instruction(instruction).unwrap();
let program = Program::token();
(instruction_data, program) instruction
} }
fn token_program_preparation_definition( fn token_program_preparation_definition(name: [u8; 6], total_supply: u128) -> Vec<u8> {
name: [u8; 6],
total_supply: u128,
) -> (InstructionData, Program) {
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction = [0; 23]; let mut instruction = vec![0u8; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(&name); instruction[17..].copy_from_slice(&name);
let instruction_data = Program::serialize_instruction(instruction).unwrap();
let program = Program::token();
(instruction_data, program) instruction
}
fn token_program_preparation_burn(amount: u128) -> Vec<u8> {
// Instruction must be: [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00].
let mut instruction = vec![0; 23];
instruction[0] = 0x03;
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
instruction
}
fn token_program_preparation_mint(amount: u128) -> Vec<u8> {
// Instruction must be: [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00].
let mut instruction = vec![0; 23];
instruction[0] = 0x04;
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
instruction
} }