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
25 changes: 25 additions & 0 deletions crates/sage-api/src/requests/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ pub struct SendXch {
pub auto_submit: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct BulkSendXch {
pub addresses: Vec<String>,
pub amount: Amount,
pub fee: Amount,
#[serde(default)]
pub memos: Vec<String>,
#[serde(default)]
pub auto_submit: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct CombineXch {
pub coin_ids: Vec<String>,
Expand Down Expand Up @@ -70,6 +81,18 @@ pub struct SendCat {
pub auto_submit: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct BulkSendCat {
pub asset_id: String,
pub addresses: Vec<String>,
pub amount: Amount,
pub fee: Amount,
#[serde(default)]
pub memos: Vec<String>,
#[serde(default)]
pub auto_submit: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
pub struct CreateDid {
pub name: String,
Expand Down Expand Up @@ -182,12 +205,14 @@ pub struct TransactionResponse {
}

pub type SendXchResponse = TransactionResponse;
pub type BulkSendXchResponse = TransactionResponse;
pub type CombineXchResponse = TransactionResponse;
pub type SplitXchResponse = TransactionResponse;
pub type CombineCatResponse = TransactionResponse;
pub type SplitCatResponse = TransactionResponse;
pub type IssueCatResponse = TransactionResponse;
pub type SendCatResponse = TransactionResponse;
pub type BulkSendCatResponse = TransactionResponse;
pub type CreateDidResponse = TransactionResponse;
pub type BulkMintNftsResponse = TransactionResponse;
pub type TransferNftsResponse = TransactionResponse;
Expand Down
2 changes: 2 additions & 0 deletions crates/sage-cli/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ routes!(
get_nft_data await: GetNftData = "/get_nft_data",

send_xch await: SendXch = "/send_xch",
bulk_send_xch await: BulkSendXch = "/bulk_send_xch",
combine_xch await: CombineXch = "/combine_xch",
split_xch await: SplitXch = "/split_xch",
combine_cat await: CombineCat = "/combine_cat",
split_cat await: SplitCat = "/split_cat",
issue_cat await: IssueCat = "/issue_cat",
send_cat await: SendCat = "/send_cat",
bulk_send_cat await: BulkSendCat = "/bulk_send_cat",
create_did await: CreateDid = "/create_did",
bulk_mint_nfts await: BulkMintNfts = "/bulk_mint_nfts",
transfer_nfts await: TransferNfts = "/transfer_nfts",
Expand Down
36 changes: 24 additions & 12 deletions crates/sage-wallet/src/wallet/cats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ impl Wallet {
pub async fn send_cat(
&self,
asset_id: Bytes32,
puzzle_hash: Bytes32,
amount: u64,
amounts: Vec<(Bytes32, u64)>,
fee: u64,
memos: Vec<Bytes>,
hardened: bool,
Expand All @@ -73,9 +72,13 @@ impl Wallet {
Vec::new()
};

let cats = self.select_cat_coins(asset_id, amount as u128).await?;
let combined_amount = amounts.iter().map(|(_, amount)| amount).sum::<u64>();

let cats = self
.select_cat_coins(asset_id, combined_amount as u128)
.await?;
let cat_selected: u128 = cats.iter().map(|cat| cat.coin.amount as u128).sum();
let cat_change: u64 = (cat_selected - amount as u128)
let cat_change: u64 = (cat_selected - combined_amount as u128)
.try_into()
.expect("change amount overflow");

Expand Down Expand Up @@ -107,9 +110,13 @@ impl Wallet {
.await?;
}

let mut output_memos = vec![puzzle_hash.into()];
output_memos.extend(memos);
let memos = ctx.memos(&output_memos)?;
for (puzzle_hash, amount) in amounts {
let mut output_memos = vec![puzzle_hash.into()];
output_memos.extend(memos.clone());
let memos = ctx.memos(&output_memos)?;
conditions = conditions.create_coin(puzzle_hash, amount, Some(memos));
}

let change_hint = ctx.hint(change_puzzle_hash)?;

self.spend_cat_coins(
Expand All @@ -119,8 +126,7 @@ impl Wallet {
return (cat, Conditions::new());
}

let mut conditions =
mem::take(&mut conditions).create_coin(puzzle_hash, amount, Some(memos));
let mut conditions = mem::take(&mut conditions);

if cat_change > 0 {
conditions =
Expand Down Expand Up @@ -159,7 +165,14 @@ mod tests {

let coin_spends = test
.wallet
.send_cat(asset_id, test.puzzle_hash, 750, 0, Vec::new(), false, true)
.send_cat(
asset_id,
vec![(test.puzzle_hash, 750)],
0,
Vec::new(),
false,
true,
)
.await?;
assert_eq!(coin_spends.len(), 1);

Expand All @@ -173,8 +186,7 @@ mod tests {
.wallet
.send_cat(
asset_id,
test.puzzle_hash,
1000,
vec![(test.puzzle_hash, 1000)],
500,
Vec::new(),
false,
Expand Down
22 changes: 12 additions & 10 deletions crates/sage-wallet/src/wallet/p2_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ impl Wallet {
/// Sends the given amount of XCH to the given puzzle hash, minus the fee.
pub async fn send_xch(
&self,
puzzle_hash: Bytes32,
amount: u64,
amounts: Vec<(Bytes32, u64)>,
fee: u64,
memos: Vec<Bytes>,
hardened: bool,
reuse: bool,
) -> Result<Vec<CoinSpend>, WalletError> {
let total = amount as u128 + fee as u128;
let combined_amount = amounts.iter().map(|(_, amount)| amount).sum::<u64>();

let total = combined_amount as u128 + fee as u128;
let coins = self.select_p2_coins(total).await?;
let selected: u128 = coins.iter().map(|coin| coin.amount as u128).sum();

Expand All @@ -28,11 +29,12 @@ impl Wallet {

let mut ctx = SpendContext::new();

let mut conditions = Conditions::new().create_coin(
puzzle_hash,
amount,
Some(Memos::new(ctx.alloc(&memos)?)),
);
let mut conditions = Conditions::new();

for (puzzle_hash, amount) in amounts {
conditions =
conditions.create_coin(puzzle_hash, amount, Some(Memos::new(ctx.alloc(&memos)?)));
}

if fee > 0 {
conditions = conditions.reserve_fee(fee);
Expand Down Expand Up @@ -60,7 +62,7 @@ mod tests {

let coin_spends = test
.wallet
.send_xch(test.puzzle_hash, 1000, 0, Vec::new(), false, true)
.send_xch(vec![(test.puzzle_hash, 1000)], 0, Vec::new(), false, true)
.await?;

assert_eq!(coin_spends.len(), 1);
Expand All @@ -80,7 +82,7 @@ mod tests {

let coin_spends = test
.wallet
.send_xch(test.puzzle_hash, 250, 250, Vec::new(), false, true)
.send_xch(vec![(test.puzzle_hash, 250)], 250, Vec::new(), false, true)
.await?;

assert_eq!(coin_spends.len(), 1);
Expand Down
68 changes: 62 additions & 6 deletions crates/sage/src/endpoints/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use chia::{
};
use chia_wallet_sdk::MetadataUpdate;
use sage_api::{
AddNftUri, AssignNftsToDid, BulkMintNfts, CombineCat, CombineXch, CreateDid, IssueCat,
NftUriKind, SendCat, SendXch, SignCoinSpends, SignCoinSpendsResponse, SplitCat, SplitXch,
SubmitTransaction, SubmitTransactionResponse, TransactionResponse, TransferDids, TransferNfts,
ViewCoinSpends, ViewCoinSpendsResponse,
AddNftUri, AssignNftsToDid, BulkMintNfts, BulkSendCat, BulkSendXch, CombineCat, CombineXch,
CreateDid, IssueCat, NftUriKind, SendCat, SendXch, SignCoinSpends, SignCoinSpendsResponse,
SplitCat, SplitXch, SubmitTransaction, SubmitTransactionResponse, TransactionResponse,
TransferDids, TransferNfts, ViewCoinSpends, ViewCoinSpendsResponse,
};
use sage_database::CatRow;
use sage_wallet::{fetch_uris, WalletNftMint};
Expand All @@ -33,11 +33,34 @@ impl Sage {
}

let coin_spends = wallet
.send_xch(puzzle_hash, amount, fee, memos, false, true)
.send_xch(vec![(puzzle_hash, amount)], fee, memos, false, true)
.await?;
self.transact(coin_spends, req.auto_submit).await
}

pub async fn bulk_send_xch(&self, req: BulkSendXch) -> Result<TransactionResponse> {
let wallet = self.wallet()?;

let amount = self.parse_amount(req.amount)?;

let mut amounts = Vec::with_capacity(req.addresses.len());

for address in req.addresses {
amounts.push((self.parse_address(address)?, amount));
}

let fee = self.parse_amount(req.fee)?;

let mut memos = Vec::new();

for memo in req.memos {
memos.push(Bytes::from(hex::decode(memo)?));
}

let coin_spends = wallet.send_xch(amounts, fee, memos, false, true).await?;
self.transact(coin_spends, req.auto_submit).await
}

pub async fn combine_xch(&self, req: CombineXch) -> Result<TransactionResponse> {
let wallet = self.wallet()?;
let fee = self.parse_amount(req.fee)?;
Expand Down Expand Up @@ -114,7 +137,40 @@ impl Sage {
}

let coin_spends = wallet
.send_cat(asset_id, puzzle_hash, amount, fee, memos, false, true)
.send_cat(
asset_id,
vec![(puzzle_hash, amount)],
fee,
memos,
false,
true,
)
.await?;
self.transact(coin_spends, req.auto_submit).await
}

pub async fn bulk_send_cat(&self, req: BulkSendCat) -> Result<TransactionResponse> {
let wallet = self.wallet()?;
let asset_id = parse_asset_id(req.asset_id)?;

let amount = parse_cat_amount(req.amount)?;

let mut amounts = Vec::with_capacity(req.addresses.len());

for address in req.addresses {
amounts.push((self.parse_address(address)?, amount));
}

let fee = self.parse_amount(req.fee)?;

let mut memos = Vec::new();

for memo in req.memos {
memos.push(Bytes::from(hex::decode(memo)?));
}

let coin_spends = wallet
.send_cat(asset_id, amounts, fee, memos, false, true)
.await?;
self.transact(coin_spends, req.auto_submit).await
}
Expand Down
18 changes: 18 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ pub async fn send_xch(state: State<'_, AppState>, req: SendXch) -> Result<Transa
Ok(state.lock().await.send_xch(req).await?)
}

#[command]
#[specta]
pub async fn bulk_send_xch(
state: State<'_, AppState>,
req: BulkSendXch,
) -> Result<TransactionResponse> {
Ok(state.lock().await.bulk_send_xch(req).await?)
}

#[command]
#[specta]
pub async fn combine_xch(
Expand Down Expand Up @@ -160,6 +169,15 @@ pub async fn send_cat(state: State<'_, AppState>, req: SendCat) -> Result<Transa
Ok(state.lock().await.send_cat(req).await?)
}

#[command]
#[specta]
pub async fn bulk_send_cat(
state: State<'_, AppState>,
req: BulkSendCat,
) -> Result<TransactionResponse> {
Ok(state.lock().await.bulk_send_cat(req).await?)
}

#[command]
#[specta]
pub async fn create_did(state: State<'_, AppState>, req: CreateDid) -> Result<TransactionResponse> {
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ pub fn run() {
commands::get_key,
commands::get_secret_key,
commands::send_xch,
commands::bulk_send_xch,
commands::combine_xch,
commands::split_xch,
commands::send_cat,
commands::bulk_send_cat,
commands::combine_cat,
commands::split_cat,
commands::issue_cat,
Expand Down
8 changes: 8 additions & 0 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ async getSecretKey(req: GetSecretKey) : Promise<GetSecretKeyResponse> {
async sendXch(req: SendXch) : Promise<TransactionResponse> {
return await TAURI_INVOKE("send_xch", { req });
},
async bulkSendXch(req: BulkSendXch) : Promise<TransactionResponse> {
return await TAURI_INVOKE("bulk_send_xch", { req });
},
async combineXch(req: CombineXch) : Promise<TransactionResponse> {
return await TAURI_INVOKE("combine_xch", { req });
},
Expand All @@ -50,6 +53,9 @@ async splitXch(req: SplitXch) : Promise<TransactionResponse> {
async sendCat(req: SendCat) : Promise<TransactionResponse> {
return await TAURI_INVOKE("send_cat", { req });
},
async bulkSendCat(req: BulkSendCat) : Promise<TransactionResponse> {
return await TAURI_INVOKE("bulk_send_cat", { req });
},
async combineCat(req: CombineCat) : Promise<TransactionResponse> {
return await TAURI_INVOKE("combine_cat", { req });
},
Expand Down Expand Up @@ -240,6 +246,8 @@ export type AssetCoinType = "cat" | "did" | "nft"
export type Assets = { xch: Amount; cats: CatAmount[]; nfts: string[] }
export type AssignNftsToDid = { nft_ids: string[]; did_id: string | null; fee: Amount; auto_submit?: boolean }
export type BulkMintNfts = { mints: NftMint[]; did_id: string; fee: Amount; auto_submit?: boolean }
export type BulkSendCat = { asset_id: string; addresses: string[]; amount: Amount; fee: Amount; memos?: string[]; auto_submit?: boolean }
export type BulkSendXch = { addresses: string[]; amount: Amount; fee: Amount; memos?: string[]; auto_submit?: boolean }
export type CancelOffer = { offer_id: string; fee: Amount; auto_submit?: boolean }
export type CatAmount = { asset_id: string; amount: Amount }
export type CatRecord = { asset_id: string; name: string | null; ticker: string | null; description: string | null; icon_url: string | null; visible: boolean; balance: Amount }
Expand Down
6 changes: 3 additions & 3 deletions src/components/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
import { useErrors } from '@/hooks/useErrors';
import { toDecimal } from '@/lib/utils';
import { useWalletState } from '@/state';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import BigNumber from 'bignumber.js';
import {
Expand All @@ -33,8 +35,6 @@ import {
import { Alert, AlertDescription, AlertTitle } from './ui/alert';
import { Badge } from './ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { Trans } from '@lingui/react/macro';
import { t } from '@lingui/core/macro';

export interface ConfirmationDialogProps {
response: TransactionResponse | TakeOfferResponse | null;
Expand Down Expand Up @@ -135,7 +135,7 @@ export default function ConfirmationDialog({
</TabsList>
<TabsContent value='simple'>
{isHighFee && !fee.isZero() && (
<Alert variant='warning' className='mb-4'>
<Alert variant='warning' className='my-3'>
<CircleAlert className='h-4 w-4' />
<AlertTitle>
<Trans>High Transaction Fee</Trans>
Expand Down
Loading
Loading