Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom submit endpoint option #1000

Merged
merged 1 commit into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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