React Context for hashport bridging operations
npm install @hashport/react-clientThe hashport React Client contains a set of React contexts and hooks that allow you to add hashport's bridging functionality to your React application. This package is meant for those who want to integrate the functionality into their native user interface. If you want a styled plug-and-play solution, please take a look at the Widget here.
1. Quick Start
1. Contexts
1. Hooks
1. Examples
1. Development Environment
1. Troubleshooting
Install the @hashport/react-client package and its dependency @hashgraph/sdk. Optionally install @rainbow-me/rainbowkit and wagmi if you plan to develop with RainbowKit, or hashconnect if you plan to develop with HashPack.
``bash
npm install @hashport/react-client @hashgraph/sdk
⚠ Note:
@hashport/react-client is a React library.$3
Import the client based on your existing application environment.
If your React app already has an EVM signer (e.g. RainbowKit, MetaMask, ethers) AND a Hedera signer (e.g. Hashconnect, Hashgraph SDK), please use the
HashportClientProvider.`javascript
import { HashportClientProvider } from @hashport/react-client/contexts;
`If your React app DOES NOT HAVE an EVM signer but has a Hedera signer, please use the
HashportClientAndRainbowKitProvider.`javascript
import { HashportClientAndRainbowKitProvider } from @hashport/react-client/contexts;
`If your React app DOES NOT HAVE EITHER SIGNER, please integrate a Hedera signer first, such as Hashconnect.
$3
Pass your EVM signer (if necessary) and Hedera signer as props to the provider that imported in the above step. Wrap your application with the configured provider.
`javascript
const App = () => {
return (
);
};
`$3
You can start building out the rest of hashport's functionality into your app using the provided hooks documented below.
For more details, view the example we have created here.
Contexts
$3
BridgeParamsProvider relies on the context feature of React to pass the necessary bridging parameters down to other components, so you need to make sure that BridgeParamsProvider is a parent of the components you are encapsulating as part of the bridging functionality.#### Props
`
children
`The content of the component.
Type:
React.ReactNode---
$3
HashportApiProvider relies on the context feature of React to pass the hashport API and core network objects down to other components, so you need to make sure that HashportApiProvider is a parent of the components you are encapsulating to be able to consume these objects. HashportApiProvider also initializes QueryClientProvider from @tanstack/react-query to help simplify data fetching.#### Props
`
children
`The content of the component.
Type:
React.ReactNode`
mode
`The API environment that the provider will set.
Type:
'mainnet' | 'testnet'Default:
'mainnet'---
$3
HashportClientProvider relies on the context feature of React to pass core hashport functionality down to other components, so you need to make sure that HashportClientProvider is a parent of the components you are encapsulating to be able to consume these objects. HashportClientProvider also includes HashportApiProvider and BridgeParamsProvider to help simplify and improve your developer experience.#### Props
`
children
`The content of the component.
Type:
React.ReactNode`
evmSigner
`An abstraction of an EVM Account, which can be used to sign messages and transactions and send signed transactions to the EVM Network.
Type:
EvmSigner | undefined`
hederaSigner
`An abstraction of a Hedera Account, which can be used to sign messages and transactions and send signed transactions to the Hedera Network.
Type:
HederaSigner | undefined`
customMirrorNodeUrl
`A URL to a custom Hedera mirror node service.
Type:
string | undefinedDefault:
'https://mainnet-public.mirrornode.hedera.com/api/v1'`
customMirrorNodeCredentials
`Credentials to
customMirrorNodeUrl (the custom Hedera mirror node service). The array indices should contain the following: ["your-x-api-key", "YOUR_API_KEY"].⚠ If
customMirrorNodeCredentials is set without a customMirrorNodeUrl defined, the HashportClientProvider will not work.Type:
[string, string] | undefined`
debug
`If
true, enables the logger.Type:
booleanDefault:
false`
mode
`The API environment that the provider will set.
Type:
'mainnet' | 'testnet'Default:
'mainnet'`
persistOptions
`Persistent storage options for ongoing transaction state. This allows users to continue their bridging process after an error.
Type:
{ persistKey?: string | undefined; storage?: StateStorage | undefined; } | undefinedDefault:
{ persistKey: 'hashportTransactionStore', storage: localStorage }`
disconnectedAccountsFallback
`A fallback React node to be displayed if either the EVM Signer or the Hedera Signer has become disconnected.
Type:
{ disconnectedAccountsFallback?: React.ReactNode }Default:
Please connect signers for both EVM and Hedera networks
---
$3
ProcessingTransactionProvider relies on the context feature of React to pass the state of the current processing transaction to other components. This provider depends on an instance of the HashportClient, so be sure that ProcessingTransactionProvider is a child of the HashportClientProvider.#### Props
`
children
`The content of the component.
---
$3
HashportClientAndRainbowKitProvider relies on the context feature of React to pass core hashport functionality _and_ RainbowKit (Ethereum interface) down to other components, so you need to make sure that HashportClientAndRainbowKitProvider is a parent of the components you are encapsulating to be able to consume these objects. HashportClientProvider also includes HashportApiProvider and BridgeParamsProvider to help simplify and improve your developer experience.#### Props
`
children
`The content of the component.
Type:
React.ReactNode`
hederaSigner
`An abstraction of a Hedera Account, which can be used to sign messages and transactions and send signed transactions to the Hedera Network.
Type:
HederaSigner | undefined`
customMirrorNodeUrl
`A URL to a custom Hedera mirror node service.
Type:
string | undefinedDefault:
'https://mainnet-public.mirrornode.hedera.com/api/v1'`
customMirrorNodeCredentials
`Credentials to
customMirrorNodeUrl (the custom Hedera mirror node service). The array indices should contain the following: ["your-x-api-key", "YOUR_API_KEY"].⚠ If
customMirrorNodeCredentials is set without a customMirrorNodeUrl defined, the HashportClientProvider will not work.Type:
[string, string] | undefined`
debug
`If
true, enables the logger.Type:
booleanDefault:
false`
mode
`The API environment that the provider will set.
Type:
'mainnet' | 'testnet'Default:
'mainnet'`
persistOptions
`Persistent storage options for ongoing transaction state. This allows users to continue their bridging process after an error.
Type:
{ persistKey?: string | undefined; storage?: StateStorage | undefined; } | undefinedDefault:
{ persistKey: 'hashportTransactionStore', storage: localStorage }---
$3
RainbowKitBoilerPlate relies on the context feature of React to initialize and pass down Ethereum interfaces to other components, so you need to make sure that RainbowKitBoilerPlate is a parent of the components you are encapsulating to be able to consume these interfaces. A configured WagmiConfig and RainbowKitProvider are provided to help simplify and improve your developer experience.#### Props
`
children
`The content of the component.
Type:
React.ReactNode`
chains
`An array of chain objects that reference different EVM-compatible chains. By default, hashport-supported chains are included in the array.
⚠ Chain objects must adhere to the interface described here.
Type:
RainbowKitChain[]#### RainbowKit Props
Props of the RainbowKit component are also available.
---
Hooks
This package comes with a number of convenience hooks that help perform a hashport bridging transaction. The recommended usage is to set bridging parameters with
useBridgeParamsDispatch, queue up the transaction with useQueueHashportTransaction, execute the transaction with useProcessingTransactionDispatch, and monitor the status with useProcessingTransaction. The hooks can be broken down into the following categories:1. Transaction Set-Up Hooks
1. Transaction Execution Hooks
1. Status Monitoring Hooks
1. Account Connection Hooks
$3
To set up a transaction, a user must define an amount, a recipient, a source asset, and a target asset. These parameters are then sent to the hashport API to obtain a list of steps that need to be executed in order to complete the transaction.
#### useBridgeParams
Returns a BridgeParams object.
##### Usage
`tsx
const BridgeParamsDisplay = () => {
const { amount, sourceNetworkId, sourceAssetId, targetNetworkId, recipient } =
useBridgeParams(); return (
Bridging Amount: {amount}
Source Chain id: {sourceNetworkId}
Source Asset for bridging: {sourceAssetId}
Target Chain id: {targetNetworkId}
Receiving account: {recipient}
);
};
`Returns dispatch functions to set fields in the
bridgeParams object. Use with useTokenList for easy token selection.##### Usage
`tsx
const SourceTokenSelection = () => {
const { sourceAssetId } = useBridgeParams();
const dispatch = useBridgeParamsDispatch();
const { data: tokens } = useTokenList(); const handleChange = e => {
const selectedToken = !tokens?.get(e.target.value);
if (selectedToken) return;
dispatch.setSourceAsset(selectedToken);
};
return tokens ? (
) : (
Loading...
);
};
`⚠ Note: When setting the
amount for bridging, it's important to take the token's decimal into account. While EVM-based tokens can have up to 18 decimal places, Hedera tokens can only have up to 8. By default, the setAmount dispatch function allows a maximum of 6 decimals. However, if a token has been selected, it will allow decimal precision as limited by the token. When the bridge params are submitted to the API, it is recommended to use the useQueueHashportTransaction hook because it converts the decimal amount to wei or tinybar, which is what the API expects.#### useTokenList
Returns a React Query result object where the data is an object that holds all supported assets on hashport, both fungible and nonfungible. Assets are stored as a Map of the token's unique ID to its respective AssetInfo.
⚠ Note: The token's unique id takes the following format:
${tokenIdOrAddress}-${chainId}.This hook also accepts an optional
onSelect function that is run when the handleSelect function of an asset is called. It is recommended to use this hook with useBridgeParamsDispatch to allow a user to select a token.##### Usage
`tsx
const TokenList = () => {
const {setSourceAsset} = useBridgeParamsDispatch();
const {data: tokens, isError, isLoading} = useTokenList({
onSelect(token) {
setSourceAsset(token);
}
})
if (isLoading) {
return Loading...
;
} else if (isError) {
return Error!
} else {
return (
{Array.from(tokens.fungible.entries()).map(([uniqueId, {handleSelect, symbol, id, chainId}]) => (
))}
)
}
}
`#### useSelectedTokens
useTokenList and useBridgeParams. It removes some of the boiler plate needed to display the selected source and target tokens if they have been set in the bridgeParams.##### Usage
`tsx
const SelectedTokens = () => {
const { sourceAsset, targetAsset } = useSelectedTokens(); return (
{sourceAsset ? Selected Asset: ${sourceAsset.symbol} : 'Select a token!'}
{targetAsset
? Target Asset: ${targetAsset.symbol}
: 'Where do you want to bridge to?'}
);
};
`#### useTargetTokens
This is another convenience hook. If a source asset has been set, it will return an array of all the possible tokens you can bridge to. Returns
undefined if no source asset has been set.⚠ Note: An additional
assetId property is added to each token to uniquely identify the token. This is helpful when providing a key for React when mapping out the tokens.##### Usage
`tsx
const TargetTokens = () => {
const targetTokens = useTargetTokens();
const { setTargetAsset } = useBridgeParamsDispatch(); return (
{targetTokens ? (
<>
Where would you like to bridge your asset?
{targetTokens.map(token => {
return (
);
})}
>
) : (
Choose a token to get started
)}
);
};
`BridgeParamsProvider. If all the proper bridge parameters have been set by the user, this hook will return a function that:- Converts the decimal amount to wei or tinybar.
- Submits the
bridgeParams to the API and queues up the steps for execution.⚠ Note: Be sure to add error handling to this function as it will throw an error if any of the parameters are set incorrectly.
If the function is called successfully, it will return a unique
id that you can pass to the execute function on the hashportClient or the executeTransaxtion dispatch function from the useProcessingTransactionDispatch hook.##### Usage
`tsx
const QueueTransaction = () => {
const queueTransaction = useQueueHashportTransaction();
const [queuedIds, setQueuedIds] = useState([]); const handleQueueTransaction = async () => {
if (!queueTransaction) return;
const id = await queueTransaction();
setQueuedIds(prev => [...prev, id]);
};
return (
Queued Transactions
{queuedIds.map(id => {
Transaction: {id}
;
})}
);
};
`#### useMinAmount
Hashport imposes a minimum amount in order to initiate a bridging operation. If a set of bridge parameters does not meet this minimum, the API will not return the steps required to perform the transaction. However, to give the user a better experience, it's good to display the minimum porting amount.
⚠ Note: This hook adds a 10% buffer to the minimum amount. It's important to understand that the minimums are dynamic. If the prices change before a transaction reaches the validators, there is a chance that the transaction may fall below the minimum, which would result in a stuck transaction. As such, adding a buffer of an extra 10% helps mitigate that risk.
BridgeParamsProvider. It reads the sourceAssetId and sourceNetworkId of the bridge parameters, fetches the minimum bridging amount, and returns it as a BigInt. If you want to display this number in its decimal form, you can get the decimals of the token from the useTokenList hook and use a function like viem's formatUnits to convert it to a readable string.##### Usage
`tsx
import { formatUnits } from 'viem';const MinimumAmountDisplay = () => {
const { data: minAmount, isLoading, isError } = useMinAmount();
const { sourceAssetId, sourceNetworkId } = useBridgeParams();
const { data: tokens } = useTokenList();
const selectedToken = tokens.fungible.get(
${sourceAssetId}-${sourceNetworkId}); if (isLoading || !selectedToken) {
return
Loading minimum amounts...
;
} else if (isError) {
return Failed to get minimum amounts!
;
} else if (minAmount === undefined) {
return Please select a token
;
} else {
return Minimum bridging amount: {formatUnits(minAmount, selectedToken.decimals)}
;
}
};
`#### usePreflightCheck
HashportClientProvider and the BridgeParamsProvider. Use this hook to display the state of the user's bridging parameters and whether or not they will be able to execute the transaction. It checks the following:- Whether or not the user has enough balance to complete the transaction
- Whether or not the user meets the minimum amounts
Returns
{isValidParams: boolean; message?: string} where the message is only defined if isValidParams is false.##### Usage
`tsx
const PreflightCheckMessage = () => {
const preflightStatus = usePreflightCheck(); return preflightStatus.isValidParams ? (
Ready to bridge!
) : (
Error: {preflightStatus.message}
);
};
`$3
Once you have set up the proper bridge parameters and queued the transaction, all that's left to do is execute the transaction. The recommended way is to use the
executeTransaction callback provided by the useProcessingTransactionDispatch hook. These functions are useful for displaying the current state of a submitted transaction.#### useProcessingTransactionDispatch
This hook returns an
executeTransaction callback to be used with the useQueueHashportTransaction hook. It also returns a confirmCompletion callback which is helpful for cleaning up bridgeParams state.#### Usage
`tsx
const ExecuteButton = () => {
const { executeTransaction } = useProcessingTransactionDispatch();
const queueTransaction = useQueueHashportTransaction(); const handleExecute = async () => {
try {
if (!queueTransaction) throw 'Set bridge parameters first';
const id = await queueTransaction();
const confirmation = await executeTransaction();
} catch (error) {
console.error(error);
}
};
return ;
};
`#### useHashportClient
If you prefer a more manual approach, you can also use this hook. It returns an instance of the
hashportClient from the @hashport/sdk. It depends on the HashportClientProvider. It is recommended to use this with the useQueueHashportTransaction hook.⚠ Note: Be sure to add error handling to this function in case there are network issues or if the user rejects a wallet interaction.
##### Usage
``tsx
const ExecuteButton = () => {
const hashportClient = useHashportClient();
const queueTransaction = useQueueHashportTransaction();
const [id, setId] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const handleExecute = async () => {
try {
if (!queueTransaction) {
setErrorMessage('Set bridge parameters first');
}
setErrorMessage('');
let tempId = id;
if (!tempId) {
tempId = await queueTransaction();
setId(tempId);
}
const confirmation = await hashportClient.execute(tempId);
console.log('Completed transaction: ', confirmation);
} catch (error) {
console.error(error);
setErrorMessage(error.message);
}
};
return (
{errorMessage}
: null}$3
There are a number of steps that must be completed in order to transfer assets across the hashport bridge. These steps may involve hedera transactions, EVM transactions, waiting for block confirmations, etc. To help keep the user updated on the progress of their transaction, you can use the following hooks.
ProcessingTransactionState object. Statuses can be 'idle', 'processing', 'complete', or 'error'. It also returns the id of the current transaction as well as the currentTransaction data related to that id.Use this with the
getStepDescription function to get a brief description of the current step the user is on.⚠ Note: You can use the
tokenAssociationStatus property on the transaction state to pass a boolean as a second argument to getStepDescription. This will help prompt the user to accept a token association request in their wallet if needed.##### Usage
`tsx
const ProcessingTransaction = () => {
const { status, id, confirmation, error, currentTransaction } = useProcessingTransaction(); if (status === 'idle') {
return
Choose tokens to start bridging!
;
} else if (status === 'processing' && currentTransaction.steps) {
const isAssociating = currentTransaction.state.tokenAssociationStatus === 'ASSOCIATING';
return {getStepDescription(currentTransaction.steps[0])}
;
} else if (stats === 'complete') {
return Complete: {confirmation.confirmationTransactionHashOrId}
;
} else {
return Error: {error}
;
}
};
`To ensure the safety of a user's transaction, the validators wait for a designated number of block confirmations before validating the transactions. This hook can be used to give users an update on the number of confirmations that must pass before their transaction can be completed. Returns a React Query result object where the data is the number of block confirmations for the given chainId.
##### Usage
`tsx
const BlockConfirmations = () => {
const { evmSigner } = useHashportClient();
const chainId = evmSigner.getChainId();
const { data: blockConfirmations, isLoading, isError } = useBlockConfirmations(chainId); if (isLoading) {
return
Loading...
;
} else if (isError) {
return Error!
;
} else {
return Block confirmations: {blockConfirmations.toString()}
;
}
};
`$3
Hooks in this section help with connecting wallets for EVM and Hedera accounts.
#### useHashConnect
hashconnect package. Because it creates a new instance of hashconnect with each call, it is recommended that this hook only be used to instantiate the hashportClient. If you need to use it throughout the application, place it in a context to maintain referential equality.##### Usage
`tsx
const HashportProvider = ({ children }: { children: React.ReactNode }) => {
const { hashConnect, pairingData, status } = useHashConnect();
const hederaSigner =
hashConnect && pairingData && createHashPackSigner(hashconnect, pairingData); return (
Hashpack Connection Status: {status}
{children}
);
};
`Examples
A full example of
@hashport/react-client in use is available in the examples directory. We currently only have an example that uses Vite. If you have an example you would like to contribute, consider making a PR!Development Environment
To set up your development environment, you will need the following:
- Hedera Testnet account (create a new account here). Testnet accounts are topped off with 10,000 testnet HBAR every 24 hours.
- EVM Testnet account (a list of supported testnet chains can be found here, with Sepolia being the most recommended).
- EVM Testnet faucet funds for gas fees. You can get Sepolia ETH from Alchemy's faucet.
- Sufficient balance for each test token _(Visit the swagger documentation to see what tokens are supported. Then visit the respective blockchain explorer and interact with the contract to mint some tokens to your testnet account(s).)_
⚠ Note: The Hedera Testnet resets every quarter, which erases all previous data and tokens. You'll need to update the Hedera Testnet tokens each time there is a reset. Learn more here.
After you have set up your testnet accounts, you can initialize the
hashportClient by connecting your wallets or using the hederaSdkSigner and localEvmSigner provided by the @hashport/sdk. Be sure to set the mode on the client to "testnet"!Troubleshooting
$3
Libraries like Hashconnect and RainbowKit rely on a few node-specific packages. Refer to RainbowKit's documentation to learn about whether or not you need to include polyfills and how to do so. You can also refer to the Vite example in the [
examples`] directory for a minimal example.