jb-relayr

0
0
Source

Relayr API reference for multi-chain transaction bundling. Pay gas on one chain, execute on many. Used for omnichain deployments, cross-chain operations, and meta-transactions.

Install

mkdir -p .claude/skills/jb-relayr && curl -L -o skill.zip "https://mcp.directory/api/skills/download/9183" && unzip -o skill.zip -d .claude/skills/jb-relayr && rm skill.zip

Installs to .claude/skills/jb-relayr

About this skill

Relayr: Multi-Chain Transaction Bundling

Relayr is a meta-transaction relay service by 0xBASED that bundles transactions across chains. Users sign transactions for multiple chains, pay gas on one chain, and Relayr relayers execute on all others.

Overview

1. User signs ERC2771 forward requests for each target chain
2. POST to Relayr API to get quote with payment options
3. User selects which chain to pay on
4. User sends single payment transaction
5. Relayr relayers execute on all other chains
6. Poll for bundle completion status

API Base URLs

Production API: https://api.relayr.ba5ed.com
Dashboard: https://relayr.ba5ed.com

Authentication

No API key required. Relayr is permissionless. Anyone can submit bundles.


API Endpoints

1. Create Bundle Quote

POST /v1/bundle/prepaid

Creates a bundle of transactions and returns payment options.

Request Body:

{
  "transactions": [
    {
      "chain": 1,
      "target": "0x...",
      "data": "0x...",
      "value": "0"
    }
  ],
  "virtual_nonce_mode": "Disabled"
}

Fields:

FieldTypeDescription
chainnumberTarget chain ID
targetstringContract to call (usually ERC2771Forwarder)
datastringEncoded calldata
valuestringETH value in wei (usually "0" for meta-txs)
virtual_nonce_modestring"Disabled" or "Enabled" for sequential ordering

Response:

{
  "bundle_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "payment_info": [
    {
      "chain": 1,
      "target": "0x...",
      "amount": "1234567890",
      "calldata": "0x..."
    },
    {
      "chain": 10,
      "target": "0x...",
      "amount": "987654321",
      "calldata": "0x..."
    }
  ],
  "per_txn": [
    {
      "txn_uuid": "...",
      "chain": 1,
      "gas_cost": "500000",
      "status": "Quoted"
    }
  ],
  "txn_uuids": ["uuid1", "uuid2"]
}

Response Fields:

FieldDescription
bundle_uuidUnique identifier for tracking
payment_infoArray of payment options (one per supported chain)
payment_info[].chainChain ID to pay on
payment_info[].targetAddress to send payment to
payment_info[].amountWei amount to pay
payment_info[].calldataTransaction data for payment
per_txnPer-transaction details
txn_uuidsArray of transaction UUIDs

2. Get Bundle Status

GET /v1/bundle/{bundle_uuid}

Poll this endpoint to check execution status.

Response:

{
  "bundle_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "payment_received": true,
  "payment_chain": 1,
  "transactions": [
    {
      "txn_uuid": "...",
      "chain": 1,
      "status": "Success",
      "tx_hash": "0x...",
      "block_number": 12345678
    },
    {
      "txn_uuid": "...",
      "chain": 10,
      "status": "Pending",
      "tx_hash": null,
      "block_number": null
    }
  ]
}

3. Get Transaction Status

GET /v1/transaction/{txn_uuid}

Get status of individual transaction within a bundle.


Transaction Status Values

StatusMeaning
QuotedBundle created, awaiting payment
PaymentReceivedPayment confirmed, queued for execution
PendingTransaction submitted, awaiting confirmation
SuccessTransaction confirmed on-chain
CompletedAlias for Success
FailedTransaction reverted
ExpiredPayment not received within deadline (48h)

ERC2771 Forward Request Format

Relayr uses ERC2771 meta-transactions. The forwarder contract validates signatures and executes calls with the original sender preserved via _msgSender().

TypedData Domain

const domain = {
  name: 'Juicebox',           // Or actual contract name
  version: '1',
  chainId: 1,                 // Target chain ID
  verifyingContract: '0x...'  // ERC2771Forwarder address
};

TypedData Types

const types = {
  ForwardRequest: [
    { name: 'from', type: 'address' },
    { name: 'to', type: 'address' },
    { name: 'value', type: 'uint256' },
    { name: 'gas', type: 'uint256' },
    { name: 'nonce', type: 'uint256' },
    { name: 'deadline', type: 'uint48' },
    { name: 'data', type: 'bytes' }
  ]
};

Message Fields

FieldTypeDescription
fromaddressOriginal signer address
toaddressTarget contract to call
valueuint256ETH to forward (usually 0)
gasuint256Gas limit for execution
nonceuint256User's forwarder nonce (query from contract)
deadlineuint48Unix timestamp expiry (max 48 hours from now)
databytesEncoded function calldata

Getting the Nonce

Query the forwarder contract for the user's current nonce:

const forwarder = new ethers.Contract(forwarderAddress, [
  'function nonces(address) view returns (uint256)'
], provider);

const nonce = await forwarder.nonces(userAddress);

Complete JavaScript Example

import { ethers } from 'ethers';

const RELAYR_API = 'https://api.relayr.ba5ed.com';

// ERC2771Forwarder addresses (same on all chains - deterministic deployment)
const FORWARDER = {
  1: '0x...', // Ethereum mainnet
  10: '0x...', // Optimism
  8453: '0x...', // Base
  42161: '0x...' // Arbitrum
};

