Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
153 changes: 77 additions & 76 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ smallvec = "2.0.0-alpha.11"
std-semaphore = "0.1.0"
syn = "2.0"
tempfile = "3.3.0"
thiserror = "1.0.59"
thiserror = "2.0.17"
time = "0.3.15"
tokio = { version = "1.47.1", default-features = false }
tokio-stream = "0.1.17"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@ use minotari_node_grpc_client::grpc;
use minotari_wallet_grpc_client::WalletGrpcClient;
use serde::Serialize;
use tari_crypto::tari_utilities::ByteArray;
use tari_engine_types::{
confidential::{AbridgedTransactionKernel, EncodedMerkleProof, MinotariBurnClaimProof},
template_lib_models::EncryptedData,
};
use tari_template_lib_types::crypto::{
PedersenCommitmentBytes,
RistrettoPublicKeyBytes,
Scalar32Bytes,
SchnorrSignatureBytes,
use tari_engine_types::confidential::{AbridgedTransactionKernel, EncodedMerkleProof, MinotariBurnClaimProof};
use tari_template_lib_types::{
crypto::{PedersenCommitmentBytes, RistrettoPublicKeyBytes, Scalar32Bytes, SchnorrSignatureBytes},
EncryptedData,
};
use tari_transaction_components::transaction_components::{memo_field::TxType, MemoField};
use tari_wallet_daemon_client::types::ClaimBurnProof;
Expand Down
9 changes: 8 additions & 1 deletion applications/tari_wallet_cli/src/command/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use tari_engine_types::{
};
use tari_ootle_address::OotleAddress;
use tari_ootle_common_types::{Epoch, SubstateAddress, SubstateRequirement};
use tari_ootle_wallet_sdk::apis::confidential_transfer::ConfidentialTransferInputSelection;
use tari_ootle_wallet_sdk::{apis::confidential_transfer::ConfidentialTransferInputSelection, crypto::memo::Memo};
use tari_template_lib::{
constants::STEALTH_TARI_RESOURCE_ADDRESS,
models::{BucketId, NonFungibleAddress, NonFungibleId},
Expand Down Expand Up @@ -153,6 +153,9 @@ pub struct ConfidentialTransferArgs {
/// The address of the resource to send. If not provided, use the default Tari confidential resource
#[clap(long)]
resource_address: Option<ResourceAddress>,
/// An optional memo to include in the confidential output
#[clap(long, short = 'm')]
memo_message: Option<String>,
}

#[derive(Debug, Subcommand, Clone)]
Expand Down Expand Up @@ -416,6 +419,7 @@ pub async fn handle_confidential_transfer(
amount,
destination_address,
common,
memo_message,
} = args;

// let AccountByNameResponse { account, .. } = client.accounts_get_by_name(&source_account_name).await?;
Expand All @@ -429,6 +433,9 @@ pub async fn handle_confidential_transfer(
max_fee: common.max_fee,
output_to_revealed: false,
proof_from_badge_resource: None,
memo: memo_message
.map(|s| Memo::new_message(s).ok_or_else(|| anyhow!("Invalid memo length")))
.transpose()?,
dry_run: false,
})
.await?;
Expand Down
23 changes: 16 additions & 7 deletions applications/tari_walletd/src/handlers/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use tari_engine_types::{
};
use tari_ootle_common_types::{optional::Optional, SubstateRequirement};
use tari_ootle_wallet_crypto::{
memo::Memo,
UnblindedOutputStatement,
UnblindedStealthInputStatement,
UnblindedStealthOutputStatement,
Expand Down Expand Up @@ -455,17 +456,18 @@ pub async fn handle_claim_burn(
.burn_public_key
.try_from_byte_type()
.map_err(|e| invalid_params("claim_proof.reciprocal_claim_public_key", Some(e)))?;
let mask_and_value = sdk.stealth_crypto_api().decrypt_value_and_mask(
let decrypted = sdk.stealth_crypto_api().decrypt_value_and_mask(
&claimed_encrypted_data,
&claim_proof.commitment,
claim_nonce_keypair.secret_key(),
&reciprocal_claim_public_key_expanded,
true,
)?;

let mask = sdk.key_manager_api().next_key(KeyBranch::StealthMask)?;

let final_amount = mask_and_value
.value
let final_amount = decrypted
.value()
.checked_sub_positive(max_fee.into())
.ok_or_else(|| invalid_params("max_fee", Some("more fees paid than claimed amount")))?;

Expand All @@ -487,12 +489,17 @@ pub async fn handle_claim_burn(
let account_owner_public_key = account_owner.to_public_key();
let view_only = sdk.key_manager_api().get_view_only_key(account.view_only_key_id())?;
let view_only_public_key = view_only.to_public_key();
let memo = Memo::new_message("Claimed burned XTR from L1").expect("valid memo");
// NOTE: the confidential encryption format and the bullet proofs currently do not support amounts larger than
// u64::MAX. Apart from it being insane/basically impossible to have that much XTR in a single UTXO, the L1 emission
// will reach this much in many thousands of years.
let encrypted_data =
sdk.stealth_crypto_api()
.encrypt_value_and_mask(final_amount_u64, &mask.key, &view_only_public_key, &nonce)?;
let encrypted_data = sdk.stealth_crypto_api().encrypt_value_and_mask(
final_amount_u64,
&mask.key,
&view_only_public_key,
&nonce,
Some(&memo),
)?;
Comment thread
sdbondi marked this conversation as resolved.

let tag = sdk.stealth_crypto_api().derive_stealth_output_tag(
network,
Expand Down Expand Up @@ -521,7 +528,7 @@ pub async fn handle_claim_burn(

// Generate the correct secret to spend the claimed output
let input = UnblindedStealthInputStatement {
mask_and_value,
mask_and_value: decrypted.into_mask_and_value(),
owner_secret: claim_nonce_keypair.secret_key().clone(),
public_nonce: reciprocal_claim_public_key_expanded,
};
Expand Down Expand Up @@ -904,6 +911,7 @@ pub async fn handle_confidential_transfer(
max_fee: req.max_fee.unwrap_or(DEFAULT_FEE),
output_to_revealed: req.output_to_revealed,
proof_from_resource: req.proof_from_badge_resource,
memo: req.memo,
is_dry_run: req.dry_run,
})
.await?;
Expand Down Expand Up @@ -946,6 +954,7 @@ pub async fn handle_stealth_transfer(
max_fee: req.max_fee,
blinded_output_amount: req.blinded_output_amount,
revealed_output_amount: req.revealed_output_amount,
output_memo: req.output_memo,
is_dry_run: req.dry_run,
};
if let Err(err) = params.validate(network) {
Expand Down
5 changes: 5 additions & 0 deletions applications/tari_walletd/src/handlers/confidential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub async fn handle_create_transfer_proof(
&output_mask.key,
&public_nonce,
&account_key.secret,
req.memo.as_ref(),
)?;

let resource = sdk.substate_api().fetch_resource(req.resource_address).await?;
Expand Down Expand Up @@ -150,6 +151,7 @@ pub async fn handle_create_transfer_proof(
&change_mask.key,
&public_nonce,
&change_mask.key,
None,
)?;

sdk.confidential_outputs_api().add_output(ConfidentialOutputModel {
Expand All @@ -163,6 +165,7 @@ pub async fn handle_create_transfer_proof(
view_only_key_id: account.view_only_key_id(),
owner_key_id: account.owner_key_id(),
encrypted_data: encrypted_data.clone(),
memo: None,
public_asset_tag: None,
status: OutputStatus::LockedUnconfirmed,
lock_id: Some(lock_id),
Expand Down Expand Up @@ -246,6 +249,8 @@ pub async fn handle_create_output_proof(
&output_mask.key,
&public_nonce,
&output_mask.key,
// TODO: Support memos
None,
)?;

let statement = UnblindedOutputStatement {
Expand Down
1 change: 1 addition & 0 deletions applications/tari_walletd/src/handlers/stealth_utxos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub async fn handle_list(
address: o.to_utxo_address(),
value: o.value,
status: o.status,
memo: o.memo,
is_burnt: o.is_burnt,
is_frozen: o.is_frozen,
is_on_chain: o.is_on_chain,
Expand Down
22 changes: 22 additions & 0 deletions applications/tari_walletd/web_ui/src/components/Memo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2025 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause
import { Memo } from "@tari-project/typescript-bindings";

export type MemoProps = {
memo?: Memo | null;
};

export function Memo({ memo }: MemoProps) {
if (!memo) {
return <span>--</span>;
}

if ("Message" in memo) {
return <span>{memo ? memo.Message : "No Memo"}</span>;
}
if ("Bytes" in memo) {
return <span>{memo ? Buffer.from(memo.Bytes).toString("hex") : "No Memo"}</span>;
}
Comment thread
sdbondi marked this conversation as resolved.

return <span>{JSON.stringify(memo)}</span>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
output_to_revealed: !transferFormState.outputToConfidential,
input_selection: transferFormState.inputSelection as ConfidentialTransferInputSelection,
badge: transferFormState.badge,
output_memo: transferFormState.memo ? { Message: transferFormState.memo } : undefined,
};

const result = await sendIt?.({ ...currentTransfer, dry_run: true, max_fee: 3000 });
Expand Down Expand Up @@ -253,6 +254,7 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) {
output_to_revealed: !transferFormState.outputToConfidential,
input_selection: transferFormState.inputSelection as ConfidentialTransferInputSelection,
badge: transferFormState.badge,
output_memo: transferFormState.memo ? { Message: transferFormState.memo } : undefined,
};

await sendIt?.({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface SendMoneyFormState {
amount: string;
fee: string;
badge: string | null;
memo: string;
}

interface FormStepProps {
Expand Down Expand Up @@ -174,6 +175,17 @@ export default function FormStep({
}
label="Send Confidential Outputs"
/>
{transferFormState.outputToConfidential ? (
<TextField
name="memo"
label="Memo message (optional, max 253 characters)"
inputProps={{ maxLength: 253 }}
value={transferFormState.memo}
onChange={onFormValueChange}
style={{ flexGrow: 1 }}
disabled={disabled}
/>
) : null}
<InputLabel id="select-input-selection">Input Selection</InputLabel>
<Select
name="inputSelection"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import PlaceHolder from "./components/PlaceHolder";
import SortableHeader from "./components/SortableHeader";
import useCurrencyStore from "@store/currencyStore";
import { useParams } from "react-router-dom";
import { Memo } from "@components/Memo";

function StealthUtxoList({ account }: { account: Account }) {
const [page, setPage] = useState(0);
Expand Down Expand Up @@ -63,12 +64,13 @@ function StealthUtxoList({ account }: { account: Account }) {
);

const columnWidths = {
1: "30%",
2: "20%",
1: "10%",
2: "15%",
3: "20%",
4: "10%",
4: "25%",
5: "10%",
6: "10%",
7: "10%",
};

return (
Expand All @@ -88,9 +90,10 @@ function StealthUtxoList({ account }: { account: Account }) {
getDisplayName={getStatusDisplayName}
/>
</TableCell>
<TableCell width={columnWidths[4]}>Burnt</TableCell>
<TableCell width={columnWidths[5]}>Frozen</TableCell>
<TableCell width={columnWidths[6]}>On Chain</TableCell>
<TableCell width={columnWidths[4]}>Memo</TableCell>
<TableCell width={columnWidths[5]}>Burnt</TableCell>
<TableCell width={columnWidths[6]}>Frozen</TableCell>
<TableCell width={columnWidths[7]}>On Chain</TableCell>
</TableRow>
</TableHead>
<TableBody>
Expand All @@ -108,6 +111,9 @@ function StealthUtxoList({ account }: { account: Account }) {
<DataTableCell>
<StatusChip status={utxo.status} />
</DataTableCell>
<DataTableCell>
<Memo memo={utxo.memo} />
</DataTableCell>
<DataTableCell>{utxo.is_burnt ? "Yes" : "No"}</DataTableCell>
<DataTableCell>{utxo.is_frozen ? "Yes" : "No"}</DataTableCell>
<DataTableCell>{utxo.is_on_chain ? "Yes" : "No"}</DataTableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
ComponentAddressOrName,
ConfidentialTransferInputSelection,
decodeOotleAddress,
Memo,
OutputStatus,
ResourceAddress,
ResourceType,
Expand Down Expand Up @@ -125,6 +126,7 @@ export interface TransferParams {
input_selection: ConfidentialTransferInputSelection;
badge: string | null;
dry_run: boolean;
output_memo?: Memo;
}

export const useAccountsTransfer = () => {
Expand All @@ -143,6 +145,7 @@ export const useAccountsTransfer = () => {
proof_from_badge_resource: params.badge,
input_selection: params.input_selection,
output_to_revealed: params.output_to_revealed,
output_memo: params.output_memo || null,
dry_run: params.dry_run,
};
return accountsConfidentialTransfer(transferRequest);
Expand All @@ -155,6 +158,7 @@ export const useAccountsTransfer = () => {
max_fee,
blinded_output_amount: params.output_to_revealed ? 0 : params.amount,
revealed_output_amount: params.output_to_revealed ? params.amount : 0,
output_memo: params.output_memo || null,
dry_run: params.dry_run,
};
return accountsStealthTransfer(transferRequest);
Expand Down
1 change: 1 addition & 0 deletions bindings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export * from "./types/LeaderFee";
export * from "./types/LockFlag";
export * from "./types/LogEntry";
export * from "./types/LogLevel";
export * from "./types/Memo";
export * from "./types/Metadata";
export * from "./types/MinotariBurnClaimProof";
export * from "./types/Network";
Expand Down
3 changes: 3 additions & 0 deletions bindings/src/types/Memo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type Memo = { Message: string } | { Bytes: string };
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 { ConfidentialTransferInputSelection } from "../ConfidentialTransferInputSelection";
import type { Memo } from "../Memo";
import type { OotleAddress } from "../OotleAddress";
import type { ResourceAddress } from "../ResourceAddress";
import type { ComponentAddressOrName } from "./ComponentAddressOrName";
Expand All @@ -14,5 +15,6 @@ export type ConfidentialTransferRequest = {
max_fee: number | null;
output_to_revealed: boolean;
proof_from_badge_resource: ResourceAddress | null;
memo?: Memo | null;
dry_run: boolean;
};
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 { Memo } from "../Memo";
import type { ResourceAddress } from "../ResourceAddress";
import type { RistrettoPublicKeyBytes } from "../RistrettoPublicKeyBytes";
import type { ComponentAddressOrName } from "./ComponentAddressOrName";
Expand All @@ -10,4 +11,5 @@ export type ProofsGenerateRequest = {
account: ComponentAddressOrName | null;
resource_address: ResourceAddress;
destination_public_key: RistrettoPublicKeyBytes;
memo?: Memo | null;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 { ConfidentialTransferInputSelection } from "../ConfidentialTransferInputSelection";
import type { Memo } from "../Memo";
import type { OotleAddress } from "../OotleAddress";
import type { ResourceAddress } from "../ResourceAddress";
import type { ComponentAddressOrName } from "./ComponentAddressOrName";
Expand All @@ -13,5 +14,6 @@ export type StealthTransferRequest = {
max_fee: number;
blinded_output_amount: Amount;
revealed_output_amount: Amount;
output_memo?: Memo | null;
dry_run: boolean;
};
2 changes: 2 additions & 0 deletions bindings/src/types/wallet-daemon-client/UtxoInfo.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// 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 { Memo } from "../Memo";
import type { OutputStatus } from "../OutputStatus";
import type { UtxoAddress } from "../UtxoAddress";

export type UtxoInfo = {
address: UtxoAddress;
value: Amount;
status: OutputStatus;
memo: Memo | null;
is_burnt: boolean;
is_frozen: boolean;
is_on_chain: boolean;
Expand Down
Loading
Loading