0.0.5 • Published 1 year ago

@hinkal/react-hooks v0.0.5

Weekly downloads
-
License
-
Repository
-
Last release
1 year ago

Hinkal React SDK

The User Flow

A new user has to go through two stages before he is able to do shielded transactions:

  1. Sign-in
  2. Getting an access token through KYC process

In the sign-in stage, the user needs to sign a message "Login To Hinkal". The signature will be used to generate a pair of shielded private and public keys. Only the user should know the shielded private key to sign shielded transactions, whereas the shielded public key will be used in order to receive shielded payments from other members. useSignIn hook gives the connectWallet function which is used in order to walk a user through the sign-in process.

The second stage for the new user involves going through the KYC process and obtaining an access token in order to be able to execute shielded transactions. useKyc hook gives a set of callbacks that are designed to facilitate a user journey through the KYC process. If a user already has an access token, then no additional actions are required.

After passing the two stages above, a user is able to access shielded transactions:

  • deposit
  • transfer
  • withdraw
  • swap
  • deposit with Meson

Sign In

This section provides sign-in functionality to Hinkal Protocol.

Description

useSignIn hook is used in order for a user to get his shielded public and private keys, which are used in order to cryptographically sign shielded transactions and receive shielded payments. Hinkal protocol prompts the user for an Ethereum Signed Message and an optional password used to derive the shieled keys associated with a Hinkal Account.

In addition, useSignIn hook sets change_network and change_account listeners which are used to recalculate shielded balances if the user changes either a network or an account.

Types of Hinkal accounts

Hinkal Protocol supports two types of accounts:

  • Normal account

    For this type of account, the shielded keys are calculated from an Ethereum Signed Message signed with the user's EOA

  • Password-protected account

    For this type of account, the shielded keys are calculated from an Ethereum Signed Message signed with the user's EOA in conjuction with a password string.

    We recommend to use this type of account to improve users' security against phishing attacks.

Minimal usage

The sign in flow is a 3-step process. To let these steps run in the background, a combined signIn function can be used.

The signIn function accepts a provider object as an argument and asks the user to sign a message which is used to derive his shielded keys.

If a password is provided to the useSignIn hook, it is configured in the hinkal instance to derive the password-protected account, otherwise a normal account is configured.

const { signIn } = useSignIn({
  password: 'MyHinkalPassword1337!',
  onSuccess: () => {
    console.log('Hinkal account connected!');
  },
  onError: () => {
    console.log('Connection error.');
  },
});

await signIn(provider);

Advanced usage

If you want to enhance the user experience by providing such features as password-checks and a richer UI, you can use all of the sign in steps in a sequence.

  • initHinkal

    In this step, a Hinkal instance is initiated by connecting to a provider and prompting the user for an Ethereum Signed Message.

  • fetchHinkalAccounts

    The cache of blockchain logs is checked to determine if the user has created a password-protected account in the past.

    The cached data represents a keccak256 hash of the user's password-protected shieldedAddress, with a salt derived from the user's signed message.

    const cachedUserData = ethers.utils.solidityKeccak256(
      ['bytes', 'uint256'],
      [shieldedAddress, ethers.BigNumber.from(ethers.utils.keccak256(hinkalSignedMessage))],
    );
  • connectHinkalUser

    In this step, a provided password is checked against the retrieved onchain logs. If no information about the user is retrieved, a register call is sent to the Hinkal smart contract. After that the final settings are applied to the Hinkal instance and the sign in process is finished.

enum SignInState {
  NOT_CONNECTED,
  FETCHING_ACCOUNTS,
  EXISTING_USER,
  NEW_USER,
  SUCCESS,
}
const [password, setPassword] = useState<string>('');
const [signInState, setSignInState] = (useState<SignInState> = SignInState.NOT_CONNECTED);

const { initHinkal, fetchHinkalUser, connectHinkalUser, checkHinkalPassword, isRecurringHinkalUser } = useSignIn({
  password,
  initHinkalCallback: {
    onSuccess: () => {
      fetchHinkalUser?.();
      setSignInState(SignInState.FETCHING_ACCOUNTS);
    },
    onError: () => {
      console.log('Init step error.');
    },
  },
  fetchHinkalUserCallback: {
    onSuccess: () => {
      if (isRecurringHinkalUser) {
        setSignInState(SignInState.EXISTING_USER);
        return;
      }

      setSignInState(SignInState.NEW_USER);
    },
    onError: () => {
      console.log('Cached user check error.');
    },
  },
  connectHinkalUserCallback: {
    onSuccess: () => {
      setSignInState(SignInState.SUCCESS);
    },
    onError: () => {
      console.log('Connection step error.');
    },
  },
});

