# Authentication Source: https://docs.rhino.fi/api-integration/authentication Most API endpoints require you to authenticate your requests using a Json Web Token (JWT). You can obtain a JWT by making a `POST` request to [https://api.rhino.fi/authentication/auth/apiKey](https://api.rhino.fi/authentication/auth/apiKey) and including your API key in the payload. ```bash cURL theme={null} curl https://api.rhino.fi/authentication/auth/apiKey \ -d '{"apiKey": "YOUR_API_KEY"}'\ -H 'content-type: application/json' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.rhino.fi/authentication/auth/apiKey', { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ apiKey: 'YOUR_API_KEY', }), }) const { jwt } = await response.json() ``` ## Using the JWT Once you have a JWT, you need to include it in your API requests using the `Authorization` header. Example: ```bash cURL theme={null} curl https://api.rhino.fi -H 'Authorization: YOUR_JWT' ``` ```javascript JavaScript theme={null} fetch('https://api.rhino.fi', { headers: { 'Authorization': 'YOUR_JWT', }, }) ``` **A JWT obtained from an API key is only valid for one hour.** Once expired, you can just use your API key again in the same way to obtain a fresh JWT. We recommend using the SDK if you use JavaScript/TypeScript for your application as it will take care of handling the JWT flow for you. # Making a bridge Source: https://docs.rhino.fi/api-integration/bridge This guide walks you through integrating our bridge API into your application. By following these steps, you’ll be able to transfer assets between chains using our API and smart contract interactions. ## Quickstart The whole process consists of: Get a JWT to authenticate your requests. Retrieve supported chains and tokens. Obtain transaction details, including fees. Confirm the transaction. Interact with the smart contract to complete the transfer. *** ### 1. Authentication All non-public API endpoints are authenticated using a JSON Web Token. To authenticate your requests, include the token in the `Authorization` header. [Learn about API authentication](/api-integration/authentication). ### 2. Fetch Bridge Configs Retrieve available chains and supported tokens to ensure your transaction uses the correct parameters. ```javascript getBridgeConfigs.js theme={null} export const getBridgeConfigs = async () => { const request = await fetch('https://api.rhino.fi/bridge/configs') return request.json() } ``` For the exact response format and more details, see the [API Reference](/api-reference/configs). ### 3. Get a Bridge Quote Before executing a bridge transaction, you must generate a quote that provides transaction details, including fees and amounts. ```javascript getBridgeUserQuote.js theme={null} export const getBridgeUserQuote = async (payload, jwt) => { const request = await fetch(`https://api.rhino.fi/bridge/quote/user`, { headers: { "content-type": "application/json", "authorization": jwt }, method: "POST", body: JSON.stringify(payload) }) return request.json() } ``` For the exact request / response format and more details, see the [API Reference](/api-reference/quote/user-quote). ### 4. Commit the Quote Once you have a quote, you must commit it to confirm the transaction before execution. The deadline to commit a quote is provided within the `expiresAt` param within the quote response. ```javascript commitBridgeUserQuote.js theme={null} export const commitBridgeUserQuote = async (quoteId, jwt) => { const request = await fetch(`https://api.rhino.fi/bridge/quote/commit/${quoteId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'authorization': jwt } }) return request.json() } ``` For the exact request / response format and more details, see the [API Reference](https://docs.rhino.fi/api-reference/bridge/quote/commit-quote). ### 5. Execute the Bridge Transaction Now, you can execute the bridge transaction by interacting with the smart contract. See [examples](/contracts/contract-examples) for interacting with the bridge smart contracts across different blockchain environments. ### 6. Full Example Here’s the complete implementation combining all the steps above: ```javascript index.js theme={null} import { getBridgeUserQuote } from "./getBridgeUserQuote"; import { getBridgeConfigs } from "./getBridgeConfigs"; import { commitBridgeUserQuote } from "./commitBridgeUserQuote"; const JWT = 'YOUR_JWT'; // Replace with your JWT generated from our API const amount = '3'; const chainIn = 'BASE'; const chainOut = 'SOLANA'; const token = 'USDT'; const depositorAddress = '0x0000000000000000000000000000000000000000'; // Replace with your depositor address const recipientAddress = '0x0000000000000000000000000000000000000000'; // Replace with your recipient address const bridge = async () => { // Get bridge configs to determine supported chains and tokens or later use for the contract call const configs = await getBridgeConfigs(); // Get a bridge quote for the transaction const quote = await getBridgeUserQuote({ amount, chainIn, chainOut, token, mode: 'receive', depositor: depositorAddress, recipient: recipientAddress, amountNative: '0' }, JWT); if (!quote?.quoteId) throw new Error('Failed to generate user quote.'); // Commit the quote to confirm the transaction when you are ready to send on-chain const commitResult = await commitBridgeUserQuote(quote.quoteId, JWT); if (!commitResult?.quoteId) throw new Error('Failed to commit user quote.'); const chainConfig = configs[chainIn]; // Execute the bridge transaction by interacting with the smart contract -- see contract examples section on exact implementation await callBridgeContract({ chainConfig, amount: quote.payAmount, token, commitmentId: commitResult.quoteId, callback: (hash) => console.info('Transaction hash:', hash) }); }; bridge(); ``` For a detailed breakdown of API endpoints and parameters, check the [API Reference](/api-reference/introduction). ## Next steps For the smart contract integration see - [contract examples](/contracts/contract-examples) To track your bridge transaction status, see [Bridge Status & History](/api-integration/status-history). # API Integration Source: https://docs.rhino.fi/api-integration/introduction Integrate the Rhino Stablecoin Activation Stack directly via REST API. ## API Integration The Rhino API is a REST API that gives you direct access to the full Rhino Stablecoin Activation Stack. Use it to: * Generate and manage Smart Deposit Addresses * Fetch bridge configs, generate quotes, commit quotes, and execute bridges * Query transaction status and history * Configure Automated Onchain Actions (Extension) All non-public endpoints are authenticated with a JSON Web Token. See [Authentication](/api-integration/authentication) for details. If you use JavaScript or TypeScript, the [SDK](/sdk/quickstart) wraps the API and reduces integration time significantly. For all other languages, use the API directly — it is straightforward REST with JSON payloads. ## Useful resources * [API Reference](/api-reference/introduction) — full endpoint documentation with request/response schemas * Swagger schemas — for generating client code in your preferred language: * [Authentication](https://api.rhino.fi/authentication/swagger.json) * [Smart Deposit Addresses](https://api.rhino.fi/sda/swagger.json) * [Bridge](https://api.rhino.fi/bridge/swagger.json) # Smart Deposit Addresses Source: https://docs.rhino.fi/api-integration/smart-deposits Use the Smart Deposit Addresses API to generate addresses that accept stablecoin deposits from any supported chain and automatically settle to a specified destination. Includes support for cross-stable swaps, Automated Onchain Actions, and configurable fee handling. **Default limits apply to Smart Deposit Addresses for initial testing purposes:** * 500 total SDAs * 50 SDAs generated per hour Please [contact us](mailto:partnerships@rhino.fi) to lift your limits. ## Introduction To bridge funds through Rhino you would normally need to generate a quote, commit it and then interact with the bridge contract on the origin chain to initiate the bridge. However, this is not always an option for all use cases where a smart contract interaction is not possible or very inconvenient.\ To also cater to those use cases, Rhino is supporting bridging through manual transfers. ## How it works Call our API with deposit chain, destination chain and destination address.\ We strongly recommend generating an SDA on all available EVM chains immediately to ensure that funds accidentally sent by customers to the wrong chain are automatically detected. Make a standard ERC20 transfer to the generated address. After the deposit has been confirmed on chain, the funds will be automatically bridged to the specified destination. Smart Deposit Addresses are reusable and can be used for multiple bridges indefinetely (see caveats below). **This makes bridging accessible to any wallet or app that supports standard ERC20 transfers.** ## Fees Standard bridging fees apply to bridging with Smart Deposit Addresses (see [Get a Bridge Quote](/api-integration/bridge#3-get-a-bridge-quote) or [try our bridge](https://app.rhino.fi)). Integrating partners can adjust the way fees are charged to their customers. Get in touch for more details at [partnerships@rhino.fi](mailto:partnerships@rhino.fi). **Fee handling** By default, fees are deducted from the deposited amount before crediting. With the Advanced Fee & Limit Management extension, you can configure fee sponsorship (client absorbs the fee) or add a markup that is collected by Rhino and remitted to your account via end-of-month invoicing. Fee configuration is set at the account level. See [Advanced Fee & Limit Management](/get-started/extensions/advanced-fee-management) for details. ## Smart Deposit Address expiration To conserve resources when monitoring Smart Deposit Addresses, we will stop monitoring addresses that have not been used for a certain period of time. This is basically a timer that runs down from initial creation and is reset whenever a bridge is performed through an address.\ An inactive Smart Deposit Address can still be used for bridging by manually reactivating it through the API if it is needed. **Funds that are sent to an inactive Smart Deposit Address are not lost**, they will only require one API call to activate the address and initate a bridge for the transfers made while inactive. ## Supported chains Deposit addresses are enabled on Ethereum, Arbitrum, BSC, and Tron, as well as other chains. The general bridge config endpoint provides a boolean `enabledDepositAddress` flag that can be used to find chains that Smart Deposit Addresses are enabled for. Example: ```javascript theme={null} const response = await fetch('https://api.rhino.fi/bridge/configs') ``` The response will then specify which chains have Smart Deposit Addresses enabled: ```json {5,10} theme={null} { "ETHEREUM": { "name": "Ethereum", ..., "enabledDepositAddress": true, }, "SOLANA": { "name": "Solana", ..., "enabledDepositAddress": false, }, } ``` [See SDA supported chains](/get-started/supported-chains#smart-deposit-address) ## API interactions The following examples showcase the use of our API to manage Smart Deposit Addresses. All those calls require authentication, see details on this [here](/api-integration/authentication). ### Generating a Smart Deposit Address You can generate a new Smart Deposit Address with the following API call: ```javascript theme={null} const response = await fetch('https://api.rhino.fi/bridge/deposit-addresses', { method: 'POST', body: JSON.stringify({ depositChains: ['ETHEREUM'], destinationChain: 'BASE', destinationAddress: '0x123...', }), headers: { 'Content-Type': 'application/json', 'authorization': 'YOUR_JWT', }, }) ``` The response will then look like this: ```json {3,6} theme={null} [{ "depositChain": "ETHEREUM", "depositAddress": "0x456...", "destinationChain": "BASE", "destinationAddress": "0x123...", "supportedTokens": [ { "symbol": "USDT", "address": "0x789...", "maxDepositLimitUsd": 100000, "minDepositLimitUsd": 10, } ], "isActive": true, }] ``` The first highlighted line contains the generated Smart Deposit Address. Funds can be sent there to be bridged.\ **`However, only tokens from the supportedTokens list will be processed.`** Transfers of tokens that are not in this list will not be processed. Transfers that are smaller than the `minDepositLimitUsd` or larger than the `maxDepositLimitUsd` will not be processed (there is a "grace" window to account for different price sources and price fluctuations). If this happens, funds can be returned through our customer service. #### Multiple deposit chains You can provide multiple deposit chains in the request. The API will then generate one Smart Deposit Address that can be used on all the provided chains. This is why the response is also a list - one element for each chain provided. The Smart Deposit Addresses in the individual elements will be identical in this case.\ **Please note that providing multiple deposit chains only works for EVM chains currently.** Only then can the same address be used for all of them. #### Swaps When creating a Smart Deposit Address you can also specify a `tokenOut` field. This will cause all withdrawals on the destination chain to be in that token, swapping if needed. This is currently only supported for stablecoins (USDC and USDT). The given `tokenOut` needs to be a bridgable token on the destination chain, otherwise creation of the SDA will fail. **1:1 Stablecoin Swaps (Extension)** If your account has the 1:1 Stablecoin Swaps extension enabled, cross-stable conversions (e.g. USDT deposited → USDC settled) are processed at a guaranteed 1:1 rate. No slippage. No exposure to market price. This applies automatically to any SDA configured with a cross-stable `tokenOut`. Contact your account representative to enable this extension. #### Post Bridge Data Rhino provides the option to execute contract calls as part of the withdrawal process on the destination chain. Then every bridge through the deposit address will execute this action on the destination. For security reasons, only predefined actions are available. Please [contact us](mailto:partnerships@rhino.fi) with details on your use case if interested. #### Refund address Rhino provides the option to pass a refund address when creating a Smart Deposit Address. If for some reason there is an issue with your smart deposit, the funds deposited will be refunded to the specified refund address. #### Custom data and reusing of addresses When creating a Smart Deposit Address you can provide a `addressNote` field to be stored in our database. This field will be returned from all status endpoints as well.\ Additionally, a `reusePolicy` can be specified when creating a Smart Deposit Address. It can have the following values: * `reuse-existing`: Reuse an existing Smart Deposit Address if it exists. Only create a new one if no existing address matches the given parameters. An existing Smart Deposit Address that is reused this way is also reactivated automatically. * `create-new`: Always create a new Smart Deposit Address. When no `reusePolicy` is specified it defaults to `reuse-existing`. To determine if a Smart Deposit Address already exists, the following combination of fields is used: * `depositChain` * `destinationChain` * `destinationAddress` * `userId` (only Smart Deposit Addresses previously created by you will be considered) * `addressNote` if provided * `tokenOut` if provided * `postBridgeData` if provided * `webhookUrl` if provided * `refundAddress` if provided Example use cases for this feature: * Your users have unique destination addresses and you want to show users their previously used Smart Deposit Address. * You consolidate multiple Smart Deposit Addresses into the same destination address but store a user ID in the `addressNote` field to make sure to only have one address per user. ### Checking Smart Deposit Address status You can also check the current status of a Smart Deposit Address with the following call: ```javascript theme={null} const depositAddress = '0x123...' const depositChain = 'ETHEREUM' const response = await fetch(`https://api.rhino.fi/bridge/deposit-addresses/${depositAddress}/${depositChain}`, { headers: { 'authorization': 'YOUR_JWT', }, }) ``` The response format will be the same as the one returned when generating a new Smart Deposit Address **`with the exception of the destinationAddress. That field will only be included if the request was made with a secret API key.`** ### Checking Smart Deposit Address history You can check the history of a Smart Deposit Address with the following call: ```javascript theme={null} const depositAddress = '0x123...' const depositChain = 'ETHEREUM' const response = await fetch(`https://api.rhino.fi/bridge/deposit-addresses/${depositAddress}/${depositChain}/history`, { headers: { 'authorization': 'YOUR_JWT_FROM_SECRET_KEY', }, }) ``` The response will contain a list of transfers that have been processed through the Smart Deposit Address. By default it gives you the transfers from the last 7 days. To see older transfers you can provide a `from` and `to` query parameter with a timestamp in milliseconds. Only a `SECRET_` key is allowed to access history. ### Checking Smart Deposit Address bridge status with a PUBLIC API key Generally the above history endpoint can be used to track the bridges through a Smart Deposit Address. However it is not possible to use this in a frontend integration as a SECRET API key would be exposed in an unsafe browser environment.\ To also track bridges in this case, a public status endpoint exists. However, this endpoint additionally takes a `destinationAddress` as query parameter that has to match the requested Smart Deposit Address (this value should still be available in the local state after creating a Smart Deposit Address). If it does not match, the request will be rejected.\ Example: ```javascript theme={null} const depositAddress = '0x123...' const depositChain = 'ETHEREUM' const destinationAddress = '0x456...' const response = await fetch(`https://api.rhino.fi/bridge/deposit-addresses/${depositAddress}/${depositChain}/public-status?destinationAddress=${destinationAddress}`, { headers: { 'authorization': 'YOUR_JWT_FROM_SECRET_KEY', }, }) ``` The response will contain the last bridge (accepted or rejected) through the Smart Deposit Address in the `lastBridges` field. If this array is empty it means that no transfer has been picked up yet.\ Please note that this array will currently only contain the last event. In the future this endpoint will be expanded with the option to return all bridges after a certain timestamp. ### Reactivating a Smart Deposit Address If the `isActive` flag returned by the status check is `false` you can reactivate the Smart Deposit Address manually with the following call: ```javascript theme={null} const depositAddress = '0x123...' const depositChain = 'ETHEREUM' const response = await fetch(`https://api.rhino.fi/bridge/deposit-addresses/${depositAddress}/${depositChain}/activate`, { method: 'PATCH', headers: { 'authorization': 'YOUR_JWT', }, }) ``` This will cause the address to be monitored again. Additionally the address will be checked for transfers since the last activity to process transfers made while the address was inactive. ### Supported tokens To get the list of tokens supported between a deposit chain and destination chain you can execute the following code: ```javascript theme={null} const depositChain = 'ETHEREUM' const destinationChain = 'BASE' const tokenOut = 'USDT' // optional const response = await fetch(`https://api.rhino.fi/bridge/deposit-addresses/${depositChain}/${destinationChain}/supported-tokens?tokenOut=${tokenOut}`, { method: 'GET', headers: { 'authorization': 'YOUR_JWT', }, }) ``` It will return the list of supported tokens between the 2 specified chains along with their associated deposit limit. ### Search your addresses Rhino provides an endpoint that allows you to search for your Smart Deposit Addresses with some optional filters. This endpoint is authenticated and will only returns the addresses associated with your user. The result is paginated and you can set the `pageToken` field to get to the next page. If not provided, it'll return the first page of results and a `nextTokenPage` value that can be used to query for the next page. The following filters are available (all are optional and can be combined): * `addressNote` * `depositChain` * `destinationChain` * `destinationAddress`: requires `destinationChain` to be set as well ```javascript theme={null} // all filters are optional and can be combined const addressNote = 'my_note' const depositChain = 'ETHEREUM' const destinationChain = 'BASE' const destinationAddress = '0x....' const pageSize = 20 // optional, default to 20, max 50 const pageToken = "eyJkZXBvc2l0Q2hhaW4iOiJCQVNFIiwiZGVwb3NpdEFkZHJlc3MiOiIweDlmYTE1MDdiNTY0YzRlNWM1YmJjN2E5Yzc1ZWQ1MmFhMTc5ODIxNjkifQ==" // optional, returns the first page of results if not provided const response = await fetch( `https://api.rhino.fi/bridge/deposit-addresses/search?addressNote=${addressNote}&depositChain=${depositChain}&destinationChain=${destinationChain}&destinationAddress=${destinationAddress}&page=${page}&limit=${limit}`, { method: 'GET', headers: { 'authorization': 'YOUR_JWT', }, }, ) ``` # Bridge Status & History Source: https://docs.rhino.fi/api-integration/status-history This section explains how to retrieve the status and history of your bridge transactions. You can use these endpoints to monitor transaction progress and confirm successful transfers. ## **1. Get Bridge Transaction Status** To check the status of a specific bridge, use the following API request: ```javascript getBridgeStatus.js theme={null} export const getBridgeStatus = async (bridgeId, jwt) => { const request = await fetch(`https://api.rhino.fi/bridge/history/bridge/${bridgeId}`, { headers: { "content-type": "application/json", "authorization": jwt }, method: "GET" }) return request.json() } ``` When you commit a bridge transaction, you receive a `quoteId` that you can use to track the transaction status. ```javascript theme={null} import { getBridgeStatus } from './getBridgeStatus'; const JWT = 'YOUR_JWT'; // Replace with your JWT generated from our API const quoteId = '123456'; // Replace with your quote ID const status = await getBridgeStatus(quoteId, JWT); const depositTxHash = status.state === 'ACCEPTED' || status.state === 'EXECUTED' ? status.depositTxHash : '' const withdrawTxHash = status.state === 'EXECUTED' ? status.withdrawTxHash : '' console.log('Bridge status:', status.state); console.log('Deposit transaction hash:', depositTxHash); console.log('Withdraw transaction hash:', withdrawTxHash); ``` #### **Possible state values:** **Bridge (non‑swap) flows** * `PENDING` – Quote committed; we’re waiting for a deposit on the source chain. * `PENDING_CONFIRMATION` – *Fast chains only (WebSocket monitored)*. We’ve detected the deposit and are waiting for network confirmations. Not every bridge moves through this state. * `ACCEPTED` – We’ve detected the deposit and are executing on the destination chain. * `EXECUTED` – Bridge is complete; funds are credited on the destination chain. * `CANCELLED` – *Cosmetic state*. Can occur only if the user cancels a **pending** bridge via API. If the deposit later arrives, the bridge will move out of this state and be executed. Useful for hiding pending records you no longer intend to fund. * `FAILED` – *Rare, manual state*. Used only when there’s a specific issue and no way to complete the bridge. The user should contact Customer Support for a refund if one hasn’t already been issued. **Additional states for swap flows** * `DEPOSIT_ACCEPTED` – We’ve accepted the deposit and are performing a pre‑swap on the source chain (occurs **before** `ACCEPTED`). * `SWAP_FAILED` – The swap failed; we’ll look into issuing a refund. * `SWAP_FAILED_REFUNDED` – The swap failed and a refund was successfully issued. For the full response format and more details, see the [API Reference](/api-reference/history/bridge-history). Rhino Bridge Status Flow Diagram *** ## **2. Get Bridge History** To retrieve a list of past transactions: rhino.fi prioritizes transaction privacy, so the history endpoint is disabled by default for API keys. If access is required, ensure your API key is kept private and properly configured. You can request access to the user history endpoint by contacting us. ```javascript getBridgeUserHistory.js theme={null} export const getBridgeUserHistory = async (jwt) => { const queryParams = new URLSearchParams({ page: '1', limit: '20', sortBy: 'createdAt', sortDirection: 'desc', }) const request = await fetch(`https://api.rhino.fi/history/user?${queryParams.toString()}`, { headers: { "content-type": "application/json", "authorization": jwt }, method: "GET" }) return await request.json() } ``` ```javascript theme={null} import { getBridgeUserHistory } from './getBridgeUserHistory'; const JWT = 'YOUR_JWT'; // Replace with your JWT generated from our API const history = await getBridgeUserHistory(JWT); console.log('Bridge history:', history.items); ``` *** ## **Next Steps** * If you haven’t integrated the bridge yet, start with the [Quickstart Guide](/api-integration/bridge#quickstart). * For contract-level interactions, see [Contract Examples](/contracts/contract-examples). # Making a bridge & swap Source: https://docs.rhino.fi/api-integration/swap This guide walks you through integrating our bridge API into your application. By following these steps, you’ll be able to transfer and swap assets between chains using our API and smart contract interactions. ## Quickstart The whole process consists of: Get a JWT to authenticate your requests. Retrieve supported chains and tokens. Obtain transaction details, including fees. Confirm the transaction. Interact with the smart contract to complete the transfer. *** ### 1. Authentication All non-public API endpoints are authenticated using a JSON Web Token. To authenticate your requests, include the token in the `Authorization` header. [Learn about API authentication](/api-integration/authentication). ### 2. Fetch Bridge Configs Retrieve available chains to ensure your transaction uses the correct parameters. ```javascript getBridgeConfigs.js theme={null} export const getBridgeConfigs = async () => { const request = await fetch('https://api.rhino.fi/bridge/configs') return request.json() } ``` For the exact response format and more details, see the [API Reference](/api-reference/configs). ### 3. Fetch Swap token config Retrieve available tokens for swap transactions. ```javascript theme={null} export const getSwapTokenConfig = async () => { const request = await fetch('https://api.rhino.fi/bridge/bridge-swap-token-configs') return request.json() } ``` For the exact response format and more details, see the [API Reference](/api-reference/configs/get-all-token-configs-for-bridge-and-swap). ### 4. Get a Bridge & Swap Quote Before executing a bridge transaction, you must generate a quote that provides transaction details, including fees and amounts. ```javascript getBridgeSwapUserQuote.js theme={null} export const getBridgeSwapUserQuote = async (payload, jwt) => { const request = await fetch(`https://api.rhino.fi/bridge/quote/bridge-swap/user`, { headers: { "content-type": "application/json", "authorization": jwt }, method: "POST", body: JSON.stringify(payload) }) return request.json() } ``` For the exact request / response format and more details, see the [API Reference](/api-reference/quote/user-quote-for-bridge-&-swap). ### 5. Commit the Quote Once you have a quote, you must commit it to confirm the transaction before execution. The deadline to commit a quote is provided within the `expiresAt`param within the quote response. ```javascript commitBridgeSwapUserQuote.js theme={null} export const commitBridgeSwapUserQuote = async (quoteId, jwt) => { const request = await fetch(`https://api.rhino.fi/bridge/quote/commit/${quoteId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'authorization': jwt } }) return request.json() } ``` For the exact request / response format and more details, see the [API Reference](https://docs.rhino.fi/api-reference/bridge/quote/commit-quote). ### 6. Execute the Bridge & Swap Transaction Now, you can execute the bridge transaction by interacting with the smart contract. See [examples](/contracts/contract-examples) for interacting with the bridge smart contracts across different blockchain environments. ### 7. Full Example Here’s the complete implementation combining all the steps above: ```javascript index.js theme={null} import { getBridgeSwapUserQuote } from "./getBridgeSwapUserQuote"; import { getBridgeConfigs } from "./getBridgeConfigs"; import { getSwapTokenConfig } from "./getSwapTokenConfig"; import { commitBridgeSwapUserQuote } from "./commitBridgeSwapUserQuote"; const JWT = 'YOUR_JWT'; // Replace with your JWT generated from our API const amount = '1'; const chainIn = 'BASE'; const chainOut = 'ARBITRUM'; const tokenIn = 'USDT'; const tokenOut = 'USDC'; const depositorAddress = '0x0000000000000000000000000000000000000000'; // Replace with your depositor address const recipientAddress = '0x0000000000000000000000000000000000000000'; // Replace with your recipient address const bridgeAndSwap = async () => { // Get bridge configs to determine supported chains and tokens or later use for the contract call const configs = await getBridgeConfigs(); const swapTokenConfig = await getSwapTokenConfig(); // Get a bridge quote for the transaction const quote = await getBridgeSwapUserQuote({ amount, chainIn, chainOut, tokenIn, tokenOut, mode: 'pay', depositor: depositorAddress, recipient: recipientAddress, amountNative: '0' }, JWT); if (!quote?.quoteId) throw new Error('Failed to generate user quote.'); // Commit the quote to confirm the transaction when you are ready to send on-chain const commitResult = await commitBridgeSwapUserQuote(quote.quoteId, JWT); if (!commitResult?.quoteId) throw new Error('Failed to commit user quote.'); const chainConfig = configs[chainIn]; const swapConfig = swapTokenConfig[chainIn][tokenIn]; // Execute the bridge transaction by interacting with the smart contract -- see contract examples section on exact implementation await callBridgeContract({ chainConfig, swapConfig, amount: quote.payAmount, tokenIn, commitmentId: commitResult.quoteId, callback: (hash) => console.info('Transaction hash:', hash) }); }; bridgeAndSwap(); ``` For a detailed breakdown of API endpoints and parameters, check the [API Reference](/api-reference/introduction). ## Same-chain swaps When `chainIn === chainOut`, the quote response is flagged as an **atomic same-chain swap** (`isAtomicSwap: true`). These go through a separate contract and a different transaction format than a regular bridge deposit — broadcasting a `depositWithId` / `depositNativeWithId` for an atomic same-chain swap commitment will be rejected by the deposit watcher. Same-chain swaps are **EVM-only**. The flow is identical up to step 5 (commit), then diverges: ### Detect the case After fetching the quote in step 4, check the response: ```javascript theme={null} const isSameChainSwap = quote.chainIn === quote.chainOut && quote.isAtomicSwap === true ``` ### Use a different approval target For ERC-20 same-chain swaps, the approval target is the chain's `sameChainSwapsAddress` (returned in `GET /bridge/configs` under the chain entry) — **not** the regular `contractAddress`. Native-token swaps don't require an approval. ### Fetch the swap calldata instead of building a deposit tx Instead of calling the deposit contract, fetch the prepared calldata. The `commitmentId` is the `quoteId` you committed. ```javascript getSwapCalldata.js theme={null} export const getSwapCalldata = async (commitmentId, jwt) => { const request = await fetch(`https://api.rhino.fi/bridge/swap/calldata/${commitmentId}`, { headers: { 'authorization': jwt } }) return request.json() } ``` The response contains a JSON-stringified transaction object: ```json theme={null} { "calldata": "{\"to\":\"0x...\",\"data\":\"0x...\",\"value\":\"...\"}" } ``` Parse it and submit it as a raw EVM transaction from the depositor wallet: ```javascript ethers theme={null} import { ethers } from 'ethers' const { calldata } = await getSwapCalldata(commitResult.quoteId, JWT) const { to, data, value } = JSON.parse(calldata) const tx = await wallet.sendTransaction({ to, data, value: BigInt(value ?? 0) }) await tx.wait() ``` ```javascript viem theme={null} const { calldata } = await getSwapCalldata(commitResult.quoteId, JWT) const { to, data, value } = JSON.parse(calldata) const hash = await walletClient.sendTransaction({ to, data, value: BigInt(value ?? 0) }) await publicClient.waitForTransactionReceipt({ hash }) ``` Do **not** call `depositWithId` / `depositNativeWithId` on the regular deposit contract for same-chain swaps. ## Next steps For the smart contract integration see - [contract examples](/contracts/contract-examples) To track your bridge transaction status, see [Bridge Status & History](/api-integration/status-history). # Configs (chains & tokens) Source: https://docs.rhino.fi/api-reference/bridge/configs/configs-chains-&-tokens https://api.rhino.fi/bridge/swagger.json get /configs Fetch bridge configs. # Get all token configs for bridge and swap Source: https://docs.rhino.fi/api-reference/bridge/configs/get-all-token-configs-for-bridge-and-swap https://api.rhino.fi/bridge/swagger.json get /bridge-swap-token-configs Fetch all bridge and swap token configs. # Single Config (token address/chain) Source: https://docs.rhino.fi/api-reference/bridge/configs/single-config-token-addresschain https://api.rhino.fi/bridge/swagger.json get /config/tokens Fetch config for a token by address # Single Config (token/chain) Source: https://docs.rhino.fi/api-reference/bridge/configs/single-config-tokenchain https://api.rhino.fi/bridge/swagger.json get /config/{chain}/{token} Fetch config for a token/chain # Create new deposit address Source: https://docs.rhino.fi/api-reference/bridge/depositaddresses/create-new-deposit-address https://api.rhino.fi/bridge/swagger.json post /deposit-addresses Please use the identical endpoint under https://api.rhino.fi/sda. The sda endpoints here exist only for backwards compatibility and will be removed in the future. If you use the SDK, please upgrade to the latest version which uses the correct endpoint. # Get deposit address history Source: https://docs.rhino.fi/api-reference/bridge/depositaddresses/get-deposit-address-history https://api.rhino.fi/bridge/swagger.json get /deposit-addresses/{depositAddress}/{depositChain}/history Please use the identical endpoint under https://api.rhino.fi/sda. The sda endpoints here exist only for backwards compatibility and will be removed in the future. If you use the SDK, please upgrade to the latest version which uses the correct endpoint. # Get deposit address status Source: https://docs.rhino.fi/api-reference/bridge/depositaddresses/get-deposit-address-status https://api.rhino.fi/bridge/swagger.json get /deposit-addresses/{depositAddress}/{depositChain} Please use the identical endpoint under https://api.rhino.fi/sda. The sda endpoints here exist only for backwards compatibility and will be removed in the future. If you use the SDK, please upgrade to the latest version which uses the correct endpoint. # Get public deposit address status Source: https://docs.rhino.fi/api-reference/bridge/depositaddresses/get-public-deposit-address-status https://api.rhino.fi/bridge/swagger.json get /deposit-addresses/{depositAddress}/{depositChain}/public-status Please use the identical endpoint under https://api.rhino.fi/sda. The sda endpoints here exist only for backwards compatibility and will be removed in the future. If you use the SDK, please upgrade to the latest version which uses the correct endpoint. # Get supported tokens between a deposit chain and a destination chain Source: https://docs.rhino.fi/api-reference/bridge/depositaddresses/get-supported-tokens-between-a-deposit-chain-and-a-destination-chain https://api.rhino.fi/bridge/swagger.json get /deposit-addresses/{depositChain}/{destinationChain}/supported-tokens Please use the identical endpoint under https://api.rhino.fi/sda. The sda endpoints here exist only for backwards compatibility and will be removed in the future. If you use the SDK, please upgrade to the latest version which uses the correct endpoint. # Reactivate deposit address Source: https://docs.rhino.fi/api-reference/bridge/depositaddresses/reactivate-deposit-address https://api.rhino.fi/bridge/swagger.json patch /deposit-addresses/{depositAddress}/{depositChain}/activate Please use the identical endpoint under https://api.rhino.fi/sda. The sda endpoints here exist only for backwards compatibility and will be removed in the future. If you use the SDK, please upgrade to the latest version which uses the correct endpoint. # Search deposit addresses by different fields Source: https://docs.rhino.fi/api-reference/bridge/depositaddresses/search-deposit-addresses-by-different-fields https://api.rhino.fi/bridge/swagger.json get /deposit-addresses/search Please use the identical endpoint under https://api.rhino.fi/sda. The sda endpoints here exist only for backwards compatibility and will be removed in the future. If you use the SDK, please upgrade to the latest version which uses the correct endpoint. # Bridge Status Source: https://docs.rhino.fi/api-reference/bridge/history/bridge-status https://api.rhino.fi/bridge/swagger.json get /history/bridge/{bridgeId} Fetch the status of a single bridge. # Bridge Status (Withdraw Hash) Source: https://docs.rhino.fi/api-reference/bridge/history/bridge-status-withdraw-hash https://api.rhino.fi/bridge/swagger.json get /history/bridge/by-hash/{withdrawTxHash} Fetch the status of a single executed bridge by withdraw transaction hash. # Bridge Status (Withdraw Hash) Source: https://docs.rhino.fi/api-reference/bridge/history/bridge-status-withdraw-hash-1 https://api.rhino.fi/bridge/swagger.json get /history/bridge/by-withdraw-hash/{withdrawTxHash} Fetch the status of a single executed bridge by withdraw transaction hash. # Bridge Statuses (Deposit Hash) Source: https://docs.rhino.fi/api-reference/bridge/history/bridge-statuses-deposit-hash https://api.rhino.fi/bridge/swagger.json get /history/bridge/by-deposit-hash/{depositTxHash} Fetch the status of bridges by deposit transaction hash. # Patch Bridge Status Source: https://docs.rhino.fi/api-reference/bridge/history/patch-bridge-status https://api.rhino.fi/bridge/swagger.json patch /history/bridge/{bridgeId} Patch the status of a bridge. # User History Source: https://docs.rhino.fi/api-reference/bridge/history/user-history https://api.rhino.fi/bridge/swagger.json get /history/user Fetch bridge history for a user. Will be removed in the future, please use /histor-cursor instead. # User History Source: https://docs.rhino.fi/api-reference/bridge/history/user-history-1 https://api.rhino.fi/bridge/swagger.json get /history-cursor/user Fetch bridge history for a user. # Commit Quote Source: https://docs.rhino.fi/api-reference/bridge/quote/commit-quote https://api.rhino.fi/bridge/swagger.json post /quote/commit/{quoteId} Commit an already cached user quote. # Public Quote Source: https://docs.rhino.fi/api-reference/bridge/quote/public-quote https://api.rhino.fi/bridge/swagger.json get /quote/public Deprecated: Use /quote/bridge-swap/public instead. Fetch public bridge quote. # Public Quote for Bridge & Swap Source: https://docs.rhino.fi/api-reference/bridge/quote/public-quote-for-bridge-&-swap https://api.rhino.fi/bridge/swagger.json get /quote/bridge-swap/public Fetch public bridge and swap quote. # User Quote Source: https://docs.rhino.fi/api-reference/bridge/quote/user-quote https://api.rhino.fi/bridge/swagger.json post /quote/user Deprecated: Use /quote/bridge-swap/user instead. Fetch bridge user quote when authenticated. # User Quote for Bridge & Swap Source: https://docs.rhino.fi/api-reference/bridge/quote/user-quote-for-bridge-&-swap https://api.rhino.fi/bridge/swagger.json post /quote/bridge-swap/user Fetch bridge and swap user quote when authenticated. # Calldata to execute a single swap Source: https://docs.rhino.fi/api-reference/bridge/swap/calldata-to-execute-a-single-swap https://api.rhino.fi/bridge/swagger.json get /swap/calldata/{commitmentId} Get the calldata needed to execute a single swap. # Rhino API Reference Source: https://docs.rhino.fi/api-reference/introduction Full endpoint reference for the Rhino Stablecoin Activation Stack — deposit address generation, bridge quotes and execution, transaction history, and Extensions. If you're looking for a quick start guide, check out the [Quick Start](/api-integration/bridge#quickstart) guide. ## Rhino API Reference The Rhino API is the integration surface for the Rhino Stablecoin Activation Stack. It covers deposit address generation, bridge quotes and execution, transaction history, and — for accounts with Extensions enabled — Automated Onchain Actions and advanced fee configuration. Each endpoint includes example requests and responses to help you implement features quickly and efficiently. **What's Covered** * Authentication – How to securely connect to our API * Endpoints – Detailed descriptions of available API calls * Error Handling – Understanding API responses and troubleshooting issues * Rate Limits – Guidelines on request limits ## Authentication All non-public API endpoints are authenticated using a JSON Web Token. To authenticate your requests, include the token in the `Authorization` header. To obtain a JWT for your app, please follow the [Authentication](/api-integration/authentication) guide. # Create new deposit address Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/create-new-deposit-address https://api.rhino.fi/sda/swagger.json post /deposit-addresses Any bridgable tokens sent to a generated deposit address will be automatically bridged to the set up address on the destination chain. # Get deposit address history Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/get-deposit-address-history https://api.rhino.fi/sda/swagger.json get /deposit-addresses/{depositAddress}/{depositChain}/history Returns the history of a deposit address on a given chain. This includes all accepted (with precise history), failed, and rejected bridge transactions. # Get deposit address status Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/get-deposit-address-status https://api.rhino.fi/sda/swagger.json get /deposit-addresses/{depositAddress}/{depositChain} Returns the status of a deposit address on a given chain # Get public deposit address status Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/get-public-deposit-address-status https://api.rhino.fi/sda/swagger.json get /deposit-addresses/{depositAddress}/{depositChain}/public-status Returns the status of a deposit address on a given chain # Get SDA rate limit status Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/get-sda-rate-limit-status https://api.rhino.fi/sda/swagger.json get /deposit-addresses/rate-limit-status Returns the authenticated user's current SDA creation rate limit configuration and usage counts. # Get supported tokens between a deposit chain and a destination chain Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/get-supported-tokens-between-a-deposit-chain-and-a-destination-chain https://api.rhino.fi/sda/swagger.json get /deposit-addresses/{depositChain}/{destinationChain}/supported-tokens Returns the supported tokens between a deposit chain and a destination chain # Reactivate deposit address Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/reactivate-deposit-address https://api.rhino.fi/sda/swagger.json patch /deposit-addresses/{depositAddress}/{depositChain}/activate Reactivates a deposit address that is inactive. # Search deposit addresses by different fields Source: https://docs.rhino.fi/api-reference/sda/depositaddresses/search-deposit-addresses-by-different-fields https://api.rhino.fi/sda/swagger.json get /deposit-addresses/search Returns the deposit addresses that match the given search criteria. # Overview Source: https://docs.rhino.fi/contracts/contract-examples This section provides integration examples for interacting with the rhino.fi bridge smart contracts across different blockchain environments. Whether you're working with **EVM-based chains** like Ethereum and Scroll or **non-EVM chains** such as Solana, Tron, TON, and Starknet, you'll find practical code samples to help you execute bridge transactions. Select your target blockchain to explore contract interaction examples: * [EVM Chains](./evm) – Ethereum, Scroll, and compatible chains * [Solana](./solana) * [Tron](./tron) * [TON](./ton) * [Starknet](./starknet) If you prefer reading code locally or want to run these examples in your local environment, you can use the [Github examples repo](https://github.com/rhinofi/bridge-examples) We recommend using the [SDK](/sdk/quickstart) to make bridges as it has all the blockchain interactions already built in. # EVM (Ethereum / L2s) Source: https://docs.rhino.fi/contracts/evm This guide explains how to interact with the [**Bridge Contract**](https://github.com/rhinofi/contracts_public/blob/master/bridge-deposit/DVFDepositContract.sol) on EVM-compatible blockchains. ## **Contract Overview** The `BridgeContract` exposes the following key function for bridging: ```solidity theme={null} /** * Deposit ERC20 tokens to the contract address with a commitment ID */ function depositWithId( address token, uint256 amount, uint256 commitmentId ) public; /** * Deposit native chain currency into contract address with a commitment ID */ function depositNativeWithId( uint256 commitmentId ) external payable; ``` ## ABIs ```json bridgeAbi.json theme={null} [ { "inputs": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "commitmentId", "type": "uint256" } ], "name": "depositWithId", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "commitmentId", "type": "uint256" } ], "name": "depositNativeWithId", "outputs": [], "stateMutability": "payable", "type": "function" } ] ``` ```json erc20Abi.json theme={null} [ { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" } ] ``` ### Setup For this example we'll use `ethers.js` to interact with the contract, but you can replace it with any EVM library of your choice. Make sure to install it first: ```bash yarn theme={null} yarn add ethers ``` ```bash npm theme={null} npm install ethers ``` ## Interacting with the Contract To execute a bridge transaction, call the `depositWithId` (ERC20) or `depositNativeWithId` (native) function as follows: The `commitmentId` required by the contract corresponds to the `quoteId` returned by the API. To obtain the `commitmentId`, first request a quote from the API, then commit it. The response will include the `quoteId`, which should be used as the `commitmentId` in the contract call. See [the API bridge example](/api-integration/bridge#6-full-example) for details. ```javascript ethers theme={null} import ethers from 'ethers' import getBridgeConfigs from './getBridgeConfigs' import erc20Abi from './erc20Abi' import bridgeAbi from './bridgeAbi' export const callEvmBridgeContract = async ({ address, amount, chain, token, commitmentId, }) => { // See API Integration -> Fetch Bridge Configs for the full implementation const configs = await getBridgeConfigs() const chainConfig = configs[chain] // Replace with your wallet implementation const wallet = new ethers.Wallet(EVM_PRIVATE_KEY, new ethers.JsonRpcProvider(chainConfig.rpc)) const tokenConfig = chainConfig.tokens[token] const tokenAmount = +amount * 10 ** tokenConfig.decimals const tokenAddress = tokenConfig.address const bridgeContractAddress = chainConfig.contractAddress const isNativeToken = chainConfig.nativeTokenName === token const bridgeContract = new ethers.Contract(bridgeContractAddress, bridgeAbi, wallet) // For ERC20 tokens we call 'depositWithId' and for native tokens we call 'depositNativeWithId' if (!isNativeToken) { // make sure to check token allowance first for erc20 tokens const erc20Contract = new ethers.Contract(tokenAddress, erc20Abi, wallet) const contractAllowance = await erc20Contract.allowance(address, bridgeContractAddress) const allowance = parseFloat(contractAllowance.toString()) / 10 ** tokenConfig.decimals if (allowance === 0 || allowance < parseFloat(amount)) { const tx = await erc20Contract.approve(bridgeContractAddress, tokenAmount) await tx.wait() } // Call the contract const tx = await bridgeContract.depositWithId(tokenAddress, tokenAmount, BigInt(`0x${commitmentId}`)) return tx.wait() } const tx = await bridgeContract.depositNativeWithId(BigInt(`0x${commitmentId}`), { value: parseUnits(amount, 'ether'), }) return tx.wait() } ``` # Solana Contracts Source: https://docs.rhino.fi/contracts/solana This guide explains how to interact with the rhino.fi Bridge program on Solana. ## **Contract Overview** The Bridge program exposes the following function for bridging: ```solidity theme={null} pub fn deposit_with_id( ctx: Context, amount: u64, commitment_id: u128, ) ``` ## IDL ```json idl theme={null} { version: '0.1.0', name: 'bridge', constants: [ { name: 'AUTHORITY_SEED', type: 'string', value: '"authority"', }, { name: 'BRIDGE_SEED', type: 'string', value: '"rhino_bridge"', }, ], instructions: [ { name: 'createBridge', accounts: [ { name: 'bridge', isMut: true, isSigner: false, }, { name: 'admin', isMut: false, isSigner: false, docs: ['The admin of the Bridge'], }, { name: 'payer', isMut: true, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'systemProgram', isMut: false, isSigner: false, docs: ['Solana ecosystem accounts'], }, ], args: [], }, { name: 'deposit', accounts: [ { name: 'bridge', isMut: false, isSigner: false, }, { name: 'pool', isMut: false, isSigner: false, }, { name: 'poolAuthority', isMut: false, isSigner: false, }, { name: 'depositor', isMut: false, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'mint', isMut: false, isSigner: false, }, { name: 'poolAccount', isMut: true, isSigner: false, }, { name: 'depositorAccount', isMut: true, isSigner: false, }, { name: 'payer', isMut: true, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'tokenProgram', isMut: false, isSigner: false, docs: ['Solana ecosystem accounts'], }, { name: 'associatedTokenProgram', isMut: false, isSigner: false, }, { name: 'systemProgram', isMut: false, isSigner: false, }, { name: 'eventAuthority', isMut: false, isSigner: false, }, { name: 'program', isMut: false, isSigner: false, }, ], args: [ { name: 'amount', type: 'u64', }, { name: 'ethAddressUpper', type: 'u32', }, { name: 'ethAddressLower', type: 'u128', }, ], }, { name: 'depositWithId', accounts: [ { name: 'bridge', isMut: false, isSigner: false, }, { name: 'pool', isMut: false, isSigner: false, }, { name: 'poolAuthority', isMut: false, isSigner: false, }, { name: 'depositor', isMut: false, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'mint', isMut: false, isSigner: false, }, { name: 'poolAccount', isMut: true, isSigner: false, }, { name: 'depositorAccount', isMut: true, isSigner: false, }, { name: 'payer', isMut: true, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'tokenProgram', isMut: false, isSigner: false, docs: ['Solana ecosystem accounts'], }, { name: 'associatedTokenProgram', isMut: false, isSigner: false, }, { name: 'systemProgram', isMut: false, isSigner: false, }, { name: 'eventAuthority', isMut: false, isSigner: false, }, { name: 'program', isMut: false, isSigner: false, }, ], args: [ { name: 'amount', type: 'u64', }, { name: 'commitmentId', type: 'u128', }, ], }, { name: 'createPool', accounts: [ { name: 'bridge', isMut: false, isSigner: false, }, { name: 'pool', isMut: true, isSigner: false, }, { name: 'poolAuthority', isMut: false, isSigner: false, }, { name: 'mint', isMut: false, isSigner: false, }, { name: 'poolAccount', isMut: true, isSigner: false, }, { name: 'admin', isMut: false, isSigner: true, docs: ['The admin of the Bridge'], }, { name: 'payer', isMut: true, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'tokenProgram', isMut: false, isSigner: false, docs: ['Solana ecosystem accounts'], }, { name: 'associatedTokenProgram', isMut: false, isSigner: false, }, { name: 'systemProgram', isMut: false, isSigner: false, }, ], args: [], }, { name: 'allowOperator', accounts: [ { name: 'bridge', isMut: false, isSigner: false, }, { name: 'operatorStorage', isMut: true, isSigner: false, }, { name: 'operator', isMut: false, isSigner: false, }, { name: 'admin', isMut: false, isSigner: true, docs: ['The admin of the Bridge'], }, { name: 'payer', isMut: true, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'systemProgram', isMut: false, isSigner: false, docs: ['Solana ecosystem accounts'], }, ], args: [ { name: 'isAllowed', type: 'bool', }, ], }, { name: 'withdraw', accounts: [ { name: 'bridge', isMut: false, isSigner: false, }, { name: 'pool', isMut: false, isSigner: false, }, { name: 'poolAuthority', isMut: false, isSigner: false, }, { name: 'operatorStorage', isMut: false, isSigner: false, }, { name: 'recipient', isMut: false, isSigner: false, }, { name: 'operator', isMut: false, isSigner: true, }, { name: 'mint', isMut: false, isSigner: false, }, { name: 'poolAccount', isMut: true, isSigner: false, }, { name: 'recipientAccount', isMut: true, isSigner: false, }, { name: 'payer', isMut: true, isSigner: true, docs: ['The account paying for all rents'], }, { name: 'tokenProgram', isMut: false, isSigner: false, docs: ['Solana ecosystem accounts'], }, { name: 'associatedTokenProgram', isMut: false, isSigner: false, }, { name: 'systemProgram', isMut: false, isSigner: false, }, ], args: [ { name: 'amount', type: 'u64', }, ], }, { name: 'transferAdmin', accounts: [ { name: 'bridge', isMut: true, isSigner: false, }, { name: 'newAdmin', isMut: false, isSigner: false, docs: ['The admin of the Bridge'], }, { name: 'admin', isMut: false, isSigner: true, docs: ['The admin of the Bridge'], }, { name: 'systemProgram', isMut: false, isSigner: false, docs: ['Solana ecosystem accounts'], }, ], args: [], }, ], accounts: [ { name: 'bridge', type: { kind: 'struct', fields: [ { name: 'admin', docs: ['Account that has admin authority over the Bridge'], type: 'publicKey', }, ], }, }, { name: 'pool', type: { kind: 'struct', fields: [ { name: 'bridge', docs: ['Primary key of the Bridge'], type: 'publicKey', }, { name: 'mint', docs: ['Mint of token A'], type: 'publicKey', }, ], }, }, { name: 'operator', type: { kind: 'struct', fields: [ { name: 'operator', docs: ['Public key of the operator'], type: 'publicKey', }, { name: 'isAllowed', docs: ['Whether the operator has access'], type: 'bool', }, ], }, }, ], events: [ { name: 'BridgedDeposit', fields: [ { name: 'from', type: 'publicKey', index: false, }, { name: 'to', type: 'publicKey', index: false, }, { name: 'token', type: 'publicKey', index: false, }, { name: 'amount', type: 'u64', index: false, }, { name: 'ethAddressUpper', type: 'u32', index: false, }, { name: 'ethAddressLower', type: 'u128', index: false, }, ], }, { name: 'BridgedDepositWithId', fields: [ { name: 'from', type: 'publicKey', index: false, }, { name: 'to', type: 'publicKey', index: false, }, { name: 'token', type: 'publicKey', index: false, }, { name: 'amount', type: 'u64', index: false, }, { name: 'commitmentId', type: 'u128', index: false, }, ], }, { name: 'BridgeWithdraw', fields: [ { name: 'from', type: 'publicKey', index: false, }, { name: 'to', type: 'publicKey', index: false, }, { name: 'amount', type: 'u64', index: false, }, ], }, ], errors: [ { code: 6000, name: 'DepositNotAllowed', msg: 'DEPOSIT_NOT_ALLOWED', }, { code: 6001, name: 'Unauthorized', msg: 'UNAUTHORIZED', }, { code: 6002, name: 'NotEnoughLiquidity', msg: 'NOT_ENOUGH_LIQUIDITY', }, { code: 6003, name: 'NotEnoughBalance', msg: 'NOT_ENOUGH_BALANCE', }, { code: 6004, name: 'InvalidAmount', msg: 'INVALID_AMOUNT', }, ], } ``` ### Setup For this example we'll use `@solana/web3.js`, `@solana/spl-token` and `@coral-xyz/anchor` to interact with the contract, but you can replace it with any Solana library of your choice. Make sure to install it first: ```bash yarn theme={null} yarn add @solana/web3.js @solana/spl-token @coral-xyz/anchor ``` ```bash npm theme={null} npm install @solana/web3.js @solana/spl-token @coral-xyz/anchor ``` ## Set-up helpers for Solana accounts ```javascript solanaBridgeHelpers.js theme={null} import { PublicKey } from '@solana/web3.js' import { getAssociatedTokenAddressSync } from '@solana/spl-token' import { Buffer } from 'buffer' export const getSolanaBridgeKey = async (bridgeContractAddress) => { const programId = new PublicKey(bridgeContractAddress) return PublicKey.findProgramAddressSync([Buffer.from('rhino_bridge')], programId)[0] } export const getSolanaPoolKey = async (bridgeContractAddress, mint) => { const programId = new PublicKey(bridgeContractAddress) const bridgeKey = await getSolanaBridgeKey(bridgeContractAddress) const mintId = new PublicKey(mint) return PublicKey.findProgramAddressSync([bridgeKey.toBuffer(), mintId.toBuffer()], programId)[0] } export const getSolanaPoolAuthority = async (bridgeContractAddress, mint) => { const programId = new PublicKey(bridgeContractAddress) const bridgeKey = await getSolanaBridgeKey(bridgeContractAddress) const mintId = new PublicKey(mint) return PublicKey.findProgramAddressSync( [bridgeKey.toBuffer(), mintId.toBuffer(), Buffer.from('authority')], programId, )[0] } export const getSolanaPoolTokenAccount = async (bridgeContractAddress, mint) => { const poolAuthority = await getSolanaPoolAuthority(bridgeContractAddress, mint) const mintId = new PublicKey(mint) return getAssociatedTokenAddressSync(mintId, poolAuthority, true) } export const getSolanaDepositorAccount = async (secondaryWalletAddress, mint) => { const mintId = new PublicKey(mint) const depositor = new PublicKey(secondaryWalletAddress) return getAssociatedTokenAddressSync(mintId, depositor, true) } ``` ## Interacting with the Contract To execute a bridge transaction, call the `depositWithId` (ERC20) or `depositNativeWithId` (native) function as follows: The `commitmentId` required by the contract corresponds to the `quoteId` returned by the API. To obtain the `commitmentId`, first request a quote from the API, then commit it. The response will include the `quoteId`, which should be used as the `commitmentId` in the contract call. See [the API bridge example](/api-integration/bridge#6-full-example) for details. ```javascript anchor theme={null} import {Connection, PublicKey} from '@solana/web3.js' import {AnchorProvider, Program, BN} from '@coral-xyz/anchor' import { getSolanaBridgeKey, getSolanaDepositorAccount, getSolanaPoolAuthority, getSolanaPoolKey, getSolanaPoolTokenAccount, } from './solanaBridgeHelpers' import {IDL} from "./idl"; import getBridgeConfigs from './getBridgeConfigs' // Import a solana wallet / or use your wallet library const secretKey = base58.decode(SOLANA_PRIVATE_KEY) const keypair = Keypair.fromSecretKey(new Uint8Array(secretKey)) const wallet = new Wallet(keypair) export const callSolanaBridgeContract = async ( { token, chain, // SOLANA commitmentId, amount, }) => { // See API Integration -> Fetch Bridge Configs for the full implementation const configs = await getBridgeConfigs() const chainConfig = configs[chain] const solanaWalletAddress = wallet.publicKey.toString() const tokenConfig = chainConfig.tokens[token] const tokenAmount = +amount * 10 ** tokenConfig.decimals const tokenAddress = tokenConfig.address const bridgeContractAddress = chainConfig.contractAddress const connection = new Connection(chainConfig.rpc) const options = AnchorProvider.defaultOptions() const anchorProvider = new AnchorProvider(connection, wallet, options) const depositor = new PublicKey(solanaWalletAddress) const bridgeKey = await getSolanaBridgeKey(bridgeContractAddress) const poolKey = await getSolanaPoolKey(bridgeContractAddress, tokenAddress) const poolAuthority = await getSolanaPoolAuthority(bridgeContractAddress, tokenAddress) const poolAccount = await getSolanaPoolTokenAccount(bridgeContractAddress, tokenAddress) const depositorAccount = await getSolanaDepositorAccount(solanaWalletAddress, tokenAddress) const program = new Program(IDL, bridgeContractAddress, anchorProvider) const accounts = { bridge: bridgeKey, pool: poolKey, poolAuthority, poolAccount: poolAccount, depositor, mint: new PublicKey(tokenAddress), depositorAccount: depositorAccount, } const depositTxHash = await program.methods['depositWithId'](new BN(tokenAmount), new BN(commitmentId, 'hex')) .accounts(accounts) .signers([]) .rpc() await connection.getSignatureStatus(depositTxHash) return depositTxHash } ``` # Starknet Contracts Source: https://docs.rhino.fi/contracts/starknet This guide explains how to interact with the rhino.fi Bridge contract on Starknet. ### Setup For this example we'll use `starknet.js` to interact with the contract, but you can replace it with any Starknet library of your choice. Make sure to install it first: ```bash yarn theme={null} yarn add starknet ``` ```bash npm theme={null} npm install starknet ``` ## Interacting with the Contract To execute a bridge transaction, call the `deposit_with_id` function as follows: The `commitmentId` required by the contract corresponds to the `quoteId` returned by the API. To obtain the `commitmentId`, first request a quote from the API, then commit it. The response will include the `quoteId`, which should be used as the `commitmentId` in the contract call. See [the API bridge example](/api-integration/bridge#6-full-example) for details. ```javascript starknet.js theme={null} import {CallData, cairo} from 'starknet' export const callStarknetBridgeContract = async ( { token, commitmentId, amount, token, }) => { // See API Integration -> Fetch Bridge Configs for the full implementation const configs = await getBridgeConfigs() const chainConfig = configs[chain] // Replace with your wallet implementation const provider = new RpcProvider({ nodeUrl: chainConfig.rpc }); const account = new Account(provider, STARKNET_WALLET_ADDRESS, STARKNET_PRIVATE_KEY); const tokenConfig = chainConfig.tokens[token] const amountWithDecimals = +amount * 10 ** tokenConfig.decimals const tokenAddress = tokenConfig.address const bridgeContractAddress = chainConfig.contractAddress const depositAmount = cairo.uint256(amountWithDecimals) const id = `0x${commitmentId}` const multiCall = await account.execute([ { contractAddress: tokenAddress, entrypoint: 'approve', calldata: CallData.compile({ spender: bridgeContractAddress, amount: depositAmount, }), }, { contractAddress: bridgeContractAddress, entrypoint: 'deposit_with_id', calldata: CallData.compile({ token: tokenAddress, amount: depositAmount, commitment_id: id, }), }, ]) const result = await provider.waitForTransaction(multiCall.transaction_hash) if ('revert_reason' in result && 'execution_status' in result) { if (result.execution_status === 'REVERTED') { throw new Error(result.revert_reason) } } return {transactionHash: multiCall.transaction_hash} } ``` # TON Contracts Source: https://docs.rhino.fi/contracts/ton This guide explains how to interact with the rhino.fi Bridge contract on TON. ### Setup For this example we'll use `@ton/ton` and `@ton/crypto` to interact with the contract, but you can replace it with any TON library of your choice. Make sure to install it first: ```bash yarn theme={null} yarn add @ton/ton @ton/crypto ``` ```bash npm theme={null} npm install @ton/ton @ton/crypto ``` ## Interacting with the Contract To execute a bridge transaction, send a transfer with the following payload: The `commitmentId` required by the contract corresponds to the `quoteId` returned by the API. To obtain the `commitmentId`, first request a quote from the API, then commit it. The response will include the `quoteId`, which should be used as the `commitmentId` in the contract call. See [the API bridge example](/api-integration/bridge#6-full-example) for details. ```javascript @ton/ton theme={null} import {beginCell, toNano, Address, TonClient, internal, WalletContractV4} from '@ton/ton' import {mnemonicToPrivateKey} from '@ton/crypto'; import getBridgeConfigs from './getBridgeConfigs' const getJettonWalletAddress = async (tonWalletAddress, jettonMasterAddress, publicProvider) => { const jettonMaster = publicProvider.open(JettonMaster.create(jettonMasterAddress)) return jettonMaster.getWalletAddress(tonWalletAddress) } export const callTonBridgeContract = async ( { commitmentId, amount, token, tonWalletAddress, }) => { // See API Integration -> Fetch Bridge Configs for the full implementation const configs = await getBridgeConfigs() const chainConfig = configs[chain] // Replace with your wallet implementation const tonClient = new TonClient({ endpoint: chainConfig.rpc, }) const key = await mnemonicToPrivateKey(TON_MNEMONIC); const workchain = 0; const wallet = WalletContractV4.create({ workchain, publicKey: keyPair.publicKey, }) const walletProvider = tonClient.open(wallet) const tokenConfig = chainConfig.tokens[token] const amountWithDecimals = +amount * 10 ** tokenConfig.decimals const tokenAddress = tokenConfig.address const bridgeContractAddress = chainConfig.contractAddress const jettonWalletContract = await getJettonWalletAddress( Address.parse(tonWalletAddress), Address.parse(tokenAddress), tonClient ) const sourceAddress = Address.parse(tonWalletAddress) const destinationAddress = Address.parse(bridgeContractAddress) const forwardPayload = beginCell() .storeUint(BigInt(`0x${commitmentId}`), 96) .endCell() const forwardAmount = 0.02 const body = beginCell() .storeUint(0xf8a7ea5, 32) // opcode for jetton transfer .storeUint(0, 64) // query id .storeCoins(BigInt(amountWithDecimals)) // jetton amount, amount * 10^9 .storeAddress(destinationAddress) // TON wallet destination address .storeAddress(sourceAddress) // response excess destination .storeBit(0) // no custom payload .storeCoins(toNano(forwardAmount)) // forward amount (if >0, will send notification message) .storeBit(1) // we store forwardPayload as a reference .storeRef(forwardPayload) .endCell() const seqno: number = await walletProvider.getSeqno(); return await walletProvider.sendTransfer({ seqno, secretKey: key.secretKey, timeout: Math.floor(Date.now() / 1000) + 360, messages: [ internal({ to: jettonWalletContract.toString(), value: toNano(0.1), body: body, }), ], }) } ``` # TRON Contracts Source: https://docs.rhino.fi/contracts/tron This guide explains how to interact with the rhino.fi Bridge contract on Tron. ## ABI ```json abi.js theme={null} [ { inputs: [ { internalType: "address", name: "token", type: "address", }, { internalType: "uint256", name: "amount", type: "uint256", }, { internalType: "uint256", name: "commitmentId", type: "uint256", }, ], name: "depositWithId", outputs: [], stateMutability: "nonpayable", type: "function", }, { inputs: [], name: "depositNative", outputs: [], stateMutability: "payable", type: "function", }, ]; ``` ### Setup For this example we'll use `tronweb` to interact with the contract, but you can replace it with any Tron library of your choice. Make sure to install it first: ```bash yarn theme={null} yarn add tronweb ``` ```bash npm theme={null} npm install tronweb ``` ## Interacting with the Contract To execute a bridge transaction, call the `depositWithId` (TRC20) function as follows: The `commitmentId` required by the contract corresponds to the `quoteId` returned by the API. To obtain the `commitmentId`, first request a quote from the API, then commit it. The response will include the `quoteId`, which should be used as the `commitmentId` in the contract call. See [the API bridge example](/api-integration/bridge#6-full-example) for details. ```javascript tronweb theme={null} import { TronWeb, providers } from 'tronweb' import getBridgeConfigs from './getBridgeConfigs' // You can find the bridgeABI above in the ABI section import bridgeAbi from './bridgeAbi' export const callTronBridgeContract = async ( { token, amount, commitmentId, tronWalletAddress }) => { // See API Integration -> Fetch Bridge Configs for the full implementation const configs = await getBridgeConfigs() const chainConfig = configs[chain] // Replace with your wallet implementation const tronWeb = new TronWeb({ fullHost: new providers.HttpProvider('https://api.trongrid.io'), // headers: { "TRON-PRO-API-KEY": 'your api key' }, -- if using a custom RPC privateKey: TRON_PRIVATE_KEY }) const tokenConfig = chainConfig.tokens[token] const amountWithDecimals = +amount * 10 ** tokenConfig.decimals const tokenAddress = tokenConfig.address const bridgeContractAddress = chainConfig.contractAddress const { abi } = await tronWeb.trx.getContract(tokenAddress); const trc20Contract = tronWeb.contract(abi.entrys, tokenAddress); const allowance = await trc20Contract.allowance(tronWalletAddress, bridgeContractAddress).call(); const formattedAllowance = parseInt(allowance, 10) / 10 ** tokenConfig.decimals; if (formattedAllowance < parseFloat(amount)) { await trc20Contract.approve(bridgeContractAddress, amountWithDecimals).send() } const contract = tronWeb.contract(bridgeAbi, bridgeContractAddress) const tx = await contract.depositWithId(tokenAddress, amountWithDecimals, `0x${commitmentId}`).send({shouldPollResponse: true}) return tx } ``` # Contract addresses Source: https://docs.rhino.fi/general/contract-addresses List of rhino.fi smart contract addresses deployed across our supported chains | Chain | Contract address | | --------------- | ------------------------------------------------------------------ | | Arbitrum One | 0x10417734001162Ea139e8b044DFe28DbB8B28ad0 | | Avalanche | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Base | 0x2f59e9086ec8130e21bd052065a9e6b2497bb102 | | BNB Smart Chain | 0xb80a582fa430645a043bb4f6135321ee01005fef | | Celo | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Ethereum | 0xbca3039a18c0d2f2f84ba8a028c67290bc045afa | | Gnosis | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | HyperEVM | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Ink | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Kaia | 0x92650ddc467ed628868bdba23cf81eafaab60175 | | Katana | 0x04317f0e4795b1e1bab333234153fa10aaac79e9 | | Linea | 0xcf68a2721394dcf5dcf66f6265c1819720f24528 | | Mantle | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Polygon | 0xBA4EEE20F434bC3908A0B18DA496348657133A7E | | opBNB | 0x2b4553122d960ca98075028d68735cc6b15deeb5 | | Optimism | 0x0bca65bf4b4c8803d2f0b49353ed57caaf3d66dc | | Plasma | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Solana | FCW1uBM3pZ7fQWvEL9sxTe4fNiH41bu9DWX4ErTZ6aMq | | Sonic | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Stable | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Starknet | 0x0259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8 | | Tempo | 0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1 | | Ton | EQAj3SoOk4MPzjn816Crw1b4RxW79fB\_Z549tyCd9HIQV6b7 | | Tron | TT3kgJohTQJNKDUWwTxtRDMHNNWNvNG3i4 | | Unichain | 0xb0d7040b93fe5b099d3ce02ea86c4a1b695732d0 | | zkSync Era | 0x1fa66e2b38d0cc496ec51f81c3e05e6a6708986f | # Status page Source: https://docs.rhino.fi/general/status-page To see up-to-date information about the health of the chains that we support and key \[[Rhino.fi](http://Rhino.fi)]\([http://rhino.fi/](http://rhino.fi/)) product status information please visit the status page: [https://status.rhino.fi](https://status.rhino.fi/posts/dashboard) # API Keys Source: https://docs.rhino.fi/get-started/api-keys To use the API or SDK, you need to obtain an API key. API keys can either be secret or public. ### Public API keys Public API keys can be safely exposed in a browser environment as they don't grant access to any sensitive data. So for example, if your application runs client-side in a browser, you should use a public API key. Public API keys can't be used to access the bridge history of your app as that would leak the entire history to the public. ### Secret API keys Secret API keys work just the same as public ones with the difference that they can be used to access the entire bridge history of your app. You should only use secret API keys in code that is not run client side. You can create a project and manage API keys there. # Extensions Source: https://docs.rhino.fi/get-started/extensions Extensions are premium features that expand the capabilities of the Rhino Stablecoin Activation Stack. ## Extensions Extensions are premium features that expand the capabilities of the Rhino Stablecoin Activation Stack. They are activated at the account level — your account representative enables them when you sign up or upgrades your account later. Extensions are designed for teams with specific requirements: custom compliance workflows, programmable settlement outcomes, custom chain coverage, or advanced fee management. *** ## Available extensions | Extension | Tier | Description | | ------------------------------------------------------------------------------------ | -------------- | ---------------------------------------------------------------------------------------- | | [Enhanced Compliance & Risk Management](/get-started/extensions/enhanced-compliance) | Free + Premium | Custom AML screening rules and real-time AML event notifications | | [Automated Onchain Actions](/get-started/extensions/automated-onchain-actions) | Premium | Execute a contract call automatically when settled funds arrive on the destination chain | | [1:1 Stablecoin Swaps](/get-started/extensions/stablecoin-swaps) | Premium | USDT↔USDC conversion at a guaranteed 1:1 rate, no slippage | | [Advanced Fee & Limit Management](/get-started/extensions/advanced-fee-management) | Free + Premium | Sponsor fees for end users, add markup, end-of-month invoicing | | [Custom Chain & Token Support](/get-started/extensions/custom-chain-token-support) | Premium | Expand supported chains or tokens beyond the standard set | | [Tron Access](/get-started/extensions/tron-access) | Premium | Enable TRON network deposits and settlement | *** ## Enabling extensions Extensions are not self-serve. To enable any extension, contact your account representative. All extensions are governed by your master service agreement. Some extensions have additional contractual requirements (e.g. Enhanced Compliance has a data processing addendum). *** ## Related * [What is Rhino?](/get-started/what-is-rhino) — overview of the full product * [Fees & Limits](/get-started/fees-and-limits) — how fees work, including fee extensions # Advanced Fee & Limit Management Source: https://docs.rhino.fi/get-started/extensions/advanced-fee-management Control how fees are presented and collected — sponsor fees for end users, add markup, or adjust deposit limits. ## Advanced Fee & Limit Management **Extension tier:** Free + Premium The Advanced Fee & Limit Management extension gives you control over how fees are presented and collected in your integration. It is designed for clients that want to either remove fee friction from their end users entirely, or use the bridge integration as a revenue source. *** ## Fee sponsorship When fee sponsorship is enabled, end users pay nothing. Rhino deducts no fee from the deposit; the client's account is charged instead. The full deposited amount (less gas cost) is credited to the destination. This is the recommended configuration for any onboarding flow where the client wants to absorb fees as a cost of acquisition or service. **Configuration:** Fee sponsorship is set at the account level. Individual SDA or bridge requests do not need additional parameters when sponsorship is active. Contact your account representative to enable it. *** ## Fee markup Clients can add a configurable markup on top of the standard Rhino fee. The markup is specified as a fixed amount or percentage, agreed at account setup. Rhino collects the combined fee (its standard fee plus the client markup) on every transaction. ### End-of-month invoicing Rhino consolidates all collected client markup fees over the calendar month and issues an invoice to the client. Payment is made to the client's designated wallet or bank account. Invoicing timelines and minimum payout thresholds are agreed in the service agreement. This means clients can generate revenue from their integration without building a fee collection mechanism, operating any smart contracts, or handling user-facing billing. *** ## Limit configuration Default deposit limits: | | Bridge & Swap | Smart Deposit Addresses | | --------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------ | | **Minimum deposit** | No minimum | \$0.5 on EVMs and Solana
\$5 USD equivalent on Ethereum and Tron (editable on demand) | | **Maximum instant deposit** | $100k–$ 1M USD (route dependent) | $100k–$ 1M USD (route dependent) | | **Maximum slow deposit** | Up to \$10M USD | Up to \$10M USD | With this extension, limits can be adjusted by agreement. Contact your account representative to discuss non-standard limits. Deposits outside the applicable limits are not processed. Funds can be returned via customer support. *** ## Configuration reference Fee and limit settings are configurable. Please contact your account representative to manage your configuration. Changes to fee structure require agreement from your account representative and may be subject to minimum volume commitments. Coming soon: * **Configurable and editable** **fees & limits** in the [Rhino Console](https://docs.rhino.fi/get-started/rhino-console). *** ## Related * [Fees & Limits](/get-started/fees-and-limits) — general fee documentation * [Smart Deposit Addresses — fee handling](/api-integration/smart-deposits) * [Extensions overview](/get-started/extensions) # Automated Onchain Actions Source: https://docs.rhino.fi/get-started/extensions/automated-onchain-actions Automated Onchain Actions let you specify a contract call that executes automatically when settled funds arrive on the destination chain. Configure once; Rhino handles approval, encoding, execution, and error handling. Automated Onchain Actions let you specify a smart contract call that executes automatically the moment settled funds arrive on the destination chain. You define the target contract, the function to call, and any custom parameters — Rhino handles everything else. This is a premium Extension. Your account must have Automated Onchain Actions enabled before the `postBridgeData` field will be accepted on quote or SDA generation requests. Contact your account representative to enable it. #### The result: a single deposit produces a fully activated on-chain outcome — no additional steps from your user or your system. *** ## How It Works Enabling an Automated Onchain Action requires no additional API calls or integration steps. You simply include a `postBridgeData` field in your bridge quote or Smart Deposit Address generation request. We take care of the contract approval, encoding, execution, and error handling on the destination chain. The `postBridgeData` field accepts: * **Tag** under `_tag` that specifies the Automated Onchain Action you want to execute * **Optional custom parameters** to specify a custom target like vault, user ID or invoice ID We configure everything else: tokens and amounts, contract specific inputs, token approval, amount injection, gas estimation, and execution sequencing. *** ## Use Cases Automated Onchain Actions apply wherever you need a defined on-chain outcome rather than just a token credit. Common patterns: **Yield and protocol integrations** Deposit bridged funds directly into a vault, lending pool, or staking contract. The user's recipient address receives the protocol's output token (e.g. vault shares, staked positions). **Custodial and off-chain crediting** For custodial platforms and exchanges, Automated Onchain Actions can trigger a contract that signals an off-chain system like crediting a user's account, updating a balance, or notifying a clearing layer, without requiring the user to interact with your chain/system directly. **Payments and holding accounts** Trigger a contract that routes funds to a payment processor, holding account, or escrow. Useful for on-chain invoicing, subscription flows, or multi-party settlement. In all cases, the integration surface is the same: one additional field on a request you're already making. *** ## Integration ### With a Bridge Quote Add `postBridgeData` to your `POST /bridge/quote/user` request body: ```json theme={null} { "token": "USDC", "chainIn": "ARBITRUM", "chainOut": "BASE", "amount": "100.5", "mode": "pay", "depositor": "0x0", "recipient": "0x0", "postBridgeData": { "_tag": "postBridgeActionTag", "userId": "1" } } ``` The destination token and amount is injected automatically into the matching function argument. You only need to specify the unique tag and any custom parameters like userId in the example above. ### With a Smart Deposit Address Add `postBridgeData` to your `POST /sda/deposit-addresses` request. Every deposit to that address will trigger the same action automatically — no per-deposit configuration needed. ```json theme={null} { "depositChains": ["ARBITRUM", "BASE", "ETHEREUM"], "destinationChain": "BASE", "destinationAddress": "0x0", "postBridgeData": { "_tag": "postBridgeActionTag", "userId": "1" } } ``` This is the recommended pattern for onboarding flows. Generate an SDA per user with their recipient address baked in, and every inbound deposit routes directly to the target contract outcome. *** ## The `postBridgeData` Field | Field | Type | Required | Description | | ---------------------- | -------------------- | -------- | ---------------------------------------------------------- | | `_tag` | `string` | ✓ | Specifies the Automated Onchain Action configuration. | | optional custom params | `string` / `integer` | - | Specifies a custom target like a user, merchant or payment | *** ## Example — Bridge into a Morpho Vault A user holds USDT on Arbitrum and wants to enter a USDC yield vault on Base. **Without Automated Onchain Actions**, they would need to: swap USDT → USDC, bridge to Base, approve the vault, and call `deposit()` themselves — four to five steps across two chains. **With Automated Onchain Actions + SDA**, you generate one deposit address per user. They send USDT on Arbitrum. We swap, bridge, and execute the vault deposit. Their wallet receives vault shares on Base. ```bash theme={null} curl -X POST https://api.rhino.fi/sda/deposit-addresses \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "depositChains": ["ARBITRUM", "ETHEREUM", "BASE"], "destinationChain": "BASE", "destinationAddress": "0x0", "postBridgeData": { "_tag": "Morpho", }, "tokenOut": "USDC" } }' ``` Just hand the user the returned `depositAddress` for any of the supported source chains. They send funds. The vault deposit happens automatically. In the example above, the user can send USDC or USDT on Arbitrum, Ethereum and Base (yes, same chain works as well). We process any supported token and your user ends up with Morpho USDC yield token on Base with on the specified destination address. *** ## What Happens If the Action Fails If the contract call reverts on the destination chain, funds are not lost. Depending on your account configuration, we will either retry (for transient failures), credit the destination token directly to the `destinationAddress`, or notify you via webhook and await instructions. Test your contract integration before going live. Contact your account representative to enable testnet access. *** ## Limitations * **ERC-20 type tokens only** — native gas tokens (ETH, SOL, etc.) cannot be the input to an Automated Onchain Action yet * **One action per bridge** — chaining multiple contract calls in sequence is not currently supported * **EVM destination chains only** — non-EVM destinations (i.e. Solana) are on the roadmap * **Automated Onchain Actions must be enabled on your account** — the `postBridgeData` field is accepted only on accounts with this Extension active. Contact your account representative to enable it. *** ## Next Steps * [Smart Deposit Addresses](/api-integration/smart-deposits) — Full SDA documentation * [API Reference — User Quote](/api-reference/bridge/quote/user-quote#body-post-bridge-data) — `postBridgeData` field schema * [API Reference — Create Deposit Address](/api-reference/sda/depositaddresses/create-new-deposit-address#body-post-bridge-data) — `postBridgeData` field schema # Custom Chain & Token Support Source: https://docs.rhino.fi/get-started/extensions/custom-chain-token-support Expand supported chains or tokens beyond the standard set. ## Custom Chain & Token Support **Extension tier:** Premium Rhino supports 20+ chains and a standard set of stablecoins out of the box. The Custom Chain & Token Support extension is for clients whose users hold assets on chains or in tokens not currently in the standard set. *** ## What can be added * **New EVM chains** — any EVM-compatible chain where Rhino can deploy liquidity and operate a solver * **New non-EVM chains** — evaluated on a case-by-case basis depending on chain tooling and liquidity availability * **Additional tokens** — stablecoins or other fungible tokens not currently listed in the bridge config *** ## How to request Custom chain or token additions are not self-serve. Rhino's engineering team evaluates each request based on: * Liquidity depth on the target chain * Security and smart contract auditability * Operational overhead and monitoring requirements * Client volume commitment To request a new chain or token, contact your account representative with details of the chain/token and expected volume. *** ## Timelines Standard evaluation and onboarding for a new EVM chain takes 1–2 days from agreement. Non-EVM chains vary depending on tooling and liquidity requirements. Token additions for established stablecoins on already-supported chains are typically faster. *** ## Related * [Supported Chains](/get-started/supported-chains) * [Extensions overview](/get-started/extensions) # Enhanced Compliance & Risk Management Source: https://docs.rhino.fi/get-started/extensions/enhanced-compliance Custom AML screening rules and real-time AML event notifications for clients with stricter compliance requirements. ## Enhanced Compliance & Risk Management **Extension tier:** Free + Premium All Rhino accounts include baseline KYT/AML screening: every deposit is screened before crediting, and any deposit that fails compliance checks is blocked. Clients never receive funds that fail Rhino's standard compliance checks. The Enhanced Compliance & Risk Management extension provides additional controls for clients with stricter compliance requirements. *** ## What's included ### Custom AML check rules Integrate your own compliance provider on top of Rhino's existing screening. You provide rules via API and can use any third-party service you prefer — for example, [Chainalysis](https://www.chainalysis.com/), [Elliptic](https://www.elliptic.co/), or your own in-house solution. This allows you to apply custom risk score thresholds, jurisdiction-based blocking rules, or additional data lookups against third-party sanctions lists tailored to your regulatory environment or risk appetite. Contact your account representative to discuss configuration options. ### Real-time AML notifications When a deposit triggers a compliance flag, a webhook event is dispatched in real time. Your system can react immediately — holding the transaction, alerting your compliance team, or triggering a review workflow — without polling the status endpoint. AML notification events appear in the standard [Webhooks](/webhook/events-list) event stream. The specific event types are documented on the [Events List](/webhook/events-list) page. ### Slack notifications (optional add-on) Rhino can enable Slack notifications for your team when any of your end user deposits fail KYT screening. This provides immediate visibility into compliance events without requiring webhook integration. Contact your account representative to set this up. *** ## Baseline compliance (all accounts) Every account includes: * 100% KYT screening on all inbound deposits via Smart Deposit Addresses * Automatic blocking of deposits from flagged addresses * Compliance event logging accessible via the Rhino Console and history API *** ## Related * [Webhooks — Events List](/webhook/events-list) * [Smart Deposit Addresses](/get-started/sda) * [Extensions overview](/get-started/extensions) # 1:1 Stablecoin Swaps Source: https://docs.rhino.fi/get-started/extensions/stablecoin-swaps USDT↔USDC conversion at a guaranteed 1:1 rate, no slippage, no market exposure. ## 1:1 Stablecoin Swaps **Extension tier:** Premium When the 1:1 Stablecoin Swaps extension is enabled, cross-stable conversions between USDT and USDC are executed at a guaranteed 1:1 rate. There is no slippage, no market exposure, and no variance between the deposited amount and the settled amount (net of the standard bridge fee). *** ## The problem this solves Without this extension, a cross-stable swap (e.g. a user deposits USDT and the destination expects USDC) is routed through a market. The settled amount depends on the USDT/USDC market price at the time of execution. In thin conditions, this can deviate from 1:1 by a meaningful amount. For applications that display a quoted settlement amount to end users — onboarding flows, payment screens, balance estimations — any variance creates a UX problem and potential customer support burden. *** ## How it works Rhino maintains its own USDT and USDC reserves on supported chains. When a cross-stable deposit arrives, Rhino credits the destination from its reserves at exactly 1:1, then rebalances its own positions off the critical path. The user's experience is: deposited USDT amount equals settled USDC amount (minus fee). This applies to: * USDT → USDC * USDC → USDT Both directions, across all supported chains where USDT and USDC are both available. *** ## Integration No API change is required. If your account has this extension enabled, any SDA or bridge request that specifies a cross-stable `tokenOut` will automatically use 1:1 pricing. To confirm this extension is active on your account, check the [Rhino Console](/get-started/rhino-console) under Configuration, or contact your account representative. *** ## Limits Deposit limits for 1:1 swap routes follow the same limits as standard SDA deposits. For very large individual transactions (above \$1M USD equivalent), contact your account representative to confirm available liquidity. Volume based limits are applied to protect your app from abuse. These limits are customizable on a case by case basis. * Account level limit - the total 1:1 swap volume limit over all transactions within a specified time frame (expressed in days) * Recipient level limit - the total 1:1 swap volume limit per specific recipient Once limits are hit, the service falls back to the market rate. If a recipient hits their limit, only their transactions will fall back to the market rate. *** ## Related * [Smart Deposit Addresses](/get-started/sda) * [Fees & Limits](/get-started/fees-and-limits) * [Extensions overview](/get-started/extensions) # Tron Access Source: https://docs.rhino.fi/get-started/extensions/tron-access Enable TRON network deposits and settlement. ## Tron Access **Extension tier:** Premium TRON is a high-volume stablecoin network, particularly for USDT. The Tron Access extension enables TRON as a deposit source and/or settlement destination in your integration. *** ## What's enabled * **TRON as a deposit source** — Smart Deposit Addresses can be generated on TRON. Users can send TRC-20 USDT (or other supported TRC-20 tokens) to a Rhino SDA on TRON, and funds are settled to the configured destination chain. * **TRON as a destination** — Bridges can settle to a TRON address. *** ## Why this is a separate extension TRON requires specific compliance and operational controls that differ from EVM chains. AML screening on TRON operates differently from EVM screening, and Rhino maintains dedicated TRON infrastructure. Enabling TRON access therefore requires a specific agreement with your account representative, including acknowledgement of the TRON-specific compliance framework. *** ## Integration Once Tron Access is enabled on your account, TRON appears in the bridge config (`bridgeConfigs`) as an available chain with `enabledDepositAddress: true`. No code changes are required beyond confirming TRON is listed. For SDA generation on TRON, use `"TRON"` in the `depositChains` array. Generating a Tron (and Solana) SDA requires a separate call. I.e. you can specify many EVMs to generate SDAs across all EVMs. Then generate a Tron (and Solana) SDA with a separate call. For TRON as a destination, use `"TRON"` as the `destinationChain`. Token symbols on TRON follow the same naming convention as other chains (e.g. `"USDT"`). *** ## SDK support TRON chain adapter documentation is available at [Chain Adapters — Tron](/sdk/chain-adapters/tron). *** ## Related * [Smart Deposit Addresses](/get-started/sda) * [Supported Chains](/get-started/supported-chains) * [Extensions overview](/get-started/extensions) # Fees & Limits Source: https://docs.rhino.fi/get-started/fees-and-limits Learn how Rhino charges fees, how deposit and withdrawal limits are enforced, and how the Advanced Fee & Limit Management extension gives you control over both. ## How [Rhino.fi](http://Rhino.fi) fees work Rhino.fi supports two types of fee approach; pay-as-you-go (PAYG) and Subscriptions. This provides fee flexibility which can best map to your useage and expected transaction flow. * **PAYG** is ideal for early-stage builders and pre-volume integrators, as well as for teams within the exploration or testing phase of selecting their stablecoin infrastructure provider. * **Subscriptions** (Growth, Scale and Enterprise) are ideal for companies with stablecoins as a core part of their offering and who are looking for predictable fee structures which scale to their needs. ### PAYG Pricing Rhino charges a per-transaction fee composed of three components: | Component | Description | | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Rhino.fi **platform fee** | A percentage of the transferred amount, volume-based and agreed at the account level | | **Destination gas** | The cost of executing the settlement transaction on the destination chain, passed through at cost | | **Source gas** (relevant for Smart Deposit Address involving transaction only) | The cost of sweeping funds that are received into the Smart Deposit Address (SDA). N.B for bridge/bridge+swap only activity, the source chain gas costs are paid directly by the user. | The total and detailed fee breakdown is provided within the `POST/quote/bridge-swap/user`endpoint, and when using Rhino.fi Smart Deposit Addresses, the fee information can be obtained prior to the transaction by providing `isSda = true`within the request body. Rhino.fi will honor the quoted fee until the quote expires — there are no surprises at settlement (the timestamp of the expiry is provides within the `expiresAt`param within the response). The total fees are then deducted from the amount that the user will be sent on the destination chain. ### Subscription Pricing Rhino.fi charges a per-month subscription fee which enables clients to have full access to the features within the Rhino.fi Stablecoin Activation Stack. Within this is an allocation of Flow Credits which can be used for bridge, bridge+swap and Smart Deposit Address related activity through Rhino.fi, as well as a maximum number of Smart Deposit Addresses which can be generated over the contract lifetime. For clients on a Subscription model, all onchain costs are borne by Rhino.fi - therefore abstracting away the complexity of the myriad of onchain costs and their nuances. The Flow Credits design has been developed to support clients with both high value and high velocity transaction activity, enabling Rhino.fi to support companies with early but growing stablecoin activity as well those running established and enterprise-grade flows. To learn more more about the allocation of Flow Credits and Smart Deposit Address generation caps available within the subscription tiers please contact us. *** ## How [Rhino.fi](http://Rhino.fi) limits work Rhino.fi supports enteprise-grade volumes and value and can work with you to ensure that the limits on your account meet your expected transaction activity. With the [**Advanced Fee & Limit Management**](https://docs.rhino.fi/get-started/extensions/advanced-fee-management) extension, these limits are viewable and configurable. Limits per SDA can be found within the response of the `POST/deposit-addresses` endpoint when a new SDA is created. If an an individual bridge/bridge+swap quote `POST/quote/bridge-swap/user` will exceed agreed limits then this will be stated within the quote response. Deposits and withdrawals above the maximum are not processed. *** ## Advanced Fee & Limit Management (Extension) The [**Advanced Fee & Limit Management**](https://docs.rhino.fi/get-started/extensions/advanced-fee-management) extension is available for clients that need more control over fee handling. ### Client Surcharge PAYG and Subscription clients may choose to add an additional fee to the quote shown to end users. This fee is charged directly to the end user and belongs to the client. For PAYG clients, the client surcharge is included within the total fees deducted from the user’s deposited funds on the destination chain. At month end, Rhino.fi will reconcile the surcharge against any Rhino.fi fees owed by the client. If the client has opted to sponsor Rhino.fi fees, Rhino.fi will deduct its fees from the client surcharge and remit the net amount to the client. If the surcharge collected is higher than the Rhino.fi fees owed, the excess amount will be sent to the destination address agreed with the client. For Subscription clients, the client surcharge is also included in the fees shown in the quote. At month end, Rhino.fi will reconcile the surcharge against the client’s invoice. If the surcharge collected is higher than the Rhino.fi fees owed, the excess amount will be sent to the destination address agreed with the client. ### End-of-month invoicing For PAYG clients who are not adding a surcharge then all Rhino.fi and onchain fees are taken at the point of the transaction and no additional invoicing is required. However for PAYG clients with a configured Client Surcharge and for clients on a Subscription tier, Rhino.fi consolidates these amounts and payment is made directly to the client’s designated wallet or bank account, depending on the arrangement, at the end-of-month. ### Limit configuration To support clients who are testing, and therefore wish to temporarily have lower limits, and to support scaling clients who require increasing volumes and transaction value sizes, the Advanced Fee & Limit Management extension provides the ability to alter these limits. If any of these configurations are required for you, please reach out to your Rhino.fi account manager. *** ## Related * [Advanced Fee & Limit Management](/get-started/extensions/advanced-fee-management) — Extension detail page * [Bridge quote endpoint](/api-reference/bridge/quote/user-quote) — API reference for fee quotes * [Smart Deposit Addresses](/get-started/sda) — How fees apply to SDA deposits # Get Started Source: https://docs.rhino.fi/get-started/introduction Rhino is stablecoin deposits infrastructure built for teams that need reliable, enterprise-grade settlement at scale. This documentation covers everything you need to integrate the Rhino Stablecoin Activation Stack. The lightning-fast stablecoin activation stack built for global scale Rhino is stablecoin deposits infrastructure built for teams that need reliable, enterprise-grade settlement at scale. This documentation covers everything you need to integrate the Rhino Stablecoin Activation Stack — from generating your first deposit address to configuring advanced fee management and automated onchain actions. ## What are you looking to do? Generate a single deposit address that accepts stablecoins from any supported chain. Funds are settled to your destination chain automatically — no bridge UI, no user complexity. Accept stablecoin deposits and settle them on 20+ chains with deterministic outcomes, MEV protection, and configurable speed tiers. Get running in minutes using the JavaScript/TypeScript SDK. Integrate directly via REST API in any language. Full endpoint reference with request/response schemas. # Rhino Console Source: https://docs.rhino.fi/get-started/rhino-console The Rhino Console is the web interface for managing your Rhino integration — transaction history, analytics, team settings, and configuration. ## Rhino Console The Rhino Console is the web interface for managing your Rhino integration. It is available to all clients at [console.rhino.fi](https://console.rhino.fi). *** ## What the Console covers | Section | Description | | --------------------------------- | --------------------------------------------------------------------------------- | | **Transaction history** | Full history of all bridges, SDA deposits, and settlement events for your account | | **Analytics** | Volume, success rate, fee totals, and per-chain breakdown | | **Team settings** | Manage team members and access levels | | **API key management** | Create, rotate, and revoke API keys | | **Configuration** (coming soon) | View account-level settings, enabled Extensions, and fee configuration | | **Status & alerts** (coming soon) | Link to the Rhino status page; incident history | *** ## API keys API keys are created and managed in the Console. Each key is scoped to your account. Two key types are available: | Key type | Use | | ---------------- | -------------------------------------------------------------------------------- | | `SECRET_` prefix | Full access — query history, manage addresses, required for sensitive operations | | Standard key | Quote generation, bridge execution, public reads | Store secret keys securely. They cannot be retrieved after creation — only rotated. *** ## Access The Console requires you to log in using a Google workspace account. You can invite teammates to contribute and manage your project on the Rhino Console. *** ## Related * [API Keys](/get-started/api-keys) — how to generate and use keys programmatically * [Authentication](/api-integration/authentication) — how to authenticate API requests with your keys # Smart Deposit Addresses Source: https://docs.rhino.fi/get-started/sda Smart Deposit Addresses are the core deposit primitive in the Rhino Stablecoin Activation Stack. Generate one address per user; Rhino handles chain detection, routing, settlement, and compliance automatically. See some live use cases here: [5 ways to use SDAs](https://rhino.fi/blog/5-ways-to-use-smart-deposit-addresses) **Premium Extension: 1:1 Stablecoin Swaps** When the 1:1 Stablecoin Swaps extension is enabled on your account, cross-stable conversions (USDT→USDC and USDC→USDT) are executed at a guaranteed 1:1 rate with no slippage and no market exposure. This removes the guesswork from mixed-stablecoin deposit flows. [Learn more →](/get-started/extensions/stablecoin-swaps) ## The Flow At a high level, the process works as follows: 1. **Generate an SDA**: Call the API to create a new deposit address, linked to a destination chain and address or a target action (coming soon).\ We strongly recommend generating an SDA on all available EVM chains immediately to ensure that funds accidentally sent by customers to the wrong chain are automatically detected. 2. **User deposits**: Any wallet, CEX, or payment service can send supported tokens to the SDA. The user only performs a normal token transfer. 3. **Monitoring & orchestration**: The system continuously watches for incoming transfers. If a valid deposit arrives (correct token, within limits), it triggers the bridge process. 4. **Cross-chain execution**: Funds are credited on the destination chain, or the requested Automated Onchain Action (e.g. swap, pool deposit) is performed. 5. **Reconciliation & sweeping**: After crediting is complete, addresses are swept periodically to consolidate balances and keep monitoring efficient. From the client/user perspective: user sends → destination credited → job done. ## The Features Key properties that make SDAs flexible: * **Many-to-one**: Multiple SDAs can funnel to the same destination address. * **Multi-chain source support**: SDAs can currently be generated on the top EVM chains like Ethereum, Arbitrum, Base, BSC, Polygon, Optimism, Kaia, Tron, and Solana. Full list available under [Supported Chains → Smart Deposit Address](https://docs.rhino.fi/get-started/supported-chains#smart-deposit-address). Generating a Tron (and Solana) SDA requires a separate call. I.e. you can specify all EVMs to generate SDAs across all EVMs. Then generate a Tron and Solana SDA with separate calls. * **Webhooks** – Real-time push updates on deposit results. * **Any-chain destination**: Funds can settle on any of the 35+ Rhino-supported chains, including non-EVMs (Starknet, Ton, Solana, etc.). * **Stablecoin primitives**: * Same-to-same (e.g. USDC → USDC) * Cross-stable swap (e.g. USDC → USDT) — with the [1:1 Stablecoin Swaps](/get-started/extensions/stablecoin-swaps) extension, cross-stable conversions execute at a guaranteed 1:1 rate with no slippage * **Programmable actions**: Trigger Automated Onchain Actions like swaps, pool deposits, or contract interactions. * **Metadata support**: Attach arbitrary data (e.g. UUID) to track users, payments, or invoices. * **Flexible fees**: End users pay standard fees by default. With the [Advanced Fee & Limit Management](/get-started/extensions/advanced-fee-management) extension, clients can sponsor fees on behalf of end users, or add a configurable markup that Rhino collects and remits via end-of-month invoicing. * **Validity:** unlimited * **Deposit size**: Typically 5-digit USD+ supported, 7-digit on request. * **100% KYT screening**: Every deposit is screened for KYT/AML before crediting. Clients never receive funds that fail compliance checks. Enhanced screening options and real-time AML notifications are available via the [Enhanced Compliance & Risk Management](/get-started/extensions/enhanced-compliance) extension. * **Privacy benefit**: SDAs are naturally decoupled from destination addresses. Coming soon: * **Refund address** – Users/clients can specify fallback if a credit fails (\< 1% of cases). ## The Constraints There are some limitations to be aware of: * **Native tokens (ETH, BNB, SOL, etc.):** Native token support is currently in beta rollout, including both direct SDA bridging and native → stablecoin swaps. [Contact us](https://rhino.fi/contact) if you're interested in using native token deposits. * Wrong tokens will not be credited and require a manual refund. * Deposits below \$5 USD equivalent or above set limits will not process and require a manual refund. * If liquidity is insufficient, deposits will retry until successful or refunded. * Fees can be fetched in real time through the API or fixed by agreement. ### Core Technologies Several technologies come together to make Smart Deposit Addresses (SDAs) work in practice: * **API / SDK (SDA generation)**: Developers generate deposit addresses via Rhino's API or SDK with zero blockchain complexity. No private key management or contract deployment is required. * **Wallet & App Transfers (funding SDA)**: Any wallet, CEX, or app that supports token transfers can send funds to an SDA. This keeps the user interaction primitive and universal. * **Rhino Solver & Liquidity Layer (orchestration & crediting)**: Our solver system monitors deposits in real time and uses Rhino's liquidity to credit the specified destination chain/address. * **Reconciliation (sweeping)**: On EVM chains we use **EIP-7702**; for non-EVMs we maintain custom implementations. This ensures SDA balances are reconciled safely after crediting. ## Before You Start **Default limits apply to Smart Deposit Addresses for initial testing purposes:** * 500 total SDAs * 50 SDAs generated per hour Please [contact us](mailto:partnerships@rhino.fi) to lift your limits. * You'll need an account in the [Rhino Console](https://console.rhino.fi/). * The [API docs](/api-integration/smart-deposits) are the source of truth for supported chains, tokens, and endpoints. # Supported Chains Source: https://docs.rhino.fi/get-started/supported-chains List of chains that rhino.fi smart contracts are deployed on and tokens supported. The chains in the "Smart Deposit Address" tab are those chains on which you can generate SDAs. Funds from these deposit addresses can be bridged to any of our supported chains. Rhino.fi supports two route types: * **Bridge:** moves the same token between chains * **Bridge + swap:** moves between chains and convert into a different supported token on the destination For bridge-only activity, always check that the source chain and destination chain support the same token > **Bridge- only Example:** > > * *USDT on Tron → USDT on Polygon works because USDT is supported on both Tron and Polygon.* > * *USDT on Sonic → USDT on Paradex will not work as USDT is not supported on Paradex* \*To understand what tokens can be swapped between chains please look at 'Bridging and Swapping' tab. | Asset | Blockchains it can be bridged between | | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | USDT | Arbitrum One, Avalanche, Base, BNB Smart Chain, Celo, Ethereum, Gnosis,
HyperEVM, Ink, Kaia, Katana, Linea, Mantle, opBNB, Optimism, Plasma,
Polygon, Solana, Sonic, Stable, Starknet, Tempo, Ton, Tron, Unichain,
zkSync Era | | USDC | Arbitrum One, Avalanche, Base, BNB Smart Chain, Celo, Ethereum, Gnosis,
HyperEVM, Ink, Katana, Linea, Mantle, Optimism, Polygon, Solana, Sonic,
Starknet, Tempo, zkSync Era | | ETH | Arbitrum One, Avalanche, Base, BNB Smart Chain, Ethereum, Ink, Katana,
Linea, Mantle, Optimism, Polygon, Sonic, Starknet, Unichain,
zkSync Era | | WBTC | Arbitrum One, Avalanche, Base, BNB Smart Chain, Ethereum, Starknet | | USDe | Avalanche, Ethereum | | BNB | BNB Smart Chain, opBNB | | EURC | Ethereum, Starknet |
Rhino.fi supports two route types: * **Bridge:** moves the same token between chains * **Bridge + swap:** moves between chains and convert into a different supported token on the destination For bridge and swap activity, check that the token you wish to send is supported on the source chain and the token you wish to receive is supported on the destination chain > **Bridge and Swap Example:** > > *USDC on Optimism → USDT on Celo works because USDC is supported on Optimism and USDT is supported on Celo.* \*To understand what blockchains the same token can be bridged between, please look at 'Bridging' tab | Chain | Stablecoins | Other Assets | | --------------- | ------------------------------------------------------------------------- | ---------------------------------------------------- | | Arbitrum One | USDC, USDT | ETH, WBTC | | Avalanche | USDC, USDT, USDe | ETH, WBTC | | Base | EURC, USDC, USDT | ETH, WBTC | | BNB Smart Chain | USDC, USDT | BNB, ETH, WBTC | | Celo | USDC, USDT | | | Ethereum | USDC, USDT, USDe | ETH, SENT, WBTC | | Gnosis | USDC, USDT | | | HyperEVM | USDC, USDT | | | Ink | USDC, USDT | ETH | | Kaia | USDT | | | Katana | USDC, USDT | ETH | | Linea | USDC, USDT | ETH | | Mantle | USDC, USDT | ETH | | opBNB | USDT | BNB | | Optimism | USDC, USDT | ETH | | Plasma | USDT | | | Polygon | USDC, USDT | ETH | | Solana | USDC, USDT | | | Sonic | USDC, USDT | ETH | | Stable | USDT | | | Starknet | USDC, USDT | ETH, WBTC | | Tempo | USDC, USDT | | | Ton | USDT | | | Tron | USDT | | | Unichain | USDT | ETH | | zkSync Era | USDC, USDT | ETH | **Smart Deposit Addresses (SDAs)** allow you to receive supported tokens on a supported chain and route them onwards. For SDA activity, always check: * that the token being **sent into the SDA** is supported on that SDA chain * that the token being **received on the destination chain** is supported on the destination chain > **SDA Examples:** > > * *USDT on Tron → USDT on Polygon works because USDT is supported on both chains.* > * *USDC on Polygon → USDT on Celo works because USDC is supported on Polygon and USDT is supported on Celo.* > * *USDT on Kaia → USDC on Tron will not work because Tron does not support USDC.* \*To understand what tokens can be swapped and/or bridged without using Rhino.fi Smart Deposit Addresses, please look at the 'Bridging' or 'Bridging and Swapping' tab. **Rhino.fi Smart Deposit Addresses have inbuilt bridging and swapping functionality, therefore once these assets are received into the SDA, then they can be swapped and bridged to the chains listed on the 'Bridging and Swapping' tab.** **The following table details what tokens can be sent to a Rhino.fi Smart Deposit Address for the full list of supported chains.** | **Chain** | **Tokens that can be sent to a Smart Deposit Address on this chain** | | :-------------- | :------------------------------------------------------------------------------------------------------------- | | Arbitrum One | USDC, USDT, WBTC | | Base | EURC, USDC, USDT, WBTC | | BNB Smart Chain | USDC, USDT, ETH, WBTC | | Celo | USDC, USDT | | Ethereum | USDC, USDT, USDe, SENT, WBTC, EURC | | Gnosis | USDC, USDT | | Kaia | USDT | | Katana | USDC, USDT | | Mantle | USDC, USDT, ETH | | Optimism | USDC, USDT | | Plasma | USDT | | Polygon | USDC, USDT, ETH | | Solana | USDC, USDT | | Tempo | USDC, USDT | | Tron | USDT | | Unichain | USDT |
# What is Rhino? Source: https://docs.rhino.fi/get-started/what-is-rhino Learn about the Rhino Stablecoin Activation Stack and how it turns stablecoin deposits into usable balances for enterprise teams. ## The Rhino Stablecoin Activation Stack Rhino turns stablecoin deposits into usable balances. Enterprise teams — exchanges, neobanks, payment processors, DeFi protocols — use Rhino to give their users a seamless stablecoin deposit experience across any chain, without building or operating the underlying infrastructure themselves. ## The value chain: deposit → settlement → activation Every transaction through Rhino follows three steps: 1. **Deposit** — A user sends stablecoins to a deposit address from any supported chain, CEX, or wallet. No bridge UI, no chain selection, no manual routing. 2. **Settlement** — Rhino detects the deposit, routes funds through its liquidity layer, and credits the destination chain with a deterministic outcome. 3. **Activation** — Optionally, Rhino executes a configurable onchain action at the destination — vault deposit, contract call, protocol interaction — so funds land in a ready-to-use state. ## Three pillars ### Universal stablecoin deposit experience One deposit address accepts stablecoins from any supported chain. Users never need to select a network or interact with a bridge. Fewer UX steps means fewer failures and higher completion rates. ### Fast, predictable, ready-to-use outcomes Settlement is deterministic: Rhino honors quoted amounts and fees, handles gas estimation, protects against MEV and gas spikes, and achieves a 99.99% route success rate. Funds land in the exact state you specify. ### Specialist, enterprise-grade execution Rhino owns the full stack: liquidity, routing, compliance, failure handling, refunds, and monitoring. 100% of deposits are screened for KYT/AML before crediting. Rhino handles rebalancing, retries, and incident response — so your team does not have to. ## What's included | Component | Description | | --------------------------- | -------------------------------------------------------------- | | **Smart Deposit Addresses** | One address per user, any source chain, KYT/AML built in | | **Bridge & Swap** | 20+ chains, deterministic settlement, configurable speed tiers | | **Webhooks** | Real-time transaction status events | | **Rhino Console** | Transaction history, analytics, team settings, configuration | | **Checkout** | *(Coming soon)* | ## Extensions (premium) Select extensions are available as premium add-ons: | Extension | Description | | ----------------------------------------- | ----------------------------------------------------------------- | | **Enhanced Compliance & Risk Management** | Custom AML screening and real-time AML notifications | | **Automated Onchain Actions** | Post-settlement contract calls that activate funds immediately | | **1:1 Stablecoin Swaps** | USDT↔USDC at a guaranteed 1:1 rate, no slippage | | **Advanced Fee & Limit Management** | Sponsor fees for end users, or add markup; end-of-month invoicing | | **Custom Chain & Token Support** | Expand supported chains or tokens beyond the standard set | | **Tron Access** | Enable TRON network deposits and settlement | ## Who uses Rhino? Rhino is an API-first, full-service infrastructure product. It is built for engineering teams at: * Exchanges and custodial platforms accepting stablecoin deposits from end users * DeFi protocols onboarding liquidity from multiple chains * Neobanks and payment processors that need predictable, compliant settlement * Any product where users arrive with stablecoins on the wrong chain If you are building a consumer bridge UI or a personal cross-chain wallet, Rhino may not be the right fit. If you are running infrastructure that processes stablecoin deposits at volume, read on. # Bridge and swap Source: https://docs.rhino.fi/sdk/bridge-and-swap Rhino also supports cross chain swaps using external swap aggregators. ## Introduction Cross chain swaps in general work the same way as a normal bridge: 1. Fetch bridge config and additionally also a new config for supported swap tokens. 2. Generate a special swap quote. 3. Commit the quote. 4. Deposit funds into the bridge contract. 5. Rhino handles the swap and bridge logic under the hood and sends the funds to the recipient on the destination chain. ## Making cross chain swaps To make cross chain swaps you can use the same `bridge` or `prepareBridge` functions. The only difference is that the arguments are slightly different compared to normal bridges: ```typescript {2,4,5} theme={null} const bridgeResult = await rhinoSdk.bridge({ type: 'bridgeSwap', amount: '100', tokenIn: SupportedTokens.USDT, tokenOut: 'agEUR', chainIn: SupportedChains.ARBITRUM_ONE, chainOut: SupportedChains.BASE, depositor: 'DEPOSITOR_ADDRESS', recipient: 'RECIPIENT_ADDRESS', mode: 'pay', }, { getChainAdapter: chainConfig => getEvmChainAdapterFromPrivateKey( 'YOUR_PRIVATE_KEY', chainConfig, ), }) ``` Instead of the `token` field, a swap requires `tokenIn` and `tokenOut` fields as well as `type: 'bridgeSwap'` to indicate that a swap should be performed. When using swaps only the `pay` mode is available. `receive` mode is only supported for bridges without swaps. ## Swap quotes When using the `prepareBridge` function with swaps or using the `checkQuote` hook, you will receive a quote object that contains some additional properties compared to the quote for normal bridges. You will need to compare the `_tag` field against `bridgeSwap` first to narrow the type down to the swap quote case to get access to those properties: * `bridgePayAmount`: The amount of `tokenIn` that will be paid * `bridgePayAmountUsd`: The USD value of the pay amout * `minReceiveAmount`: The minimum amount of `tokenOut` that will be received on the destination chain after considering slippage. * `minReceiveAmountUsd`: The USD value of the minimum receive amount * `usdPriceTokenIn`: The current USD price of `tokenIn` * `usdPriceTokenOut`: The current USD price of `tokenOut` ## Failed swaps Swaps can fail for a number of reasons, most commonly due to market volatility. Rhino will automatically refund failed swaps to the depositor address on the origin chain. The bridge function will in this case return an error with type `SwapFailed` that contains metadata about the refund (refund chain, token, amount and transaction hash).\ Additionally, the `onBridgeStatusChange` hook will also first report `swap-failed` and then follow up with `failed-swap-refunded` once the refund has been processed. The latter update will contain the same metadata about the swap refund as the returned error. # Bridge API Source: https://docs.rhino.fi/sdk/bridge-api Learn how to use the SDK to interact with the bridge API directly. ## Introduction You can use the bridge API directly if you need some additional data or if your application requires more control over the bridge flow. For that the SDK offers a typesafe wrapper around the Rhino API through a dedicated object. The following sections showcase some common use cases for this. ## Fetch bridge or token configs ```typescript theme={null} const bridgeConfigResult = await rhinoSdk.api.bridge.getBridgeConfig() const tokenConfigResult = await rhinoSdk.api.bridge.getTokenConfig({ chain: 'ETHEREUM', token: 'USDT' }) ``` ## Generate and commit bridge quotes ```typescript theme={null} const publicQuoteResult = await rhinoSdk.api.bridge.getPublicQuote({ token: SupportedTokens.USDT, chainIn: SupportedChains.ETHEREUM, chainOut: SupportedChains.SOLANA, amount: '100', mode: 'pay', }) const userQuoteResult = await rhinoSdk.api.bridge.getUserQuote({ /// Same args as public quote with additional depositor and recipient address added }) if(userQuoteResult.error) { throw new Error(`Error fetching user quote: ${userQuoteResult.error}`) } const quoteId = userQuoteResult.data.quoteId const commitmentResult = await rhinoSdk.api.bridge.commitQuote(quoteId) if(commitmentResult.error) { throw new Error(`Error committing quote: ${commitmentResult.error}`) } ``` The [bridge functions](/sdk/bridge-functions/bridge) in the SDK will already handle this logic for you. You should only do this manually if you have a very specific use case that is not covered by those. ## Fetch bridge status or history If you want to check the status of a bridge or fetch the history of your app, you can use the following functions: ```typescript theme={null} const statusResult = await rhinoSdk.api.bridge.getBridgeStatus(quoteId) const historyResult = await rhinoSdk.api.bridge.getUserBridgeHistory({page: 1, limit: 10 }) ``` Querying the bridge history is only possible with a secret API key. [Learn more about API keys](/get-started/api-keys) # Bridge Source: https://docs.rhino.fi/sdk/bridge-functions/bridge The SDK offers convenience functions to make bridges that handle all the low level logic of fetching/committing quotes and making the correct blockchain transactions needed. ## Example A call to the bridge function that uses all possible options can look like this: ```typescript theme={null} import { SupportedChains, SupportedTokens } from '@rhino.fi/sdk' import { getEvmChainAdapterFromPrivateKey } from '@rhino.fi/sdk/adapters/evm' const bridgeResult = await rhinoSdk.bridge({ type: 'bridge', amount: '100', token: SupportedTokens.USDT, chainIn: SupportedChains.ARBITRUM_ONE, chainOut: SupportedChains.SOLANA, depositor: 'DEPOSITOR_ADDRESS', recipient: 'RECIPIENT_ADDRESS', mode: 'receive', gasBoost: { amountNative: '4' } }, { getChainAdapter: chainConfig => getEvmChainAdapterFromPrivateKey( 'YOUR_PRIVATE_KEY', chainConfig, ), hooks: { checkQuote: quote => quote.fees.feeUsd < 5, onBridgeStatusChange: status => console.log('Bridge status changed', status), }, timeoutSeconds: 600, bridgeConfig: bridgeConfig, }) if (bridgeResult.data) { console.log('Bridge successful', bridgeResult.data.withdrawTxHash) } else { console.log('Bridge error', bridgeResult.error) } ``` ## Bridge data The first parameter of this function specifies the main parameters of the bridge. * `amount` & `token` specify how much of which token should be bridged. * `chainIn` & `chainOut` specify the origin (`chainIn`) and destination (`chainOut`) chain. * `depositor` specifies the address that will send the funds on the origin chain. * `recipient` specifies the address that will receive the funds on the destination chain. * `mode` specifies if the fees should be added on top of the `amount` or subtracted from it. [Learn more about the bridge modes](/get-started/architecture#bridge-modes) * `gasBoost.amountNative` specifies the amount of native tokens that should be sent to the recipient address on top of the bridged tokens. ## Options The second parameter of this function is an options object that allows you to customize the bridge flow. Only `getChainAdapter` is mandatory. ### getChainAdapter This function has to return a [chain adapter](/sdk/chain-adapters/overview) that matches the origin chain. It will be used to check if the depositors balance is sufficient, handle token approvals (if needed) and deposit the funds into the bridge contract.\ Since all chain adapters included in this SDK will require the Rhino chain config to be initialized, this function will provide it as an argument to simply be passed through into the chain adapter function.\ This modular design allows you to only import the chain adapters that you actually need which will ensure that only the code you actually need will be included in your final bundle. ### hooks.checkQuote (optional) You can provide a predicate function that will receive the generated quote and has to return a boolean indicating if the quote is acceptable or not. If `false` is returned, the bridge will be aborted.\ You can use this to make sure you only make bridges under a certain fee threshold for example. ### hooks.onBridgeStatusChange (optional) Since the bridge only provides a result at the end, you can use this callback to be notified whenever the status of the bridge changes. ### timeoutSeconds (optional) Once the deposit into the bridge contract is made, the `bridge` function will poll the Rhino API until the bridge has been confirmed as completed. By default the polling will be aborted after a default timeout of 10 minutes. With this property you can specify a different timeout. ### gasOptions (optional) Gas options for EVM deposit and approval transactions. Use these to customize gas pricing and limits, for example to add buffer during fee spikes. These options are only used for EVM chains and ignored by other chain adapters. ```typescript theme={null} const bridgeResult = await rhinoSdk.bridge(bridgeData, { getChainAdapter: chainConfig => getEvmChainAdapterFromPrivateKey('YOUR_PRIVATE_KEY', chainConfig), gasOptions: { maxFeePerGas: 50000000000n, // 50 gwei maxPriorityFeePerGas: 2000000000n, // 2 gwei }, }) ``` Available properties: * `maxFeePerGas` - Maximum fee per gas unit in wei (EIP-1559). Use this to ensure your transaction gets included during fee spikes. * `maxPriorityFeePerGas` - Maximum priority fee per gas unit in wei (EIP-1559). This is the tip paid to validators. * `gasPrice` - Gas price in wei (legacy transactions). Use this for chains that don't support EIP-1559. * `gasLimit` - Gas limit for the transaction. If not provided, it will be estimated automatically. ### bridgeConfig and swapTokensConfig (optional) Internally the `bridge` function will fetch the current bridge and swap tokens (if needed) config from the Rhino API. If you already fetched those configs manually for some other purpose you can pass it into this property to avoid the extra network requests. Please make sure that you pass in a somewhat recent bridge/swap tokens config. Using a too old config can lead to errors in the quote handling later. ## Handling the bridge result The `bridge` function will return a result object with nullable `data` and `error` properties. If the bridge was successful, the `data` property will contain: * `depositTxHash`: The hash of the transaction on the origin chain that sent the funds to the bridge contract. * `withdrawTxHash`: The hash of the transaction on the destination chain that sent the funds to the recipient address. If an error occurred during the bridge, the `error` property will contain the exact error that happened and potentially some additional properties to provide more context to the error. For example, if the given route is not supported you might receive a bridge result like this: ```typescript theme={null} { error: { type: 'TokenNotSupported', token: 'ETH', chains: ['SOLANA'] } } ``` This error would indicate that you tried to bridge ETH from or to Solana but Rhino does not support this combination. # Prepare bridge Source: https://docs.rhino.fi/sdk/bridge-functions/prepare-bridge If you need more control over the bridge flow, you can use the `prepareBridge` function instead of the previously discussed `bridge` function. ## Use case The `prepareBridge` function is particularly useful if you want to make bridges based on user input in a UI. The standard bridge function will execute the entire bridge flow in one go, meaning only one user input to start the entire process is possible. The `prepareBridge` function however will allow you to first present the user with the generated quote and then only start the token approval (if needed) and actual bridge when the user explicitly initiates them. ## Usage example ```typescript highlight={5,8,16} theme={null} // Same parameters as the bridge function const preparedBridge = await rhinoSdk.prepareBridge(bridgeData, options) switch (preparedBridge.type) { case 'no-approval-needed': const bridgeResult = await preparedBridge.bridge() break case 'approval-needed': const approvalResult = await preparedBridge.approve() if (approvalResult.type === 'success') { const bridgeResult = await approvalResult.bridge() } else { console.log('Approval error', approvalResult.error) } break case 'error': console.log('Bridge error', preparedBridge.error) break } ``` Unlike the `bridge` function, `prepareBridge` will not make any transactions immediately. The initial call will only * Fetch the bridge config and validate the given parameters * Set up the chain adapter * Fetch a quote * Check if a token approval is needed Then it will return with a discriminated union type with three different cases, highlighted in the code example: `no-approval-needed`\ This means that no token approval is needed and the bridge can be initiated directly. The object will contain the generated quote and a `bridge` function that will initiate the actual bridge transaction when called. It will return the same result as the [standard bridge function](/sdk/bridge-functions/bridge). `approval-needed`\ This means that a token approval is needed before the bridge can be initiated. The object will contain: * The generated quote (including the expiration date that the quote must be committed by - the `expiresAt`param.) * Information about the currently available and required token allowance * An `approve` function that can be called to initiate the token approval The `approval` function will return on object containing a function to make the actual bridge or an error if the token approval failed. This bridge function will behave the same as in the previous case. `error`\ This means an error occurred during the preparation. It will contain the same error type as the standard bridge function result. # Custom chain adapters Source: https://docs.rhino.fi/sdk/chain-adapters/custom-chain-adapters If you need more control over the chain interactions than the included chain adapters provider, you can also implement your own or extend an existing one. Implementing and using your own chain adapters can lead to a loss of funds if implemented incorrectly. Please make sure to test your adapter on a testnet thoroughly before using it with real funds. ## Implementing your own custom chain adapter All you need to implement your own chain adapter is to implement the `ChainAdapter` type exported by the SDK. You can find a list of the required properties in the [chain adapters overview](/sdk/chain-adapters/overview). It would like something like this: ```typescript theme={null} import type { ChainAdapter } from '@rhino.fi/sdk' const customEvmChainAdapter: ChainAdapter = { networkId: '', getApprovalAmount: () => ..., handleTokenApproval: () => ..., handleDeposit: () => ..., getTokenBalance: () => ..., getAllTokenBalances: () => ..., } ``` ## Extending an existing chain adapter If you only want to tweak a specific aspect of an existing chain adapter, you could simply take one as base and only re-implement some specific functions. Example: ```typescript From provider theme={null} import { getEvmChainAdapterFromProvider } from '@rhino.fi/sdk/adapters/evm' import type { ChainAdapter } from '@rhino.fi/sdk' const evmChainAdapter = getEvmChainAdapterFromProvider( provider, chainConfig ) const customEvmChainAdapter: ChainAdapter = { ...chainAdapter, handleDeposit: (args) => { // Your custom bridge deposit function } } ``` Please check [this section](/contracts/contract-examples) for guidance on how to interact with the Rhino bridge contracts if you plan on implementing a custom bridge deposit function. # EVM Chain Adapter Source: https://docs.rhino.fi/sdk/chain-adapters/evm-chains Since all EVM chains can use the same tooling and libraries, you can use the same chain adapter function for all of them. You can create one either from a EIP-1193 compatible provider or as a shortcut from a private key. ### From EIP-1193 provider Use this method to use connected wallets in webapps or if you want to set up a provider yourself. ```typescript theme={null} import { getEvmChainAdapterFromProvider } from '@rhino.fi/sdk/adapters/evm' const chainAdapter = getEvmChainAdapterFromProvider( provider, chainConfig ) ``` ### From private key Use this method as a shortcut to setting up a provider yourself. ```typescript theme={null} import { getEvmChainAdapterFromPrivateKey } from '@rhino.fi/sdk/adapters/evm' const chainAdapter = getEvmChainAdapterFromPrivateKey( 'YOUR_PRIVATE_KEY', chainConfig ) ``` ### Options Both functions also accept a 3rd options parameter. Currently it only includes a `tokenAllowanceOverride` property that allows you to specify a higher token allowance to be used instead of the calculated one. This can be useful to optimize gas usage by setting bigger allowances that will last for multiple bridges. ## Fetching EVM balances Normally you would use the `getAllBalances` function on a chain adapter to fetch all balances of an address on a chain. However as the same address is can hold assets on any EVM chain, it would be very cumbersome to create a chain adapter for each EVM chain and query it's balances.\ To make this easier, the SDK provides a shortcut function to fetch all balances on all supported EVM chains by only providing the [bridge config](/sdk/concepts#bridge-and-chain-configs) and a wallet address. You can use it like this: ```typescript theme={null} import { getAllEvmBalances } from '@rhino.fi/sdk/adapters/evm' const balances = await getAllEvmBalances(bridgeConfig, 'WALLET_ADDRESS') console.log(balances) ``` The result will look like this: ```typescript theme={null} { ETHEREUM: { USDT: { tokenConfig: {...}, balance: 10550000n, balanceWithDecimals; 10.55, balanceFormatted: '10.55', }, USDC: {...}, }, BASE: {...} } ``` # Chain Adapters Overview Source: https://docs.rhino.fi/sdk/chain-adapters/overview The SDK ships with ready-to-use chain adapters for all chains that Rhino supports. A chain adapter encapsulates all the logic required to interact with a specific blockchain type. Each chain adapter has the following properties: * `networkId`: A unique identifier for the chain. This matches the `networkId` field that is used in the bridge config returned by the Rhino API. * `getTokenBalance`: Returns the balance of a given address for a specific token. * `getAllTokenBalances`: Returns the balances of all tokens that are supported by Rhino on a chain for a given address. Ideally some form of multicall is used to get all the data in one request. If that is not possible, multiple requests using `getTokenBalance` for each token individually could be made. * `getApprovalAmount`: Returns the amount of tokens that need to be approved for the bridge contract call to work. If the existing allowance is enough or the chain has no concept of token approvals, this function returns `undefined`. * `handleTokenApproval`: Makes the correct call to the token contract to approve the bridge contract as a spender for the needed amount. * `handleDeposit`: Makes the correct call to the bridge contract to deposit the needed amount of tokens to initiate the bridge. Returns the transaction hash if successful. * `estimateDepositGas` (optional): Given the same arguments as `handleDeposit`, return the estimated amount of gas tokens needed to pay the transaction fee for the bridge deposit. The [bridge functions](/sdk/bridge-functions/bridge) included in the SDK will take care of using chain adapters correctly so generally you will not have a need to use most of those functions directly. You might however want to use a chain adapter manually to fetch balances for example. ## General usage Each chain adapter has a different way of constructing it, as each chain uses different libraries. However, one parameter all included chain adapters have in common is the config of the chain from the Rhino API. The included [bridge functions](/sdk/bridge-functions) will handle fetching the correct chain config for you. If you wan to construct a chain adapter ahead of time, you will need to [fetch the chain config](/sdk/concepts#bridge-and-chain-configs) manually before. ## Included chain adapters * [EVM chains](/sdk/chain-adapters/evm-chains) * [Solana](/sdk/chain-adapters/solana) * [Starknet](/sdk/chain-adapters/starknet) * [Ton](/sdk/chain-adapters/ton) * [Tron](/sdk/chain-adapters/tron) * [Paradex](/sdk/chain-adapters/paradex) # Paradex Chain Adapter Source: https://docs.rhino.fi/sdk/chain-adapters/paradex ### Prerequisites You will need to install the [Paradex SDK](https://www.npmjs.com/package/@paradex/sdk) to instantiate a Paradex chain adapter. As Paradex can derive an account from either a Starknet or an EVM based signature, you have two options of initializing a Paradex account to be passed into the chain adapter function. ```typescript From Starknet account theme={null} import { Account, Config, ParaclearProvider } from '@paradex/sdk' const config = Config.fetchConfig('prod') const paradexAccount = await Account.fromStarknetAccount( { provider: new ParaclearProvider.DefaultProvider(config), config, account // your starknet.js account }, ) ``` ```typescript From EVM account theme={null} import { Account, Config, ParaclearProvider, Signer } from '@paradex/sdk' const config = await Config.fetchConfig('prod') const account = await Account.fromEthSigner({ provider: new ParaclearProvider.DefaultProvider(config), config, signer: Signer.ethersSignerAdapter(ethersSigner) // Pass your ethers signer here }) ``` Once you have your Paradex account object set up, you can create the chain adapter from it: ```typescript theme={null} import { getParadexChainAdapterFromAccount } from '@rhino.fi/sdk/adapters/paradex' const chainAdapter = getParadexChainAdapterFromAccount( paradexAccount, chainConfig ) ``` # Solana Chain Adapter Source: https://docs.rhino.fi/sdk/chain-adapters/solana You can either create a Solana chain adapter from an [Anchor](https://www.npmjs.com/package/@coral-xyz/anchor) `Wallet` instance or as a shortcut from a Solana secret key: ### From wallet Use this method to use connected wallets in webapps or if you want to set up a wallet yourself. ```typescript theme={null} import { getSolanaChainAdapterFromWallet } from '@rhino.fi/sdk/adapters/solana' // You can initialize the wallet yourself or depending on the specific browser // wallet find it in th global window object const chainAdapter = getSolanaChainAdapterFromWallet( wallet, chainConfig, rpcUrl, ) ``` ### From secret key Use this method as a shortcut to setting up a wallet yourself. ```typescript theme={null} import { getSolanaChainAdapterFromSecretKey } from '@rhino.fi/sdk/adapters/solana' const chainAdapter = getSolanaChainAdapterFromSecretKey( 'YOUR_SECRET_KEY', chainConfig rpcUrl, ) ``` As there are no stable public Solana RPC endpoints, you will need to provide your own. # Starknet Chain Adapter Source: https://docs.rhino.fi/sdk/chain-adapters/starknet You can create a Starknet chain adapter from either a [starknet.js](https://www.npmjs.com/package/starknet) account or as a shortcut from a private key. ### From account Use this method to use connected wallets in webapps or if you want to set up an account yourself. ```typescript theme={null} import { getStarknetChainAdapterFromAccount } from '@rhino.fi/sdk/adapters/starknet' const chainAdapter = getStarknetChainAdapterFromAccount( account, chainConfig ) ``` ### From private key Use this method as a shortcut to setting up an account yourself. ```typescript theme={null} import { getStarknetChainAdapterFromPrivateKey } from '@rhino.fi/sdk/adapters/starknet' const chainAdapter = getStarknetChainAdapterFromPrivateKey({ privateKey: 'YOUR_PRIVATE_KEY', address: 'YOUR_ADDRESS', chainConfig, }) ``` ### Options Both functions also accept a 3rd options parameter. Currently it only includes a `tokenAllowanceOverride` property that allows you to specify a higher token allowance to be used instead of the calculated one. This can be useful to optimize gas usage by setting bigger allowances that will last for multiple bridges. # Ton Chain Adapter Source: https://docs.rhino.fi/sdk/chain-adapters/ton You can either create a TON chain adapter from a [TonConnectUI](https://www.npmjs.com/package/@tonconnect/ui) instance or from a TON private key: ### From wallet Use this method to use connected wallets. ```typescript theme={null} import { getTonChainAdapter } from '@rhino.fi/sdk/adapters/ton' const chainAdapter = getTonChainAdapter( tonConnectUI, chainConfig, rpcUrl, ) ``` ### From private key Use this method to pass a private key directly. ```typescript theme={null} import { getTonChainAdapterFromSecretKey } from '@rhino.fi/sdk/adapters/ton' const chainAdapter = getTonChainAdapterFromSecretKey( 'YOUR_PRIVATE_KEY', 'YOUR_PUBLIC_KEY', chainConfig, rpcUrl, ) ``` As there are no stable public TON RPC endpoints, you will need to provide your own. # Tron Chain Adapter Source: https://docs.rhino.fi/sdk/chain-adapters/tron To create a chain adapter for Tron you will need to pass a [TronWeb](https://www.npmjs.com/package/tronweb) instance. ```typescript theme={null} import { getTronChainAdapter } from '@rhino.fi/sdk/adapters/tron' const chainAdapter = getTronChainAdapter( tronWeb, chainConfig ) ``` # Concepts Source: https://docs.rhino.fi/sdk/concepts Learn about the core concepts of the SDK. ## Bridge & Chain configs Rhino has one bridge configuration that lists all the chains and tokens that are available for bridging. Additionally there is a configuration that lists all tokens that are available for bridge and swap. You can get those configs through the following SDK calls: ```typescript theme={null} // General bridge config const bridgeConfigResult = await sdk.api.config.bridge() // Tokens available for swapping by chain const swapTokensResult = await sdk.api.config.swapTokens() // Shortcut to fetch both configs in one call const allConfigsResult = await sdk.api.config.all() ``` [Learn about the bridge API](/sdk/bridge-api). Assuming no error occurred, the result of the bridge config call will look like this: ```typescript Bridge config [expandable] theme={null} { ETHEREUM: { name: "Ethereum", type: "EVM", networkId: "1", contractAddress: "0xbca3039a18c0d2f2f84ba8a028c67290bc045afa", multicallContractAddress: "0x0dbBD1bB03Ed63AE2beA0Ce892567884dffb70a5", confirmationBlocks: 5, avgBlockTime: 12, nativeTokenName: "ETH", nativeTokenDecimals: 18, nativeTokenSafeguard: 0.008, blockExplorer: "https://etherscan.io", rpc: "https://eth-mainnet.blastapi.io/7efcf8bf-0f7a-4364-8a59-c1917983dbdc", site: "https://ethereum.org", status: "enabled", tokens: { ETH: { token: "ETH", address: "0x0000000000000000000000000000000000000000", decimals: 18 }, USDC: { token: "USDC", address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", decimals: 6 }, USDT: { token: "USDT", address: "0xdac17f958d2ee523a2206206994597c13d831ec7", decimals: 6 } }, gasBoostEnabled: false }, SOLANA: { // same format as above }, // Every supported chain will have an entry here } ``` The whole object corrosponds to the `BridgeConfig` type that is exported by the SDK. Each key will be a chain while the value will represent the chain config (`ChainConfig` type). Each value in the `tokens` field of a chain config represents a `TokenConfig`. A lot of functions in the SDK will have the `BridgeConfig` or a specific `ChainConfig`/`TokenConfig` as parameters. So for most advanced use cases you will most likely want to fetch the bridge config first. ## Chain Adapters The SDK follows a modular approach where all blockchain interactions are encapsulated in chain adapters. When using one of the provided bridge functions, you always need to explicitly pass in a chain adapter that matches the origin chain.\ Chain adapters handle the following: * Fetching balances * Checking and setting token allowances * Making a deposit to a bridge contract * Estimating the gas fees for a bridge transaction (optional) ### Resources * [More about chain adapters](/sdk/chain-adapters/overview) * [How to implement custom chain adapters](/sdk/chain-adapters/custom-chain-adapters) ## Supported chains and tokens Since the bridge functions will often take chains or tokens as `string` parameters, the SDK provides a list of supported chains and tokens to avoid the usage of magic strings. You can use them like this: ```typescript theme={null} import { SupportedChains, SupportedTokens } from '@rhino.fi/sdk' const chainIn = SupportedChains.ETHEREUM const chainOut = SupportedChains.SOLANA const token = SupportedTokens.USDT ``` Those lists are generated at release time of the SDK and are not guaranteed to be always up to date. Therefore those lists might be out sync with the bridge config if Rhino adds a new supported chain/token or temporarily suspends one.\ **Always use a freshly fetched bridge config if you want to display a list of available chains or tokens to a user for example. Do not rely on hardcoded lists for such purposes.** `SupportedTokens` does not include swap tokens as those are too dynamic to be included in a static list like this. [See SDA supported chains](/get-started/supported-chains#smart-deposit-address) ## Error handling A lot of functions in the SDK will return an object with nullable `data` and `error` fields instead of throwing. Checking one for being defined will automatically narrow down the type of the other to non-nullable in the opposite code path. Errors are always a discriminated union type with a `_tag` or `type` field as discriminator. Some error types will also contain some additional fields with specific information about the error.\ For example, an error that could occur when generating a user quote could be: ```typescript theme={null} { _tag: 'WithdrawLimitReached', token: 'USDT', chain: 'ETHEREUM', receiveAmount: '5000', maxWithdrawAmount: '1000' } ``` # From API to SDK Source: https://docs.rhino.fi/sdk/migration-guides/from-api This guide will help you migrate to the SDK if you have been using the API directly before. Depending on how sophisticated your use case is, you can choose between different levels of using the SDK. ## Use the bridge functions For the vast majority of use cases you should be able to replace your manual API and blockchain calls with the [bridge](/sdk/bridge-functions/bridge) or [prepareBridge](/sdk/bridge-functions/prepare-bridge) function and the included [chain adapters](/sdk/chain/adapters/overview). Those will handle all the intricacies of the Rhino API and smart contracts correctly. However, if you have a use case that is not covered by those, read further to learn how to use the underlying primitives of the SDK directly. If the included chain adapters do not cover your use case, you can [implement a custom chain adapter](/sdk/chain-adapters/custom-chain-adapters) and use that with the `bridge` or `prepareBridge` function. ## Using the SDK API wrapper If your use case requires you to be in control of all API calls, you can still replace your manual API calls with the wrappers from the SDK to reduce the amount of code significantly: ```typescript theme={null} // Manual API call const result = await fetch('https://api.rhino.fi/bridge/quote/user', { method: 'POST', headers: { "content-type": "application/json", "authorization": jwt, }, body: JSON.stringify(quoteArgs), }) // SDK replacement const result = rhinoSdk.api.bridge.getUserQuote(quoteArgs) ``` Under `api.bridge` and `api.config` in the SDK you will find equivalent functions to all the manual API calls that you may have used before. The parameters are mostly the same, although some minor convenience tweaks have been made in certain wrappers. ## Using the SDK chain adapters If you would like to be directly in control of the blockchain interactions, you can use the included chain adapters directly: ```typescript theme={null} // Set up the chain adapter, can be any of the included ones const evmChainAdapter = getEvmChainAdapterFromPrivateKey( 'YOUR_PRIVATE_KEY', chainConfig ) // Check if a token approval is needed. Could be skipped if you know that the blockchain does not have a concept of token approvals, like Solana. const approvalAmount = await evmChainAdapter.getApprovalAmount(depositAmount, walletAddress, tokenConfig) if(approvalAmount) { // Set up the token approval if needed await evmChainAdapter.handleTokenApproval(approvalAmount, tokenConfig) } // Send the funds to be bridged to the bridge contract. Once successful, the status // of the bridge can be tracked through the Rhino API as usual. const { depositTxHash } = await evmChainAdapter.handleDeposit({ tokenConfig, depositAmount, commitmentId, }) ``` # To version 1.x Source: https://docs.rhino.fi/sdk/migration-guides/to-1x Learn how to upgrade your code to version 1.x if you have been using a 0.x version of the SDK before. ## Summary Version 1 of the SDK introduces bridge and swap and marks the first major stable release. It also introduces a shortcut to fetch bridge and token swap configs. ## Breaking changes This release contains some minor breaking changes. Migration is very straightforward. ### Bridge functions change The first argument of the `bridge` and `prepareBridge` functions now require an additional `type` field with the possible values of `bridge` and `bridgeSwap`. This is required as a swap requires slighly different arguments. To adjust your existing code, you only need to add the highlighted field to your existing `bridge` or `prepareBridge` calls: ```typescript {2} theme={null} const bridgeResult = await rhinoSdk.bridge({ type: 'bridge', amount: '100', token: SupportedTokens.USDT, chainIn: SupportedChains.ARBITRUM_ONE, chainOut: SupportedChains.SOLANA, depositor: 'DEPOSITOR_ADDRESS', recipient: 'RECIPIENT_ADDRESS', mode: 'receive', }, { getChainAdapter: chainConfig => getEvmChainAdapterFromPrivateKey( 'YOUR_PRIVATE_KEY', chainConfig, ), }) ``` ### Chain adapter interface changes The `getApprovalAmount` function on chain adapters previously only returned the required approval amount (or undefined if no approval needed). Now this function returns an object with `requiredAllowance` and `availableAllowance` fields.\ This change was neccessary to include those two fields into the `approval-needed` variant of the `prepareBridge` function. It allows integrations to display the available and required allowance before users initiate the approval transaction. There is no action needed regarding this change unless you have implemented your own chain adapter. ## Config shortcuts Previously you might have fetched the bridge config like this: ```typescript theme={null} const bridgeConfig = await rhinoSdk.api.bridge.getBridgeConfig() ``` To streamline this with the addition of the new swap tokens config, a new API is now available: ```typescript theme={null} // New way to fetch bridge config const bridgeConfig = await rhinoSdk.api.config.bridge() // New token swap config const swapTokens = await rhinoSdk.api.config.swapTokens() // Fetch both configs in one call const configs = await rhinoSdk.api.config.all() ``` The `getBridgeConfig` in the bridge API wrapper is still available and will keep working. However, we recommend to use the new function for more idiomatic code. # Quickstart Source: https://docs.rhino.fi/sdk/quickstart Learn how to use the bridge SDK to make bridges with only a few lines of code. The Rhino SDK is a JavaScript/TypeScript client for the Rhino Stablecoin Activation Stack. It handles authentication, config fetching, quote generation, and transaction execution — letting you bridge assets with a few lines of code. For non-JS environments, use the [REST API](/api-integration/introduction) directly. ### Install the package ```bash npm theme={null} npm install @rhino.fi/sdk ``` ```bash yarn theme={null} yarn add @rhino.fi/sdk ``` ```bash pnpm theme={null} pnpm install @rhino.fi/sdk ``` ```bash bun theme={null} bun install @rhino.fi/sdk ``` ### Initialize the SDK ```typeScript rhino-sdk.ts theme={null} import { RhinoSdk } from '@rhino.fi/sdk' export const rhinoSdk = RhinoSdk({ apiKey: 'YOUR_API_KEY', }) ``` You can create a project and manage API keys there. ### Make a bridge ```typescript bridge.ts theme={null} import { SupportedChains, SupportedTokens } from '@rhino.fi/sdk' import { getEvmChainAdapterFromPrivateKey } from '@rhino.fi/sdk/adapters/evm' import { rhinoSdk } from './rhino-sdk' const bridgeResult = await rhinoSdk.bridge({ type: 'bridge', amount: '100', token: SupportedTokens.USDT, chainIn: SupportedChains.ARBITRUM_ONE, chainOut: SupportedChains.SOLANA, depositor: 'DEPOSITOR_ADDRESS', recipient: 'RECIPIENT_ADDRESS', mode: 'receive', gasBoost: { amountNative: '4' } }, { getChainAdapter: chainConfig => getEvmChainAdapterFromPrivateKey( 'YOUR_PRIVATE_KEY', chainConfig, ), hooks: { checkQuote: quote => quote.fees.feeUsd < 5, onBridgeStatusChange: status => console.log('Bridge status changed', status), }, }) if (bridgeResult.data) { console.log('Bridge successful', bridgeResult.data.withdrawTxHash) } else { console.log('Bridge error', bridgeResult.error) } ``` This would bridge **100 USDT** from **Arbitrum to Base** while also receiving **4 SOL** tokens at the recipient address. Through the `checkQuote` hook a bridge that would cost over \$5 in fees would be aborted. ### Create a Smart Deposit Address Smart Deposit Addresses let users bridge by making a simple token transfer — no contract interaction needed. ```typescript sda.ts theme={null} import { rhinoSdk } from './rhino-sdk' const sda = await rhinoSdk.api.depositAddresses.create({ depositChains: ['ETHEREUM', 'ARBITRUM', 'BASE'], destinationChain: 'BASE', destinationAddress: '0x123...', }) // Result: // { // depositAddress: '0x457...', // depositChain: 'ETHEREUM', // destinationChain: 'BASE', // destinationAddress: '0x123...', // supportedTokens: [{ symbol: 'USDT', address: '0x789...' }], // isActive: true, // } ``` Once created, anyone can send supported tokens to the deposit address and funds will be automatically bridged to the destination. ```typescript sda-status.ts theme={null} import { rhinoSdk } from './rhino-sdk' // Check status of an existing SDA const status = await rhinoSdk.api.depositAddresses.getStatus({ depositAddress: '0x457...', depositChain: 'ETHEREUM', }) // Reactivate an inactive SDA await rhinoSdk.api.depositAddresses.activate({ depositAddress: '0x457...', depositChain: 'ETHEREUM', }) ``` We recommend generating SDAs on all available EVM chains at once to ensure funds sent to the wrong chain are still detected automatically. For more details, see the full [SDK SDA reference](/sdk/smart-deposits) or the [API integration guide](/api-integration/smart-deposits). ## Resources Learn about the core concepts of the SDK Learn about the overall architecture of the Rhino bridge Learn more on how to use the bridge functions Learn how to use chain adapters to connect to all supported chains # Smart Deposit Addresses Source: https://docs.rhino.fi/sdk/smart-deposits Manage Smart Deposit Addresses conveniently with the SDK. Please read about the [general concepts of Smart Deposit Addresses](/api-integration/smart-deposits) before continuing here. **Default limits apply to Smart Deposit Addresses for initial testing purposes:** * 500 total SDAs * 50 SDAs generated per hour Please [contact us](mailto:partnerships@rhino.fi) to lift your limits. ## Using the SDK wrappers The SDK provides wrappers around the [API calls](/api-integration/smart-deposits#api-interactions) to manage Smart Deposit Addresses. Those wrappers are available under `sdk.api.depositAddresses`. ### Examples ```typescript Find supported chains theme={null} const depositAddresses = await sdk.api.depositAddresses.getSupportedChains() // Result: ['ETHEREUM', ...] ``` ```typescript Set up a new Smart Deposit Address theme={null} const depositAddresses = await sdk.api.depositAddresses.create({ depositChains: ['ETHEREUM'], destinationChain: 'BASE', destinationAddress: '0x123...', }) // Result: { depositAddress: '0x457...', depositChain: 'ETHEREUM', destinationChain: 'BASE', destinationAddress: '0x123...', supportedTokens: [{symbol: 'USDT', address: '0x789...'}], isActive: true, } ``` ```typescript Check the status of a Smart Deposit Address theme={null} // Same result as the create function above const status = await sdk.api.depositAddresses.getStatus({ depositAddress: '0x123...', depositChain: 'ETHEREUM', }) ``` ```typescript Activate an existing Smart Deposit Address theme={null} await sdk.api.depositAddresses.activate({ depositAddress: '0x123...', depositChain: 'ETHEREUM', }) ``` # Token Prices Source: https://docs.rhino.fi/sdk/token-prices Learn how to easily fetch token prices through the SDK. ## Introduction If you are building any kind of UI application, you most likely want to show the user the USD amount of tokens they are about to bridge. For this the SDK includes a set of function that can provide this data to you. ## Usage You can fetch a USD prices as follows: ```typescript Single token price theme={null} // Fetch the price of a single token const ethPrice = await sdk.api.usdPrices.getTokenPrice('ETH') console.log(ethPrice) // => { token: "ETH", price: 4500, timestamp: ''} ``` ```typescript All token prices theme={null} // Fetch the prices of all supported tokens const allPrices = await sdk.api.usdPrices.getAllPrices() console.log(allPrices) // => { ETH: { token: "ETH", price: 4500, timestamp: '' }, ... } ``` In both cases, the timestamp of a token price will tell you how old this specific price is. # Server setup Source: https://docs.rhino.fi/webhook/endpoint-setup Rhino webhooks deliver real-time transaction status events to your server — covering deposit detection, settlement confirmation, and action outcomes for Smart Deposit Addresses and bridges. Setting up a webhook server involves creating an HTTP endpoint that can receive POST requests from Rhino.fi, process the webhook payloads, and respond appropriately. This guide covers the essential components and best practices for webhook server implementation. ## Basic Server Requirements ### **Endpoint** Your endpoint must be a `POST` endpoint and called `/rhinofi-event`. It will receive a signature header called: `X-Rhino-Signature` which can be then verified ([see verification details](/webhook/verify-webhook-signature)). The received body structure will be the following: ``` { event: WebhookEvent, // check the Webhook Events section for more details eventId: string, ts: number, } ``` It should return a 204 status with no content. ### **HTTPS Only** Your webhook endpoint must use HTTPS to ensure secure data transmission. Rhino.fi will not send webhooks to HTTP endpoints. ### **Public Accessibility** The endpoint must be publicly accessible. Rhino.fi will provide a signature when calling your endpoint for you to be able to identify the sender. Your endpoint might implement rate limiting to prevent anyone from abusing it. ### **Reliable Uptime** Ensure your server has good uptime. Failed webhook deliveries will be retried a few times, but consistent failures may result in events being lost. # Events List Source: https://docs.rhino.fi/webhook/events-list Different events can be received depending on whether the transaction is a bridge/bridge+swap (generated through the `POST/quote/bridge-swap/user`endpoint) or a Smart Deposit Address involving transaction (funds are sent to an address as generated through the `POST/deposit-addresses`endpoint). ## Bridge/bridge+swap events The following bridge events will be sent to your webhook servers. The bridge events data structure is the same as the [**Bridge Status endpoint**](https://docs.rhino.fi/api-reference/bridge/history/bridge-status) excluding the `state` field. The `_tag` field denotes the event type. * **BRIDGE\_PENDING** - sent when the bridge quote is committed * **BRIDGE\_ACCEPTED** - sent when the deposit transaction was received and validated * **BRIDGE\_EXECUTED** - sent when the bridge is completed and received on the destination chain * **BRIDGE\_REFUNDED** - when a swap is involved, the bridge might get refunded if the token price moved too much since the quote was emitted * **BRIDGE\_CANCELLED** - sent when a committed bridge is CANCELLED (note that that cancelled bridges can still be actioned if a matching deposit is detected on-chain, subsequent bridge events will be sent) ## Smart Deposit Addresses events The following events will be sent to your webhook servers for any activity involving a [Rhino.fi](http://Rhino.fi) Smart Deposit Address. * **DEPOSIT\_ADDRESS\_CREATED -** sent when a new Smart Deposit Address is generated Event structure: ```text theme={null} { _tag: "DEPOSIT_ADDRESS_CREATED", depositChain: string depositAddress: string destinationChain: string destinationAddress: string tokenOut?: string postBridgeData?: PostBridgeData addressNote?: string } ``` * **BRIDGE\_\* -** the same BRIDGE\_PENDING, BRIDGE\_ACCEPTED and BRIDGE\_EXECUTED events as described above will be sent for Smart Deposit Address related activity. * **DEPOSIT\_ADDRESS\_BRIDGE\_REJECTED -** sent when a Smart Deposit Address bridge is rejected. Event structure: ```text theme={null} { _tag: "DEPOSIT_ADDRESS_BRIDGE_REJECTED", _id: string, // bridge ID tokenSymbol: string tokenAddress: string amount: string amountUsd: string amountWei: string sender: string // depositor address txHash: string // deposit transaction hash reason: string // reason why it was rejected, e.g "UNDER_MIN" createdAt: string depositAddress: { // associated deposit address parameters depositChain: string depositAddress: string destinationChain: string destinationAddress: string } } ``` * **DEPOSIT\_ADDRESS\_BRIDGE\_REJECTED - UNSUPPORTED\_TOKEN** - sent when a Smart Deposit Address bridge is rejected as the token sent is not supported by [Rhino.fi](http://Rhino.fi). The full list of supported chains and assets can be found here: [rhino.fi](http://rhino.fi)[ - Docs\*\*Supported Chains - \*\*](https://docs.rhino.fi/get-started/supported-chains)[**rhino.fi**](http://rhino.fi)**[ - Docs](https://docs.rhino.fi/get-started/supported-chains)**. N.B [Rhino.fi](http://Rhino.fi) monitors for a selection of unsupported tokens per chain however this does not cover all scenarios where an unsupported token may be sent to an Smart Deposit Address. The event structure is: ```text theme={null} { _tag: "DEPOSIT_ADDRESS_BRIDGE_REJECTED - UNSUPPORTED_TOKEN", _id: string, // bridge ID tokenSymbol: string tokenAddress: string amount: string amountUsd: string amountWei: string sender: string // depositor address txHash: string // deposit transaction hash reason: string // reason why it was rejected, e.g "UNDER_MIN" createdAt: string depositAddress: { // associated deposit address parameters depositChain: string depositAddress: string destinationChain: string destinationAddress: string } } ``` * **DEPOSIT\_ADDRESS\_BRIDGE\_DELAYED -** send when a bridged transaction is taking longer than expected. This may be due to onchain congestion. The bridge transaction will be re-tried, there is no action required of the user. The event structure is: ```text theme={null} { _tag: "DEPOSIT_ADDRESS_BRIDGE_DELAYED", _id: string, // bridge ID tokenSymbol: string tokenAddress: string amount: string amountUsd: string amountWei: string sender: string // depositor address txHash: string // deposit transaction hash originalErrorTag: string // reason why the bridge is delayed depositAddress: { // associated deposit address parameters depositChain: string depositAddress: string destinationChain: string destinationAddress: string } } ``` ## Webhook Event Flows Information about the transition between different webhook events can be found [here](https://learn.rhino.fi/articles/8943216896-webhook-event-state-transitions) # Get missed events Source: https://docs.rhino.fi/webhook/get-missed-events If your webhook server goes down for a while and you need to fetch the events that occurred during the down time you can use the following endpoint: ``` GET https://api.rhino.fi/webhook/user-events ``` The endpoint is authenticated and it takes a few query parameters: * `sinceTimestamp`: you'll receive all the events that happened after this timestamp * `page`: the result is paginated so you can specify the page number * `limit`: how many events you want to receive per page (max 50) [Full API spec](https://api.rhino.fi/webhook/docs) # Subscribe to webhooks Source: https://docs.rhino.fi/webhook/subscribe You can either setup your webhook URL once for your project in the Rhino Console or pass it on invidual bridges. ## Using the [Rhino.fi](http://Rhino.fi) Console Within the Rhino.fi Console, navigate to the ‘Integrate’ section within the navigation bar. Under the ‘Webhooks’ section you are able to set up a new webhook and view an existing webhook. To add a new webhook, enter the webhook URL. Please make sure NOT to include the suffix `/rhinofi-event`. e.g you should provide: [https://my-server.com/prefix](https://my-server.com/prefix), and [Rhino.fi](http://Rhino.fi) will then call: [https://my-server.com/prefix/rhinofi-event](https://my-server.com/prefix/rhinofi-event) The list of webhook event types are available here: [https://docs.rhino.fi/webhook/events-list](https://docs.rhino.fi/webhook/events-list) ## Including within API calls The [Rhino.fi](http://Rhino.fi) Console routes enables you to receive all webhook evenst through to a single webhook URL. However it’s also possible to specify a different URL per bridge, bridge+swap or Smart Deposit Address. This can be achieved by passing a URL within the `webhookUrl` param in the: * `POST/quote/bridge-swap/user` endpoint: for bridge and bridge+swap related activity * `POST/deposit-addresses` endpoint: for Smart Deposit Address set up and related activity For the exact specification, [see the API Reference](https://docs.rhino.fi/api-reference/sda/depositaddresses/create-new-deposit-address) # Verify signature Source: https://docs.rhino.fi/webhook/verify-webhook-signature As your endpoint is public, anyone can send it some data. For you to make sure the data is coming from Rhino.fi, you'll receive a signature in the request header, called `X-Rhino-Signature`. To verify the signature you have 2 options: * Using the SDK * Manual verification ### SDK To verify the signature received using the SDK, you can do the following: ```typescript theme={null} const isValid = await sdk.api.webhook.verifySignature(receivedEventBody, signature) ``` Under the hood it will fetch the Rhino.fi webhook public key and verify the data provided was signed by Rhino.fi. ### Manual verification Manually verifying the received signature is a two step process. You first need to fetch Rhino.fi webhook public key and then use it to verify the signature. This can be done with the following function: ```typescript theme={null} import { createHash, createVerify } from 'crypto' const isSignatureValid = async (receivedEventBody, signature) => { // Fetch Rhino.fi webhook public key const res = await fetch('https://api.rhino.fi/webhook/public-key') const publicKeyBase64 = await res.json() const publicKey = Buffer.from(publicKeyBase64, 'base64').toString() // Hash the stringified event const hash = createHash('sha256') const hashedMessage = hash.update( JSON.stringify(receivedEventBody), ) // Verify the signature const verify = createVerify('RSA-SHA256') verify.update(hashedMessage.digest('hex')) return verify.verify(publicKey, signature, 'hex') } ``` # iframe SDA Widget Source: https://docs.rhino.fi/widget/sda-widget The SDA Widget lets you integrate a fully functional Smart Deposit Address bridge UI into your application with only a few lines of code ## Embedding the Rhino.fi SDA Widget (iframe) You can easily embed the Rhino.fi SDA Widget into any site using a simple `