Skip to content

Commit

Permalink
Merge pull request #322 from dkackman/check_address
Browse files Browse the repository at this point in the history
Check address
  • Loading branch information
Rigidity authored Feb 9, 2025
2 parents 944b526 + e2b32a1 commit 086af61
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 10 deletions.
12 changes: 12 additions & 0 deletions crates/sage-api/src/requests/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ use crate::{
NftRecord, PendingTransactionRecord, TransactionRecord, Unit,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
pub struct CheckAddress {
pub address: String,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
pub struct CheckAddressResponse {
pub valid: bool,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
pub struct GetDerivations {
Expand Down
1 change: 1 addition & 0 deletions crates/sage-cli/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ routes!(
get_keys: GetKeys = "/get_keys",

get_sync_status await: GetSyncStatus = "/get_sync_status",
check_address await: CheckAddress = "/check_address",
get_derivations await: GetDerivations = "/get_derivations",
get_xch_coins await: GetXchCoins = "/get_xch_coins",
get_cat_coins await: GetCatCoins = "/get_cat_coins",
Expand Down
28 changes: 20 additions & 8 deletions crates/sage/src/endpoints/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ use chia::{
singleton::SINGLETON_LAUNCHER_PUZZLE_HASH,
},
};
use chia_wallet_sdk::{encode_address, Nft};
use chia_wallet_sdk::{decode_address, encode_address, Nft};
use clvmr::Allocator;
use hex_literal::hex;
use sage_api::{
AddressKind, Amount, AssetKind, CatRecord, CoinRecord, DerivationRecord, DidRecord, GetCat,
GetCatCoins, GetCatCoinsResponse, GetCatResponse, GetCats, GetCatsResponse, GetDerivations,
GetDerivationsResponse, GetDids, GetDidsResponse, GetMinterDidIds, GetMinterDidIdsResponse,
GetNft, GetNftCollection, GetNftCollectionResponse, GetNftCollections,
GetNftCollectionsResponse, GetNftData, GetNftDataResponse, GetNftResponse, GetNfts,
GetNftsResponse, GetPendingTransactions, GetPendingTransactionsResponse, GetSyncStatus,
GetSyncStatusResponse, GetTransaction, GetTransactionResponse, GetTransactions,
AddressKind, Amount, AssetKind, CatRecord, CheckAddress, CheckAddressResponse, CoinRecord,
DerivationRecord, DidRecord, GetCat, GetCatCoins, GetCatCoinsResponse, GetCatResponse, GetCats,
GetCatsResponse, GetDerivations, GetDerivationsResponse, GetDids, GetDidsResponse,
GetMinterDidIds, GetMinterDidIdsResponse, GetNft, GetNftCollection, GetNftCollectionResponse,
GetNftCollections, GetNftCollectionsResponse, GetNftData, GetNftDataResponse, GetNftResponse,
GetNfts, GetNftsResponse, GetPendingTransactions, GetPendingTransactionsResponse,
GetSyncStatus, GetSyncStatusResponse, GetTransaction, GetTransactionResponse, GetTransactions,
GetTransactionsResponse, GetXchCoins, GetXchCoinsResponse, NftCollectionRecord, NftData,
NftRecord, NftSortMode as ApiNftSortMode, PendingTransactionRecord, TransactionCoin,
TransactionRecord,
Expand Down Expand Up @@ -65,6 +65,18 @@ impl Sage {
})
}

pub async fn check_address(&self, req: CheckAddress) -> Result<CheckAddressResponse> {
let wallet = self.wallet()?;

let Some((puzzle_hash, _prefix)) = decode_address(&req.address).ok() else {
return Ok(CheckAddressResponse { valid: false });
};

let is_valid = wallet.db.is_p2_puzzle_hash(puzzle_hash.into()).await?;

Ok(CheckAddressResponse { valid: is_valid })
}

pub async fn get_derivations(&self, req: GetDerivations) -> Result<GetDerivationsResponse> {
let wallet = self.wallet()?;

Expand Down
9 changes: 9 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,15 @@ pub async fn get_sync_status(
Ok(state.lock().await.get_sync_status(req).await?)
}

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

#[command]
#[specta]
pub async fn get_derivations(
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub fn run() {
commands::view_coin_spends,
commands::submit_transaction,
commands::get_sync_status,
commands::check_address,
commands::get_derivations,
commands::get_xch_coins,
commands::get_cat_coins,
Expand Down
5 changes: 5 additions & 0 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ async submitTransaction(req: SubmitTransaction) : Promise<SubmitTransactionRespo
async getSyncStatus(req: GetSyncStatus) : Promise<GetSyncStatusResponse> {
return await TAURI_INVOKE("get_sync_status", { req });
},
async checkAddress(req: CheckAddress) : Promise<CheckAddressResponse> {
return await TAURI_INVOKE("check_address", { req });
},
async getDerivations(req: GetDerivations) : Promise<GetDerivationsResponse> {
return await TAURI_INVOKE("get_derivations", { req });
},
Expand Down Expand Up @@ -274,6 +277,8 @@ export type BulkSendXch = { addresses: string[]; amount: Amount; fee: Amount; me
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 }
export type CheckAddress = { address: string }
export type CheckAddressResponse = { valid: boolean }
export type Coin = { parent_coin_info: string; puzzle_hash: string; amount: number }
export type CoinJson = { parent_coin_info: string; puzzle_hash: string; amount: Amount }
export type CoinRecord = { coin_id: string; address: string; amount: Amount; created_height: number | null; spent_height: number | null; create_transaction_id: string | null; spend_transaction_id: string | null; offer_id: string | null }
Expand Down
88 changes: 86 additions & 2 deletions src/pages/Addresses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,28 @@ import { useErrors } from '@/hooks/useErrors';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { LoaderCircleIcon } from 'lucide-react';
import { LoaderCircleIcon, CheckIcon, XCircleIcon } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { commands, events } from '../bindings';
import AddressList from '../components/AddressList';
import { useWalletState } from '../state';
import { toast } from 'react-toastify';
import { CustomError } from '@/contexts/ErrorContext';

export default function Addresses() {
const { addError } = useErrors();
const walletState = useWalletState();
const ticker = walletState.sync.unit.ticker;

const [hardened, setHardened] = useState(false);
const [addresses, setAddresses] = useState<string[]>([]);
const [deriveOpen, setDeriveOpen] = useState(false);
const [pending, setPending] = useState(false);
const [addressToCheck, setAddressToCheck] = useState('');
const [checkStatus, setCheckStatus] = useState<'idle' | 'valid' | 'invalid'>(
'idle',
);

const updateAddresses = useCallback(() => {
commands
Expand Down Expand Up @@ -111,6 +116,22 @@ export default function Addresses() {
.finally(() => setPending(false));
};

const handleCheckAddress = async () => {
try {
// TODO: Replace with actual backend call
const result = await commands.checkAddress({ address: addressToCheck });
setCheckStatus(result.valid ? 'valid' : 'invalid');
if (result.valid) {
toast.success(t`Address belongs to your wallet`);
} else {
toast.error(t`Address not found in your wallet`);
}
} catch (error) {
addError(error as CustomError);
setCheckStatus('idle');
}
};

return (
<>
<Header title={t`Receive ${ticker}`} />
Expand All @@ -132,6 +153,69 @@ export default function Addresses() {
<ReceiveAddress />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className='text-lg font-medium'>
<Trans>Check Address</Trans>
</CardTitle>
<CardDescription>
<Trans>Check if an address is owned by this wallet.</Trans>
</CardDescription>
</CardHeader>
<CardContent className='flex gap-2'>
<Input
placeholder={t`Enter address`}
aria-label={t`Address to check`}
value={addressToCheck}
onChange={(e) => {
setAddressToCheck(e.target.value);
setCheckStatus('idle');
}}
onKeyDown={(e) => {
if (e.key === 'Enter' && addressToCheck) {
handleCheckAddress();
}
}}
aria-describedby='checkAddressDescription'
/>
<Button
variant='secondary'
size='sm'
onClick={handleCheckAddress}
disabled={!addressToCheck}
style={{
...(checkStatus === 'valid' && {
backgroundColor: '#16a34a',
color: 'white',
}),
...(checkStatus === 'invalid' && {
backgroundColor: '#dc2626',
color: 'white',
}),
}}
aria-live='polite'
aria-label={
checkStatus === 'idle'
? t`Check address`
: checkStatus === 'valid'
? t`Address is valid`
: t`Address is invalid`
}
className='w-10'
>
{checkStatus === 'idle' ? (
<CheckIcon className='h-4 w-4' aria-hidden='true' />
) : checkStatus === 'valid' ? (
<CheckIcon className='h-4 w-4' aria-hidden='true' />
) : (
<XCircleIcon className='h-4 w-4' aria-hidden='true' />
)}
</Button>
<div id='checkAddressDescription' className='sr-only'>
<Trans>Press Enter to check the address after typing</Trans>
</div>
</CardContent>
</Card>
<Card className='max-w-full'>
<CardHeader>
<CardTitle className='text-lg font-medium'>
Expand Down

0 comments on commit 086af61

Please sign in to comment.