/**
 * Deploy or execute across multiple chains with single payment
 */
async function executeOmnichain(signer, targetChains, targetContract, calldata) {
  const address = await signer.getAddress();
  const signedRequests = [];

  // Step 1: Sign forward request for each chain
  for (const chainId of targetChains) {
    // Get nonce from forwarder contract
    const nonce = await getNonce(chainId, address);

    const domain = {
      name: 'Juicebox',
      version: '1',
      chainId: chainId,
      verifyingContract: FORWARDER[chainId]
    };

    const types = {
      ForwardRequest: [
        { name: 'from', type: 'address' },
        { name: 'to', type: 'address' },
        { name: 'value', type: 'uint256' },
        { name: 'gas', type: 'uint256' },
        { name: 'nonce', type: 'uint256' },
        { name: 'deadline', type: 'uint48' },
        { name: 'data', type: 'bytes' }
      ]
    };

    // 48-hour deadline (maximum allowed)
    const deadline = Math.floor(Date.now() / 1000) + 48 * 60 * 60;

    const message = {
      from: address,
      to: targetContract,
      value: '0',
      gas: '1000000',
      nonce: nonce,
      deadline: deadline,
      data: calldata
    };

    // Sign the typed data
    const signature = await signer.signTypedData(domain, types, message);

    // Encode for forwarder.execute()
    const forwarderAbi = [
      'function execute((address from, address to, uint256 value, uint256 gas, uint256 nonce, uint48 deadline, bytes data) request, bytes signature)'
    ];
    const iface = new ethers.Interface(forwarderAbi);
    const encodedData = iface.encodeFunctionData('execute', [
      [message.from, message.to, message.value, message.gas, message.nonce, message.deadline, message.data],
      signature
    ]);

    signedRequests.push({
      chain: chainId,
      target: FORWARDER[chainId],
      data: encodedData,
      value: '0'
    });
  }

  // Step 2: Get quote from Relayr
  const quoteResponse = await fetch(`${RELAYR_API}/v1/bundle/prepaid`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      transactions: signedRequests,
      virtual_nonce_mode: 'Disabled'
    })
  });

  if (!quoteResponse.ok) {
    throw new Error(`Quote failed: ${quoteResponse.statusText}`);
  }

  const quote = await quoteResponse.json();
  console.log('Bundle UUID:', quote.bundle_uuid);
  console.log('Payment options:', quote.payment_info.map(p =>
    `Chain ${p.chain}: ${ethers.formatEther(p.amount)} ETH`
  ));

  return quote;
}

/**
 * Execute payment on chosen chain
 */
async function payForBundle(signer, paymentInfo) {
  const tx = await signer.sendTransaction({
    to: paymentInfo.target,
    value: paymentInfo.amount,
    data: paymentInfo.calldata
  });

  console.log('Payment tx:', tx.hash);
  await tx.wait();
  console.log('Payment confirmed');

  return tx;
}

/**
 * Poll until all transactions complete
 */
async function waitForCompletion(bundleUuid, onUpdate) {
  while (true) {
    const response = await fetch(`${RELAYR_API}/v1/bundle/${bundleUuid}`);
    const status = await response.json();

    if (onUpdate) onUpdate(status);

    const allDone = status.transactions.every(
      tx => ['Success', 'Completed', 'Failed'].includes(tx.status)
    );

    if (allDone) {
      const anyFailed = status.transactions.some(tx => tx.status === 'Failed');
      if (anyFailed) {
        const failed = status.transactions.filter(tx => tx.status === 'Failed');
        throw new Error(`Transactions failed on chains: ${failed.map(t => t.chain).join(', ')}`);
      }
      return status;
    }

    console.log('Status:', status.transactions.map(t => `Chain ${t.chain}: ${t.status}`).join(', '));
    await new Promise(r => setTimeout(r, 3000));
  }
}

/**
 * Helper: Get nonce from forwarder on specific chain
 */
async function getNonce(chainId, address) {
  const rpcUrls = {
    1: 'https://eth.llamarpc.com',
    10: 'https://mainnet.optimism.io',
    8453: 'https://mainnet.base.org',
    42161: 'https://arb1.arbitrum.io/rpc'
  };

  const provider = new ethers.JsonRpcProvider(rpcUrls[chainId]);
  const forwarder = new ethers.Contract(
    FORWARDER[chainId],
    ['function nonces(address) view returns (uint256)'],
    provider
  );

  return await forwarder.nonces(address);
}

/**
 * Complete flow example
 */
async function main() {
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();

  // Example: Deploy to Ethereum, Optimism, and

---

*Content truncated.*

You might also like

flutter-development

aj-geddes

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

1,4071,302

drawio-diagrams-enhanced

jgtolentino

Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.

1,2201,024

ui-ux-pro-max

nextlevelbuilder

"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."

9001,013

godot

bfollington

This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.

958658

nano-banana-pro

garg-aayush

Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.

970608

pdf-to-markdown

aliceisjustplaying

Convert entire PDF documents to clean, structured Markdown for full context loading. Use this skill when the user wants to extract ALL text from a PDF into context (not grep/search), when discussing or analyzing PDF content in full, when the user mentions "load the whole PDF", "bring the PDF into context", "read the entire PDF", or when partial extraction/grepping would miss important context. This is the preferred method for PDF text extraction over page-by-page or grep approaches.

1,033496

Stay ahead of the MCP ecosystem

Get weekly updates on new skills and servers.