A guide on how to set up cross-chain royalty payments using deBridge.
In this tutorial, we will explore how to use deBridge to perform cross-chain royalty payments. For this tutorial specifically, we’ll pay an IP Asset on Story using Base $ETH.From a high level, it involves:
Constructing a deBridge API call that will return tx data to swap tokens across chains and perform some action (e.g. pay royalty to an IP Asset on Story)
Executing the API call to receive that tx data
Executing the transaction (using the returned tx data) on the source chain
The first step is to construct a deBridge API call. The purpose of this API call is to receive back a response that will contain transaction data so we can then execute it on the source chain.This deBridge order swaps tokens from one chain to another. We can also optionally attach a dlnHook that will execute an arbitrary action upon order completion (ex. after $ETH has been swapped for $WIP). This is where the magic happens.
In this case, the dlnHook will be a call to payRoyaltyOnBehalf, which is a function in this contract that pays royalties to an IP Asset on Story.You may be wondering, “where does the Royalty Module (the contract spending the tokens on Story) get approved to spend the $WIP?” The answer is that deBridge automatically handles token approvals for you using their IExternalCallExecutor, where it approves the target address to spend the tokens on the destination chain. In this case because the target address is the Royalty Module itself, token approval is handled for us without having to create a custom multicall contract like we do in the cross-chain license minting tutorial.
You can learn more about the IExternalCallExecutorhere.
To summarize, we will construct a deBridge API call that says “we want to swap $ETH for $IP, then use a dlnHook to call a smart contract on Story that pays royalties to an IP Asset on Story”.
Now that we have the dlnHook, we can construct the whole deBridge API call, including the dlnHook.
You can view deBridge’s documentation on the create-tx endpoint here. I also highly recommend checking out the Swagger UI for the create-tx endpoint as well.
Attribute
Description
srcChainId
The ID of the source blockchain (e.g., Ethereum mainnet is 1).
srcChainTokenIn
The address of the token being swapped on the source chain (ETH in this case).
srcChainTokenInAmount
The amount of the source token to swap, set to auto for automatic calculation.
dstChainId
The ID of the destination blockchain (e.g., Story mainnet is 100000013).
dstChainTokenOut
The address of the token to receive on the destination chain (WIP token).
dstChainTokenOutAmount
The amount of the destination token to receive. It should be the same as the amount we’re paying in payRoyaltyOnBehalf in step 1a.
dstChainTokenOutRecipient
This can just be the same as senderAddress.
senderAddress
The address initiating the transaction.
srcChainOrderAuthorityAddress
The address authorized to manage the order on the source chain. This can just be the same as senderAddress.
dstChainOrderAuthorityAddress
The address authorized to manage the order on the destination chain. This can just be the same as senderAddress.
enableEstimate
A flag to enable transaction simulation and estimation.
prependOperatingExpenses
A flag to include operating expenses in the transaction.
dlnHook
The URL-encoded hook that specifies additional actions to execute post-swap.
main.ts
Copy
Ask AI
import { base } from "viem/chains";import { WIP_TOKEN_ADDRESS } from "@story-protocol/core-sdk";// ... previous code here ...// Build deBridge API URL for cross-chain royalty paymentconst buildDeBridgeApiUrl = ({ ipId: `0x${string}`, senderAddress: `0x${string}`, paymentAmount: string // should be in wei}): string => { const dlnHook = buildRoyaltyPaymentHook({ ipId, paymentAmount }); const encodedHook = encodeURIComponent(dlnHook); const url = `https://dln.debridge.finance/v1.0/dln/order/create-tx?` + `srcChainId=${base.id}` + // we make this zero address which represents the native token ($ETH) `&srcChainTokenIn=0x0000000000000000000000000000000000000000` + // we set this to auto which will automatically calculate the amount of $ETH to swap `&srcChainTokenInAmount=auto` + // Story's mainnet chain ID `&dstChainId=100000013` + // we make this zero address which represents the native token ($IP) `&dstChainTokenOut=${WIP_TOKEN_ADDRESS}` + // we set the amount of $IP to pay for the payment on Story `&dstChainTokenOutAmount=${paymentAmount}` + // the address of the contract that will pay the royalty on Story `&dstChainTokenOutRecipient=${senderAddress}` + // the address of the user initiating the transaction `&senderAddress=${senderAddress}` + // the address authorized to manage the order on the source chain `&srcChainOrderAuthorityAddress=${senderAddress}` + // the address authorized to manage the order on the destination chain `&dstChainOrderAuthorityAddress=${senderAddress}` + // we set this to true to enable transaction simulation and estimation `&enableEstimate=true` + // we set this to true to include operating expenses in the transaction `&prependOperatingExpenses=true` + `&dlnHook=${encodedHook}`; return url;};
Once the API call is constructed, execute it to receive a response. This response includes transaction data and an estimate for running the transaction on the source swap chain (e.g., Ethereum, Solana).
Copy
Ask AI
// ... previous code here ...const getDeBridgeTransactionData = async ({ ipId: `0x${string}`, senderAddress: `0x${string}`, paymentAmount: string // should be in wei}): Promise<DeBridgeApiResponse> => { try { const apiUrl = buildDeBridgeApiUrl({ ipId, senderAddress, paymentAmount }); const response = await fetch(apiUrl, { method: "GET", headers: { Accept: "application/json", }, }); if (!response.ok) { throw new Error( `deBridge API error: ${response.status} ${response.statusText}` ); } const data = (await response.json()) as DeBridgeApiResponse; // Validate the response if (!data.tx || !data.estimation || !data.orderId) { throw new Error("Invalid deBridge API response: missing required fields"); } return data; } catch (error) { console.error("Error calling deBridge API:", error); throw error; }};
Step 3: Executing the Transaction on the Source Chain
Next, you would take the API response and execute the transaction on the source chain.
View the docs here on submitting the transaction, including how this would be done differently on Solana.
TypeScript
Copy
Ask AI
import { base } from "viem/chains";import { createWalletClient, http, WalletClient, parseEther } from "viem";import { privateKeyToAccount, Address, Account } from "viem/accounts";import dotenv from "dotenv";dotenv.config();// Validate environment variablesif (!process.env.WALLET_PRIVATE_KEY) { throw new Error("WALLET_PRIVATE_KEY is required in .env file");}// Create account from private keyconst account: Account = privateKeyToAccount( `0x${process.env.WALLET_PRIVATE_KEY}` as Address);// Initialize the wallet clientconst walletClient = createWalletClient({ chain: base, transport: http("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID"), // Use Infura or another Ethereum provider account,}) as WalletClient;// ... previous code here ...// Function to send a transactionconst executeLicenseMint = async (params: { ipId: `0x${string}`; senderAddress: `0x${string}`; paymentAmount: string; // should be in wei}) => { // Get transaction data from deBridge const deBridgeResponse = await getDeBridgeTransactionData(params); try { // Execute the transaction using the user's wallet const txHash = await walletClient.sendTransaction({ to: deBridgeResponse.tx.to as Address, data: deBridgeResponse.tx.data as Address, value: BigInt(deBridgeResponse.tx.value), account: account as Account, chain: base, }); console.log("Transaction sent:", txHash); // Wait for the transaction to be mined const receipt = await walletClient.waitForTransactionReceipt(txHash); console.log("Transaction mined:", receipt.transactionHash); } catch (error) { console.error("Error sending transaction:", error); }};// Example usage with a mock API responseconst params = { ipId: "0xcb6B9CCae4108A103097B30cFc25e1E257D4b5Fe", senderAddress: account.address, paymentAmount: parseEther("5"), // 5 $WIP};// Execute the function to send the transactionexecuteLicenseMint(params);