Skip to content

Commit

Permalink
Merge pull request #8 from useElven/update-deps-guardians-support
Browse files Browse the repository at this point in the history
Update deps guardians support
  • Loading branch information
juliancwirko authored Jul 28, 2023
2 parents f0785cc + 47a567e commit 43be739
Show file tree
Hide file tree
Showing 19 changed files with 2,152 additions and 2,592 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### [0.9.0](https://github.com/useElven/core/releases/tag/v0.9.0) (2023-07-28)
- support for guardians (big thanks to @radumojic)
- update all dependencies

### [0.8.0](https://github.com/useElven/core/releases/tag/v0.8.0) (2023-06-29)
- switch to tsup instead of bare esbuild and tsc, now the configuration should be more reliable
- remove problematic export of `PairingTypes` (**breaking change**). Now it is a custom `PairingTypesStruct`, which is similar to the original one from WalletConnect
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const handleSendTx = () => {
const demoMessage = 'Transaction demo!';
triggerTx({
address: 'erd123.....',
gasLimit: 50000 + 1500 * demoMessage.length,
gasLimit: 50000 + 1500 * demoMessage.length, // + 50000 when guarded
data: new TransactionPayload(demoMessage),
value: TokenTransfer.egldFromBigInteger(1_000_000_000_000_000_000),
});
Expand Down
4,453 changes: 1,910 additions & 2,543 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@useelven/core",
"version": "0.8.0",
"version": "0.9.0",
"description": "Core hooks for MultiversX React DApps",
"license": "MIT",
"author": "Julian Ćwirko <julian.io>",
Expand Down Expand Up @@ -61,33 +61,33 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
"@multiversx/sdk-core": "12.4.3",
"@multiversx/sdk-extension-provider": "2.0.7",
"@multiversx/sdk-hw-provider": "6.1.0",
"@multiversx/sdk-core": "12.6.0",
"@multiversx/sdk-extension-provider": "3.0.0",
"@multiversx/sdk-hw-provider": "6.2.0",
"@multiversx/sdk-native-auth-client": "1.0.4",
"@multiversx/sdk-network-providers": "1.5.0",
"@multiversx/sdk-wallet-connect-provider": "3.2.1",
"@multiversx/sdk-wallet-connect-provider": "4.0.3",
"@multiversx/sdk-web-wallet-provider": "3.0.0",
"lodash.clonedeep": "4.5.0",
"swr": "2.1.5",
"valtio": "1.10.6"
"swr": "2.2.0",
"valtio": "1.11.1"
},
"devDependencies": {
"@types/lodash.clonedeep": "4.5.7",
"@types/node": "20.3.2",
"@types/react": "18.2.14",
"@typescript-eslint/eslint-plugin": "5.60.1",
"@typescript-eslint/parser": "5.60.1",
"eslint": "8.43.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.32.2",
"@types/node": "20.4.5",
"@types/react": "18.2.17",
"@typescript-eslint/eslint-plugin": "6.2.0",
"@typescript-eslint/parser": "6.2.0",
"eslint": "8.45.0",
"eslint-config-prettier": "8.9.0",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-react": "7.33.0",
"eslint-plugin-react-hooks": "4.6.0",
"prettier": "2.8.8",
"prettier": "3.0.0",
"rimraf": "5.0.1",
"terser": "5.18.2",
"terser": "5.19.2",
"tsup": "7.1.0",
"typescript": "5.1.5"
"typescript": "5.1.6"
},
"peerDependencies": {
"react": "18.2.0"
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/common-helpers/getLoginToken.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getNativeAuthClient } from 'src/utils/getNativeAuthClient';
import { getNativeAuthClient } from '../../utils/getNativeAuthClient';
import {
setLoginInfoState,
loginInfoState,
setLoggingInState,
} from '../../store/auth';
import { errorParse } from 'src/utils/errorParse';
import { errorParse } from '../../utils/errorParse';