switch (signInState) {
  case SignInState.NOT_CONNECTED:
    await initHinkal(provider);
    return;

  case SignInState.FETCHING_ACCOUNTS:
    displayLoader();
    return;

  case SignInState.EXISTING_USER:
    const usingPassword = promptForPasswordUsage();

    if (usingPassword) {
      setPassword(promptUserForPassword());
      const passwordCorrect = checkHinkalPassword();
      if (passwordCorrect) {
        await connectHinkalUser();
        return;
      }

      displayPasswordIncorrectMessage();
      return;
    }

    setPassword('');
    await connectHinkalUser();
    return;

  case SignInState.NEW_USER:
    const usingPassword = promptForPasswordUsage();

    if (usingPassword) {
      setPassword(promptUserForPassword());
      await connectHinkalUser();
      return;
    }

    setPassword('');
    await connectHinkalUser();
    return;

  case SignInState.SUCESS:
    displaySuccessMessage();
    return;
}

API Interface

interface HinkalCallback {
  onError?: (err: Error) => void;
  onSuccess?: () => void;
}

interface SignInCallback extends HinkalCallback {
  onChainChanged?: (chainId: string) => void;
  onAccountChanged?: () => void;
}

interface UseSignInParams extends SignInCallback {
  password?: string;
  initHinkalCallback?: HinkalCallback;
  fetchHinkalUserCallback?: HinkalCallback;
  connectHinkalUserCallback?: HinkalCallback;
}

type UseSignInResult {
    signIn: () => void,
    isConnecting: boolean,
    isFetching: boolean,
    initHinkal: () => void,
    fetchHinkalUser: () => void,
    connectHinkalUser: () => void,
    checkHinkalPassword: () => void,
    isRecurringHinkalUser: boolean,
    hasNormalAccount: boolean,
}

function useSignIn({
  password,
  onError,
  onSuccess,
  onChainChanged,
  onAccountChanged,
  initHinkalCallback,
  fetchHinkalUserCallback,
  connectHinkalUserCallback
}: UseSignInParams): UseSignInResult;

KYC hook

This section gives an overview of a KYC status management in Hinkal Protocol.

useKyc hook encapsulates the whole KYC logic of Hinkal Protocol including user state management and user information forms. aiPrise is an external provider of KYC flow used in the Hinkal SDK.

There are four stages of KYC process:

  • Not-Started. The user did not start the KYC process yet.
  • Started. The user is in process of inputting KYC data.
  • Processing. KYC information is sent to KYC provider and is being processed.
  • Finished. The KYC process has finished and there is an outcome: either success or failure.

useKyc accepts callbacks that are fired after the success or failure events of some user actions. onKycStatusChanged callback is invoked after the KYC status of the user has changed, it has a kycStatus argument that shows updated information. onAiPriseSuccess and onAiPriseError callbacks govern actions after the success and failure events of opening aiPrise window with user information inputs. onMintSuccess and onMintError callbacks fire in case of successful minting of access token or failure to mint one. enabled variable controls whether the user should proceed to KYC input form in case he does not possess an access token.

function useKyc({
  enabled,
  onKycStatusChanged,
  onAiPriseSuccess,
  onAiPriseFinished,
  onAiPriseError,
  onMintSuccess,
  onMintError,
}: UseKycResult);

type UseKycParams = {
  enabled?: boolean;
  onKycStatusChanged?: (kycStatus?: KycStatus) => void;
  onAiPriseSuccess?: () => void;
  onAiPriseFinished?: () => void;
  onAiPriseError?: (err: Error) => void;
  onMintSuccess?: () => void;
  onMintError?: (err: Error) => void;
};

type UseKycResult = {
  kycStatus?: KycStatus;
  openAiPrise: () => void;
  isAiPriseProcessing: boolean;
  mintToken?: () => void;
  isMintTokenProcessing: boolean;
  mintingTransactionStatus: TransactionStatus;
  closeMinting: () => void;
};

Shielded Transactions

This section contains the main functions a user interacts with Hinkal smart contract. All functions listed are shielded transactions.

1. Deposit

The deposit function allows Ether and ERC-20 deposits to Hinkal smart contract. After the deposit, the shielded balance of a user will be credited with the deposit amount. useDeposit hook accepts two optional callbacks for success and for error. The hook returns the deposit function and isProcessing boolean variable which shows whether the deposit action is currently in process. The deposit function accepts the token object and the deposit amount.

function useDeposit({ onError, onSuccess }: UseDepositParams): UseDepositReturn;

type UseDepositParams = {
  onError?: (err: Error) => void;
  onSuccess?: () => void;
};

type UseDepositResult = {
  isProcessing: boolean;
  deposit?: (token: ERC20Token, depositAmount: string) => void;
};

2. Transfer

The transfer function allows shielded transfers between two shielded addresses. During the transfer, neither the transfer amount nor the recipient address is revealed on-chain. Transfer does not involve the invocation of ERC-20 transfer function, instead, UTXO changes its owner. The useTransfer hook returns the transfer function which accepts the token object, transfer amount, and the transfer address. The latter is a shielded address of the recipient.

