diff --git a/.sqlx/query-05d7a87d04e669dc8ed1a1152be390d5abfdd0cf202744c65597aec1f2f03ff1.json b/.sqlx/query-05d7a87d04e669dc8ed1a1152be390d5abfdd0cf202744c65597aec1f2f03ff1.json new file mode 100644 index 00000000..01fef587 --- /dev/null +++ b/.sqlx/query-05d7a87d04e669dc8ed1a1152be390d5abfdd0cf202744c65597aec1f2f03ff1.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT DISTINCT minter_did FROM nfts WHERE minter_did IS NOT NULL", + "describe": { + "columns": [ + { + "name": "minter_did", + "ordinal": 0, + "type_info": "Blob" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + true + ] + }, + "hash": "05d7a87d04e669dc8ed1a1152be390d5abfdd0cf202744c65597aec1f2f03ff1" +} diff --git a/crates/sage-api/src/requests/data.rs b/crates/sage-api/src/requests/data.rs index bc367f54..68051c52 100644 --- a/crates/sage-api/src/requests/data.rs +++ b/crates/sage-api/src/requests/data.rs @@ -76,6 +76,14 @@ pub struct GetDidsResponse { pub dids: Vec, } +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)] +pub struct GetMinterDidIds {} + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct GetMinterDidIdsResponse { + pub did_ids: Vec, +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)] pub struct GetPendingTransactions {} diff --git a/crates/sage-cli/src/router.rs b/crates/sage-cli/src/router.rs index c002b846..8fef4db7 100644 --- a/crates/sage-cli/src/router.rs +++ b/crates/sage-cli/src/router.rs @@ -78,6 +78,7 @@ routes!( get_cats await: GetCats = "/get_cats", get_cat await: GetCat = "/get_cat", get_dids await: GetDids = "/get_dids", + get_minter_did_ids await: GetMinterDidIds = "/get_minter_did_ids", get_pending_transactions await: GetPendingTransactions = "/get_pending_transactions", get_transactions await: GetTransactions = "/get_transactions", get_transaction await: GetTransaction = "/get_transaction", diff --git a/crates/sage-database/src/primitives/nfts.rs b/crates/sage-database/src/primitives/nfts.rs index 384b117e..4dbb27b7 100644 --- a/crates/sage-database/src/primitives/nfts.rs +++ b/crates/sage-database/src/primitives/nfts.rs @@ -64,6 +64,10 @@ impl Database { fetch_nft_data(&self.pool, hash).await } + pub async fn distinct_minter_dids(&self) -> Result>> { + distinct_minter_dids(&self.pool).await + } + pub async fn search_nfts( &self, params: NftSearchParams, @@ -438,6 +442,16 @@ async fn insert_nft_data( Ok(()) } +async fn distinct_minter_dids(conn: impl SqliteExecutor<'_>) -> Result>> { + let rows = sqlx::query!("SELECT DISTINCT minter_did FROM nfts WHERE minter_did IS NOT NULL") + .fetch_all(conn) + .await?; + + rows.into_iter() + .map(|row| row.minter_did.map(|bytes| to_bytes32(&bytes)).transpose()) + .collect() +} + async fn fetch_nft_data(conn: impl SqliteExecutor<'_>, hash: Bytes32) -> Result> { let hash = hash.as_ref(); @@ -754,7 +768,10 @@ async fn search_nfts( } // Bind group parameters if present - if let Some(NftGroup::Collection(id) | NftGroup::MinterDid(id)) = ¶ms.group { + //if let Some(NftGroup::Collection(id) | NftGroup::MinterDid(id)) = ¶ms.group { + if let Some(NftGroup::Collection(id) | NftGroup::MinterDid(id) | NftGroup::OwnerDid(id)) = + ¶ms.group + { query = query.bind(id.as_ref()); } diff --git a/crates/sage/src/endpoints/data.rs b/crates/sage/src/endpoints/data.rs index 4da06db5..6b063e60 100644 --- a/crates/sage/src/endpoints/data.rs +++ b/crates/sage/src/endpoints/data.rs @@ -13,13 +13,14 @@ use hex_literal::hex; use sage_api::{ AddressKind, Amount, AssetKind, CatRecord, CoinRecord, DerivationRecord, DidRecord, GetCat, GetCatCoins, GetCatCoinsResponse, GetCatResponse, GetCats, GetCatsResponse, GetDerivations, - GetDerivationsResponse, GetDids, GetDidsResponse, 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, + 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, }; use sage_database::{ CoinKind, CoinStateRow, Database, NftGroup, NftRow, NftSearchParams, NftSortMode, @@ -244,6 +245,24 @@ impl Sage { Ok(GetDidsResponse { dids }) } + pub async fn get_minter_did_ids( + &self, + _req: GetMinterDidIds, + ) -> Result { + let wallet = self.wallet()?; + + let did_ids = wallet + .db + .distinct_minter_dids() + .await? + .into_iter() + .filter_map(|did| did.map(|d| encode_address(d.to_bytes(), "did:chia:").ok())) + .flatten() + .collect(); + + Ok(GetMinterDidIdsResponse { did_ids }) + } + pub async fn get_pending_transactions( &self, _req: GetPendingTransactions, diff --git a/package.json b/package.json index 808c81d7..d520b099 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "react-hook-form": "^7.53.0", "react-imask": "^7.6.1", "react-router-dom": "^6.26.0", + "react-toastify": "^11.0.3", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "tauri-plugin-safe-area-insets": "^0.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fb59215..1613f47f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,6 +137,9 @@ importers: react-router-dom: specifier: ^6.26.0 version: 6.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-toastify: + specifier: ^11.0.3 + version: 11.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^2.5.2 version: 2.5.2 @@ -3246,6 +3249,12 @@ packages: '@types/react': optional: true + react-toastify@11.0.3: + resolution: {integrity: sha512-cbPtHJPfc0sGqVwozBwaTrTu1ogB9+BLLjd4dDXd863qYLj7DGrQ2sg5RAChjFUB4yc3w8iXOtWcJqPK/6xqRQ==} + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -6975,6 +6984,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + react-toastify@11.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 2.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index d996c725..bcefa852 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -376,6 +376,15 @@ pub async fn get_dids(state: State<'_, AppState>, req: GetDids) -> Result, + req: GetMinterDidIds, +) -> Result { + Ok(state.lock().await.get_minter_did_ids(req).await?) +} + #[command] #[specta] pub async fn get_pending_transactions( diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3d28b9e6..5ca914af 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -54,6 +54,7 @@ pub fn run() { commands::get_cats, commands::get_cat, commands::get_dids, + commands::get_minter_did_ids, commands::get_nft_collections, commands::get_nft_collection, commands::get_nfts, diff --git a/src/App.tsx b/src/App.tsx index 660c8ee6..ce1e2556 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,7 +23,6 @@ import useInitialization from './hooks/useInitialization'; import { useWallet } from './hooks/useWallet'; import { loadCatalog } from './i18n'; import Addresses from './pages/Addresses'; -import Collection from './pages/Collection'; import CreateProfile from './pages/CreateProfile'; import CreateWallet from './pages/CreateWallet'; import { DidList } from './pages/DidList'; @@ -47,6 +46,9 @@ import { ViewSavedOffer } from './pages/ViewSavedOffer'; import Wallet from './pages/Wallet'; import { fetchState } from './state'; import QRScanner from './pages/QrScanner'; +import { ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; +import { Slide } from 'react-toastify'; export interface DarkModeContext { toggle: () => void; @@ -76,11 +78,11 @@ const router = createHashRouter( }> } /> } /> + } /> + } /> + } /> } /> - }> - } /> - }> } /> } /> @@ -133,6 +135,25 @@ export default function App() { + ); } diff --git a/src/bindings.ts b/src/bindings.ts index 3cf74a43..854d8f77 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -116,6 +116,9 @@ async getCat(req: GetCat) : Promise { async getDids(req: GetDids) : Promise { return await TAURI_INVOKE("get_dids", { req }); }, +async getMinterDidIds(req: GetMinterDidIds) : Promise { + return await TAURI_INVOKE("get_minter_did_ids", { req }); +}, async getNftCollections(req: GetNftCollections) : Promise { return await TAURI_INVOKE("get_nft_collections", { req }); }, @@ -293,6 +296,8 @@ export type GetKey = { fingerprint?: number | null } export type GetKeyResponse = { key: KeyInfo | null } export type GetKeys = Record export type GetKeysResponse = { keys: KeyInfo[] } +export type GetMinterDidIds = Record +export type GetMinterDidIdsResponse = { did_ids: string[] } export type GetNetworks = Record export type GetNetworksResponse = { networks: { [key in string]: Network } } export type GetNft = { nft_id: string } diff --git a/src/components/MultiSelectActions.tsx b/src/components/MultiSelectActions.tsx index 7ca69599..76525d33 100644 --- a/src/components/MultiSelectActions.tsx +++ b/src/components/MultiSelectActions.tsx @@ -25,6 +25,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from './ui/dropdown-menu'; +import { toast } from 'react-toastify'; export interface MultiSelectActionsProps { selected: string[]; @@ -37,16 +38,12 @@ export function MultiSelectActions({ }: MultiSelectActionsProps) { const walletState = useWalletState(); const offerState = useOfferState(); - const { addError } = useErrors(); - const selectedCount = selected.length; - const [transferOpen, setTransferOpen] = useState(false); const [assignOpen, setAssignOpen] = useState(false); const [burnOpen, setBurnOpen] = useState(false); const [response, setResponse] = useState(null); - const onTransferSubmit = (address: string, fee: string) => { commands .transferNfts({ @@ -58,7 +55,6 @@ export function MultiSelectActions({ .catch(addError) .finally(() => setTransferOpen(false)); }; - const onAssignSubmit = (profile: string | null, fee: string) => { commands .assignNftsToDid({ @@ -70,7 +66,6 @@ export function MultiSelectActions({ .catch(addError) .finally(() => setAssignOpen(false)); }; - const onBurnSubmit = (fee: string) => { commands .transferNfts({ @@ -85,15 +80,25 @@ export function MultiSelectActions({ return ( <> -
- +
+ {selectedCount} selected - @@ -104,8 +109,9 @@ export function MultiSelectActions({ e.stopPropagation(); setTransferOpen(true); }} + aria-label={t`Transfer ${selectedCount} selected NFTs`} > - +