use crate::{PublicKeyId, Signer, SigningError};
use sha2::{Digest, Sha256};
use thruput_domain_separator::transaction_signature_preimage;
use thruput_xdr::auth_claim::{AuthClaim, AuthClaim_source_address_claim};
use thruput_xdr::contract::{ContractDeploymentHashPreimage, ContractEntrypointID};
use thruput_xdr::transaction::{
DecoratedSignature, Invocation, SignedTransaction, Transaction, Uniquifier,
};
use thruput_xdr::types::{BlockHeight, ContractAddress, NetworkHash, PublicKey, SHA256Hash};
use xdr_common::{BytesM, VecM};
pub const WALLET_DEPLOY_ENTRYPOINT: [u8; 8] = 0xADAC_4982_2214_8E4F_u64.to_be_bytes();
pub const DEFAULT_TX_GAS_LIMIT: u64 = 10_000_000;
#[derive(Clone, Copy, Debug, Default)]
pub struct SignTransactionParams {
pub public_key_id: PublicKeyId,
}
#[derive(Clone, Debug)]
pub struct BuildCallTransaction {
pub invoked_address: ContractAddress,
pub entrypoint: ContractEntrypointID,
pub calldata: Vec<u8>,
pub gas_limit: u64,
pub block_expiry_height: i64,
pub nonce: u64,
pub auth_claim: AuthClaim,
}
#[derive(Clone, Copy, Debug)]
pub struct BuildDevWalletDoActionTransaction<'a> {
pub wallet_address: ContractAddress,
pub wallet_entrypoint: ContractEntrypointID,
pub target_address: ContractAddress,
pub target_entrypoint: ContractEntrypointID,
pub target_calldata: &'a [u8],
pub gas_limit: u64,
pub target_gas_limit: u64,
pub block_expiry_height: i64,
pub nonce: u64,
}
#[derive(Clone, Debug)]
pub struct BuildDeployTransaction {
pub invoked_address: ContractAddress,
pub entrypoint: ContractEntrypointID,
pub calldata: Vec<u8>,
pub gas_limit: u64,
pub block_expiry_height: i64,
pub nonce: u64,
pub contracts_to_deploy: VecM<thruput_xdr::contract::Contract>,
pub auth_claim: AuthClaim,
}
pub fn source_address_auth_claim() -> AuthClaim {
AuthClaim::kSourceAddressAuthData {
source_address_claim: AuthClaim_source_address_claim {
allowed_keys: 0x01,
required_weight: 1,
},
}
}
pub fn build_call_transaction(args: BuildCallTransaction) -> Transaction {
let invocation = Invocation {
invoked_address: args.invoked_address,
entrypoint: args.entrypoint,
gas_limit: args.gas_limit,
calldata: BytesM(args.calldata),
};
let uniquifier = Uniquifier {
block_expiry_height: BlockHeight::from(args.block_expiry_height),
nonce: args.nonce,
};
Transaction {
invocation,
uniquifier,
contracts_to_deploy: VecM(vec![]),
auth_claim: args.auth_claim,
}
}
pub fn dev_wallet_do_action_calldata(
target_address: ContractAddress,
target_entrypoint: ContractEntrypointID,
target_gas_limit: u64,
target_calldata: &[u8],
) -> Result<Vec<u8>, SigningError> {
let inner_invocation = xdrs::txs::encode(&(
target_address,
target_entrypoint,
target_gas_limit,
target_calldata,
))
.map_err(|error| SigningError::XdrEncode(error.to_string()))?;
xdrs::cxs::encode(&inner_invocation.as_slice())
.map_err(|error| SigningError::XdrEncode(error.to_string()))
}
pub fn build_dev_wallet_do_action_transaction(
args: BuildDevWalletDoActionTransaction<'_>,
) -> Result<Transaction, SigningError> {
let calldata = dev_wallet_do_action_calldata(
args.target_address,
args.target_entrypoint,
args.target_gas_limit,
args.target_calldata,
)?;
Ok(build_call_transaction(BuildCallTransaction {
invoked_address: args.wallet_address,
entrypoint: args.wallet_entrypoint,
calldata,
gas_limit: args.gas_limit,
block_expiry_height: args.block_expiry_height,
nonce: args.nonce,
auth_claim: source_address_auth_claim(),
}))
}
pub fn build_deploy_transaction(args: BuildDeployTransaction) -> Transaction {
let invocation = Invocation {
invoked_address: args.invoked_address,
entrypoint: args.entrypoint,
gas_limit: args.gas_limit,
calldata: BytesM(args.calldata),
};
let uniquifier = Uniquifier {
block_expiry_height: BlockHeight::from(args.block_expiry_height),
nonce: args.nonce,
};
Transaction {
invocation,
uniquifier,
contracts_to_deploy: args.contracts_to_deploy,
auth_claim: args.auth_claim,
}
}
pub fn derive_deploy_address(
deployer_address: &ContractAddress,
deploy_nonce: &SHA256Hash,
contract_hash: &SHA256Hash,
) -> Result<ContractAddress, SigningError> {
let preimage = ContractDeploymentHashPreimage {
deployer_address: *deployer_address,
nonce: *deploy_nonce,
contract_hash: *contract_hash,
};
let preimage =
xdrs::txs::encode(&preimage).map_err(|e| SigningError::XdrEncode(e.to_string()))?;
let addr: [u8; 32] = Sha256::digest(&preimage).into();
Ok(ContractAddress::from(addr))
}
pub fn sign_transaction(
tx: Transaction,
signer: &impl Signer,
params: SignTransactionParams,
network_hash: &NetworkHash,
) -> Result<SignedTransaction, SigningError> {
let pubkey_claim_key = pubkey_claim_key(&tx, signer)?;
let preimage = transaction_signature_preimage(network_hash, &tx)
.map_err(|error| SigningError::XdrEncode(error.to_string()))?;
let signature = signer.sign(&preimage)?;
let decorated_signature = DecoratedSignature {
public_key_id: params.public_key_id.0,
signature,
};
Ok(SignedTransaction {
tx,
signatures: VecM(vec![decorated_signature]),
pubkey_claim_key,
})
}
fn pubkey_claim_key(
tx: &Transaction,
signer: &impl Signer,
) -> Result<Option<PublicKey>, SigningError> {
let AuthClaim::kPubkeySignature { pubkey_preimage } = &tx.auth_claim else {
return Ok(None);
};
let public_key = signer.public_key();
if crate::public_key_hash(&public_key) != pubkey_preimage.pubkey_hash {
return Err(SigningError::SignerMismatch);
}
Ok(Some(public_key))
}
pub fn sign_and_encode_transaction_hex(
tx: Transaction,
signer: &impl Signer,
params: SignTransactionParams,
network_hash: &NetworkHash,
) -> Result<String, SigningError> {
let signed_tx = sign_transaction(tx, signer, params, network_hash)?;
let tx_xdr = crate::encode_signed_transaction_xdr(&signed_tx)?;
Ok(format!("0x{}", hex::encode(tx_xdr)))
}