function useTransfer({ onError, onSuccess }: UseTransferParams): UseTransferResult;

type UseTransferParams = {
  onError?: (err: Error) => void;
  onSuccess?: () => void;
};

type UseTransferResult = {
  isProcessing: boolean;
  transfer?: (token: ERC20Token, transferAmount: string, transferAddress: string) => void;
};

3. Withdraw

The withdraw function allows returning the deposited funds from the Hinkal smart contract to the EOA (externally owned account) of the user. There are two ways to invoke withdraw function: either with or without a relayer. If a user withdraws using the relayer then his wallet address will not be posted on-chain, and the relayer's address will be revealed. In case of no relayer, the user will submit a transaction himself.

function useWithdraw({ onError, onSuccess }: UseWithdrawParams): UseWithdrawResult;

type UseWithdrawParams = {
  onError?: (err: Error) => void;
  onSuccess?: () => void;
};

type UseWithdrawResult = {
  isProcessing: boolean;
  withdraw?: (token: ERC20Token, withdrawAmount: string, recipientAddress: string, isRelayerOff: boolean) => void;
};

4. Swap

The swap function allows shielded swaps using Uniswap API. The swap operation is always done using the relayer to preserve the privacy of the transaction. As an input, the swap function accepts in and out swap amounts and tokens in addition to Uniswap pool fee. The pool fee is required as a parameter since for a given pair of tokens there may exist more than one pool with different fees.

function useSwap({ onError, onSuccess }: UseSwapParams): UseSwapResult;

type UseSwapParams = {
  onError?: (err: Error) => void;
  onSuccess?: () => void;
};

type UseSwapResult = {
  isProcessing: boolean;
  swap?: (
    inSwapAmount: string,
    inSwapToken: ERC20Token,
    outSwapAmount: string,
    outSwapToken: ERC20Token,
    fee: number,
  ) => void;
};

5. Cross-chain deposit using Meson

Hinkal allows cross-chain private deposits using Meson Finance. useMesonDeposit hook returns mesonDeposit function which allows creating utxo commitment in Hinkal smart contract given that a user has already called depositWithBeneficiary function. In addition to a token and an amount, mesonDeposit accepts nonce which is a unique number assigned to each user transaction.

function useMesonDeposit({ onError, onSuccess }: UseMesonDepositParams): UseMesonDepositResult;

type UseMesonDepositParams = {
  onError?: (err: Error) => void;
  onSuccess?: () => void;
};

type UseMesonDepositResult = {
  isProcessing: boolean;
  mesonDeposit: (token: ERC20Token, depositAmount: string, nonce: number) => void;
};

Hinkal Context

Hinkal Context contains information about user's public shielded address, current network and SDK mode.

Wrap your main application component to context provider (HinkalContextProvider).

<HinkalContextProvider isSandboxMode=true|false>
    <your app component />
</HinkalContextProvider>

After that you'll get able to use Hinkal Context.

function useHinkalContext(): UseHinkalContextResult;
​
type UseHinkalContextResult = {
    isSandboxMode: boolean;
    selectedNetwork?: EthereumNetwork;
    shieldedAddress?: string;
    addEventListener: (eventType: EventType, listener: <K>(event: K) => void) => void;
    removeEventListener: (eventType: EventType, listener: <K>(event: K) => void) => void;
};
​
type EthereumNetwork {
  name: string;
  chainId: number;
  rpcUrl: string;
  supported: boolean;
  contractData?: ContractData;
};
​
enum EventType {
  BalanceChange = 'BalanceChange',
  NetworkChange = 'NetworkChange',
  KycNeeded = 'KycNeeded',
}

Token List and Balances

useErc20List hook gives the list of all supported ERC-20 tokens.

function useErc20List(): UseErc20ListResult;

type UseErc20ListResult = {
  erc20List: ERC20Token[];
};

type ERC20Token = {
  chainId: number;
  erc20TokenAddress: string;
  wrappedErc20TokenAddress?: string;
  aaveTokenAddress?: string;
  wrappedAaveTokenAddress?: string;
  name: string;
  symbol: string;
  decimals: number;
  logoURI?: string;
  whitelisted?: boolean;
};

useHinkalBalances hook gives the list of ERC-20 token balances.

function useHinkalBalances({ enabled }: UseHinkalBalancesParams): UseHinkalBalancesResult;

type UseHinkalBalancesParams = {
  enabled?: boolean;
};

type UseHinkalBalancesResult = {
  tokenBalances: TokenBalance[];
};

type TokenBalance {
  token: ERC20Token;
  balance: bigint;
};
0.0.5

1 year ago

0.0.3

1 year ago

0.0.4

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago