mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-06-03 23:59:32 +00:00
feat(wallet): add bridge withdraw command
This commit is contained in:
parent
619b087d57
commit
b9d9c802e9
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -4102,6 +4102,7 @@ dependencies = [
|
|||||||
"bytesize",
|
"bytesize",
|
||||||
"common",
|
"common",
|
||||||
"faucet_core",
|
"faucet_core",
|
||||||
|
"futures",
|
||||||
"hex",
|
"hex",
|
||||||
"indexer_ffi",
|
"indexer_ffi",
|
||||||
"indexer_service_protocol",
|
"indexer_service_protocol",
|
||||||
@ -4110,6 +4111,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"logos-blockchain-core",
|
"logos-blockchain-core",
|
||||||
"logos-blockchain-http-api-common",
|
"logos-blockchain-http-api-common",
|
||||||
|
"logos-blockchain-zone-sdk",
|
||||||
"nssa",
|
"nssa",
|
||||||
"nssa_core",
|
"nssa_core",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -10682,6 +10684,7 @@ dependencies = [
|
|||||||
"base58",
|
"base58",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bip39",
|
"bip39",
|
||||||
|
"bridge_core",
|
||||||
"clap",
|
"clap",
|
||||||
"common",
|
"common",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
|||||||
@ -46,7 +46,7 @@ _wallet() {
|
|||||||
cword=$COMP_CWORD
|
cword=$COMP_CWORD
|
||||||
}
|
}
|
||||||
|
|
||||||
local commands="auth-transfer chain-info account pinata token amm ata check-health config restore-keys deploy-program help"
|
local commands="auth-transfer chain-info account pinata token amm ata bridge check-health config restore-keys deploy-program help"
|
||||||
|
|
||||||
# Find the main command and subcommand by scanning words before the cursor.
|
# Find the main command and subcommand by scanning words before the cursor.
|
||||||
# Global options that take a value are skipped along with their argument.
|
# Global options that take a value are skipped along with their argument.
|
||||||
@ -535,6 +535,26 @@ _wallet() {
|
|||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
bridge)
|
||||||
|
case "$subcmd" in
|
||||||
|
"")
|
||||||
|
COMPREPLY=($(compgen -W "withdraw help" -- "$cur"))
|
||||||
|
;;
|
||||||
|
withdraw)
|
||||||
|
case "$prev" in
|
||||||
|
--from)
|
||||||
|
_wallet_complete_account_id "$cur"
|
||||||
|
;;
|
||||||
|
--amount | --bedrock-account-pk)
|
||||||
|
;; # no specific completion
|
||||||
|
*)
|
||||||
|
COMPREPLY=($(compgen -W "--from --amount --bedrock-account-pk" -- "$cur"))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
config)
|
config)
|
||||||
case "$subcmd" in
|
case "$subcmd" in
|
||||||
"")
|
"")
|
||||||
|
|||||||
@ -25,6 +25,7 @@ _wallet() {
|
|||||||
'token:Token program interaction subcommand'
|
'token:Token program interaction subcommand'
|
||||||
'amm:AMM program interaction subcommand'
|
'amm:AMM program interaction subcommand'
|
||||||
'ata:Associated Token Account program interaction subcommand'
|
'ata:Associated Token Account program interaction subcommand'
|
||||||
|
'bridge:Bridge program interaction subcommand'
|
||||||
'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions'
|
'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions'
|
||||||
'config:Command to setup config, get and set config fields'
|
'config:Command to setup config, get and set config fields'
|
||||||
'restore-keys:Restoring keys from given password at given depth'
|
'restore-keys:Restoring keys from given password at given depth'
|
||||||
@ -56,6 +57,9 @@ _wallet() {
|
|||||||
ata)
|
ata)
|
||||||
_wallet_ata
|
_wallet_ata
|
||||||
;;
|
;;
|
||||||
|
bridge)
|
||||||
|
_wallet_bridge
|
||||||
|
;;
|
||||||
config)
|
config)
|
||||||
_wallet_config
|
_wallet_config
|
||||||
;;
|
;;
|
||||||
@ -442,6 +446,35 @@ _wallet_ata() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# bridge subcommand
|
||||||
|
_wallet_bridge() {
|
||||||
|
local -a subcommands
|
||||||
|
|
||||||
|
_arguments -C \
|
||||||
|
'1: :->subcommand' \
|
||||||
|
'*:: :->args'
|
||||||
|
|
||||||
|
case $state in
|
||||||
|
subcommand)
|
||||||
|
subcommands=(
|
||||||
|
'withdraw:Withdraw native tokens through the bridge'
|
||||||
|
'help:Print this message or the help of the given subcommand(s)'
|
||||||
|
)
|
||||||
|
_describe -t subcommands 'bridge subcommands' subcommands
|
||||||
|
;;
|
||||||
|
args)
|
||||||
|
case $line[1] in
|
||||||
|
withdraw)
|
||||||
|
_arguments \
|
||||||
|
'--from[Sender account with privacy prefix]:from:_wallet_account_ids' \
|
||||||
|
'--amount[Amount of native tokens to withdraw]:amount:' \
|
||||||
|
'--bedrock-account-pk[Bedrock account public key (32-byte hex)]:bedrock_pk:'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# config subcommand
|
# config subcommand
|
||||||
_wallet_config() {
|
_wallet_config() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
@ -515,6 +548,7 @@ _wallet_help() {
|
|||||||
'token:Token program interaction subcommand'
|
'token:Token program interaction subcommand'
|
||||||
'amm:AMM program interaction subcommand'
|
'amm:AMM program interaction subcommand'
|
||||||
'ata:Associated Token Account program interaction subcommand'
|
'ata:Associated Token Account program interaction subcommand'
|
||||||
|
'bridge:Bridge program interaction subcommand'
|
||||||
'check-health:Check the wallet can connect to the node'
|
'check-health:Check the wallet can connect to the node'
|
||||||
'config:Command to setup config, get and set config fields'
|
'config:Command to setup config, get and set config fields'
|
||||||
'restore-keys:Restoring keys from given password at given depth'
|
'restore-keys:Restoring keys from given password at given depth'
|
||||||
|
|||||||
@ -32,6 +32,7 @@ indexer_service_protocol.workspace = true
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||||
|
futures.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
bytesize.workspace = true
|
bytesize.workspace = true
|
||||||
@ -39,3 +40,4 @@ reqwest.workspace = true
|
|||||||
borsh.workspace = true
|
borsh.workspace = true
|
||||||
logos-blockchain-http-api-common.workspace = true
|
logos-blockchain-http-api-common.workspace = true
|
||||||
logos-blockchain-core.workspace = true
|
logos-blockchain-core.workspace = true
|
||||||
|
logos-blockchain-zone-sdk.workspace = true
|
||||||
|
|||||||
@ -9,7 +9,8 @@ use std::time::Duration;
|
|||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use borsh::BorshSerialize;
|
use borsh::BorshSerialize;
|
||||||
use common::transaction::NSSATransaction;
|
use common::transaction::NSSATransaction;
|
||||||
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext};
|
use futures::StreamExt as _;
|
||||||
|
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention};
|
||||||
use log::info;
|
use log::info;
|
||||||
use logos_blockchain_core::mantle::{ledger::Inputs, ops::channel::deposit::DepositOp};
|
use logos_blockchain_core::mantle::{ledger::Inputs, ops::channel::deposit::DepositOp};
|
||||||
use logos_blockchain_http_api_common::bodies::{
|
use logos_blockchain_http_api_common::bodies::{
|
||||||
@ -19,6 +20,9 @@ use logos_blockchain_http_api_common::bodies::{
|
|||||||
transfer_funds::{WalletTransferFundsRequestBody, WalletTransferFundsResponseBody},
|
transfer_funds::{WalletTransferFundsRequestBody, WalletTransferFundsResponseBody},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use logos_blockchain_zone_sdk::{
|
||||||
|
CommonHttpClient, ZoneMessage, adapter::NodeHttpClient, indexer::ZoneIndexer,
|
||||||
|
};
|
||||||
use nssa::{
|
use nssa::{
|
||||||
AccountId, execute_and_prove, privacy_preserving_transaction, program::Program,
|
AccountId, execute_and_prove, privacy_preserving_transaction, program::Program,
|
||||||
public_transaction,
|
public_transaction,
|
||||||
@ -26,6 +30,7 @@ use nssa::{
|
|||||||
use nssa_core::{InputAccountIdentity, account::AccountWithMetadata};
|
use nssa_core::{InputAccountIdentity, account::AccountWithMetadata};
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
use sequencer_service_rpc::RpcClient as _;
|
||||||
use tokio::test;
|
use tokio::test;
|
||||||
|
use wallet::cli::{Command, execute_subcommand, programs::bridge::BridgeSubcommand};
|
||||||
|
|
||||||
const TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK: Duration = Duration::from_mins(2);
|
const TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK: Duration = Duration::from_mins(2);
|
||||||
|
|
||||||
@ -194,6 +199,7 @@ async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
async fn submit_bedrock_deposit(
|
async fn submit_bedrock_deposit(
|
||||||
bedrock_addr: std::net::SocketAddr,
|
bedrock_addr: std::net::SocketAddr,
|
||||||
|
bedrock_account_pk: &str,
|
||||||
recipient_id: AccountId,
|
recipient_id: AccountId,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
@ -206,15 +212,13 @@ async fn submit_bedrock_deposit(
|
|||||||
let metadata = borsh::to_vec(&DepositMetadata { recipient_id })
|
let metadata = borsh::to_vec(&DepositMetadata { recipient_id })
|
||||||
.context("Failed to encode deposit metadata")?;
|
.context("Failed to encode deposit metadata")?;
|
||||||
|
|
||||||
let funding_key = "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26";
|
|
||||||
|
|
||||||
let channel_id = integration_tests::config::bedrock_channel_id();
|
let channel_id = integration_tests::config::bedrock_channel_id();
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
let query_balance = || async {
|
let query_balance = || async {
|
||||||
let balance_response = client
|
let balance_response = client
|
||||||
.get(format!(
|
.get(format!(
|
||||||
"http://{bedrock_addr}/wallet/{funding_key}/balance"
|
"http://{bedrock_addr}/wallet/{bedrock_account_pk}/balance"
|
||||||
))
|
))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
@ -231,13 +235,13 @@ async fn submit_bedrock_deposit(
|
|||||||
let mut balance = query_balance().await?;
|
let mut balance = query_balance().await?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Queried Bedrock balance for key {funding_key}: {:?}",
|
"Queried Bedrock balance for key {bedrock_account_pk}: {:?}",
|
||||||
balance.balance
|
balance.balance
|
||||||
);
|
);
|
||||||
|
|
||||||
if balance.balance < amount {
|
if balance.balance < amount {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Bedrock wallet with key {funding_key} has insufficient balance {:?} for deposit amount {:?}",
|
"Bedrock wallet with key {bedrock_account_pk} has insufficient balance {:?} for deposit amount {:?}",
|
||||||
balance.balance,
|
balance.balance,
|
||||||
amount
|
amount
|
||||||
);
|
);
|
||||||
@ -363,11 +367,18 @@ async fn wait_for_vault_balance(
|
|||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test deposit and withdraw round trip.
|
||||||
|
///
|
||||||
|
/// Implemented as one test instead of two separate tests for deposit and withdraw, because the
|
||||||
|
/// withdraw test depends on the deposit to set up the necessary state (funds in vault) for testing
|
||||||
|
/// withdraw functionality.
|
||||||
#[test]
|
#[test]
|
||||||
async fn bedrock_deposit_mints_to_vault_then_claim_succeeds() -> anyhow::Result<()> {
|
async fn bedrock_deposit_claim_and_withdraw_round_trip_succeeds() -> anyhow::Result<()> {
|
||||||
let ctx = TestContext::new().await?;
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let bedrock_account_pk = "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26";
|
||||||
let recipient_id = ctx.existing_public_accounts()[0];
|
let recipient_id = ctx.existing_public_accounts()[0];
|
||||||
|
let amount = 1_u64;
|
||||||
let vault_program_id = Program::vault().id();
|
let vault_program_id = Program::vault().id();
|
||||||
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
|
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
|
||||||
|
|
||||||
@ -381,10 +392,17 @@ async fn bedrock_deposit_mints_to_vault_then_claim_succeeds() -> anyhow::Result<
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Submit deposit to Bedrock
|
// Submit deposit to Bedrock
|
||||||
submit_bedrock_deposit(ctx.bedrock_addr(), recipient_id, 1).await?;
|
submit_bedrock_deposit(ctx.bedrock_addr(), bedrock_account_pk, recipient_id, amount)
|
||||||
|
.await
|
||||||
|
.context("Failed to submit Bedrock deposit for round-trip setup")?;
|
||||||
|
|
||||||
// Wait for vault to receive the deposit (minted from bridge to vault)
|
// Wait for vault to receive the deposit (minted from bridge to vault)
|
||||||
wait_for_vault_balance(&ctx, recipient_vault_id, vault_balance_before + 1).await?;
|
wait_for_vault_balance(
|
||||||
|
&ctx,
|
||||||
|
recipient_vault_id,
|
||||||
|
vault_balance_before + u128::from(amount),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Now claim funds from vault back to recipient
|
// Now claim funds from vault back to recipient
|
||||||
let nonces = ctx
|
let nonces = ctx
|
||||||
@ -404,7 +422,9 @@ async fn bedrock_deposit_mints_to_vault_then_claim_succeeds() -> anyhow::Result<
|
|||||||
vault_program_id,
|
vault_program_id,
|
||||||
vec![recipient_id, recipient_vault_id],
|
vec![recipient_id, recipient_vault_id],
|
||||||
nonces,
|
nonces,
|
||||||
vault_core::Instruction::Claim { amount: 1 },
|
vault_core::Instruction::Claim {
|
||||||
|
amount: u128::from(amount),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.context("Failed to build vault claim message")?;
|
.context("Failed to build vault claim message")?;
|
||||||
|
|
||||||
@ -439,9 +459,88 @@ async fn bedrock_deposit_mints_to_vault_then_claim_succeeds() -> anyhow::Result<
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recipient_balance_after_claim,
|
recipient_balance_after_claim,
|
||||||
recipient_balance_before + 1,
|
recipient_balance_before + u128::from(amount),
|
||||||
"Recipient balance should increase by claimed amount"
|
"Recipient balance should increase by claimed amount"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Withdraw back to Bedrock and wait for finalized withdraw event.
|
||||||
|
let sender_id = recipient_id;
|
||||||
|
|
||||||
|
let observer = create_zone_indexer_observer(ctx.bedrock_addr())?;
|
||||||
|
let observe_fut = wait_for_finalized_withdraw_op(&observer, amount);
|
||||||
|
|
||||||
|
let withdraw_fut = execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Bridge(BridgeSubcommand::Withdraw {
|
||||||
|
from: public_mention(sender_id),
|
||||||
|
amount,
|
||||||
|
bedrock_account_pk: bedrock_account_pk.to_owned(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (observe_result, withdraw_result) = tokio::join!(observe_fut, withdraw_fut);
|
||||||
|
|
||||||
|
withdraw_result.context("Failed to execute wallet bridge withdraw command")?;
|
||||||
|
|
||||||
|
observe_result
|
||||||
|
.context("Failed while waiting for finalized withdraw event from zone indexer")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_zone_indexer_observer(
|
||||||
|
bedrock_addr: std::net::SocketAddr,
|
||||||
|
) -> anyhow::Result<ZoneIndexer<NodeHttpClient>> {
|
||||||
|
let bedrock_url = integration_tests::config::addr_to_url(
|
||||||
|
integration_tests::config::UrlProtocol::Http,
|
||||||
|
bedrock_addr,
|
||||||
|
)
|
||||||
|
.context("Failed to convert Bedrock addr to URL for zone indexer observer")?;
|
||||||
|
|
||||||
|
let node = NodeHttpClient::new(CommonHttpClient::new(None), bedrock_url);
|
||||||
|
|
||||||
|
Ok(ZoneIndexer::new(
|
||||||
|
integration_tests::config::bedrock_channel_id(),
|
||||||
|
node,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_finalized_withdraw_op(
|
||||||
|
observer: &ZoneIndexer<NodeHttpClient>,
|
||||||
|
expected_amount: u64,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let timeout = TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK
|
||||||
|
+ Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS);
|
||||||
|
|
||||||
|
tokio::time::timeout(timeout, async {
|
||||||
|
loop {
|
||||||
|
let stream = observer
|
||||||
|
.follow()
|
||||||
|
.await
|
||||||
|
.context("Failed to read zone indexer message batch")?;
|
||||||
|
let mut stream = std::pin::pin!(stream);
|
||||||
|
|
||||||
|
while let Some(message) = stream.next().await {
|
||||||
|
info!("Observed zone message {message:?}");
|
||||||
|
|
||||||
|
let ZoneMessage::Withdraw(withdraw) = message else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let amount = withdraw.outputs.amount().context(
|
||||||
|
"Failed to compute finalized withdraw amount from zone indexer message",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if amount == expected_amount {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Timed out waiting for finalized withdraw message with amount {expected_amount}")
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ use std::{pin::Pin, sync::Arc, time::Duration};
|
|||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use common::block::Block;
|
use common::block::Block;
|
||||||
use log::warn;
|
use log::{info, warn};
|
||||||
use logos_blockchain_core::mantle::{Note, ledger::Outputs};
|
use logos_blockchain_core::mantle::{Note, ledger::Outputs, ops::channel::inscribe::Inscription};
|
||||||
pub use logos_blockchain_key_management_system_service::keys::{Ed25519Key, ZkKey};
|
pub use logos_blockchain_key_management_system_service::keys::{Ed25519Key, ZkKey};
|
||||||
pub use logos_blockchain_zone_sdk::sequencer::SequencerCheckpoint;
|
pub use logos_blockchain_zone_sdk::sequencer::SequencerCheckpoint;
|
||||||
use logos_blockchain_zone_sdk::{
|
use logos_blockchain_zone_sdk::{
|
||||||
@ -135,19 +135,23 @@ impl BlockPublisherTrait for ZoneSdkPublisher {
|
|||||||
bridge_withdrawals: Vec<BridgeWithdrawData>,
|
bridge_withdrawals: Vec<BridgeWithdrawData>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let data = borsh::to_vec(block).context("Failed to serialize block")?;
|
let data = borsh::to_vec(block).context("Failed to serialize block")?;
|
||||||
let data_bounded = data
|
let data_bounded: Inscription = data
|
||||||
.try_into()
|
.try_into()
|
||||||
.context("Block data exceeds maximum allowed size")?;
|
.context("Block data exceeds maximum allowed size")?;
|
||||||
|
let data_byte_size = data_bounded.len();
|
||||||
|
|
||||||
if bridge_withdrawals.is_empty() {
|
if bridge_withdrawals.is_empty() {
|
||||||
self.handle
|
self.handle
|
||||||
.publish_message(data_bounded)
|
.publish_message(data_bounded)
|
||||||
.await
|
.await
|
||||||
.context("Failed to publish block")?;
|
.context("Failed to publish block")?;
|
||||||
|
|
||||||
|
info!("Published block with the size of {data_byte_size} bytes");
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let withdraws = bridge_withdrawals
|
let withdraws: Vec<_> = bridge_withdrawals
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|withdrawal| {
|
.map(|withdrawal| {
|
||||||
let recipient_pk =
|
let recipient_pk =
|
||||||
@ -161,10 +165,16 @@ impl BlockPublisherTrait for ZoneSdkPublisher {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let withdraw_count = withdraws.len();
|
||||||
self.handle
|
self.handle
|
||||||
.publish_atomic_withdraw(data_bounded, withdraws)
|
.publish_atomic_withdraw(data_bounded, withdraws)
|
||||||
.await
|
.await
|
||||||
.context("Failed to publish block with withdrawals")?;
|
.context("Failed to publish block with withdrawals")?;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Published block with the size of {data_byte_size} bytes and {withdraw_count} bridge withdrawals",
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,16 +14,17 @@ common.workspace = true
|
|||||||
authenticated_transfer_core.workspace = true
|
authenticated_transfer_core.workspace = true
|
||||||
key_protocol.workspace = true
|
key_protocol.workspace = true
|
||||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||||
token_core.workspace = true
|
|
||||||
amm_core.workspace = true
|
amm_core.workspace = true
|
||||||
testnet_initial_state.workspace = true
|
testnet_initial_state.workspace = true
|
||||||
|
token_core.workspace = true
|
||||||
ata_core.workspace = true
|
ata_core.workspace = true
|
||||||
|
bridge_core.workspace = true
|
||||||
|
keycard_wallet.workspace = true
|
||||||
|
|
||||||
bip39.workspace = true
|
bip39.workspace = true
|
||||||
pyo3.workspace = true
|
pyo3.workspace = true
|
||||||
rpassword = "7"
|
rpassword = "7"
|
||||||
zeroize = "1"
|
zeroize = "1"
|
||||||
keycard_wallet.workspace = true
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|||||||
@ -20,7 +20,7 @@ use crate::{
|
|||||||
group::GroupSubcommand,
|
group::GroupSubcommand,
|
||||||
keycard::KeycardSubcommand,
|
keycard::KeycardSubcommand,
|
||||||
programs::{
|
programs::{
|
||||||
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand,
|
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand, bridge::BridgeSubcommand,
|
||||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||||
token::TokenProgramAgnosticSubcommand,
|
token::TokenProgramAgnosticSubcommand,
|
||||||
},
|
},
|
||||||
@ -65,6 +65,9 @@ pub enum Command {
|
|||||||
/// Associated Token Account program interaction subcommand.
|
/// Associated Token Account program interaction subcommand.
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Ata(AtaSubcommand),
|
Ata(AtaSubcommand),
|
||||||
|
/// Bridge program interaction subcommand.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Bridge(BridgeSubcommand),
|
||||||
/// Group key management (create, invite, join, derive keys).
|
/// Group key management (create, invite, join, derive keys).
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Group(GroupSubcommand),
|
Group(GroupSubcommand),
|
||||||
@ -233,6 +236,9 @@ pub async fn execute_subcommand(
|
|||||||
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
|
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
|
||||||
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?,
|
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?,
|
||||||
Command::Ata(ata_subcommand) => ata_subcommand.handle_subcommand(wallet_core).await?,
|
Command::Ata(ata_subcommand) => ata_subcommand.handle_subcommand(wallet_core).await?,
|
||||||
|
Command::Bridge(bridge_subcommand) => {
|
||||||
|
bridge_subcommand.handle_subcommand(wallet_core).await?
|
||||||
|
}
|
||||||
Command::Group(group_subcommand) => group_subcommand.handle_subcommand(wallet_core).await?,
|
Command::Group(group_subcommand) => group_subcommand.handle_subcommand(wallet_core).await?,
|
||||||
Command::Keycard(keycard_subcommand) => {
|
Command::Keycard(keycard_subcommand) => {
|
||||||
keycard_subcommand.handle_subcommand(wallet_core).await?
|
keycard_subcommand.handle_subcommand(wallet_core).await?
|
||||||
|
|||||||
64
wallet/src/cli/programs/bridge.rs
Normal file
64
wallet/src/cli/programs/bridge.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
WalletCore,
|
||||||
|
account::AccountIdWithPrivacy,
|
||||||
|
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
|
||||||
|
program_facades::bridge::Bridge,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents generic CLI subcommand for a wallet working with bridge program.
|
||||||
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
|
pub enum BridgeSubcommand {
|
||||||
|
/// Withdraw native tokens from a public account to Bedrock through the bridge.
|
||||||
|
Withdraw {
|
||||||
|
/// Sender account mention - account id with privacy prefix or a label.
|
||||||
|
#[arg(long)]
|
||||||
|
from: CliAccountMention,
|
||||||
|
/// Amount of native tokens to withdraw.
|
||||||
|
#[arg(long)]
|
||||||
|
amount: u64,
|
||||||
|
/// Bedrock account public key encoded as a 32-byte hex string.
|
||||||
|
#[arg(long)]
|
||||||
|
bedrock_account_pk: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletSubcommand for BridgeSubcommand {
|
||||||
|
async fn handle_subcommand(
|
||||||
|
self,
|
||||||
|
wallet_core: &mut WalletCore,
|
||||||
|
) -> Result<SubcommandReturnValue> {
|
||||||
|
match self {
|
||||||
|
Self::Withdraw {
|
||||||
|
from,
|
||||||
|
amount,
|
||||||
|
bedrock_account_pk,
|
||||||
|
} => {
|
||||||
|
let from = from.resolve(wallet_core.storage())?;
|
||||||
|
let AccountIdWithPrivacy::Public(sender_account_id) = from else {
|
||||||
|
anyhow::bail!("Bridge withdraw supports only public sender accounts");
|
||||||
|
};
|
||||||
|
|
||||||
|
let bedrock_account_pk = parse_bedrock_account_pk(&bedrock_account_pk)?;
|
||||||
|
|
||||||
|
let tx_hash = Bridge(wallet_core)
|
||||||
|
.send_withdraw(sender_account_id, amount, bedrock_account_pk)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
|
||||||
|
Ok(SubcommandReturnValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_bedrock_account_pk(raw: &str) -> Result<[u8; 32]> {
|
||||||
|
let raw = raw.strip_prefix("0x").unwrap_or(raw);
|
||||||
|
let mut bedrock_account_pk = [0_u8; 32];
|
||||||
|
hex::decode_to_slice(raw, &mut bedrock_account_pk)
|
||||||
|
.context("Invalid `bedrock-account-pk`: expected hex string of 32 bytes")?;
|
||||||
|
Ok(bedrock_account_pk)
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
pub mod amm;
|
pub mod amm;
|
||||||
pub mod ata;
|
pub mod ata;
|
||||||
|
pub mod bridge;
|
||||||
pub mod native_token_transfer;
|
pub mod native_token_transfer;
|
||||||
pub mod pinata;
|
pub mod pinata;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
|||||||
35
wallet/src/program_facades/bridge.rs
Normal file
35
wallet/src/program_facades/bridge.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use common::HashType;
|
||||||
|
use nssa::{AccountId, program::Program};
|
||||||
|
|
||||||
|
use crate::{AccountIdentity, ExecutionFailureKind, WalletCore};
|
||||||
|
|
||||||
|
pub struct Bridge<'wallet>(pub &'wallet WalletCore);
|
||||||
|
|
||||||
|
impl Bridge<'_> {
|
||||||
|
pub async fn send_withdraw(
|
||||||
|
&self,
|
||||||
|
sender_account_id: AccountId,
|
||||||
|
amount: u64,
|
||||||
|
bedrock_account_pk: [u8; 32],
|
||||||
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
|
let program = Program::bridge();
|
||||||
|
let bridge_account_id = nssa::system_bridge_account_id();
|
||||||
|
let instruction = bridge_core::Instruction::Withdraw {
|
||||||
|
amount,
|
||||||
|
bedrock_account_pk,
|
||||||
|
};
|
||||||
|
let instruction_data =
|
||||||
|
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send_pub_tx(
|
||||||
|
vec![
|
||||||
|
AccountIdentity::Public(sender_account_id),
|
||||||
|
AccountIdentity::PublicNoSign(bridge_account_id),
|
||||||
|
],
|
||||||
|
instruction_data,
|
||||||
|
&program.into(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
pub mod amm;
|
pub mod amm;
|
||||||
pub mod ata;
|
pub mod ata;
|
||||||
|
pub mod bridge;
|
||||||
pub mod native_token_transfer;
|
pub mod native_token_transfer;
|
||||||
pub mod pinata;
|
pub mod pinata;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user