Hyphen SDK is a javascript-based SDK written in typescript that helps in integrating Hyphen services easily. It provides some helper methods that make the Hyphen integration very easy. Even though you can interact with the LiquidityPool smart contract directly and deposit tokens using the depositErc20 method from your DApp to do the deposit transactions, we recommend using the SDK as it provides methods like checking available liquidity before doing the transaction, checking exit transaction status, checking approvals, etc.
Let's get started
1. Installation
npminstall@biconomy/hyphen
yarn add @biconomy/hyphen
2. Importing and Instantiation
HYPHEN accepts a provider object as the first argument and an option object as the second argument.
import { Hyphen, SIGNATURE_TYPES } from"@biconomy/hyphen";let hyphen =newHyphen(<ProviderObject>, { debug: true,// If 'true', it prints debug logs on console window environment: "test",// It can be "test" or "prod" onFundsTransfered: (data) => {// Optional Callback method which will be called when funds transfer across// chains will be completed }});
Note: <Provider Object> is the provider object exposed by your connected wallet. It can be window.ethereum for Metamask or portis.provider for Portis and so on. The first provider object should be a provider object with accounts information.
It can either be a JSON RPC Provider object or HttpProvider object with an RPC URL given that you have passed walletProvider object in the options and Biconomy support is present. Check Network Agnostic Support section.
3. Initialization
After the SDK object is instantiated you need to initialize the SDK by calling its init function. The initialization is kept separated from the constructor to give flexibility to the developer to use await statement for initializing the SDK.
awaithyphen.init();
Keep your code in a try-catch block to catch any error that might be thrown from the SDK during instantiation or initialization.
Time to do a Cross-Chain Transfer
Before we proceed let's define some terms which will be used frequently in this documentation
Now that we have initialized the SDK and defined the terms we can do the cross-chain transfer transaction. In order to do a cross-chain transfer, some pre-checks need to be done and some pre-actions need to be taken if required.
SDK provides methods to do these pre-checks and do some pre-actions so you don't have to write extra code to do the same.
Pre-Checks
These checks can be done using the method preDepositStatusthat will check for a given fromChainId, toChainId, tokenAddress, userAddress and amount, whether there is enough liquidity on toChain, the given tokenAddress is supported, enough approval is given by the user to LiquidityPoolManager contract on fromChain and the networks are supported or not.
import { RESPONSE_CODES } from"@biconomy/hyphen";let preTransferStatus =awaithyphen.depositManager.preDepositStatus({ tokenAddress:"",// Token address on fromChain which needs to be transferred amount:"",// Amount of tokens to be transferred in smallest unit eg wei fromChainId:""// Chain id from where tokens needs to be transferred toChainId: "",// Chain id where tokens are supposed to be sent userAddress:""// User wallet address who want's to do the transfer});if (preTransferStatus.code ===RESPONSE_CODES.OK) {// β ALL CHECKS PASSED. Proceed to do deposit transaction} elseif(preTransferStatus.code ===RESPONSE_CODES.ALLOWANCE_NOT_GIVEN) {// β Not enough apporval from user address on LiquidityPoolManager contract on fromChainlet infiniteApproval =false;let useBiconomy =false;let approveTx =awaithyphen.tokens.approveERC20(tokenAddress,preTransferStatus.depositContract,amount.toString(), infiniteApproval, useBiconomy );// β±Wait for the transaction to confirm, pass a number of blocks to wait as paramawaitapproveTx.wait(2);// NOTE: Whenever there is a transaction done via SDK, all responses// will be ethers.js compatible with an async wait() function that// can be called with 'await' to wait for transaction confirmation.// πNow proceed to do the deposit transaction} elseif (preTransferStatus.code ===RESPONSE_CODES.UNSUPPORTED_NETWORK) {// β Target chain id is not supported yet} elseif (preTransferStatus.code ===RESPONSE_CODES.NO_LIQUIDITY) {// β No liquidity available on target chain for given tokenn} elseif (preTransferStatus.code ===RESPONSE_CODES.UNSUPPORTED_TOKEN) {// β Requested token is not supported on fromChain yet} else {// β Any other unexpected error}
Pre-Actions
If the approval for the token to be transferred is not given to LiquidityPool contract on fromChain then the first approval transaction needs to be done. You can directly interact with the token contract and call its approve or permit method but to make things simple SDK also provides a method to do this.
/** * tokenAddress Token address whose approval needs to be given * spender LiquidityPool address on fromChain * amount Amount to be transferred. It should be in the smallest unit on token eg in wei * userAddress User address who is giving the approval * infiniteApproval Boolean flag whether to give infinite approval for the selected token * useBiconomy Boolean flag whether to use Biconomy for gasless transaction for approval */let approveTx =awaithyphen.tokens.approveERC20(tokenAddress, spender, amount, userAddress, infiniteApproval, useBiconomy);// Wait for 1 block confirmationawaitapproveTx.wait(1);
Once the approvals are checked and if required approval transaction is done. All you need to do is make only one deposit transaction on LiquidityPool on fromChain and the rest will be done by Hyphen.
Let's do the Deposit Transaction
You can call the deposit method provided in the SDK to initiate a deposit transaction. Make sure to call preDepositStatus method before calling deposit method.
let depositTx =awaithyphen.depositManager.deposit({ sender:"User wallet address", receiver:"Receiver address on toChain. Can be different than sender", tokenAddress:"Address of the token on fromChain to be transferred", depositContractAddress:"LiquidityPool address on fromChain", amount:"30000000000",//Amount to be transferred. Denoted in smallest unit eg in wei", fromChainId:137,// chainId of fromChain toChainId:1,// chainId of toChain useBiconomy:true,// OPTIONAL boolean flag specifying whether to use Biconomy for gas less transaction or not dAppName: "Dapp specific identifier" // Can be any string, emitted from the contract during the deposit call; used for analytics
});// Wait for 1 block confirmationawaitdepositTx.wait(1);
The deposit method checks if there's enough liquidity available on toChain for the given amount of tokens to be transferred considering all the pending transactions for the same token. It also checks if there's enough approval given to LiquidityPool contract by the sender address for the given amount of tokens to be transferred.
Once the deposit transaction is confirmed, rest of the transactions to make the cross-chain transfer will be done by off-chain Executor nodes run by Biconomy. Now you can wait for the cross-chain transfer transaction to be done.
In the next section, we'll see how to listen for the status of our cross-chain transfers once our deposit transaction is successful. Or what if you don't get the funds on the destination chain within 5 min after your successful deposit transaction.
Enable Network Agnostic Gasless Transactions
You can enable network agnostic gasless transactions using Biconomy so that the user can make Approve and Deposit transactions on a chain e.g., Polygon while still keeping his wallet pointing to the Ethereum chain.
To enable this, you need to pass walletProvider object and biconomy options in the second parameter of Hyphen constructor as mentioned below:
let hyphen =newHyphen(<ProviderObject 1>, {... walletProvider: <ProviderObject 2>, // Pass this to enable network agnostic transfers biconomy: { enable: true, debug: true, apiKey: "Biconomy API Key for meta transaction support" }, signatureType: SIGNATURE_TYPES.EIP712, // Optional. To specify signature types in case of meta transactions ...});
Here, Provider Object 1 is the JSON RPC provider object pointing to the 'fromChain' where the user will be depositing his tokens. It doesn't need to have any accounts information.
Provider Object 2 has to be a provider object from the connected user wallet. It doesn't matter on which network the User Wallet is because we'll be using this provider object to just get the user signatures to do gasless transactions on the chain pointed by Provider Object 1.
So when the user initiates a deposit transaction using deposit method, Hyphen SDK will use Biconomy SDK to send the deposit transaction, internally Biconomy SDK will check the details of the called method and smart contract from Biconomy Dashboard. This step is mandatory and the called method needs to be registered on the Biconomy Dashboard.
Once the details are matched with the Biconomy dashboard data, Biconomy SDK will ask the user for the signature using walletProviderobject passed and the user will get a signature popup on his wallet.
Once the user provides his signatures, the transaction is routed via Biconomy and the transaction will be executed on the fromChain pointed by Provider Object 1.
Signing Transactions Using An Imported Private Key
In some cases, the SDK may be used outside of the browser and will require an automated experience. The following snippet demonstrates how to perform the same steps above but with your private key.
Instantiate Hyphen and an Ethers Wallet
import { RESPONSE_CODES } from"@biconomy/hyphen";import { ethers } from"ethers";constproviderURL=process.env.PROVIDER_URL;constprovider=newethers.providers.JsonRpcProvider(providerURL);constprivateKey=process.env.PRIVATE_KEY!;constwallet=newethers.Wallet(privateKey);constsigner=wallet.connect(provider);// instantiate hyphenlet hyphen =newHyphen(signer.provider, { debug:true,// If 'true', it prints debug logs on console window environment:"test",// It can be "test" or "prod"onFundsTransfered: (data) => {// Optional Callback method which will be called when funds transfer across// chains will be completed },// signatureType: SIGNATURE_TYPES.PERSONAL, infiniteApproval:false, transferCheckInterval:-1,// Interval in milli seconds to check for transfer status biconomy: { enable:false, apiKey:"<your_biconomy_api_key>", debug:true }});
Pre-Deposit Operations
These are the same as before except that the approveERC20 function receives the wallet object.
let preTransferStatus =awaithyphen.depositManager.preDepositStatus({ tokenAddress:"",// Token address on fromChain which needs to be transferred amount:"",// Amount of tokens to be transferred in smallest unit eg wei fromChainId:""// Chain id from where tokens needs to be transferred toChainId: "",// Chain id where tokens are supposed to be sent userAddress:""// User wallet address who want's to do the transfer});if (preTransferStatus.code ===RESPONSE_CODES.OK) {// β ALL CHECKS PASSED. Proceed to do deposit transaction} elseif(preTransferStatus.code ===RESPONSE_CODES.ALLOWANCE_NOT_GIVEN) {// β Not enough apporval from user address on LiquidityPoolManager contract on fromChainlet infiniteApproval =false;let useBiconomy =false;let approveTx =awaithyphen.tokens.approveERC20( tokenAddress,preTransferStatus.depositContract,amount.toString(), wallet, infiniteApproval, useBiconomy ); // !!!NOTE: the previously created wallet is added here// β±Wait for the transaction to confirm, pass a number of blocks to wait as paramawaitapproveTx.wait(2);// NOTE: Whenever there is a transaction done via SDK, all responses// will be ethers.js compatible with an async wait() function that// can be called with 'await' to wait for transaction confirmation.// πNow proceed to do the deposit transaction} elseif (preTransferStatus.code ===RESPONSE_CODES.UNSUPPORTED_NETWORK) {// β Target chain id is not supported yet} elseif (preTransferStatus.code ===RESPONSE_CODES.NO_LIQUIDITY) {// β No liquidity available on target chain for given tokenn} elseif (preTransferStatus.code ===RESPONSE_CODES.UNSUPPORTED_TOKEN) {// β Requested token is not supported on fromChain yet} else {// β Any other unexpected error}
Perform the Deposit Transaction
The wallet object is passed as the last parameter to the deposit function.
let depositTx =awaithyphen.depositManager.deposit({ sender:"User wallet address", receiver:"Receiver address on toChain. Can be different than sender", tokenAddress:"Address of the token on fromChain to be transferred", depositContractAddress:"LiquidityPool address on fromChain", amount:"30000000000",//Amount to be transferred. Denoted in smallest unit eg in wei", fromChainId:137,// chainId of fromChain toChainId:1,// chainId of toChain useBiconomy:true,// OPTIONAL boolean flag specifying whether to use Biconomy for gas less transaction or not tag: "Dapp specific identifier" // Can be any string, emitted from the contract during the deposit call; used for analytics
}, wallet); // !!!NOTE: the previously created wallet is added here// Wait for 1 block confirmationawaitdepositTx.wait(1);