export const getLoginToken = async () => {
const client = getNativeAuthClient();
Expand Down
95 changes: 92 additions & 3 deletions src/hooks/common-helpers/signAndSendTxOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
import { Dispatch, SetStateAction } from 'react';
import {
Account,
Address,
TransactionWatcher,
Transaction,
ITransactionOnNetwork,
TransactionOptions,
TransactionVersion,
} from '@multiversx/sdk-core';
import { ExtensionProvider } from '@multiversx/sdk-extension-provider';
import { WalletConnectV2Provider } from '@multiversx/sdk-wallet-connect-provider';
import { HWProvider } from '@multiversx/sdk-hw-provider';
import { setAccountState, LoginInfoState } from '../../store/auth';
import { NetworkState } from '../../store/network';
import { ApiNetworkProvider } from '@multiversx/sdk-network-providers';
import { LoginMethodsEnum } from '../../types/enums';
import { LoginMethodsEnum, WebWalletUrlParamsEnum } from '../../types/enums';
import { WalletProvider } from '@multiversx/sdk-web-wallet-provider';
import { DappProvider } from '../../types/network';
import { errorParse } from '../../utils/errorParse';
import { DAPP_INIT_ROUTE } from '../../config/network';

export interface TransactionCallbackParams {
transaction?: Transaction | null;
Expand All @@ -32,6 +36,25 @@ export const preSendTxOperations = (signedTx: Transaction) => {
setAccountState('nonce', currentNonce + 1);
};

export const guardianPreSignTxOperations = (
tx: Transaction,
dappProvider: DappProvider,
activeGuardianAddress?: string
) => {
if (activeGuardianAddress) {
const isLedger = dappProvider instanceof HWProvider;
const options = {
guarded: true,
...(isLedger ? { hashSign: true } : {}),
};
tx.setVersion(TransactionVersion.withTxOptions());
tx.setOptions(TransactionOptions.withOptions(options));
tx.setGuardian(Address.fromBech32(activeGuardianAddress));
}

return tx;
};

export const postSendTxOperations = async (
signedTx: Transaction,
setTransaction: Dispatch<SetStateAction<Transaction | null>>,
Expand All @@ -55,6 +78,53 @@ export const postSendTxOperations = async (
}
};

/**
* Redirect to wallet for signing if:
* - account is guarded &
* - 2FA will not be provided locally &
* - transactions were not signed by guardian
*/
export const checkNeedsGuardianSigning = (
signedTx: Transaction,
activeGuardianAddress?: string,
walletAddress?: string
) => {
if (!walletAddress || !activeGuardianAddress) {
return false;
}

if (signedTx.isGuardedTransaction()) {
return false;
}

return true;
};

export const sendTxToGuardian = async (
signedTx: Transaction,
walletAddress?: string,
webWalletRedirectUrl?: string
) => {
const webWalletProvider = new WalletProvider(
`${walletAddress}${DAPP_INIT_ROUTE}`
);
const currentUrl = window?.location.href;
const callbackUrl =
webWalletRedirectUrl && window
? `${window.location.origin}${webWalletRedirectUrl}`
: currentUrl;

const alteredCallbackUrl = new URL(callbackUrl);
alteredCallbackUrl.searchParams.set(
WebWalletUrlParamsEnum.hasWebWalletGuardianSign,
'true'
);

await webWalletProvider.guardTransactions([signedTx], {
callbackUrl: encodeURIComponent(alteredCallbackUrl.toString()),
});
};

export const signAndSendTxOperations = async (
dappProvider: DappProvider,
tx: Transaction,
Expand All @@ -65,9 +135,16 @@ export const signAndSendTxOperations = async (
setError: Dispatch<SetStateAction<string>>,
setPending: Dispatch<SetStateAction<boolean>>,
webWalletRedirectUrl?: string,
cb?: (params: TransactionCallbackParams) => void
cb?: (params: TransactionCallbackParams) => void,
activeGuardianAddress?: string,
walletAddress?: string
) => {
let signedTx = tx;
let signedTx = guardianPreSignTxOperations(
tx,
dappProvider,
activeGuardianAddress
);

try {
if (dappProvider instanceof WalletProvider) {
const currentUrl = window?.location.href;
Expand All @@ -90,6 +167,18 @@ export const signAndSendTxOperations = async (
signedTx = await dappProvider.signTransaction(tx);
}
if (loginInfoSnap.loginMethod !== LoginMethodsEnum.wallet) {
const needsGuardianSign = checkNeedsGuardianSigning(
signedTx,
activeGuardianAddress,
walletAddress
);

if (needsGuardianSign) {
await sendTxToGuardian(signedTx, walletAddress, webWalletRedirectUrl);

return;
}

preSendTxOperations(signedTx);
await apiNetworkProvider.sendTransaction(signedTx);
await postSendTxOperations(
Expand Down
10 changes: 10 additions & 0 deletions src/hooks/common-helpers/useAccountNetworkSync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,20 @@ export const useAccountNetworkSync = (
const userAccountOnNetwork = await apiNetworkProvider.getAccount(
userAddressInstance
);
const userGuardianOnNetwork =
await apiNetworkProvider.getGuardianData(userAddressInstance);

userAccountInstance.update(userAccountOnNetwork);
setAccountState('address', address);
setAccountState('nonce', userAccountInstance.nonce.valueOf());
setAccountState('balance', userAccountInstance.balance.toString());
setAccountState(
'activeGuardianAddress',
userGuardianOnNetwork.guarded &&
userGuardianOnNetwork.activeGuardian?.address.bech32()
? userGuardianOnNetwork.activeGuardian.address.bech32()
: ''
);
setLoggingInState('loggedIn', Boolean(address));
} catch (e) {
const err = errorParse(e);
Expand Down
44 changes: 36 additions & 8 deletions src/hooks/common-helpers/useWebWalletTxSend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
WALLET_PROVIDER_CALLBACK_PARAM_TX_SIGNED,
} from '@multiversx/sdk-web-wallet-provider';
import { Transaction, ITransactionOnNetwork } from '@multiversx/sdk-core';
import { WalletProvider } from '@multiversx/sdk-web-wallet-provider';
import { PlainSignedTransaction } from '@multiversx/sdk-web-wallet-provider/out/plainSignedTransaction';
import { getParamFromUrl } from '../../utils/getParamFromUrl';
import {
postSendTxOperations,
Expand All @@ -14,7 +16,11 @@ import { errorParse } from '../../utils/errorParse';
import { ApiNetworkProvider } from '@multiversx/sdk-network-providers/out';
import { useAccount } from '../useAccount';
import { setAccountState } from '../../store/auth';
import { useConfig } from '../useConfig';
import { useNetwork } from '../useNetwork';
import { useLoginInfo } from '../useLoginInfo';
import { LoginMethodsEnum, WebWalletUrlParamsEnum } from '../../types/enums';
import { DAPP_INIT_ROUTE } from '../../config/network';

interface UseWebWalletTxSendProps {
setPending: Dispatch<SetStateAction<boolean>>;
Expand All @@ -33,27 +39,49 @@ export const useWebWalletTxSend = ({
}: UseWebWalletTxSendProps) => {
const accountSnap = useAccount();
const networkStateSnap = useNetwork();
const loginInfoSnap = useLoginInfo();
const configStateSnap = useConfig();
const currentNonce = accountSnap.nonce;

useEffect(() => {
const walletProviderStatus = getParamFromUrl(
WALLET_PROVIDER_CALLBACK_PARAM
);
const hasWebWalletGuardianSign = getParamFromUrl(
WebWalletUrlParamsEnum.hasWebWalletGuardianSign
);

const send = async () => {
if (
networkStateSnap.dappProvider &&
'getTransactionsFromWalletUrl' in networkStateSnap.dappProvider &&
networkStateSnap.apiNetworkProvider
) {
const txs =
networkStateSnap.dappProvider.getTransactionsFromWalletUrl();
// For now it is prepared for handling one transaction at a time
const transactionObj = txs?.[0];
let transactionObj: PlainSignedTransaction;
if ('getTransactionsFromWalletUrl' in networkStateSnap.dappProvider) {
const txs =
networkStateSnap.dappProvider.getTransactionsFromWalletUrl();

// For now it is prepared for handling one transaction at a time
transactionObj = txs?.[0];
if (!transactionObj) return;
transactionObj.data = Buffer.from(transactionObj.data).toString(
'base64'
);
} else if (
accountSnap.activeGuardianAddress &&
loginInfoSnap.loginMethod !== LoginMethodsEnum.wallet &&
hasWebWalletGuardianSign
) {
const webWalletProvider = new WalletProvider(
`${configStateSnap.walletAddress}${DAPP_INIT_ROUTE}`
);
const txs = webWalletProvider.getTransactionsFromWalletUrl();
transactionObj = txs?.[0];
} else {
return;
}

if (!transactionObj) return;
transactionObj.data = Buffer.from(transactionObj.data).toString(
'base64'
);

setPending(true);
cb?.({ pending: true });
Expand Down
5 changes: 3 additions & 2 deletions src/hooks/useAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { useProxy } from './useProxy';
import { accountState } from '../store/auth';

export const useAccount = () => {
const { addressIndex, address, balance, nonce } = useProxy(accountState);
const { addressIndex, address, balance, nonce, activeGuardianAddress } =
useProxy(accountState);

return { addressIndex, address, balance, nonce };
return { addressIndex, address, balance, nonce, activeGuardianAddress };
};
12 changes: 9 additions & 3 deletions src/hooks/useApiCall.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useSWR from 'swr';
import useSwrMutation from 'swr/mutation';
import useSWR, { KeyedMutator } from 'swr';
import useSwrMutation, { SWRMutationResponse } from 'swr/mutation';
import { apiCall } from '../utils/apiCall';

interface ApiCallData {
Expand Down Expand Up @@ -43,7 +43,13 @@ export function useApiCall<T>({
options,
autoInit = true,
baseEndpoint,
}: ApiCallData) {
}: ApiCallData): {
data: T;
isLoading: boolean;
isValidating: boolean;
error: any;
fetch: KeyedMutator<any> | SWRMutationResponse['trigger'];
} {
const { data, error, mutate, isValidating, isLoading } = useSWR(
autoInit ? { url, payload, type, baseEndpoint } : null,
apiCallFetcher,
Expand Down
14 changes: 13 additions & 1 deletion src/hooks/useExtensionLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useLoggingIn } from './useLoggingIn';
import { errorParse } from '../utils/errorParse';
import { getLoginToken } from './common-helpers/getLoginToken';
import { useNetwork } from './useNetwork';
import { getNativeAuthClient } from 'src/utils/getNativeAuthClient';
import { getNativeAuthClient } from '../utils/getNativeAuthClient';

export const useExtensionLogin = (params?: Login) => {
const { logout } = useLogout();
Expand Down Expand Up @@ -77,8 +77,20 @@ export const useExtensionLogin = (params?: Login) => {
await networkStateSnap.apiNetworkProvider.getAccount(
userAddressInstance
);
const userGuardianOnNetwork =
await networkStateSnap.apiNetworkProvider.getGuardianData(
userAddressInstance
);

userAccountInstance.update(userAccountOnNetwork);

setAccountState(
'activeGuardianAddress',
userGuardianOnNetwork.guarded &&
userGuardianOnNetwork.activeGuardian?.address.bech32()
? userGuardianOnNetwork.activeGuardian.address.bech32()
: ''
);
setAccountState('address', userAccountInstance.address.bech32());
setAccountState('nonce', userAccountInstance.nonce.valueOf());
setAccountState('balance', userAccountInstance.balance.toString());
Expand Down
Loading

0 comments on commit 43be739

Please sign in to comment.