From b9b26fdaf490c77652b23d3162e20be967e1d94d Mon Sep 17 00:00:00 2001 From: Izumi Hoshino Date: Thu, 14 Nov 2024 04:20:48 +0900 Subject: [PATCH] Adding Walletconnect endpoints for DIDs, Datalayer, and NFT Bulk Mint (#2521) * added walletconnect endpoints for datalayer and dids * added nft_mint_bulk and push_tx walletconnect endpoints * added permissionRequest walletconnect command * cleaned up requestPermissions and got it mostly working * made requestPermission filter out commands that don't allow bypass * Fixed prettier errors * Checkpoint * Checkpoint * Checkpoint * Checkpoint * Checkpoint --------- Co-authored-by: Gaerax --- packages/api-react/src/services/dataLayer.ts | 198 ++++ packages/api-react/src/services/fullNode.ts | 9 +- packages/api-react/src/services/index.ts | 38 + packages/api-react/src/services/wallet.ts | 32 +- packages/api/src/@types/DataLayerChange.ts | 5 + packages/api/src/@types/DataLayerRootHash.ts | 8 + packages/api/src/services/DataLayer.ts | 265 +++++ packages/api/src/services/FullNode.ts | 5 + packages/api/src/services/index.ts | 1 + packages/api/src/wallets/DID.ts | 71 +- packages/api/src/wallets/DL.ts | 253 ++++ packages/api/src/wallets/NFT.ts | 44 + packages/api/src/wallets/index.ts | 1 + .../@types/WalletConnectCommandParamName.ts | 43 + ...ConnectRequestPermissionsConfirmDialog.tsx | 168 +++ .../src/constants/WalletConnectCommands.tsx | 1046 ++++++++++++++++- .../gui/src/hooks/useWalletConnectCommand.tsx | 29 +- .../gui/src/hooks/useWalletConnectPairs.ts | 24 + 18 files changed, 2212 insertions(+), 28 deletions(-) create mode 100644 packages/api-react/src/services/dataLayer.ts create mode 100644 packages/api/src/@types/DataLayerChange.ts create mode 100644 packages/api/src/@types/DataLayerRootHash.ts create mode 100644 packages/api/src/services/DataLayer.ts create mode 100644 packages/api/src/wallets/DL.ts create mode 100644 packages/gui/src/components/walletConnect/WalletConnectRequestPermissionsConfirmDialog.tsx diff --git a/packages/api-react/src/services/dataLayer.ts b/packages/api-react/src/services/dataLayer.ts new file mode 100644 index 0000000000..916618353d --- /dev/null +++ b/packages/api-react/src/services/dataLayer.ts @@ -0,0 +1,198 @@ +import { DataLayer } from '@chia-network/api'; + +import api from '../api'; +import { query, mutation } from '../utils/reduxToolkitEndpointAbstractions'; + +export const apiWithTag = api.enhanceEndpoints({ + addTagTypes: [ + 'OwnedDataStores', + 'Roots', + 'RootHistories', + 'LocalRoots', + 'Mirrors', + 'Keys', + 'KeysValues', + 'Values', + 'Ancestors', + 'SyncStatus', + 'KvDiff', + 'Subscriptions', + 'Offers', + 'StoreMirrors', + 'PendingRoots', + 'Plugins', + 'Files', + ], +}); +export const dataLayerApi = apiWithTag.injectEndpoints({ + endpoints: (build) => ({ + addMirror: mutation(build, DataLayer, 'addMirror', { + invalidatesTags: (_result, _error, { id }) => [ + { type: 'StoreMirrors', id: 'LIST' }, + { type: 'StoreMirrors', id }, + ], + }), + + addMissingFiles: mutation(build, DataLayer, 'addMissingFiles', { + invalidatesTags: (_result, _error, { ids }) => (ids ? ids.map((id: string) => ({ type: 'Files', id })) : []), + }), + + batchUpdate: mutation(build, DataLayer, 'batchUpdate', { + invalidatesTags: (_result, _error, { id }) => [ + { type: 'Keys', id }, + { type: 'KeysValues', id }, + ], + }), + + cancelOffer: mutation(build, DataLayer, 'cancelOffer', { + invalidatesTags: (_result, _error, { tradeId }) => [{ type: 'Offers', id: tradeId }], + }), + + checkPlugins: query(build, DataLayer, 'checkPlugins', { + providesTags: [{ type: 'Plugins' }], + }), + + clearPendingRoots: mutation(build, DataLayer, 'clearPendingRoots', { + invalidatesTags: (_result, _error, { storeId }) => [{ type: 'PendingRoots', id: storeId }], + }), + + createDataStore: mutation(build, DataLayer, 'createDataStore', { + invalidatesTags: [{ type: 'OwnedDataStores' }], + }), + + deleteKey: mutation(build, DataLayer, 'deleteKey', { + invalidatesTags: (_result, _error, { id }) => [ + { type: 'Keys', id }, + { type: 'KeysValues', id }, + ], + }), + + deleteMirror: mutation(build, DataLayer, 'deleteMirror', { + invalidatesTags: (_result, _error, { coinId: id }) => [{ type: 'Mirrors', id }], + }), + + getAncestors: query(build, DataLayer, 'getAncestors', { + providesTags: (_result, _error, { id }) => [{ type: 'Ancestors', id }], + }), + + getKeys: query(build, DataLayer, 'getKeys', { + providesTags: (_result, _error, { id }) => [{ type: 'Keys', id }], + }), + + getKeysValues: query(build, DataLayer, 'getKeysValues', { + providesTags: (_result, _error, { id }) => [{ type: 'KeysValues', id }], + }), + + getKvDiff: query(build, DataLayer, 'getKvDiff', { + providesTags: [{ type: 'KvDiff' }], + }), + + getLocalRoot: query(build, DataLayer, 'getLocalRoot', { + providesTags: (_result, _error, { id }) => [{ type: 'LocalRoots', id }], + }), + + getMirrors: query(build, DataLayer, 'getMirrors', { + providesTags: (_result, _error, { id }) => [{ type: 'Mirrors', id }], + }), + + getOwnedStores: query(build, DataLayer, 'getOwnedStores', { + providesTags: [{ type: 'OwnedDataStores' }], + }), + + getRoot: query(build, DataLayer, 'getRoot', { + providesTags: (_result, _error, { id }) => [{ type: 'Roots', id }], + }), + + getRoots: query(build, DataLayer, 'getRoots', { + providesTags: [{ type: 'Roots', id: 'LIST' }], + }), + + getRootHistory: query(build, DataLayer, 'getRootHistory', { + providesTags: (_result, _error, { id }) => [{ type: 'RootHistories', id }], + }), + + getSyncStatus: query(build, DataLayer, 'getSyncStatus', { + providesTags: (_result, _error, { id }) => [{ type: 'SyncStatus', id }], + }), + + getValue: query(build, DataLayer, 'getValue', { + providesTags: (_result, _error, { id, key }) => [{ type: 'Values', id: id + key }], + }), + + insert: mutation(build, DataLayer, 'insert', { + invalidatesTags: (_result, _error, { id }) => [ + { type: 'Offers', id: 'LIST' }, + { type: 'Offers', id }, + ], + }), + + makeOffer: mutation(build, DataLayer, 'makeOffer', { + invalidatesTags: [{ type: 'Offers', id: 'LIST' }], + }), + + removeSubscriptions: mutation(build, DataLayer, 'removeSubscriptions', { + invalidatesTags: (_result, _error, { id }) => [ + { type: 'Subscriptions', id: 'LIST' }, + { type: 'Subscriptions', id }, + ], + }), + + subscribe: mutation(build, DataLayer, 'subscribe', { + invalidatesTags: (_result, _error, { id }) => [ + { type: 'Subscriptions', id: 'LIST' }, + { type: 'Subscriptions', id }, + ], + }), + + subscriptions: query(build, DataLayer, 'subscriptions', { + providesTags: [{ type: 'Subscriptions' }], + }), + + takeOffer: mutation(build, DataLayer, 'takeOffer', { + invalidatesTags: [{ type: 'Offers', id: 'LIST' }], + }), + + unsubscribe: mutation(build, DataLayer, 'unsubscribe', { + invalidatesTags: (_result, _error, { id }) => [ + { type: 'Subscriptions', id: 'LIST' }, + { type: 'Subscriptions', id }, + ], + }), + + verifyOffer: query(build, DataLayer, 'verifyOffer', { + providesTags: [{ type: 'Offers' }], + }), + }), +}); + +export const { + useAddMirrorMutation, + useAddMissingFilesMutation, + useBatchUpdateMutation, + useCancelOfferMutation, + useCheckPluginsQuery, + useClearPendingRootsMutation, + useCreateDataStoreMutation, + useDeleteKeyMutation, + useDeleteMirrorMutation, + useGetAncestorsQuery, + useGetKeysQuery, + useGetKeysValuesQuery, + useGetKvDiffQuery, + useGetLocalRootQuery, + useGetMirrorsQuery, + useGetOwnedStoresQuery, + useGetRootQuery, + useGetRootsQuery, + useGetRootHistoryQuery, + useGetSyncStatusQuery, + useGetValueQuery, + useInsertMutation, + useMakeOfferMutation, + useRemoveSubscriptionsMutation, + useSubscribeMutation, + useSubscriptionsQuery, + useTakeOfferMutation, + useUnsubscribeMutation, + useVerifyOfferQuery, +} = dataLayerApi; diff --git a/packages/api-react/src/services/fullNode.ts b/packages/api-react/src/services/fullNode.ts index be30bef5f4..b9b1a38dc2 100644 --- a/packages/api-react/src/services/fullNode.ts +++ b/packages/api-react/src/services/fullNode.ts @@ -7,7 +7,9 @@ import api, { baseQuery } from '../api'; import onCacheEntryAddedInvalidate from '../utils/onCacheEntryAddedInvalidate'; import { query, mutation } from '../utils/reduxToolkitEndpointAbstractions'; -const apiWithTag = api.enhanceEndpoints({ addTagTypes: ['BlockchainState', 'FeeEstimate', 'FullNodeConnections'] }); +const apiWithTag = api.enhanceEndpoints({ + addTagTypes: ['BlockchainState', 'FeeEstimate', 'FullNodeConnections', 'Transactions'], +}); // // Examples with levels of abstraction // export const myTestApi = apiWithTag.injectEndpoints({ @@ -142,6 +144,10 @@ export const fullNodeApi = apiWithTag.injectEndpoints({ getFeeEstimate: query(build, FullNode, 'getFeeEstimate', { providesTags: [{ type: 'FeeEstimate' }], }), + + pushTx: mutation(build, FullNode, 'pushTx', { + invalidatesTags: [{ type: 'Transactions', id: 'LIST' }], + }), }), }); @@ -156,4 +162,5 @@ export const { useGetBlockQuery, useGetBlockRecordQuery, useGetFeeEstimateQuery, + usePushTxMutation, } = fullNodeApi; diff --git a/packages/api-react/src/services/index.ts b/packages/api-react/src/services/index.ts index d6711c5e01..8e01ec3e88 100644 --- a/packages/api-react/src/services/index.ts +++ b/packages/api-react/src/services/index.ts @@ -1,5 +1,6 @@ import * as client from './client'; import * as daemon from './daemon'; +import * as dataLayer from './dataLayer'; import * as farmer from './farmer'; import * as fullNode from './fullNode'; import * as harvester from './harvester'; @@ -43,6 +44,39 @@ export const { useGetPublicKeyQuery, } = daemon; +export const { + dataLayerApi, + + useAddMirrorMutation, + useAddMissingFilesMutation, + useBatchUpdateMutation, + useCheckPluginsQuery, + useClearPendingRootsMutation, + useCreateDataStoreMutation, + useDeleteKeyMutation: useDeleteDataLayerKeyMutation, + useDeleteMirrorMutation, + useGetAncestorsQuery, + useGetKeysQuery: useGetDataLayerKeysQuery, + useGetKeysValuesQuery, + useGetKvDiffQuery, + useGetLocalRootQuery, + useGetMirrorsQuery, + useGetOwnedStoresQuery, + useGetRootQuery, + useGetRootsQuery, + useGetRootHistoryQuery, + useGetSyncStatusQuery: useGetDataLayerSyncStatusQuery, + useGetValueQuery, + useInsertMutation, + useMakeOfferMutation, + useRemoveSubscriptionsMutation, + useSubscribeMutation, + useSubscriptionsQuery, + useTakeOfferMutation: useTakeDataLayerOfferMutation, + useUnsubscribeMutation, + useVerifyOfferQuery, +} = dataLayer; + // farmer hooks export const { farmerApi, @@ -86,6 +120,7 @@ export const { useGetBlockQuery, useGetBlockRecordQuery, useGetFeeEstimateQuery, + usePushTxMutation, } = fullNode; // wallet hooks @@ -167,6 +202,8 @@ export const { useGetDIDNameQuery, useSetDIDNameMutation, useGetDIDRecoveryListQuery, + useGetDIDMetadataQuery, + useUpdateDIDMetadataMutation, useGetDIDInformationNeededForRecoveryQuery, useGetDIDCurrentCoinInfoQuery, useGetDIDInfoQuery, @@ -181,6 +218,7 @@ export const { useGetNFTWalletsWithDIDsQuery, useGetNFTInfoQuery, useLazyGetNFTInfoQuery, + useMintBulkMutation, useMintNFTMutation, useTransferNFTMutation, useSetNFTDIDMutation, diff --git a/packages/api-react/src/services/wallet.ts b/packages/api-react/src/services/wallet.ts index 696b0214ff..ccbd11d726 100644 --- a/packages/api-react/src/services/wallet.ts +++ b/packages/api-react/src/services/wallet.ts @@ -14,6 +14,7 @@ const tagTypes = [ 'DID', 'DIDCoinInfo', 'DIDInfo', + 'DIDMetadata', 'DIDName', 'DIDPubKey', 'DIDRecoveryInfo', @@ -1132,7 +1133,28 @@ export const walletApi = apiWithTag.injectEndpoints({ }), getDIDInfo: query(build, DID, 'getDidInfo', { - providesTags: (result, _error, { coinOrDIDId }) => (result ? [{ type: 'DIDInfo', id: coinOrDIDId }] : []), + providesTags: (result, _error, { coinId }) => (result ? [{ type: 'DIDInfo', id: coinId }] : []), + }), + + // findLostDID: mutation(build, DID, 'findLostDID', { + // invalidatesTags: (_result, _error, { coinId }) => [ + // { type: 'DIDInfo', id: coinId }, + // { type: 'DIDCoinInfo', id: coinId }, + // ], + // }), + + getDIDMetadata: query(build, DID, 'getDidMetadata', { + providesTags: (result, _error, { walletId }) => (result ? [{ type: 'DIDMetadata', id: walletId }] : []), + }), + + updateDIDMetadata: mutation(build, DID, 'updateDidMetadata', { + invalidatesTags: (_result, _error, { walletId }) => [ + { type: 'DIDInfo' }, + { type: 'DIDCoinInfo', id: walletId }, + { type: 'Wallets', id: walletId }, + { type: 'DIDWallet', id: walletId }, + { type: 'DIDMetadata', id: walletId }, + ], }), // createDIDBackup: did_create_backup_file needs an RPC change (remove filename param, return file contents) @@ -1354,6 +1376,10 @@ export const walletApi = apiWithTag.injectEndpoints({ providesTags: (result, _error) => (result ? [{ type: 'NFTInfo', id: result.launcherId }] : []), }), + mintBulk: mutation(build, NFT, 'mintBulk', { + invalidatesTags: (result, _error) => (result ? [{ type: 'NFTInfo', id: 'LIST' }] : []), + }), + mintNFT: mutation(build, NFT, 'mintNFT', { invalidatesTags: (result, _error) => (result ? [{ type: 'NFTInfo', id: 'LIST' }] : []), }), @@ -1527,6 +1553,9 @@ export const { useGetDIDPubKeyQuery, useGetDIDQuery, useGetDIDsQuery, + useGetDIDMetadataQuery, + useUpdateDIDMetadataMutation, + // useFindLostDIDMutation, useGetDIDNameQuery, useSetDIDNameMutation, useGetDIDRecoveryListQuery, @@ -1544,6 +1573,7 @@ export const { useGetNFTWalletsWithDIDsQuery, useGetNFTInfoQuery, useLazyGetNFTInfoQuery, + useMintBulkMutation, useMintNFTMutation, useTransferNFTMutation, useSetNFTDIDMutation, diff --git a/packages/api/src/@types/DataLayerChange.ts b/packages/api/src/@types/DataLayerChange.ts new file mode 100644 index 0000000000..adb09b6d83 --- /dev/null +++ b/packages/api/src/@types/DataLayerChange.ts @@ -0,0 +1,5 @@ +export interface DataLayerChange { + action: 'delete' | 'insert'; + key: string; + value?: string; +} diff --git a/packages/api/src/@types/DataLayerRootHash.ts b/packages/api/src/@types/DataLayerRootHash.ts new file mode 100644 index 0000000000..d496b6b1e7 --- /dev/null +++ b/packages/api/src/@types/DataLayerRootHash.ts @@ -0,0 +1,8 @@ +type DataLayerRootHash = { + confirmed: boolean; + hash: string; + id: string; + timestamp: number; +}; + +export default DataLayerRootHash; diff --git a/packages/api/src/services/DataLayer.ts b/packages/api/src/services/DataLayer.ts new file mode 100644 index 0000000000..6a9e66c339 --- /dev/null +++ b/packages/api/src/services/DataLayer.ts @@ -0,0 +1,265 @@ +import { DataLayerChange } from '../@types/DataLayerChange'; +import Client from '../Client'; +import ServiceName from '../constants/ServiceName'; + +import Service from './Service'; +import type { Options } from './Service'; + +export default class DataLayer extends Service { + constructor(client: Client, options?: Options) { + super(ServiceName.DATALAYER, client, options); + } + + async addMirror(args: { id: string; urls: string[]; amount: number; fee?: number }) { + return this.command<{}>('add_mirror', args); + } + + async addMissingFiles(args: { ids?: string[]; override?: boolean; foldername?: string }) { + return this.command<{}>('add_missing_files', args); + } + + async batchUpdate(args: { id: string; changelist: DataLayerChange[]; fee?: number; submitOnChain?: boolean }) { + return this.command<{ txId?: string }>('batch_update', args); + } + + async cancelOffer(args: { tradeId: string; secure: boolean; fee?: number }) { + return this.command<{ success: boolean }>('cancel_offer', args); + } + + async checkPlugins(args: {}) { + return this.command<{ + pluginStatus: { + uploaders: Record; + downloaders: Record; + }; + }>('check_plugins', args); + } + + async clearPendingRoots(args: { storeId: string }) { + return this.command<{ + success: boolean; + root: + | { + treeId: string; + nodeHash: string | undefined; + generation: number; + status: number; + } + | undefined; + }>('clear_pending_roots', args); + } + + async createDataStore(args: { fee?: number; verbose?: boolean }) { + return this.command<{ txs?: any[]; id: string }>('create_data_store', args); + } + + async deleteKey(args: { id: string; key: string; fee?: number }) { + return this.command<{ txId: string }>('delete_key', args); + } + + async deleteMirror(args: { coinId: string; fee?: number }) { + return this.command<{}>('delete_mirror', args); + } + + async getAncestors(args: { id: string; hash: string }) { + return this.command<{ + ancestors: Array<{ + hash: string; + leftHash: string; + rightHash: string; + }>; + }>('get_ancestors', args); + } + + async getKeys(args: { id: string; rootHash?: string; page?: number; maxPageSize?: number }) { + return this.command< + | { + keys: string[]; + } + | { + keys: string[]; + totalPages: number; + totalBytes: number; + rootHash: string | undefined; + } + >('get_keys', args); + } + + async getKeysValues(args: { id: string; rootHash?: string; page?: number; maxPageSize?: number }) { + return this.command< + | { + keysValues: Array<{ + hash: string; + key: string; + value: string; + }>; + } + | { + keysValues: Array<{ + hash: string; + key: string; + value: string; + }>; + totalPages: number; + totalBytes: number; + rootHash: string | undefined; + } + >('get_keys_values', args); + } + + async getKvDiff(args: { id: string; hash1: string; hash2: string; page?: number; maxPageSize?: number }) { + return this.command< + | { + diff: Array<{ + type: string; + key: string; + value: string; + }>; + } + | { + diff: Array<{ + type: string; + key: string; + value: string; + }>; + totalPages: number; + totalBytes: number; + } + >('get_kv_diff', args); + } + + async getLocalRoot(args: { id: string }) { + return this.command<{ hash: string | undefined }>('get_local_root', args); + } + + async getMirrors(args: { id: string }) { + return this.command<{ + mirrors: Array<{ + coinId: string; + launcherId: string; + amount: number; + urls: string[]; + ours: boolean; + }>; + }>('get_mirrors', args); + } + + async getOwnedStores(args: {}) { + return this.command<{ storeIds: string[]; success: boolean }>('get_owned_stores', args); + } + + async getRoot(args: { id: string }) { + return this.command<{ + hash: string; + confirmed: boolean; + timestamp: number; + }>('get_root', args); + } + + async getRoots(args: { ids: string[] }) { + return this.command<{ + rootHashes: Array<{ + id: string; + hash: string; + confirmed: boolean; + timestamp: number; + }>; + }>('get_roots', args); + } + + async getRootHistory(args: { id: string }) { + return this.command<{ + rootHistory: Array<{ + rootHash: string; + confirmed: boolean; + timestamp: number; + }>; + }>('get_root_history', args); + } + + async getSyncStatus(args: { id: string }) { + return this.command<{ + syncStatus: { + rootHash: string; + generation: number; + targetRootHash: string; + targetGeneration: number; + }; + }>('get_sync_status', args); + } + + async getValue(args: { id: string; key: string; rootHash?: string }) { + return this.command<{ + value: string | undefined; + }>('get_value', args); + } + + async insert(args: { id: string; key: string; value: string; fee?: number }) { + return this.command<{ txId: string }>('insert', args); + } + + async makeOffer(args: { + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + fee?: number; + }) { + return this.command<{ + success: boolean; + offer: { + tradeId: string; + offer: string; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + }; + }>('make_offer', args); + } + + async removeSubscriptions(args: { id: string; urls: string[] }) { + return this.command<{}>('remove_subscriptions', args); + } + + async subscribe(args: { id: string; urls: string[] }) { + return this.command<{}>('subscribe', args); + } + + async subscriptions(args: {}) { + return this.command<{ storeIds: string[] }>('subscriptions', args); + } + + async takeOffer(args: { + offer: { + tradeId: string; + offer: string; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + }; + fee?: number; + }) { + return this.command<{ success: boolean; tradeId: string }>('take_offer', args); + } + + async unsubscribe(args: { id: string; retain?: boolean }) { + return this.command<{}>('unsubscribe', args); + } + + async verifyOffer(args: { + offer: { + tradeId: string; + offer: string; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + }; + fee?: number; + }) { + return this.command<{ + success: boolean; + valid: boolean; + error: string | undefined; + fee: number | undefined; + }>('verify_offer', args); + } + + async walletLogIn(args: { fingerprint: number }) { + return this.command<{}>('wallet_log_in', args); + } +} diff --git a/packages/api/src/services/FullNode.ts b/packages/api/src/services/FullNode.ts index a8f13d4643..4df3166b35 100644 --- a/packages/api/src/services/FullNode.ts +++ b/packages/api/src/services/FullNode.ts @@ -4,6 +4,7 @@ import type BlockRecord from '../@types/BlockRecord'; import type BlockchainState from '../@types/BlockchainState'; import type Connection from '../@types/Connection'; import type FeeEstimate from '../@types/FeeEstimate'; +import SpendBundle from '../@types/SpendBundle'; import Client from '../Client'; import type Message from '../Message'; import ServiceName from '../constants/ServiceName'; @@ -52,6 +53,10 @@ export default class FullNode extends Service { return this.command('get_fee_estimate', args); } + async pushTx(args: { spendBundle: SpendBundle }) { + return this.command<{ status: string }>('push_tx', args); + } + onBlockchainState(callback: (data: any, message: Message) => void, processData?: (data: any) => any) { return this.onCommand('get_blockchain_state', callback, processData); } diff --git a/packages/api/src/services/index.ts b/packages/api/src/services/index.ts index b90549fa89..fb29bb2ec6 100644 --- a/packages/api/src/services/index.ts +++ b/packages/api/src/services/index.ts @@ -1,4 +1,5 @@ export { default as Daemon } from './Daemon'; +export { default as DataLayer } from './DataLayer'; export { default as Events } from './Events'; export { default as Farmer } from './Farmer'; export type { LatencyData, LatencyInfo, LatencyRecord } from './Farmer'; diff --git a/packages/api/src/wallets/DID.ts b/packages/api/src/wallets/DID.ts index d1a1083f81..099da0e760 100644 --- a/packages/api/src/wallets/DID.ts +++ b/packages/api/src/wallets/DID.ts @@ -1,5 +1,6 @@ import type BigNumber from 'bignumber.js'; +import type { Transaction } from '../@types'; import type SpendBundle from '../@types/SpendBundle'; import type Message from '../Message'; import Wallet from '../services/WalletService'; @@ -106,21 +107,63 @@ export default class DIDWallet extends Wallet { }>('did_get_current_coin_info', args); } - async getDidInfo(args: { coinOrDIDId: string }) { + async getDidInfo(args: { coinId: string; latest?: boolean }) { + return this.command< + | { success: false; error: string } + | { + success: true; + didId: string; + latestCoin: string; + p2Address: string; + publicKey: string; + recoveryListHash: string; + numVerification: number; + metadata: Record; + launcherId: string; + fullPuzzle: string; + solution: any; + hints: string[]; + } + >('did_get_info', args); + } + + async getDidMetadata(args: { walletId: number }) { return this.command<{ - latestCoin: string; - p2Address: string; - publicKey: string; - recoveryListHash: string; - numVerification: number; - metadata: Record; - launcherId: string; - fullPuzzle: string; // hex bytes of serialized CLVM program - solution: any; - hints: string[]; - }>('did_get_info', { - coinId: args.coinOrDIDId, - }); + metadata: any; + }>('did_get_metadata', args); + } + + async updateDidMetadata(args: { + walletId: number; + metadata: Record; + fee?: number; + reusePuzhash?: boolean; + }) { + return this.command< + | { + success: true; + walletId: number; + spendBundle: SpendBundle; + transactions: Transaction[]; + signingResponses?: string[]; + } + | { success: false; error: string } + >('did_update_metadata', args); + } + + async findLostDid(args: { + coinId: string; + recoveryListHash?: string; + numVerification?: number; + metadata?: Record; + }) { + return this.command< + | { + success: true; + latestCoinId: number; + } + | { success: false; error: false } + >('did_find_lost', args); } onDIDCoinAdded(callback: (data: any, message: Message) => void) { diff --git a/packages/api/src/wallets/DL.ts b/packages/api/src/wallets/DL.ts new file mode 100644 index 0000000000..f069a07bf0 --- /dev/null +++ b/packages/api/src/wallets/DL.ts @@ -0,0 +1,253 @@ +import { DataLayerChange } from '../@types/DataLayerChange'; +import Wallet from '../services/WalletService'; + +export default class DLWallet extends Wallet { + async addMirror(args: { id: string; urls: string[]; amount: number; fee?: number }) { + return this.command<{}>('add_mirror', args); + } + + async addMissingFiles(args: { ids?: string[]; override?: boolean; foldername?: string }) { + return this.command<{}>('add_missing_Files', args); + } + + async batchUpdate(args: { id: string; changelist: DataLayerChange[]; fee?: number; submitOnChain?: boolean }) { + return this.command<{ txId?: string }>('batch_update', args); + } + + async cancelDataLayerOffer(args: { tradeId: string; secure: boolean; fee?: number }) { + return this.command<{ success: boolean }>('cancel_offer', args); + } + + async checkPlugins(args: {}) { + return this.command<{ + pluginStatus: { + uploaders: Record; + downloaders: Record; + }; + }>('check_plugins', args); + } + + async clearPendingRoots(args: { storeId: string }) { + return this.command<{ + success: boolean; + root: + | { + treeId: string; + nodeHash: string | undefined; + generation: number; + status: number; + } + | undefined; + }>('clear_pending_roots', args); + } + + async createDataStore(args: { fee?: number; verbose?: boolean }) { + return this.command<{ txs?: any[]; id: string }>('create_data_store', args); + } + + async deleteDataLayerKey(args: { id: string; key: string; fee?: number }) { + return this.command<{ txId: string }>('delete_key', args); + } + + async deleteMirror(args: { coinId: string; fee?: number }) { + return this.command<{}>('delete_mirror', args); + } + + async getAncestors(args: { id: string; hash: string }) { + return this.command<{ + ancestors: Array<{ + hash: string; + leftHash: string; + rightHash: string; + }>; + }>('get_ancestors', args); + } + + async getKeys(args: { id: string; rootHash?: string; page?: number; maxPageSize?: number }) { + return this.command< + | { + keys: string[]; + } + | { + keys: string[]; + totalPages: number; + totalBytes: number; + rootHash: string | undefined; + } + >('get_keys', args); + } + + async getKeysValues(args: { id: string; rootHash?: string; page?: number; maxPageSize?: number }) { + return this.command< + | { + keysValues: Array<{ + hash: string; + key: string; + value: string; + }>; + } + | { + keysValues: Array<{ + hash: string; + key: string; + value: string; + }>; + totalPages: number; + totalBytes: number; + rootHash: string | undefined; + } + >('get_keys_values', args); + } + + async getKvDiff(args: { id: string; hash1: string; hash2: string; page?: number; maxPageSize?: number }) { + return this.command< + | { + diff: Array<{ + type: string; + key: string; + value: string; + }>; + } + | { + diff: Array<{ + type: string; + key: string; + value: string; + }>; + totalPages: number; + totalBytes: number; + } + >('get_kv_diff', args); + } + + async getLocalRoot(args: { id: string }) { + return this.command<{ hash: string | undefined }>('get_local_root', args); + } + + async getMirrors(args: { id: string }) { + return this.command<{ + mirrors: Array<{ + coinId: string; + launcherId: string; + amount: number; + urls: string[]; + ours: boolean; + }>; + }>('get_mirrors', args); + } + + async getOwnedStores(args: {}) { + return this.command<{ storeIds: string[]; success: boolean }>('get_owned_stores', args); + } + + async getRoot(args: { id: string }) { + return this.command<{ + hash: string; + confirmed: boolean; + timestamp: number; + }>('get_root', args); + } + + async getRoots(args: { ids: string[] }) { + return this.command<{ + rootHashes: Array<{ + id: string; + hash: string; + confirmed: boolean; + timestamp: number; + }>; + }>('get_roots', args); + } + + async getRootHistory(args: { id: string }) { + return this.command<{ + rootHistory: Array<{ + rootHash: string; + confirmed: boolean; + timestamp: number; + }>; + }>('get_root_history', args); + } + + async getDataLayerSyncStatus(args: { id: string }) { + return this.command<{ + syncStatus: { + rootHash: string; + generation: number; + targetRootHash: string; + targetGeneration: number; + }; + }>('get_sync_status', args); + } + + async getValue(args: { id: string; key: string; rootHash?: string }) { + return this.command<{ + value: string | undefined; + }>('get_value', args); + } + + async insert(args: { id: string; key: string; value: string; fee?: number }) { + return this.command<{ txId: string }>('insert', args); + } + + async makeDataLayerOffer(args: { + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + fee?: number; + }) { + return this.command<{ + success: boolean; + offer: { + tradeId: string; + offer: string; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + }; + }>('make_offer', args); + } + + async removeSubscriptions(args: { id: string; urls: string[] }) { + return this.command<{}>('remove_subscriptions', args); + } + + async subscribe(args: { id: string; urls: string[] }) { + return this.command<{}>('subscribe', args); + } + + async subscriptions(args: {}) { + return this.command<{ storeIds: string[] }>('subscriptions', args); + } + + async takeDataLayerOffer(args: { + offer: { + tradeId: string; + offer: string; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + }; + fee?: number; + }) { + return this.command<{ success: boolean; tradeId: string }>('take_offer', args); + } + + async unsubscribe(args: { id: string; retain?: boolean }) { + return this.command<{}>('unsubscribe', args); + } + + async verifyOffer(args: { + offer: { + tradeId: string; + offer: string; + taker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + maker: { storeId: string; inclusions: Array<{ key: string; value: string }> }; + }; + fee?: number; + }) { + return this.command<{ + success: boolean; + valid: boolean; + error: string | undefined; + fee: number | undefined; + }>('verify_offer', args); + } +} diff --git a/packages/api/src/wallets/NFT.ts b/packages/api/src/wallets/NFT.ts index cce76583eb..03df4b03a6 100644 --- a/packages/api/src/wallets/NFT.ts +++ b/packages/api/src/wallets/NFT.ts @@ -1,8 +1,10 @@ import type BigNumber from 'bignumber.js'; import type CalculateRoyaltiesRequest from '../@types/CalculateRoyaltiesRequest'; +import type Coin from '../@types/Coin'; import type NFTInfo from '../@types/NFTInfo'; import type SpendBundle from '../@types/SpendBundle'; +import type Transaction from '../@types/Transaction'; import Wallet from '../services/WalletService'; export default class NFTWallet extends Wallet { @@ -36,6 +38,48 @@ export default class NFTWallet extends Wallet { }>('nft_get_wallet_did', args); } + async mintBulk(args: { + walletId: number; + metadataList: Array<{ + uris: string[]; + metaUris: string[]; + licenseUris: string[]; + hash: string; + editionNumber?: number; + editionTotal?: number; + metaHash?: string; + licenseHash?: string; + }>; + royaltyPercentage?: number; + royaltyAddress?: string; + targetList?: string[]; + mintNumberStart?: number; + mintTotal?: number; + xchCoins?: Coin[]; + xchChangeTarget?: string; + newInnerpuzhash?: string; + newP2Puzhash?: string; + didCoin?: Coin; + didLineageParent?: string; + mintFromDid?: boolean; + fee?: number; + reusePuzhash?: boolean; + }) { + return this.command< + | { + success: true; + spendBundle: SpendBundle; + nftIdList: string[]; + transactions: Transaction[]; + signingResponse?: string; + } + | { + success: false; + error: string; + } + >('nft_mint_bulk', args); + } + async mintNFT(args: { walletId: number; royaltyAddress: string; diff --git a/packages/api/src/wallets/index.ts b/packages/api/src/wallets/index.ts index 7eebdaed50..e543fd6346 100644 --- a/packages/api/src/wallets/index.ts +++ b/packages/api/src/wallets/index.ts @@ -1,5 +1,6 @@ export { default as CAT } from './CAT'; export { default as DID } from './DID'; +export { default as DL } from './DL'; export { default as NFT } from './NFT'; export { default as Pool } from './Pool'; export { default as RL } from './RL'; diff --git a/packages/gui/src/@types/WalletConnectCommandParamName.ts b/packages/gui/src/@types/WalletConnectCommandParamName.ts index ec7f94f473..db207c1c04 100644 --- a/packages/gui/src/@types/WalletConnectCommandParamName.ts +++ b/packages/gui/src/@types/WalletConnectCommandParamName.ts @@ -3,12 +3,19 @@ enum WalletConnectCommandParamName { ALL_FINGERPRINTS = 'allFingerprints', AMOUNT = 'amount', ASSET_ID = 'assetId', + ATTEST_DATA = 'attestData', BACKUP_DIDS = 'backupDids', + CHANGELIST = 'changelist', + COIN_ANNOUNCEMENTS = 'coinAnnouncements', COIN_ID = 'coinId', COIN_IDS = 'coinIds', + COIN_NAME = 'coinName', + COMMANDS = 'commands', COUNT = 'count', DID = 'did', + DID_COIN = 'didCoin', DID_ID = 'didId', + DID_LINEAGE_PARENT = 'didLineageParent', DISABLE_JSON_FORMATTING = 'disableJSONFormatting', DRIVER_DICT = 'driverDict', EDITION_NUMBER = 'editionNumber', @@ -17,60 +24,96 @@ enum WalletConnectCommandParamName { FEE = 'fee', FINGERPRINT = 'fingerprint', FINGERPRINTS = 'fingerprints', + FOLDER_NAME = 'foldername', HASH = 'hash', + HASH1 = 'hash1', + HASH2 = 'hash2', ID = 'id', + IDS = 'ids', INCLUDE_DATA = 'includeData', INCLUDE_MY_OFFERS = 'includeMyOffers', INCLUDE_TAKEN_OFFERS = 'includeTakenOffers', INDEX = 'index', + INNER_ADDRESS = 'innerAddress', IS_HEX = 'isHex', + KEY = 'key', LAUNCHER_ID = 'launcherId', LICENSE_HASH = 'licenseHash', LICENSE_URIS = 'licenseUris', + MAKER = 'maker', MEMOS = 'memos', MESSAGE = 'message', META_HASH = 'metaHash', META_URIS = 'metaUris', + METADATA = 'metadata', + METADATA_LIST = 'metadataList', + MINT_FROM_DID = 'mintFromDid', + MINT_NUMBER_START = 'mintNumberStart', + MINT_TOTAL = 'mintTotal', NAME = 'name', NEW_ADDRESS = 'newAddress', + NEW_INNERPUZHASH = 'newInnerpuzhash', + NEW_LIST = 'newList', NEW_PROOF_HASH = 'newProofHash', NEW_PUZHASH = 'newPuzhash', + NEW_P2_PUZHASH = 'newP2Puzhash', NFT_COIN_IDS = 'nftCoinIds', NFT_LAUNCHER_ID = 'nftLauncherId', NON_OBSERVER_DERIVATION = 'nonObserverDerivation', NUM = 'num', NUM_OF_BACKUP_IDS_NEEDED = 'numOfBackupIdsNeeded', + NUM_VERIFICATION = 'numVerification', + NUM_VERIFICATIONS_REQUIRED = 'numVerificationsRequired', OFFER = 'offer', OFFER_DATA = 'offerData', OFFER_ID = 'offerId', + OVERWRITE = 'overwrite', + PAGE = 'page', + MAX_PAGE_SIZE = 'maxPageSize', PROOFS = 'proofs', PROVIDER_INNER_PUZHASH = 'providerInnerPuzhash', PUBKEY = 'pubkey', + PUZHASH = 'puzhash', + PUZZLE_ANNOUNCEMENTS = 'puzzleAnnouncements', PUZZLE_DECORATOR = 'puzzleDecorator', + RECOVERY_LIST_HASH = 'recoveryListHash', REVERSE = 'reverse', + RETAIN = 'retain', REUSE_PUZHASH = 'reusePuzhash', ROOT = 'root', + ROOT_HASH = 'rootHash', ROYALTY_ADDRESS = 'royaltyAddress', ROYALTY_PERCENTAGE = 'royaltyPercentage', SECURE = 'secure', SIGNATURE = 'signature', SIGNING_MODE = 'signingMode', SORT_KEY = 'sortKey', + SPEND_BUNDLE = 'spendBundle', START = 'start', START_INDEX = 'startIndex', + STORE_ID = 'storeId', + SUBMIT_ON_CHAIN = 'submitOnChain', + TAKER = 'taker', TARGET_ADDRESS = 'targetAddress', + TARGET_LIST = 'targetList', TRADE_ID = 'tradeId', TRANSACTION_ID = 'transactionId', TYPE = 'type', URIS = 'uris', URL = 'url', + URLS = 'urls', VALIDATE_ONLY = 'validateOnly', + VALUE = 'value', VC_ID = 'vcId', VC_PARENT_ID = 'vcParentId', + VERBOSE = 'verbose', WAIT_FOR_CONFIRMATION = 'waitForConfirmation', WALLET_ID = 'walletId', WALLET_IDS = 'walletIds', WALLET_IDS_AND_AMOUNTS = 'walletIdsAndAmounts', + WITH_RECOVERY_INFO = 'withRecoveryInfo', + XCH_COINS = 'xchCoins', + XCH_CHANGE_TARGET = 'xchChangeTarget', SAFE_MODE = 'safeMode', } diff --git a/packages/gui/src/components/walletConnect/WalletConnectRequestPermissionsConfirmDialog.tsx b/packages/gui/src/components/walletConnect/WalletConnectRequestPermissionsConfirmDialog.tsx new file mode 100644 index 0000000000..97979b539c --- /dev/null +++ b/packages/gui/src/components/walletConnect/WalletConnectRequestPermissionsConfirmDialog.tsx @@ -0,0 +1,168 @@ +import { useGetKeysQuery } from '@chia-network/api-react'; +import { ConfirmDialog, Flex, LoadingOverlay } from '@chia-network/core'; +import { Trans } from '@lingui/macro'; +import { Alert, Typography, Divider } from '@mui/material'; +import React from 'react'; + +import type WalletConnectCommandParam from '../../@types/WalletConnectCommandParam'; +import walletConnectCommands from '../../constants/WalletConnectCommands'; +import useWalletConnectPairs from '../../hooks/useWalletConnectPairs'; + +import WalletConnectMetadata from './WalletConnectMetadata'; + +export type WalletConnectRequestPermissionsConfirmDialogProps = { + topic: string; + fingerprint: number; + isDifferentFingerprint: boolean; + params: WalletConnectCommandParam[]; + values: Record; + onChange?: (values: Record) => void; + onClose?: (confirmed?: boolean) => void; + open?: boolean; +}; + +export default function WalletConnectRequestPermissionsConfirmDialog( + props: WalletConnectRequestPermissionsConfirmDialogProps, +) { + const { + topic, + fingerprint, + isDifferentFingerprint, + onClose = () => {}, + open = false, + params, + values: defaultValues, + onChange, + } = props; + + const [values, setValues] = React.useState(defaultValues); + const { getPairBySession, bypassCommands } = useWalletConnectPairs(); + const { data: keys, isLoading: isLoadingPublicKeys } = useGetKeysQuery({}); + const key = keys?.find((item) => item.fingerprint === fingerprint); + + const pair = React.useMemo(() => getPairBySession(topic), [topic, getPairBySession]); + + const targetCommands = React.useMemo(() => { + for (let i = 0; i < params.length; i++) { + const p = params[i]; + if (p.name === 'commands') { + return (values.commands as string[]).map((cmd) => { + const cmdWithoutPrefix = cmd.replace('chia_', ''); + const cmdDescription = walletConnectCommands.find((item) => item.command === cmdWithoutPrefix); + return { + command: cmdDescription?.command ?? cmdWithoutPrefix, + bypassConfirm: Boolean(cmdDescription?.bypassConfirm), + }; + }); + } + } + return []; + }, [params, values]); + + const disableApprove = React.useMemo(() => targetCommands.some((cmd) => !cmd.bypassConfirm), [targetCommands]); + + const handleClose = React.useCallback( + (confirmed: boolean) => { + if (confirmed) { + // filter out commands that don't allow bypassing confirmation + const bypassingCommands = targetCommands.filter((cmd) => cmd.bypassConfirm).map((cmd) => cmd.command); + bypassCommands(topic, bypassingCommands, true); + } + + onClose?.(confirmed); + }, + [onClose, topic, bypassCommands, targetCommands], + ); + + const handleChangeValues = React.useCallback( + (newValues: Record) => { + setValues(newValues); + onChange?.(newValues); + }, + [setValues, onChange], + ); + + return ( + Requesting Permissions} + confirmColor="primary" + confirmTitle={Approve} + cancelTitle={Reject} + onClose={handleClose} + open={open} + disableConfirmButton={disableApprove} + > + + + + An app has requested permission to execute the following commands. + + + + After you approve, the connected app can execute these commands on your behalf without confirmation. + + + + {disableApprove && ( + + The following commands are not allowed to bypass confirmation. + + {targetCommands + .filter((cmd) => !cmd.bypassConfirm) + .map((cmd) => cmd.command) + .join(', ')} + + + )} + + {params.length > 0 && ( + + {params.map(({ label, name, hide, displayComponent }) => { + if (hide || !(name in values)) { + return null; + } + + const value = values[name]; + return ( + + {label ?? name} + + {displayComponent + ? displayComponent(value, params, values, handleChangeValues) + : (value?.toString() ?? Not Available)} + + + ); + })} + + )} + + + + + Key + + {!!key && ( + + {key.label ?? Wallet} + + )} + + + {fingerprint} + + + + + + + + Application + + {pair && } + + + + + ); +} diff --git a/packages/gui/src/constants/WalletConnectCommands.tsx b/packages/gui/src/constants/WalletConnectCommands.tsx index 2099218602..a86973a58d 100644 --- a/packages/gui/src/constants/WalletConnectCommands.tsx +++ b/packages/gui/src/constants/WalletConnectCommands.tsx @@ -11,6 +11,21 @@ import WalletConnectCreateOfferPreview from '../components/walletConnect/WalletC import WalletConnectOfferPreview from '../components/walletConnect/WalletConnectOfferPreview'; const walletConnectCommands: WalletConnectCommand[] = [ + { + command: 'requestPermissions', + label: Request Permissions, + description: App is requesting permission to execute these commands, + service: 'EXECUTE', + execute: (values) => ({ values }), + params: [ + { + name: WalletConnectCommandParamName.COMMANDS, + type: 'object', + label: Commands, + displayComponent: (value) => <>{JSON.stringify(value, null, 2)}, + }, + ], + }, { command: 'logIn', label: Log In, @@ -303,6 +318,19 @@ const walletConnectCommands: WalletConnectCommand[] = [ service: ServiceName.WALLET, bypassConfirm: true, }, + { + command: 'pushTx', + label: Push Transaction, + description: Push a spend bundle (transaction) to the blockchain, + service: ServiceName.FULL_NODE, + params: [ + { + name: WalletConnectCommandParamName.SPEND_BUNDLE, + label: Spend Bundle, + type: 'object', + }, + ], + }, // offers { @@ -624,6 +652,109 @@ const walletConnectCommands: WalletConnectCommand[] = [ }, ], }, + { + command: 'mintBulk', + label: Mint Bulk, + description: Create a spend bundle to mint multiple NFTs, + service: ServiceName.WALLET, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + label: Wallet Id, + type: 'number', + }, + { + name: WalletConnectCommandParamName.METADATA_LIST, + label: Metadata List, + type: 'object', + }, + { + name: WalletConnectCommandParamName.ROYALTY_PERCENTAGE, + label: Royalty Percentage, + type: 'BigNumber', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.ROYALTY_ADDRESS, + label: Royalty Address, + type: 'string', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.TARGET_LIST, + label: Target List, + type: 'object', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.MINT_NUMBER_START, + label: Mint Start Number, + type: 'number', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.MINT_TOTAL, + label: Mint Total, + type: 'number', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.XCH_COINS, + label: XCH Coins, + type: 'object', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.XCH_CHANGE_TARGET, + label: XCH Change Target, + type: 'string', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.NEW_INNERPUZHASH, + label: New Inner Puzzle Hash, + type: 'object', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.NEW_P2_PUZHASH, + label: New P2 Puzzle Hash, + type: 'string', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.DID_COIN, + label: DID Coin Dictionary, + type: 'object', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.DID_LINEAGE_PARENT, + label: DID Lineage Parent, + type: 'string', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.MINT_FROM_DID, + label: Mint From DID, + type: 'boolean', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.FEE, + label: Fee, + type: 'BigNumber', + displayComponent: (value) => , + isOptional: true, + }, + { + name: WalletConnectCommandParamName.REUSE_PUZHASH, + label: Reuse Puzzle Hash, + type: 'boolean', + isOptional: true, + }, + ], + }, { command: 'mintNFT', label: Mint NFT, @@ -757,32 +888,931 @@ const walletConnectCommands: WalletConnectCommand[] = [ ], }, - // DIDs + // DataLayer { - command: 'createNewDIDWallet', - label: Create new DID Wallet, - service: ServiceName.WALLET, + command: 'addMirror', + label: Add Mirror, + service: ServiceName.DATALAYER, params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.URLS, + label: URLs, + type: 'object', + }, { name: WalletConnectCommandParamName.AMOUNT, label: Amount, + type: 'number', + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + { + command: 'addMissingFiles', + label: Add Missing Files, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.IDS, + label: Store Ids, + type: 'object', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.OVERWRITE, + label: Overwrite, + type: 'boolean', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.FOLDER_NAME, + label: Folder Name, + type: 'string', + isOptional: true, + }, + ], + }, + { + command: 'batchUpdate', + label: Batch Update, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.CHANGELIST, + label: Changelist, + type: 'object', + }, + { + name: WalletConnectCommandParamName.FEE, type: 'BigNumber', + label: Fee, displayComponent: (value) => , + isOptional: true, + }, + { + name: WalletConnectCommandParamName.SUBMIT_ON_CHAIN, + type: 'boolean', + label: Submit on chain, + isOptional: true, + }, + ], + }, + { + command: 'cancelDataLayerOffer', + label: Cancel DataLayer Offer, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.TRADE_ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.SECURE, + label: Secure, + type: 'boolean', }, { name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + { + command: 'checkPlugins', + label: Check Plugins, + service: ServiceName.DATALAYER, + params: [], + }, + { + command: 'clearPendingRoots', + label: Clear Pending Roots, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.STORE_ID, + label: Store Id, + type: 'string', + }, + ], + }, + { + command: 'createDataStore', + label: Create DataStore, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.FEE, type: 'BigNumber', + label: Fee, displayComponent: (value) => , + isOptional: true, }, { - name: WalletConnectCommandParamName.BACKUP_DIDS, - label: Backup DIDs, + name: WalletConnectCommandParamName.VERBOSE, + type: 'boolean', + label: Verbose, + isOptional: true, }, + ], + }, + { + command: 'deleteKey', + label: Delete Key, + service: ServiceName.DATALAYER, + params: [ { - name: WalletConnectCommandParamName.NUM_OF_BACKUP_IDS_NEEDED, - label: Number of Backup Ids Needed, + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.KEY, + label: Key, + type: 'string', + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + { + command: 'deleteMirror', + label: Delete Mirror, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.COIN_ID, + label: Coin Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + { + command: 'getAncestors', + label: Get Ancestors, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.HASH, + label: Hash, + type: 'string', + }, + ], + }, + { + command: 'getKeys', + label: Get Keys, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.ROOT_HASH, + label: Root Hash, + type: 'string', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.PAGE, + label: Page, + type: 'number', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.MAX_PAGE_SIZE, + label: Max page size, type: 'number', + isOptional: true, + }, + ], + }, + { + command: 'getKeysValues', + label: Get Keys Values, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.ROOT_HASH, + label: Root Hash, + type: 'string', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.PAGE, + label: Page, + type: 'number', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.MAX_PAGE_SIZE, + label: Max page size, + type: 'number', + isOptional: true, + }, + ], + }, + { + command: 'getKvDiff', + label: Get Kv Diff, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.HASH1, + label: Hash 1, + type: 'string', + }, + { + name: WalletConnectCommandParamName.HASH2, + label: Hash 2, + type: 'string', + }, + { + name: WalletConnectCommandParamName.PAGE, + label: Page, + type: 'number', + isOptional: true, + }, + { + name: WalletConnectCommandParamName.MAX_PAGE_SIZE, + label: Max page size, + type: 'number', + isOptional: true, + }, + ], + }, + { + command: 'getLocalRoot', + label: Get Local Root, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + ], + }, + { + command: 'getMirrors', + label: Get Mirrors, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + ], + }, + { + command: 'getOwnedStores', + label: Get Owned Stores, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [], + }, + { + command: 'getRoot', + label: Get Root, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + ], + }, + { + command: 'getRoots', + label: Get Roots, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.IDS, + label: Store Ids, + type: 'object', + }, + ], + }, + { + command: 'getRootHistory', + label: Get Root History, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + ], + }, + { + command: 'getDataLayerSyncStatus', + label: Get DataLayer Sync Status, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + ], + }, + { + command: 'getValue', + label: Get Value, + service: ServiceName.DATALAYER, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.KEY, + label: Key, + type: 'string', + }, + { + name: WalletConnectCommandParamName.ROOT_HASH, + label: Root Hash, + type: 'string', + isOptional: true, + }, + ], + }, + { + command: 'insert', + label: Insert, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.KEY, + label: Key, + type: 'string', + }, + { + name: WalletConnectCommandParamName.VALUE, + label: Value, + type: 'string', + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + { + command: 'makeDataLayerOffer', + label: Make DataLayer Offer, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.MAKER, + label: Maker, + type: 'object', + }, + { + name: WalletConnectCommandParamName.TAKER, + label: Taker, + type: 'object', + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + { + command: 'removeSubscriptions', + label: Remove Subscriptions, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.URLS, + label: URLs, + type: 'object', + }, + ], + }, + { + command: 'subscribe', + label: Subscribe, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.URLS, + label: URLs, + type: 'object', + }, + ], + }, + { + command: 'subscriptions', + label: Subscriptions, + service: ServiceName.DATALAYER, + params: [], + }, + { + command: 'takeDataLayerOffer', + label: Take DataLayer Offer, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.OFFER, + label: Offer, + type: 'object', + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + { + command: 'unsubscribe', + label: Unsubscribe, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.ID, + label: Store Id, + type: 'string', + }, + { + name: WalletConnectCommandParamName.RETAIN, + label: retain, + type: 'boolean', + isOptional: true, + }, + ], + }, + { + command: 'verifyOffer', + label: Verify Offer, + service: ServiceName.DATALAYER, + params: [ + { + name: WalletConnectCommandParamName.OFFER, + label: Offer, + type: 'object', + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + ], + }, + + // DIDs + { + command: 'createNewDIDWallet', + label: Create new DID Wallet, + service: ServiceName.WALLET, + params: [ + { + name: WalletConnectCommandParamName.AMOUNT, + label: Amount, + type: 'BigNumber', + displayComponent: (value) => , + }, + { + name: WalletConnectCommandParamName.FEE, + label: Fee, + type: 'BigNumber', + displayComponent: (value) => , + }, + { + name: WalletConnectCommandParamName.BACKUP_DIDS, + label: Backup DIDs, + }, + { + name: WalletConnectCommandParamName.NUM_OF_BACKUP_IDS_NEEDED, + label: Number of Backup Ids Needed, + type: 'number', + }, + ], + }, + // { + // command: 'didCreateAttest', + // label: Create DID Attest, + // service: ServiceName.WALLET, + // params: [ + // { + // name: WalletConnectCommandParamName.WALLET_ID, + // type: 'number', + // label: Wallet Id, + // }, + // { + // name: WalletConnectCommandParamName.COIN_NAME, + // type: 'string', + // label: Coin Name, + // }, + // { + // name: WalletConnectCommandParamName.PUBKEY, + // type: 'string', + // label: Public Key, + // }, + // { + // name: WalletConnectCommandParamName.PUZHASH, + // type: 'string', + // label: Puzzle Hash, + // }, + // ], + // }, + // { + // command: 'didCreateBackupFile', + // label: Create DID Backup File, + // service: ServiceName.WALLET, + // params: [ + // { + // name: WalletConnectCommandParamName.WALLET_ID, + // type: 'number', + // label: Wallet Id, + // }, + // ], + // }, + { + command: 'findLostDID', + label: Find Lost DID, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.COIN_ID, + type: 'string', + label: Coin Id, + }, + { + name: WalletConnectCommandParamName.RECOVERY_LIST_HASH, + type: 'string', + label: Recovery List Hash, + isOptional: true, + }, + { + name: WalletConnectCommandParamName.NUM_VERIFICATION, + type: 'number', + label: Required Number of DIDs for Verification, + isOptional: true, + }, + { + name: WalletConnectCommandParamName.METADATA, + type: 'string', + label: DID Metadata, + isOptional: true, + }, + ], + }, + { + command: 'getDIDCurrentCoinInfo', + label: Get DID Current Coin Info, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + ], + }, + { + command: 'getDID', + label: Get DID, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + ], + }, + { + command: 'getDIDInfo', + label: Get DID Info, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.COIN_ID, + type: 'string', + label: Coin Id, + }, + ], + }, + { + command: 'getDIDInformationNeededForRecovery', + label: Get Information Needed For DID Recovery, + service: ServiceName.WALLET, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + ], + }, + { + command: 'getDIDMetadata', + label: Get DID Metadata, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + ], + }, + { + command: 'getDIDPubkey', + label: Get DID Public Key, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + ], + }, + { + command: 'getDIDRecoveryList', + label: Get DID Recovery List, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + ], + }, + // { + // command: 'didMessageSpend', + // label: DID Message Spend, + // service: ServiceName.WALLET, + // params: [ + // { + // name: WalletConnectCommandParamName.WALLET_ID, + // type: 'number', + // label: Wallet Id, + // }, + // { + // name: WalletConnectCommandParamName.COIN_ANNOUNCEMENTS, + // type: 'object', + // label: Coin Announcements, + // isOptional: true, + // }, + // { + // name: WalletConnectCommandParamName.PUZZLE_ANNOUNCEMENTS, + // type: 'object', + // label: Puzzle Announcements, + // isOptional: true, + // }, + // ], + // }, + // { + // command: 'didRecoverySpend', + // label: DID Recovery Spend, + // service: ServiceName.WALLET, + // params: [ + // { + // name: WalletConnectCommandParamName.WALLET_ID, + // type: 'number', + // label: Wallet Id, + // }, + // { + // name: WalletConnectCommandParamName.ATTEST_DATA, + // type: 'object', + // label: Attest Data, + // }, + // { + // name: WalletConnectCommandParamName.PUBKEY, + // type: 'string', + // label: DID Public Key, + // isOptional: true, + // }, + // { + // name: WalletConnectCommandParamName.PUZHASH, + // type: 'string', + // label: Puzzle Hash, + // isOptional: true, + // }, + // { + // name: WalletConnectCommandParamName.FEE, + // type: 'BigNumber', + // label: Fee, + // displayComponent: (value) => , + // isOptional: true, + // }, + // ], + // }, + { + command: 'transferDID', + label: Transfer DID, + service: ServiceName.WALLET, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + { + name: WalletConnectCommandParamName.INNER_ADDRESS, + type: 'string', + label: Inner Address, + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + { + name: WalletConnectCommandParamName.WITH_RECOVERY_INFO, + type: 'boolean', + label: With Recovery Info, + isOptional: true, + }, + { + name: WalletConnectCommandParamName.REUSE_PUZHASH, + type: 'boolean', + label: Reuse Puzzle Hash, + isOptional: true, + }, + ], + }, + { + command: 'updateDIDMetadata', + label: Update DID Metadata, + service: ServiceName.WALLET, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + { + name: WalletConnectCommandParamName.METADATA, + type: 'object', + label: DID Metadata, + isOptional: true, + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + { + name: WalletConnectCommandParamName.REUSE_PUZHASH, + type: 'boolean', + label: Reuse Puzzle Hash, + isOptional: true, + }, + ], + }, + { + command: 'updateDIDRecoveryIds', + label: Update DID Recovery Ids, + service: ServiceName.WALLET, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, + }, + { + name: WalletConnectCommandParamName.NEW_LIST, + type: 'object', + label: New Recovery DID List, + }, + { + name: WalletConnectCommandParamName.NUM_VERIFICATIONS_REQUIRED, + type: 'number', + label: Number Of DIDs Required For Recovery, + isOptional: true, + }, + { + name: WalletConnectCommandParamName.FEE, + type: 'BigNumber', + label: Fee, + displayComponent: (value) => , + isOptional: true, + }, + { + name: WalletConnectCommandParamName.REUSE_PUZHASH, + type: 'boolean', + label: Reuse Puzzle Hash, + isOptional: true, + }, + ], + }, + { + command: 'getDIDName', + label: Get DID Name, + service: ServiceName.WALLET, + bypassConfirm: true, + params: [ + { + name: WalletConnectCommandParamName.WALLET_ID, + type: 'number', + label: Wallet Id, }, ], }, diff --git a/packages/gui/src/hooks/useWalletConnectCommand.tsx b/packages/gui/src/hooks/useWalletConnectCommand.tsx index 045533229e..c5e4acc0ff 100644 --- a/packages/gui/src/hooks/useWalletConnectCommand.tsx +++ b/packages/gui/src/hooks/useWalletConnectCommand.tsx @@ -8,6 +8,7 @@ import type Notification from '../@types/Notification'; import type Pair from '../@types/Pair'; import type WalletConnectCommandParam from '../@types/WalletConnectCommandParam'; import WalletConnectConfirmDialog from '../components/walletConnect/WalletConnectConfirmDialog'; +import WalletConnectRequestPermissionsConfirmDialog from '../components/walletConnect/WalletConnectRequestPermissionsConfirmDialog'; import NotificationType from '../constants/NotificationType'; import walletConnectCommands from '../constants/WalletConnectCommands'; import prepareWalletConnectCommand from '../util/prepareWalletConnectCommand'; @@ -112,6 +113,27 @@ export default function useWalletConnectCommand(options: UseWalletConnectCommand log(`bypassing command ${command} with value ${pair.bypassCommands[command]}`); return pair.bypassCommands[command]; } + if (command === 'requestPermissions') { + if (!values.commands || values.commands.some((cmd: string) => cmd === 'requestPermissions')) { + return false; + } + const { bypassCommands } = pair; + const hasPermissions = !!bypassCommands && values.commands.every((cmd: string) => bypassCommands[cmd]); + if (hasPermissions) { + return true; + } + const isConfirmed = await openDialog( + , + ); + return isConfirmed; + } const isConfirmed = await openDialog( , ); - return isConfirmed; } @@ -219,9 +240,9 @@ export default function useWalletConnectCommand(options: UseWalletConnectCommand } // validate current fingerprint again - const currentLoggedInFingerptintPromise = store.dispatch(api.endpoints.getLoggedInFingerprint.initiate()); - const { data: currentFingerprintAfterWait } = await currentLoggedInFingerptintPromise; - currentLoggedInFingerptintPromise.unsubscribe(); + const currentLoggedInFingerprintPromise = store.dispatch(api.endpoints.getLoggedInFingerprint.initiate()); + const { data: currentFingerprintAfterWait } = await currentLoggedInFingerprintPromise; + currentLoggedInFingerprintPromise.unsubscribe(); if (currentFingerprintAfterWait !== fingerprint) { throw new Error(`Fingerprint changed during execution`); diff --git a/packages/gui/src/hooks/useWalletConnectPairs.ts b/packages/gui/src/hooks/useWalletConnectPairs.ts index 905848932e..e5cdb18deb 100644 --- a/packages/gui/src/hooks/useWalletConnectPairs.ts +++ b/packages/gui/src/hooks/useWalletConnectPairs.ts @@ -20,6 +20,7 @@ export type Pairs = { removeSessionFromPair: (sessionTopic: string) => void; bypassCommand: (sessionTopic: string, command: string, confirm: boolean) => void; + bypassCommands: (sessionTopic: string, commands: string[], confirm: boolean) => void; removeBypassCommand: (sessionTopic: string, command: string) => void; resetBypassForAllPairs: () => void; resetBypassForPair: (pairTopic: string) => void; @@ -121,6 +122,27 @@ export default function useWalletConnectPairs(): Pairs { }); }, []); + const bypassCommands = useCallback((sessionTopic: string, commands: string[], confirm: boolean) => { + const [, setPairs] = pairsRef.current; + setPairs((pairs: Pair[]) => { + const pair = pairs.find((item) => item.sessions?.find((session) => session.topic === sessionTopic)); + if (!pair) { + throw new Error('Pair not found'); + } + + return pairs.map((item) => ({ + ...item, + bypassCommands: + item.topic === pair.topic + ? { + ...item.bypassCommands, + ...commands.reduce((acc, command) => ({ ...acc, [command]: confirm }), {}), + } + : item.bypassCommands, + })); + }); + }, []); + const removeBypassCommand = useCallback((sessionTopic: string, command: string) => { const deleteCommand = (commands: Record | undefined) => { const newBypassCommands = { ...commands }; @@ -185,6 +207,7 @@ export default function useWalletConnectPairs(): Pairs { removeSessionFromPair, bypassCommand, + bypassCommands, removeBypassCommand, resetBypassForAllPairs, resetBypassForPair, @@ -201,6 +224,7 @@ export default function useWalletConnectPairs(): Pairs { removePairBySession, removeSessionFromPair, bypassCommand, + bypassCommands, removeBypassCommand, resetBypassForAllPairs, resetBypassForPair,