Skip to content

Commit

Permalink
feat: add custom submit endpoint option
Browse files Browse the repository at this point in the history
  • Loading branch information
AngelCastilloB authored and greatertomi committed Apr 24, 2024
1 parent 30324be commit 573bdae
Show file tree
Hide file tree
Showing 37 changed files with 455 additions and 50 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, PostHogAction } from '@lace/common';
import { Button, PostHogAction, WarningBanner } from '@lace/common';
import Fail from '../../../assets/icons/exclamation-circle.svg';
import styles from './Layout.module.scss';
import { Image } from 'antd';
import { useAnalyticsContext } from '@providers';
import { TX_CREATION_TYPE_KEY, TxCreationType } from '@providers/AnalyticsProvider/analyticsTracker';
import { useWalletStore } from '@stores';
import { useCustomSubmitApi } from '@hooks';

export const DappTransactionFail = (): React.ReactElement => {
const { t } = useTranslation();
const analytics = useAnalyticsContext();
const { environmentName } = useWalletStore();
const onClose = async () => {
await analytics?.sendEventToPostHog(PostHogAction.SendSomethingWentWrongCancelClick, {
[TX_CREATION_TYPE_KEY]: TxCreationType.External
});
window.close();
};
const { getCustomSubmitApiForNetwork } = useCustomSubmitApi();

useEffect(() => {
analytics?.sendEventToPostHog(PostHogAction.SendSomethingWentWrongView, {
Expand All @@ -33,6 +37,9 @@ export const DappTransactionFail = (): React.ReactElement => {
<div data-testid="dapp-sign-tx-fail-description" className={styles.description}>
{t('dapp.sign.failure.description')}
</div>
{getCustomSubmitApiForNetwork(environmentName).status && (
<WarningBanner message={t('browserView.transaction.send.customSubmitApiBannerText')} />
)}
</div>
<div className={styles.footer}>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
letter-spacing: 0.02em;
line-height: size_unit(3);
margin-top: size_unit(2);
margin-bottom: size_unit(4);
text-align: center;
padding: 0 size_unit(4);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useExternalLinkOpener,
useTheme
} from '@providers';
import { useBalances, useFetchCoinPrice, useLocalStorage, useStakingRewards } from '@hooks';
import { useBalances, useCustomSubmitApi, useFetchCoinPrice, useLocalStorage, useStakingRewards } from '@hooks';
import { useDelegationStore } from '@src/features/delegation/stores';
import { usePassword, useSubmitingState } from '@views/browser/features/send-transaction';
import { networkInfoStatusSelector, useWalletStore } from '@stores';
Expand Down Expand Up @@ -37,6 +37,7 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
const { priceResult } = useFetchCoinPrice();
const { balance } = useBalances(priceResult?.cardano?.price);
const stakingRewards = useStakingRewards();
const { getCustomSubmitApiForNetwork } = useCustomSubmitApi();
const {
walletType,
inMemoryWallet,
Expand All @@ -48,7 +49,8 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
networkInfo,
blockchainProvider,
walletInfo,
currentChain
currentChain,
environmentName
} = useWalletStore((state) => ({
walletType: state.walletType,
inMemoryWallet: state.inMemoryWallet,
Expand All @@ -60,7 +62,8 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
fetchNetworkInfo: state.fetchNetworkInfo,
blockchainProvider: state.blockchainProvider,
walletInfo: state.walletInfo,
currentChain: state.currentChain
currentChain: state.currentChain,
environmentName: state.environmentName
}));
const sendAnalytics = useCallback(() => {
// TODO implement analytics for the new flow
Expand Down Expand Up @@ -140,7 +143,8 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
compactNumber: compactNumberWithUnit,
walletAddress,
currentChain,
isMultidelegationSupportedByDevice
isMultidelegationSupportedByDevice,
isCustomSubmitApiEnabled: getCustomSubmitApiForNetwork(environmentName).status
}}
>
<ContentLayout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ describe('Testing useWalletManager hook', () => {
]
});
expect(clearBackgroundStorage).toBeCalledWith({
except: ['fiatPrices', 'userId', 'usePersistentUserId', 'experimentsConfiguration']
except: ['fiatPrices', 'userId', 'usePersistentUserId', 'experimentsConfiguration', 'customSubmitTxUrl']
});
expect(resetWalletLock).toBeCalledWith();
expect(setCardanoWallet).toBeCalledWith();
Expand Down
2 changes: 2 additions & 0 deletions apps/browser-extension-wallet/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ export * from './useOnAddressSave';
export * from './useAppInit';
export * from './useChainHistoryProvider';
export * from './useWalletAvatar';
export * from './useActionExecution';
export * from './useCustomSubmitApi';
11 changes: 11 additions & 0 deletions apps/browser-extension-wallet/src/hooks/useAppInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { isKeyHashAddress } from '@cardano-sdk/wallet';
import { AddressesDiscoveryStatus } from '@lib/communication/addresses-discoverer';
import { useWalletManager } from './useWalletManager';
import { useWalletState } from './useWalletState';
import { setBackgroundStorage } from '@lib/scripts/background/storage';
import { useCustomSubmitApi } from '@hooks/useCustomSubmitApi';

export const useAppInit = (): void => {
const {
Expand All @@ -19,6 +21,8 @@ export const useAppInit = (): void => {
} = useWalletStore();
const { loadWallet, walletManager, walletRepository } = useWalletManager();
const walletState = useWalletState();
const { environmentName } = useWalletStore();
const { getCustomSubmitApiForNetwork } = useCustomSubmitApi();

useEffect(() => {
setWalletState(walletState);
Expand Down Expand Up @@ -46,6 +50,13 @@ export const useAppInit = (): void => {
}
}, [walletInfo, initialHdDiscoveryCompleted, setAddressesDiscoveryCompleted]);

useEffect(() => {
(async () => {
environmentName &&
(await setBackgroundStorage({ customSubmitTxUrl: getCustomSubmitApiForNetwork(environmentName).url }));
})();
}, [environmentName, getCustomSubmitApiForNetwork]);

const wallets = useObservable(walletRepository.wallets$);
const activeWalletProps = useObservable(walletManager.activeWalletId$);
useEffect(() => {
Expand Down
26 changes: 26 additions & 0 deletions apps/browser-extension-wallet/src/hooks/useCustomSubmitApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useLocalStorage } from '@hooks/useLocalStorage';
import { EnvironmentTypes } from '@stores';
import { CustomSubmitApiConfig } from '@types';

interface useCustomSubmitApiReturn {
getCustomSubmitApiForNetwork: (network: EnvironmentTypes) => CustomSubmitApiConfig;
updateCustomSubmitApi: (network: EnvironmentTypes, data: CustomSubmitApiConfig) => void;
}

export const useCustomSubmitApi = (): useCustomSubmitApiReturn => {
const [isCustomSubmitApiEnabled, { updateLocalStorage: updateCustomSubmitApiEnabled }] =
useLocalStorage('isCustomSubmitApiEnabled');

const getCustomSubmitApiForNetwork = (network: EnvironmentTypes) => {
const networkConfig = isCustomSubmitApiEnabled?.[network];
const status = networkConfig?.status || false;
const url = networkConfig?.url || '';
return { status, url };
};

const updateCustomSubmitApi = (network: EnvironmentTypes, data: CustomSubmitApiConfig) => {
updateCustomSubmitApiEnabled({ ...isCustomSubmitApiEnabled, [network]: data });
};

return { getCustomSubmitApiForNetwork, updateCustomSubmitApi };
};
50 changes: 46 additions & 4 deletions apps/browser-extension-wallet/src/hooks/useWalletManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable unicorn/no-null */
import { useCallback } from 'react';
import { Wallet } from '@lace/cardano';
import { useWalletStore } from '@stores';
import { EnvironmentTypes, useWalletStore } from '@stores';
import { useAppSettingsContext } from '@providers/AppSettings';
import { useBackgroundServiceAPIContext } from '@providers/BackgroundServiceAPI';
import { AddressBookSchema, addressBookSchema, NftFoldersSchema, nftFoldersSchema, useDbState } from '@src/lib/storage';
Expand Down Expand Up @@ -32,6 +32,8 @@ import {
import { deepEquals, HexBlob } from '@cardano-sdk/util';
import { BackgroundService } from '@lib/scripts/types';
import { getChainName } from '@src/utils/get-chain-name';
import { useCustomSubmitApi } from '@hooks/useCustomSubmitApi';
import { setBackgroundStorage } from '@lib/scripts/background/storage';

const { AVAILABLE_CHAINS, CHAIN } = config();
const DEFAULT_CHAIN_ID = Wallet.Cardano.ChainIds[CHAIN];
Expand Down Expand Up @@ -89,8 +91,15 @@ export interface UseWalletManager {
*/
deleteWallet: (isForgotPasswordFlow?: boolean) => Promise<WalletManagerActivateProps | undefined>;
switchNetwork: (chainName: Wallet.ChainName) => Promise<void>;

/**
* Force the wallet to recreate all providers and reload. This is useful for changing
* provider properties or configurations without switching the wallet.
*/
reloadWallet: () => Promise<void>;
addAccount: (props: WalletManagerAddAccountProps) => Promise<void>;
getMnemonic: (passphrase: Uint8Array) => Promise<string[]>;
enableCustomNode: (network: EnvironmentTypes, value: string) => Promise<void>;
}

const clearBytes = (bytes: Uint8Array) => {
Expand Down Expand Up @@ -219,6 +228,7 @@ export const useWalletManager = (): UseWalletManager => {
} = useDbState<NftFoldersSchema, NftFoldersSchema>([], nftFoldersSchema);
const backgroundService = useBackgroundServiceAPIContext();
const userIdService = getUserIdService();
const { getCustomSubmitApiForNetwork, updateCustomSubmitApi } = useCustomSubmitApi();

const getCurrentChainId = useCallback(() => {
if (currentChain) return currentChain;
Expand Down Expand Up @@ -592,7 +602,7 @@ export const useWalletManager = (): UseWalletManager => {
deleteFromLocalStorage('userInfo');
deleteFromLocalStorage('keyAgentData');
await backgroundService.clearBackgroundStorage({
except: ['fiatPrices', 'userId', 'usePersistentUserId', 'experimentsConfiguration']
except: ['fiatPrices', 'userId', 'usePersistentUserId', 'experimentsConfiguration', 'customSubmitTxUrl']
});
resetWalletLock();
setCardanoWallet();
Expand Down Expand Up @@ -639,6 +649,12 @@ export const useWalletManager = (): UseWalletManager => {
]
);

const reloadWallet = useCallback(async (): Promise<void> => {
const activeWallet = await firstValueFrom(walletManager.activeWalletId$);

await walletManager.activate(activeWallet, true);
}, []);

/**
* Deactivates current wallet and activates it again with the new network
*/
Expand All @@ -651,11 +667,22 @@ export const useWalletManager = (): UseWalletManager => {

setAddressesDiscoveryCompleted(false);
updateAppSettings({ ...settings, chainName });
const customSubmitApi = getCustomSubmitApiForNetwork(chainName);
await setBackgroundStorage({ customSubmitTxUrl: customSubmitApi.url });
await reloadWallet();

setCurrentChain(chainName);
setCardanoCoin(chainId);
},
[setAddressesDiscoveryCompleted, updateAppSettings, settings, setCurrentChain, setCardanoCoin]
[
setAddressesDiscoveryCompleted,
updateAppSettings,
settings,
getCustomSubmitApiForNetwork,
reloadWallet,
setCurrentChain,
setCardanoCoin
]
);

/**
Expand Down Expand Up @@ -738,6 +765,19 @@ export const useWalletManager = (): UseWalletManager => {
[getCurrentChainId]
);

const enableCustomNode = useCallback(
async (network: EnvironmentTypes, value: string) => {
const customApiData = {
status: !!value,
url: value
};
updateCustomSubmitApi(network, customApiData);
await backgroundService.setBackgroundStorage({ customSubmitTxUrl: value });
await reloadWallet();
},
[backgroundService, reloadWallet, updateCustomSubmitApi]
);

return {
activateWallet,
addAccount,
Expand All @@ -751,9 +791,11 @@ export const useWalletManager = (): UseWalletManager => {
connectHardwareWalletRevamped,
saveHardwareWallet,
deleteWallet,
reloadWallet,
switchNetwork,
walletManager,
walletRepository,
getMnemonic
getMnemonic,
enableCustomNode
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Wallet } from '@lace/cardano';
import { RemoteApiProperties, RemoteApiPropertyType } from '@cardano-sdk/web-extension';
import { getBaseUrlForChain } from '@src/utils/chain';
import { BackgroundService, UserIdService as UserIdServiceInterface } from '../types';
import { getBackgroundStorage } from '@lib/scripts/background/storage';

export const backgroundServiceProperties: RemoteApiProperties<BackgroundService> = {
requestMessage$: RemoteApiPropertyType.HotObservable,
Expand All @@ -20,16 +21,16 @@ export const backgroundServiceProperties: RemoteApiProperties<BackgroundService>
backendFailures$: RemoteApiPropertyType.HotObservable
};

export const getProviders = (chainName: Wallet.ChainName): Wallet.WalletProvidersDependencies => {
export const getProviders = async (chainName: Wallet.ChainName): Promise<Wallet.WalletProvidersDependencies> => {
const baseCardanoServicesUrl = getBaseUrlForChain(chainName);
const { customSubmitTxUrl } = await getBackgroundStorage();

return Wallet.createProviders({
axiosAdapter: axiosFetchAdapter,
baseUrl: baseCardanoServicesUrl
baseUrl: baseCardanoServicesUrl,
customSubmitTxUrl
});
};
export const ownOrigin = globalThis.location.origin;

export const BASE_EXTENSION_APP_URL = 'app.html';

export const cip30WalletProperties = {
// eslint-disable-next-line max-len
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const chainIdToChainName = (chainId: Cardano.ChainId): Wallet.ChainName => {
const walletFactory: WalletFactory<Wallet.WalletMetadata, Wallet.AccountMetadata> = {
create: async ({ chainId, accountIndex }, wallet, { stores, witnesser }) => {
const chainName: Wallet.ChainName = chainIdToChainName(chainId);
const providers = getProviders(chainName);
const providers = await getProviders(chainName);
if (wallet.type === WalletType.Script || typeof accountIndex !== 'number') {
throw new NotImplementedError('Script wallet support is not implemented');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface BackgroundStorage {
userId?: string;
usePersistentUserId?: boolean;
experimentsConfiguration?: Record<string, string | boolean>;
customSubmitTxUrl?: string;
}

export type BackgroundStorageKeys = keyof BackgroundStorage;
Expand Down
15 changes: 15 additions & 0 deletions apps/browser-extension-wallet/src/lib/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@
"browserView.transaction.send.confirmationTitle": "Transaction confirmation",
"browserView.transaction.send.signTransactionWithPassword": "Please sign the transaction with your password.",
"browserView.transaction.send.enterWalletPasswordToConfirmTransaction": "Enter your wallet password to confirm transaction",
"browserView.transaction.send.customSubmitApiBannerText": "Custom submit API enabled to send transactions. To disable, go to Settings >> Custom submit API",
"browserView.settings.heading": "Settings",
"browserView.settings.wallet.title": "Wallet",
"browserView.settings.wallet.about.title": "About",
Expand Down Expand Up @@ -798,6 +799,20 @@
"browserView.settings.wallet.network.description": "Switch from mainnet to testnet",
"browserView.settings.wallet.network.drawerDescription": "Select the network you would like your wallet to operate on.",
"browserView.settings.wallet.network.networkSwitched": "Switched network",
"browserView.settings.wallet.customSubmitApi.title": "Custom submit API",
"browserView.settings.wallet.customSubmitApi.description": "Ensure your local node is on the same network as your wallet and fully synced to submit transactions.",
"browserView.settings.wallet.customSubmitApi.descriptionLink": "Learn more about Cardano-submit-API",
"browserView.settings.wallet.customSubmitApi.defaultAddress": "Default address: {{url}}",
"browserView.settings.wallet.customSubmitApi.settingsLinkTitle": "Custom Submit API",
"browserView.settings.wallet.customSubmitApi.settingsLinkDescription": "Send transactions through a local Cardano node and cardano-submit-api",
"browserView.settings.wallet.customSubmitApi.usingCustomTxSubmitEndpoint": "You custom submit API is enabled to submit transactions",
"browserView.settings.wallet.customSubmitApi.usingStandardTxSubmitEndpoint": "You custom submit API is disabled. Lace will send transactions through its infrastructure",
"browserView.settings.wallet.customSubmitApi.inputLabel": "Insert the URL here",
"browserView.settings.wallet.customSubmitApi.enable": "Enable",
"browserView.settings.wallet.customSubmitApi.disable": "Disable",
"browserView.settings.wallet.customSubmitApi.enabled": "Enabled",
"browserView.settings.wallet.customSubmitApi.disabled": "Disabled",
"browserView.settings.wallet.customSubmitApi.validationError": "Invalid URL",
"browserView.settings.wallet.authorizedDApps.title": "Authorized DApps",
"browserView.settings.wallet.authorizedDApps.description": "See and manage authorized DApps",
"browserView.settings.wallet.walletSync.description": "Sync new addresses of your HD wallet. This may be needed if you're using other HD wallets at the same time as Lace",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const IBlockchainProvider = {

export const getProviderByChain: BlockchainProviderFactory = (chain = CHAIN) => {
const baseCardanoServicesUrl = getBaseUrlForChain(chain);

const providers = Wallet.createProviders({
axiosAdapter: axiosFetchAdapter,
baseUrl: baseCardanoServicesUrl
Expand Down
Loading

0 comments on commit 573bdae

Please sign in to comment.