diff --git a/features/deposit-addresses.mdx b/features/deposit-addresses.mdx index f7c7fa8..cde781c 100644 --- a/features/deposit-addresses.mdx +++ b/features/deposit-addresses.mdx @@ -31,7 +31,6 @@ We have automatic handling for common edge cases that can occur when using depos - Depositing to the Wrong Chain -- For example, if the bridge request requires a deposit on Base but the funds are deposited on Optimism, this amount is bridged to the destination chain originally requested as long as its enough to cover the fees and gas associated with the bridge. Please note that this will mean a new quote will be generated in-flight. NOTE: This is only supported for relay supported cross-chain currencies, see limitations. -- Double Spend -- If funds are deposited to the same deposit addressed twice, we automatically refund the second deposit amount to the refundTo address if its specified. Otherwise, the funds require manual intervention. - Extra Funds are Received -- If the user deposits more funds than what is quoted, the quote will be regenerated to account for this surplus amount and the bridge will still be filled. - Less Funds are Received -- If the user deposits less funds than what is quoted, a new quote will be generated to account for the lower amount, and the bridge will be filled. @@ -39,10 +38,8 @@ We have automatic handling for common edge cases that can occur when using depos ## Limitations - Calldata execution on destination is not allowed. -- If an ERC20 is sent to the wrong chain, if it is supported by relay (like USDC on major chains), it will be auto-bridged. Make sure to confirm that its supported before allowing this type of flow. - Only exact input bridging is allowed. Specifying exact output will not work. -- When non-supported currency or NFTs are sent to the deposit address, we - currently do not support recovering of those assets. +- When non-supported currency or NFTs are sent to the deposit address, we currently do not support recovering of those assets. ## Debugging @@ -77,8 +74,8 @@ curl -X POST \ ``` - `useDepositAddress` must be set to `true`
-- `refundTo` must be set when `useDepositAddress` is being used. - This is the address that refunds will be sent to in case of an issue. +- `refundTo` can be optionally set when `useDepositAddress` is being used. + This is the address that refunds will be sent to in case of an issue. If not specified then manual refund is required. ## Example Response diff --git a/playground-enhancer.js b/playground-enhancer.js new file mode 100644 index 0000000..45853dd --- /dev/null +++ b/playground-enhancer.js @@ -0,0 +1,337 @@ +//Logic for prefilling vms +// Utility functions for playground inputs +function findInputByLabel(labelText) { + const inputs = document.querySelectorAll("#api-playground-input"); + return Array.from(inputs).find((input) => + input.getAttribute("aria-label")?.includes(labelText) + ); +} + +function findSelectByLabel(labelText) { + const selects = document.querySelectorAll("select"); + return Array.from(selects).find((select) => + select.getAttribute("aria-label")?.includes(labelText) + ); +} + +function setInputValue(input, value, delay = 0) { + return new Promise((resolve) => { + if (!input) { + resolve(false); + return; + } + input.value = value; + const reactPropsKey = Object.keys(input).find((k) => + k.startsWith("__reactProps$") + ); + if (reactPropsKey) { + const reactProps = input[reactPropsKey]; + if (reactProps?.onChange) { + reactProps.onChange({ target: input }); + } + } + setTimeout(() => resolve(true), delay); + }); +} + +function createButton(icon, label, onClick) { + const button = document.createElement("button"); + const iconElement = document.createElement("img"); + iconElement.src = icon; + iconElement.alt = label; + button.appendChild(iconElement); + button.addEventListener("click", onClick); + button.id = `${label.toLowerCase().replace(" ", "-")}-button`; + return button; +} + +const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + const modal = document.querySelector( + '[data-testid="api-playground-modal"]' + ); + const title = document.querySelector("#page-title"); + const isQuotePage = "Get Quote" === title?.textContent; + if (modal && isQuotePage) { + if (document.getElementById("custom-vm-quicksets")) { + return; + } + const target = modal.querySelector(".grid.grid-cols-1"); + if (target) { + const introHeading = document.createElement("h2"); + introHeading.textContent = "Explore Blockchain Ecosystems"; + introHeading.id = "custom-vm-quicksets-intro"; + + const vmQuicksetsDiv = document.createElement("div"); + vmQuicksetsDiv.id = "custom-vm-quicksets"; + + const evmButton = createButton( + "https://assets.relay.link/icons/square/1/light.png", + "Solana", + async (e) => { + try { + e.target.disabled = true; + const userInput = findInputByLabel("Enter user"); + const originChainIdInput = findInputByLabel( + "Enter originChainId" + ); + const destinationChainIdInput = findInputByLabel( + "Enter destinationChainId" + ); + const originCurrencyInput = findInputByLabel( + "Enter originCurrency" + ); + const destinationCurrencyInput = findInputByLabel( + "Enter destinationCurrency" + ); + const protocolVersionInput = findSelectByLabel( + "Select protocolVersion" + ); + const amountInput = findInputByLabel("Enter amount"); + const recipientInput = findInputByLabel("Enter recipient"); + await setInputValue( + userInput, + "0x03508bb71268bba25ecacc8f620e01866650532c" + ); + await setInputValue(originChainIdInput, 8453); + await setInputValue(destinationChainIdInput, 10); + await setInputValue( + originCurrencyInput, + "0x0000000000000000000000000000000000000000" + ); + await setInputValue( + destinationCurrencyInput, + "0x0000000000000000000000000000000000000000" + ); + await setInputValue(amountInput, "1000000000000000000"); + await setInputValue( + recipientInput, + "0x03508bb71268bba25ecacc8f620e01866650532c" + ); + await setInputValue(protocolVersionInput, "preferV2"); + } catch (error) { + console.error(error); + } finally { + e.target.disabled = false; + } + } + ); + const solanaButton = createButton( + "https://assets.relay.link/icons/square/792703809/light.png", + "Solana", + async (e) => { + try { + e.target.disabled = true; + const userInput = findInputByLabel("Enter user"); + const originChainIdInput = findInputByLabel( + "Enter originChainId" + ); + const destinationChainIdInput = findInputByLabel( + "Enter destinationChainId" + ); + const originCurrencyInput = findInputByLabel( + "Enter originCurrency" + ); + const destinationCurrencyInput = findInputByLabel( + "Enter destinationCurrency" + ); + const amountInput = findInputByLabel("Enter amount"); + const recipientInput = findInputByLabel("Enter recipient"); + const protocolVersionInput = findSelectByLabel( + "Select protocolVersion" + ); + await setInputValue( + userInput, + "CbKGgVKLJFb8bBrf58DnAkdryX6ubewVytn7X957YwNr" + ); + await setInputValue(originChainIdInput, 792703809); + await setInputValue(destinationChainIdInput, 8453); + await setInputValue( + originCurrencyInput, + "11111111111111111111111111111111" + ); + await setInputValue( + destinationCurrencyInput, + "0x0000000000000000000000000000000000000000" + ); + await setInputValue(amountInput, "1000000000"); + await setInputValue( + recipientInput, + "0x03508bb71268bba25ecacc8f620e01866650532c" + ); + await setInputValue(protocolVersionInput, "preferV2"); + } catch (error) { + console.error(error); + } finally { + e.target.disabled = false; + } + } + ); + const bitcoinButton = createButton( + "https://assets.relay.link/icons/square/8253038/light.png", + "Bitcoin", + async (e) => { + e.target.disabled = true; + try { + const userInput = findInputByLabel("Enter user"); + const originChainIdInput = findInputByLabel( + "Enter originChainId" + ); + const destinationChainIdInput = findInputByLabel( + "Enter destinationChainId" + ); + const originCurrencyInput = findInputByLabel( + "Enter originCurrency" + ); + const destinationCurrencyInput = findInputByLabel( + "Enter destinationCurrency" + ); + const amountInput = findInputByLabel("Enter amount"); + const recipientInput = findInputByLabel("Enter recipient"); + const protocolVersionInput = findSelectByLabel( + "Select protocolVersion" + ); + await setInputValue( + userInput, + "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa" + ); + await setInputValue(originChainIdInput, 8253038); + await setInputValue(destinationChainIdInput, 8453); + await setInputValue( + originCurrencyInput, + "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8" + ); + await setInputValue( + destinationCurrencyInput, + "0x0000000000000000000000000000000000000000" + ); + await setInputValue(amountInput, "10000"); + await setInputValue( + recipientInput, + "0x03508bb71268bba25ecacc8f620e01866650532c" + ); + await setInputValue(protocolVersionInput, "preferV2"); + } catch (error) { + console.error(error); + } finally { + e.target.disabled = false; + } + } + ); + const hyperliquidButton = createButton( + "https://assets.relay.link/icons/square/1337/light.png", + "Hyperliquid", + async (e) => { + e.target.disabled = true; + try { + const userInput = findInputByLabel("Enter user"); + const originChainIdInput = findInputByLabel( + "Enter originChainId" + ); + const destinationChainIdInput = findInputByLabel( + "Enter destinationChainId" + ); + const originCurrencyInput = findInputByLabel( + "Enter originCurrency" + ); + const destinationCurrencyInput = findInputByLabel( + "Enter destinationCurrency" + ); + const protocolVersionInput = findSelectByLabel( + "Select protocolVersion" + ); + const amountInput = findInputByLabel("Enter amount"); + const recipientInput = findInputByLabel("Enter recipient"); + await setInputValue( + userInput, + "0x03508bb71268bba25ecacc8f620e01866650532c" + ); + await setInputValue(originChainIdInput, 1337); + await setInputValue(destinationChainIdInput, 8453); + await setInputValue( + originCurrencyInput, + "0x00000000000000000000000000000000" + ); + await setInputValue( + destinationCurrencyInput, + "0x0000000000000000000000000000000000000000" + ); + await setInputValue(amountInput, "100000000"); + await setInputValue( + recipientInput, + "0x03508bb71268bba25ecacc8f620e01866650532c" + ); + await setInputValue(protocolVersionInput, "preferV2"); + } catch (error) { + console.error(error); + } finally { + e.target.disabled = false; + } + } + ); + const tronButton = createButton( + "https://assets.relay.link/icons/square/728126428/light.png", + "Tron", + async (e) => { + e.target.disabled = true; + try { + const userInput = findInputByLabel("Enter user"); + const originChainIdInput = findInputByLabel( + "Enter originChainId" + ); + const destinationChainIdInput = findInputByLabel( + "Enter destinationChainId" + ); + const originCurrencyInput = findInputByLabel( + "Enter originCurrency" + ); + const destinationCurrencyInput = findInputByLabel( + "Enter destinationCurrency" + ); + const protocolVersionInput = findSelectByLabel( + "Select protocolVersion" + ); + const amountInput = findInputByLabel("Enter amount"); + const recipientInput = findInputByLabel("Enter recipient"); + await setInputValue( + userInput, + "THa7BwoPfacfiELa63pbmm3g5PGKYmtJyt" + ); + await setInputValue(originChainIdInput, 728126428); + await setInputValue(destinationChainIdInput, 8453); + await setInputValue( + originCurrencyInput, + "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" + ); + await setInputValue( + destinationCurrencyInput, + "0x0000000000000000000000000000000000000000" + ); + await setInputValue(amountInput, "1000000"); + await setInputValue( + recipientInput, + "0x03508bb71268bba25ecacc8f620e01866650532c" + ); + await setInputValue(protocolVersionInput, "preferV2"); + } catch (error) { + console.error(error); + } finally { + e.target.disabled = false; + } + } + ); + vmQuicksetsDiv.append( + evmButton, + solanaButton, + bitcoinButton, + hyperliquidButton, + tronButton + ); + target.parentNode.insertBefore(introHeading, target); + target.parentNode.insertBefore(vmQuicksetsDiv, target); + } + } + } + } +}).observe(document.body, { childList: true, subtree: true }); diff --git a/script.js b/script.js index 289b67f..70d471a 100644 --- a/script.js +++ b/script.js @@ -1,340 +1,5 @@ -//Logic for prefilling vms -// Utility functions for playground inputs -function findInputByLabel(labelText) { - const inputs = document.querySelectorAll("#api-playground-input"); - return Array.from(inputs).find((input) => - input.getAttribute("aria-label")?.includes(labelText) - ); -} - -function findSelectByLabel(labelText) { - const selects = document.querySelectorAll("select"); - return Array.from(selects).find((select) => - select.getAttribute("aria-label")?.includes(labelText) - ); -} - -function setInputValue(input, value, delay = 0) { - return new Promise((resolve) => { - if (!input) { - resolve(false); - return; - } - input.value = value; - const reactPropsKey = Object.keys(input).find((k) => - k.startsWith("__reactProps$") - ); - if (reactPropsKey) { - const reactProps = input[reactPropsKey]; - if (reactProps?.onChange) { - reactProps.onChange({ target: input }); - } - } - setTimeout(() => resolve(true), delay); - }); -} - -function createButton(icon, label, onClick) { - const button = document.createElement("button"); - const iconElement = document.createElement("img"); - iconElement.src = icon; - iconElement.alt = label; - button.appendChild(iconElement); - button.addEventListener("click", onClick); - button.id = `${label.toLowerCase().replace(" ", "-")}-button`; - return button; -} - -const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - const modal = document.querySelector( - '[data-testid="api-playground-modal"]' - ); - const title = document.querySelector("#page-title"); - const isQuotePage = "Get Quote" === title?.textContent; - if (modal && isQuotePage) { - if (document.getElementById("custom-vm-quicksets")) { - return; - } - const target = modal.querySelector(".grid.grid-cols-1"); - if (target) { - const introHeading = document.createElement("h2"); - introHeading.textContent = "Explore Blockchain Ecosystems"; - introHeading.id = "custom-vm-quicksets-intro"; - - const vmQuicksetsDiv = document.createElement("div"); - vmQuicksetsDiv.id = "custom-vm-quicksets"; - - const evmButton = createButton( - "https://assets.relay.link/icons/square/1/light.png", - "Solana", - async (e) => { - try { - e.target.disabled = true; - const userInput = findInputByLabel("Enter user"); - const originChainIdInput = findInputByLabel( - "Enter originChainId" - ); - const destinationChainIdInput = findInputByLabel( - "Enter destinationChainId" - ); - const originCurrencyInput = findInputByLabel( - "Enter originCurrency" - ); - const destinationCurrencyInput = findInputByLabel( - "Enter destinationCurrency" - ); - const protocolVersionInput = findSelectByLabel( - "Select protocolVersion" - ); - const amountInput = findInputByLabel("Enter amount"); - const recipientInput = findInputByLabel("Enter recipient"); - await setInputValue( - userInput, - "0x03508bb71268bba25ecacc8f620e01866650532c" - ); - await setInputValue(originChainIdInput, 8453); - await setInputValue(destinationChainIdInput, 10); - await setInputValue( - originCurrencyInput, - "0x0000000000000000000000000000000000000000" - ); - await setInputValue( - destinationCurrencyInput, - "0x0000000000000000000000000000000000000000" - ); - await setInputValue(amountInput, "1000000000000000000"); - await setInputValue( - recipientInput, - "0x03508bb71268bba25ecacc8f620e01866650532c" - ); - await setInputValue(protocolVersionInput, "preferV2"); - } catch (error) { - console.error(error); - } finally { - e.target.disabled = false; - } - } - ); - const solanaButton = createButton( - "https://assets.relay.link/icons/square/792703809/light.png", - "Solana", - async (e) => { - try { - e.target.disabled = true; - const userInput = findInputByLabel("Enter user"); - const originChainIdInput = findInputByLabel( - "Enter originChainId" - ); - const destinationChainIdInput = findInputByLabel( - "Enter destinationChainId" - ); - const originCurrencyInput = findInputByLabel( - "Enter originCurrency" - ); - const destinationCurrencyInput = findInputByLabel( - "Enter destinationCurrency" - ); - const amountInput = findInputByLabel("Enter amount"); - const recipientInput = findInputByLabel("Enter recipient"); - const protocolVersionInput = findSelectByLabel( - "Select protocolVersion" - ); - await setInputValue( - userInput, - "CbKGgVKLJFb8bBrf58DnAkdryX6ubewVytn7X957YwNr" - ); - await setInputValue(originChainIdInput, 792703809); - await setInputValue(destinationChainIdInput, 8453); - await setInputValue( - originCurrencyInput, - "11111111111111111111111111111111" - ); - await setInputValue( - destinationCurrencyInput, - "0x0000000000000000000000000000000000000000" - ); - await setInputValue(amountInput, "1000000000"); - await setInputValue( - recipientInput, - "0x03508bb71268bba25ecacc8f620e01866650532c" - ); - await setInputValue(protocolVersionInput, "preferV2"); - } catch (error) { - console.error(error); - } finally { - e.target.disabled = false; - } - } - ); - const bitcoinButton = createButton( - "https://assets.relay.link/icons/square/8253038/light.png", - "Bitcoin", - async (e) => { - e.target.disabled = true; - try { - const userInput = findInputByLabel("Enter user"); - const originChainIdInput = findInputByLabel( - "Enter originChainId" - ); - const destinationChainIdInput = findInputByLabel( - "Enter destinationChainId" - ); - const originCurrencyInput = findInputByLabel( - "Enter originCurrency" - ); - const destinationCurrencyInput = findInputByLabel( - "Enter destinationCurrency" - ); - const amountInput = findInputByLabel("Enter amount"); - const recipientInput = findInputByLabel("Enter recipient"); - const protocolVersionInput = findSelectByLabel( - "Select protocolVersion" - ); - await setInputValue( - userInput, - "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa" - ); - await setInputValue(originChainIdInput, 8253038); - await setInputValue(destinationChainIdInput, 8453); - await setInputValue( - originCurrencyInput, - "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8" - ); - await setInputValue( - destinationCurrencyInput, - "0x0000000000000000000000000000000000000000" - ); - await setInputValue(amountInput, "10000"); - await setInputValue( - recipientInput, - "0x03508bb71268bba25ecacc8f620e01866650532c" - ); - await setInputValue(protocolVersionInput, "preferV2"); - } catch (error) { - console.error(error); - } finally { - e.target.disabled = false; - } - } - ); - const hyperliquidButton = createButton( - "https://assets.relay.link/icons/square/1337/light.png", - "Hyperliquid", - async (e) => { - e.target.disabled = true; - try { - const userInput = findInputByLabel("Enter user"); - const originChainIdInput = findInputByLabel( - "Enter originChainId" - ); - const destinationChainIdInput = findInputByLabel( - "Enter destinationChainId" - ); - const originCurrencyInput = findInputByLabel( - "Enter originCurrency" - ); - const destinationCurrencyInput = findInputByLabel( - "Enter destinationCurrency" - ); - const protocolVersionInput = findSelectByLabel( - "Select protocolVersion" - ); - const amountInput = findInputByLabel("Enter amount"); - const recipientInput = findInputByLabel("Enter recipient"); - await setInputValue( - userInput, - "0x03508bb71268bba25ecacc8f620e01866650532c" - ); - await setInputValue(originChainIdInput, 1337); - await setInputValue(destinationChainIdInput, 8453); - await setInputValue( - originCurrencyInput, - "0x00000000000000000000000000000000" - ); - await setInputValue( - destinationCurrencyInput, - "0x0000000000000000000000000000000000000000" - ); - await setInputValue(amountInput, "100000000"); - await setInputValue( - recipientInput, - "0x03508bb71268bba25ecacc8f620e01866650532c" - ); - await setInputValue(protocolVersionInput, "preferV2"); - } catch (error) { - console.error(error); - } finally { - e.target.disabled = false; - } - } - ); - const tronButton = createButton( - "https://assets.relay.link/icons/square/728126428/light.png", - "Tron", - async (e) => { - e.target.disabled = true; - try { - const userInput = findInputByLabel("Enter user"); - const originChainIdInput = findInputByLabel( - "Enter originChainId" - ); - const destinationChainIdInput = findInputByLabel( - "Enter destinationChainId" - ); - const originCurrencyInput = findInputByLabel( - "Enter originCurrency" - ); - const destinationCurrencyInput = findInputByLabel( - "Enter destinationCurrency" - ); - const protocolVersionInput = findSelectByLabel( - "Select protocolVersion" - ); - const amountInput = findInputByLabel("Enter amount"); - const recipientInput = findInputByLabel("Enter recipient"); - await setInputValue( - userInput, - "THa7BwoPfacfiELa63pbmm3g5PGKYmtJyt" - ); - await setInputValue(originChainIdInput, 728126428); - await setInputValue(destinationChainIdInput, 8453); - await setInputValue( - originCurrencyInput, - "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" - ); - await setInputValue( - destinationCurrencyInput, - "0x0000000000000000000000000000000000000000" - ); - await setInputValue(amountInput, "1000000"); - await setInputValue( - recipientInput, - "0x03508bb71268bba25ecacc8f620e01866650532c" - ); - await setInputValue(protocolVersionInput, "preferV2"); - } catch (error) { - console.error(error); - } finally { - e.target.disabled = false; - } - } - ); - vmQuicksetsDiv.append( - evmButton, - solanaButton, - bitcoinButton, - hyperliquidButton, - tronButton - ); - target.parentNode.insertBefore(introHeading, target); - target.parentNode.insertBefore(vmQuicksetsDiv, target); - } - } - } - } -}).observe(document.body, { childList: true, subtree: true }); +// ---- GLOBAL STATE ------------------------- +let pageObserver = null; const addLearnMore = ( targetId, @@ -367,8 +32,13 @@ const addLearnMore = ( } }; -function waitForElementId(elementId, callback) { - const check = () => document.querySelector(elementId); +function waitForElementId(elementId, text, callback) { + const check = () => { + const element = document.querySelector(elementId); + return element && text + ? element && element.textContent.includes(text) + : element; + }; // If it's already there, run immediately if (check()) { @@ -391,36 +61,92 @@ function waitForElementId(elementId, callback) { }); } -//Logic for detecting page changes +// Safely reruns logic on DOM change +function startPageObserver(onDomChange) { + // Kill old observer (if any) + if (pageObserver) { + pageObserver.disconnect(); + } + + pageObserver = new MutationObserver(() => { + onDomChange(); + }); + + pageObserver.observe(document.body, { + subtree: true, + childList: true, + }); + + // Run once immediately + onDomChange(); +} + +// ---- PAGE LOGIC --------------------------- + +function enhanceGetQuotePage() { + waitForElementId("#body-trade-type", undefined, () => { + addLearnMore( + "body-trade-type", + "/references/api/api_core_concepts/trade-types", + "Learn more about trade types", + "learn-more-trade-type" + ); + + addLearnMore( + "body-app-fees", + "/features/app-fees", + "Learn more about app fees", + "learn-more-app-fees" + ); + + addLearnMore( + "response-fees", + "/references/api/api_core_concepts/fees", + "Learn more about fees", + "learn-more-fees", + "before" + ); + }); +} + +function enhanceGetChainsPage() { + waitForElementId("#response-chains-token-support", undefined, () => { + addLearnMore( + "response-chains-token-support", + "/references/api/api_resources/supported-routes#step-1:-check-token-support-level", + "Learn more about token support", + "learn-more-token-support", + "before" + ); + }); +} + +// ---- MAIN NAVIGATION HANDLER -------------- + function onPageChange() { - if (window.location.pathname.includes("/references/api/get-quote")) { - waitForElementId("#body-trade-type", (bodyTradeType) => { - addLearnMore( - "body-trade-type", - "/references/api/api_core_concepts/trade-types", - "Learn more about trade types", - "learn-more-trade-type" - ); - addLearnMore( - "body-app-fees", - "/features/app-fees", - "Learn more about app fees", - "learn-more-app-fees" - ); - addLearnMore( - "response-fees", - "/references/api/api_core_concepts/fees", - "Learn more about fees", - "learn-more-fees", - "before" - ); - }); + const path = window.location.pathname; + + // Stop any existing watchers + if (pageObserver) { + pageObserver.disconnect(); + pageObserver = null; + } + + // GET QUOTE + if (path.includes("/references/api/get-quote")) { + startPageObserver(enhanceGetQuotePage); + + // GET CHAINS + } else if (path.includes("/references/api/get-chains")) { + startPageObserver(enhanceGetChainsPage); } } // Run on first page load onPageChange(); +// ---- NAVIGATION PATCHING -------------- + (function () { // Patch pushState const pushState = history.pushState;