The Braodcaster network, forward private transactions on behalf of users and covering the associated gas costs. Users pay a small premium on top of the gas cost from their private balance to the Broadcaster's private balance in exchange for the service.
Broadcasters are only needed for transactions that originate from a 0zk address (i.e. not needed for Shield transactions). Through Broadcaster, private transactions within the RAILGUN privacy system remain unlinked to public addresses, ensuring anonymity even during interactions with smart contracts.
Broadcasters broadcast their fees and accept transactions via the Waku Network (as opposed to using HTTP requests) so that nobody can snoop on a user's IP address when submitting a transaction.
import {
RailgunWakuRelayerClient,
RelayerConnectionStatusCallback,
RelayerDebugger,
RelayerOptions,
} from '@railgun-community/waku-relayer-client';
import {
Chain,
ChainType,
RelayerConnectionStatus,
} from '@railgun-community/shared-models';
const statusCallback: RelayerConnectionStatusCallback = (
chain: Chain,
status: RelayerConnectionStatus,
) => {
// Handle connection status update
};
const relayerDebugger: RelayerDebugger = {
log: (msg: string) => {
// Log a debug message
},
error: (err: Error) => {
// Log a debug error
},
};
const initRelayerClient = async (
statusCallback: RelayerConnectionStatusCallback,
relayerDebugger: RelayerDebugger
) => {
const chain: Chain = {
type: ChainType.EVM;
id: 1; // Chain number, 1 for Ethereum
}
const pubSubTopic = '/waku/2/railgun-relayer'; // The waku topic that Relayers publish to. This value is the default
const additionalDirectPeers = []; // Optional direct connection to broadcasting Relayers
const peerDiscoveryTimeout = 60000; // Timeout to discover Relayers
const relayerOptions: RelayerOptions = {
pubSubTopic,
additionalDirectPeers,
peerDiscoveryTimeout,
};
await RailgunWakuRelayerClient.start(
chain,
relayerOptions,
statusCallback,
relayerDebugger,
);
}
// Call this somewhere on app launch
await initRelayerClient(statusCallback, RelayerDebugger);
The initialized RailgunWakuRelayerClient will continue monitoring the connection to the Broadcaster network throughout the app session. It will call the statusCallback function with updates, and you must wait to use a Broadcaster until the status is Connected.
Finding a Broadcaster
When you are connected to the Broadcaster network and you are ready to submit a private transaction, you must first select a token from the user's private balance to pay the Broadcaster fee (likely from user input).
Next, we need to find the best Broadcaster based on the user's selected fee token and the fee amounts that the Broadcasters are broadcasting.
import {
RailgunWakuRelayerClient,
} from '@railgun-community/waku-relayer-client';
import {
Chain,
ChainType,
SelectedRelayer,
} from '@railgun-community/shared-models';
const chain: Chain = {
type: ChainType.EVM;
id: 1; // Chain number, 1 for Ethereum
}
const feeTokenAddress = '0x123...123'; // Address of token that the user will pay the Relayer fee in
// Only set to true if you are making a cross-contract call.
// For instance, if you are using a Recipe from the Cookbook. See "Cross-Contract Calls" in the "Transactions" section.
const useRelayAdapt = false;
const relayer: Optional<SelectedRelayer> = RailgunWakuRelayerClient.findBestRelayer(
chain,
feeTokenAddress,
useRelayAdapt,
);
If no Broadcaster is available that accepts fees in the selected token, then the findBestRelayer function will return undefined.
Calculating the Broadcaster Fee
Once we have a SelectedRelayer, we need to calculate the total Broadcaster fee for our transaction based on the current gas price, gas estimate of the transaction, and the fee that the Broadcaster charges.
import {
...,
calculateMaximumGas,
TransactionGasDetails,
} from '@railgun-community/shared-models';
...
const selectedRelayer = ...; // From the previous step
const tokenFeePerUnitGas = BigInt(selectedRelayer.tokenFee.feePerUnitGas);
const oneUnitGas = 10n ** 18n;
// See "Transactions" > "Private Transfers" > "Private ERC-20 Transfers"
// for an example of how to format transactionGasDetails
const transactionGasDetails: TransactionGasDetails = ...;
const maximumGas = calculateMaximumGas(transactionGasDetails);
const relayerFee = (tokenFeePerUnitGas * maximumGas) / oneUnitGas;
Submit via Broadcaster
import {
RailgunPopulateTransactionResponse
} from '@railgun-community/shared-models';
const populateResponse: RailgunPopulateTransactionResponse = ...; // See the examples in the "Transactions" section for getting this value
const overallBatchMinGasPrice: bigint = ...; // See the examples in the "Transactions" section for getting this value
const selectedRelayer = ...; // Same as examples above
const chain: Chain = ...; // Same as examples above
const useRelayAdapt = ...; // Same as examples above
const nullifiers: string[] = populateResponse.nullifiers ?? [];
const relayerTransaction = await RelayerTransaction.create(
populateResponse.transaction.to,
populateResponse.transaction.data,
selectedRelayer.railgunAddress,
selectedRelayer.tokenFee.feesID,
chain,
nullifiers,
overallBatchMinGasPrice,
useRelayAdapt,
);
const txHash = await relayerTransaction.send();