Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions applications/tari_app_utilities/src/transaction_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::sync::Arc;

use log::*;
use tari_engine::{
executables::Executable,
fees::{FeeModule, FeeTable},
runtime::{AuthParams, RuntimeModule},
state_store::{memory::ReadOnlyMemoryStateStore, StateStoreError},
Expand Down Expand Up @@ -120,10 +121,7 @@ where TTemplateProvider: TemplateProvider<Template = LoadedTemplate>
// Include signature public key badges for all transaction signers in the initial auth scope
// NOTE: we assume all signatures have already been validated.
let initial_ownership_proofs = transaction
.signatures()
.iter()
.map(|p| p.public_key())
.chain(Some(transaction.seal_signature().public_key()).filter(|_| transaction.is_seal_signer_authorized()))
.signers_iter()
.map(|pk| NonFungibleAddress::from_public_key(*pk))
.collect();
let auth_params = AuthParams {
Expand Down
31 changes: 10 additions & 21 deletions applications/tari_walletd/src/handlers/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use tari_ootle_wallet_sdk::{
stealth_transfer::StealthTransferParams,
substate::ValidatorScanResult,
},
models::{KeyBranch, KeyId, NewAccountData},
models::{KeyBranch, NewAccountData},
};
use tari_ootle_wallet_sdk_services::events::TransactionSubmittedEvent;
use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS;
Expand Down Expand Up @@ -534,11 +534,14 @@ pub async fn handle_claim_burn(
public_nonce: reciprocal_claim_public_key_expanded,
};

let public_signer_key = sdk.key_manager_api().next_public_key(KeyBranch::Nonce)?;

let pay_fee_and_mint_output = sdk.stealth_crypto_api().generate_transfer_statement(
array::from_ref(&input),
0,
array::from_ref(&output_statement),
max_fee,
public_signer_key.public_key.to_byte_type(),
)?;
// We'll create an output with the same encrypted data that was used on L1 burn. Note that this is not strictly
// necessary. The engine will create the output with whatever you give it, so we could reencrypt.
Expand All @@ -556,12 +559,9 @@ pub async fn handle_claim_burn(
.add_input(XTR)
.build();

// The signer does not authorize this transaction, as the claim burn instruction is authorized by the proofs. So we
// can sign with any key.
let nonce = sdk.key_manager_api().next_public_key(KeyBranch::Nonce)?;
let transaction = sdk
.local_signer_api()
.sign(KeyBranch::Nonce, nonce.key_id, transaction)?;
.sign(KeyBranch::Nonce, public_signer_key.key_id, transaction)?;

let tx_id = context.transaction_service().submit_transaction(transaction).await?;

Expand Down Expand Up @@ -959,7 +959,7 @@ pub async fn handle_stealth_transfer(
let network = sdk.sdk_config().network;
let notifier = context.notifier().clone();
let owner_account = get_account(&req.owner_account, &sdk.accounts_api())?;
let Some(owner_key_id) = owner_account.owner_key_id() else {
if owner_account.owner_key_id().is_none() {
return Err(invalid_params(
"owner_account",
Some("cannot transfer from an account without an owner key"),
Expand Down Expand Up @@ -987,22 +987,11 @@ pub async fn handle_stealth_transfer(
task::spawn(async move {
let transfer = sdk.stealth_transfer_api().transfer(owner_account, params).await?;

let must_sign_with_account_key =
transfer.fee_inputs.revealed.is_positive() || transfer.transfer_inputs.revealed.is_positive();

let transaction = transfer.transaction.authorized_sealed_signer().build(vec![]);

let (key_branch, key_id) = if must_sign_with_account_key {
(KeyBranch::Account, owner_key_id)
} else {
// Since we don't require account auth, use a throwaway nonce to sign the transaction
(
KeyBranch::Nonce,
KeyId::derived(sdk.key_manager_api().next_derived_key_index(KeyBranch::Nonce)?),
)
};
let transaction = transfer.transaction.authorized_sealed_signer().build();

let transaction = sdk.local_signer_api().sign(key_branch, key_id, transaction)?;
let transaction =
sdk.local_signer_api()
.sign(transfer.signing_key_branch, transfer.signing_key_id, transaction)?;

// TODO: if submitting fails we need to unlock the inputs again
if req.dry_run {
Expand Down
2 changes: 1 addition & 1 deletion applications/tari_walletd/src/handlers/nfts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ pub async fn handle_transfer(
.with_inputs(inputs.into_iter().map(|input| input.into_unversioned()))
// Seal signer is the fee payer account
.with_authorized_seal_signer()
.then(|builder| {
.map(|builder| {
sdk.local_signer_api().sign_with_context(
KeyBranch::Account,
account_owner_key_id,
Expand Down
4 changes: 2 additions & 2 deletions applications/tari_walletd/src/handlers/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ pub async fn handle_submit_manifest(
}
})
.with_instructions(instructions.instructions)
.then(|builder| {
.map(|builder| {
if signing_key_id == account_owner_key_id {
Ok(builder)
} else {
Expand All @@ -350,7 +350,7 @@ pub async fn handle_submit_manifest(
let transaction = transaction
.with_inputs(inputs)
.authorized_sealed_signer()
.build(signatures);
.build_with_signatures(signatures);

let transaction = sdk
.local_signer_api()
Expand Down
23 changes: 10 additions & 13 deletions applications/tari_walletd/src/handlers/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,26 +179,23 @@ pub async fn handle_claim_validator_fees(
.with_inputs(inputs.into_iter().map(|input| input.into_unversioned()))
.with_inputs(fee_pool_addresses.map(SubstateRequirement::unversioned))
.add_input(XTR)
.then(|builder| {
.map(|builder| {
if let Some(index) = req.claim_key_index {
if claim_public_key == *account.address.account_public_key() {
builder
Ok(builder)
} else {
// If the claim key is different from the account secret, we need to sign with both
sdk.local_signer_api()
.sign_with_context(
KeyBranch::Account,
KeyId::derived(index),
account.address.account_public_key(),
builder.with_authorized_seal_signer(),
)
// We happen to know that signing with a derived key is infallible
.expect("Signing with should work")
sdk.local_signer_api().sign_with_context(
KeyBranch::Account,
KeyId::derived(index),
account.address.account_public_key(),
builder.with_authorized_seal_signer(),
)
}
} else {
builder
Ok(builder)
}
})
})?
.build();

let transaction = sdk
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
XTR,
} from "@tari-project/typescript-bindings";
import { transactionsWaitResult } from "@utils/json_rpc";
import FormStep, { SendMoneyFormState } from "../steps/FormStep";
import FormStep, { FormError, SendMoneyFormState } from "../steps/FormStep";
import ConfirmationStep from "../steps/ConfirmationStep";
import ResultStep, { TransferResult } from "../steps/ResultStep";
import PopupTitle from "@/components/PopupTitle";
Expand Down Expand Up @@ -66,6 +66,7 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
const [isEstimatingFee, setIsEstimatingFee] = useState(false);
const [transferFormState, setTransferFormState] = useState(INITIAL_VALUES);
const [transferResult, setTransferResult] = useState<TransferResult | undefined>();
const [formError, setFormError] = useState<FormError | null>(null);
const { mutateAsync: sendIt } = useAccountsTransfer();

const { account } = useAccountStore();
Expand Down Expand Up @@ -120,6 +121,7 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
const availableBalance = calculateAvailableBalance();

function setFormValue(e: React.ChangeEvent<HTMLInputElement>) {
setFormError(null);
const { name, value } = e.target;

// For amount field, parse the input to allow decimal values
Expand All @@ -145,20 +147,23 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
}

function setSelectFormValue(e: SelectChangeEvent<unknown>) {
setFormError(null);
setTransferFormState({
...transferFormState,
[e.target.name]: e.target.value,
});
}

function setCheckboxFormValue(e: React.ChangeEvent<HTMLInputElement>) {
setFormError(null);
setTransferFormState({
...transferFormState,
[e.target.name]: e.target.checked,
});
}

const handleUseBadgeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormError(null);
setUseBadge(e.target.checked);
if (!e.target.checked) {
setTransferFormState({
Expand Down Expand Up @@ -208,9 +213,6 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
fee += 100;
}
setTransferFormState((prevState) => ({ ...prevState, fee: fee.toString() }));
} catch (error) {
console.error("Fee estimation error:", error);
// Don't block the user if fee estimation fails
} finally {
setIsEstimatingFee(false);
}
Expand All @@ -232,6 +234,10 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
try {
await estimateFee();
} catch (error) {
setFormError({
type: "general",
message: `Failed to estimate fee: ${error}`,
});
console.error("Fee estimation failed:", error);
return;
}
Expand Down Expand Up @@ -316,6 +322,7 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
availableBalance={availableBalance}
token_symbol={props.token_symbol}
divisibility={balanceEntry.divisibility}
formError={formError}
onSubmit={handleFormSubmit}
onCancel={handleClose}
onFormValueChange={setFormValue}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ interface FormStepProps {
availableBalance?: number;
token_symbol: string;
divisibility: number;
formError?: FormError | null;
onSubmit: (e: FormEvent) => void;
onCancel: () => void;
onFormValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
Expand All @@ -63,6 +64,11 @@ interface FormStepProps {
onUseBadgeChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export type FormError = {
type: "general" | "address" | "amount" | "fee";
message: string;
};

export default function FormStep({
resource_address,
resource_type,
Expand All @@ -74,6 +80,7 @@ export default function FormStep({
availableBalance,
token_symbol,
divisibility,
formError,
onSubmit,
onCancel,
onFormValueChange,
Expand Down Expand Up @@ -110,6 +117,7 @@ export default function FormStep({
maximumFractionDigits: divisibility,
});
};
console.log(formError);
Comment thread
sdbondi marked this conversation as resolved.
Outdated

return (
<Form onSubmit={onSubmit}>
Expand Down Expand Up @@ -151,6 +159,7 @@ export default function FormStep({
</>
)}
<Stack direction="column" spacing={0.5}>
<DisplayFormError forType="address" formError={formError} />
<TextField
name="address"
label="To Address"
Expand Down Expand Up @@ -202,6 +211,7 @@ export default function FormStep({
</>
)}

<DisplayFormError forType="amount" formError={formError} />
<TextField
name="amount"
label="Amount"
Expand Down Expand Up @@ -248,6 +258,8 @@ export default function FormStep({
/>

<Divider />

<DisplayFormError forType="general" formError={formError} />
<Stack direction="row" justifyContent="space-between" sx={{ mt: 3 }}>
<Button variant="outlined" onClick={onCancel} disabled={disabled}>
Cancel
Expand All @@ -260,3 +272,13 @@ export default function FormStep({
</Form>
);
}

function DisplayFormError({ forType, formError }: { forType: FormError["type"]; formError?: FormError | null }) {
if (!formError) return null;
if (formError.type !== forType) return null;
return (
<Typography color="error" sx={{ mb: 2 }}>
{formError.message}
</Typography>
);
}
2 changes: 1 addition & 1 deletion bindings/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tari-project/typescript-bindings",
"version": "1.18.0",
"version": "1.18.1",
"description": "TypeScript types synchronized to the Tari Ootle Rust codebase",
"homepage": "https://github.com/tari-project/tari-ootle#readme",
"bugs": {
Expand Down
1 change: 1 addition & 0 deletions bindings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export * from "./types/FeeSource";
export * from "./types/FinalizeResult";
export * from "./types/ForeignProposalAtom";
export * from "./types/FunctionDef";
export * from "./types/Hash64";
export * from "./types/Hash";
export * from "./types/IndexedValue";
export * from "./types/IndexedWellKnownTypes";
Expand Down
6 changes: 6 additions & 0 deletions bindings/src/types/Hash64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* Representation of a 32-byte hash value
*/
export type Hash64 = string;
Comment thread
sdbondi marked this conversation as resolved.
5 changes: 5 additions & 0 deletions bindings/src/types/StealthInputsStatement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Amount } from "./Amount";
import type { RistrettoPublicKeyBytes } from "./RistrettoPublicKeyBytes";
import type { StealthInput } from "./StealthInput";

/**
Expand All @@ -14,4 +15,8 @@ export type StealthInputsStatement = {
* The total amount of revealed funds being spent.
*/
revealed_amount: Amount;
/**
* The signer that must sign the transaction to allow these inputs to be spent.
*/
required_signer: RistrettoPublicKeyBytes;
};
1 change: 1 addition & 0 deletions crates/engine/src/executables/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub trait Executable {
fn all_inputs_iter(&self) -> impl Iterator<Item = SubstateRequirementRef<'_>> + '_;

fn main_signer(&self) -> Option<RistrettoPublicKeyBytes>;
fn signers_iter(&self) -> impl Iterator<Item = &RistrettoPublicKeyBytes>;

fn into_instructions(self) -> Instructions;
}
Expand Down
11 changes: 7 additions & 4 deletions crates/engine/src/executables/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ impl Executable for Transaction {
// TODO: If the seal signer is authorized we use this as the signer public key, if not we use the first
// signature as the "default" owner. This is due to limitations of the current transaction model.
// We could remove the idea of a default owner (OwnedBySigner) entirely.
Some(self.seal_signature())
self.signers_iter().next().copied()
}

fn signers_iter(&self) -> impl Iterator<Item = &RistrettoPublicKeyBytes> {
Some(self.seal_signature().public_key())
.filter(|_| self.is_seal_signer_authorized())
.map(|s| s.public_key())
.or(self.signatures().first().map(|s| s.public_key()))
.copied()
.into_iter()
.chain(self.signatures().iter().map(|s| s.public_key()))
}

fn into_instructions(self) -> Instructions {
Expand Down
3 changes: 3 additions & 0 deletions crates/engine/src/runtime/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use tari_template_lib::{
ResourceAddress,
VaultId,
},
prelude::RistrettoPublicKeyBytes,
types::{Amount, TemplateAddress},
};
use tari_transaction::args::{WorkspaceId, WorkspaceOffsetId};
Expand Down Expand Up @@ -173,6 +174,8 @@ pub enum RuntimeError {
AccessDeniedAuthHook { action_ident: ActionIdent, details: String },
#[error("Access Denied: You must be the owner to perform this action: {action}")]
AccessDeniedOwnerRequired { action: ActionIdent },
#[error("Access Denied: Stealth transfer requires a signer with public key {required_signer}")]
AccessDeniedStealthTransferSigner { required_signer: RistrettoPublicKeyBytes },
#[error("Invalid method address rule for {template_name}: {details}")]
InvalidMethodAccessRule { template_name: String, details: String },
#[error("Runtime module error: {0}")]
Expand Down
Loading
Loading